diff --git a/Cargo.toml b/Cargo.toml index 67b8269..798b20f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ icondata = { version = "0.1.0", features = [ "AiMinusOutlined", ] } icondata_core = "0.0.2" +uuid = { version = "1.5.0", features = ["v4"] } [workspace] members = ["demo"] diff --git a/demo/src/app.rs b/demo/src/app.rs index 18ad8d2..1685d25 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -1,43 +1,47 @@ use crate::pages::*; use leptos::*; use leptos_router::*; +use melt_ui::*; #[component] pub fn App() -> impl IntoView { view! { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } } diff --git a/demo/src/pages/components.rs b/demo/src/pages/components.rs index 9c2efb8..1aad13b 100644 --- a/demo/src/pages/components.rs +++ b/demo/src/pages/components.rs @@ -172,6 +172,10 @@ fn gen_menu_data() -> Vec { value: "badge".into(), label: "Badge".into(), }, + MenuItemOption { + value: "message".into(), + label: "Message".into(), + }, MenuItemOption { value: "modal".into(), label: "Modal".into(), diff --git a/demo/src/pages/message/mod.rs b/demo/src/pages/message/mod.rs new file mode 100644 index 0000000..0539af6 --- /dev/null +++ b/demo/src/pages/message/mod.rs @@ -0,0 +1,77 @@ +use crate::components::{Demo, DemoCode}; +use leptos::*; +use melt_ui::*; +use prisms::highlight_str; + +#[component] +pub fn MessagePage() -> impl IntoView { + let message = use_message(); + let success = move |_| { + message.create( + "Success".into(), + MessageVariant::Success, + Default::default(), + ); + }; + let warning = move |_| { + message.create( + "Warning".into(), + MessageVariant::Warning, + Default::default(), + ); + }; + let error = move |_| { + message.create("Error".into(), MessageVariant::Error, Default::default()); + }; + view! { +
+

"Message"

+ + "If you want to use message, you need to wrap the component where you call related methods inside MessageProvider and use use_message to get the API." + + + + + + + + + + + + + } + "#, + "rust" + ) + > + + "" + + +
+ } +} diff --git a/demo/src/pages/mod.rs b/demo/src/pages/mod.rs index 6017702..79fba8a 100644 --- a/demo/src/pages/mod.rs +++ b/demo/src/pages/mod.rs @@ -15,6 +15,7 @@ mod image; mod input; mod input_number; mod menu; +mod message; mod mobile; mod modal; mod nav_bar; @@ -43,6 +44,7 @@ pub use image::*; pub use input::*; pub use input_number::*; pub use menu::*; +pub use message::*; pub use mobile::*; pub use modal::*; pub use nav_bar::*; diff --git a/src/icon/mod.rs b/src/icon/mod.rs index 916b061..a1d28fb 100644 --- a/src/icon/mod.rs +++ b/src/icon/mod.rs @@ -1,6 +1,6 @@ // copy https://github.com/Carlosted/leptos-icons // leptos updated version causes leptos_icons error -pub use icondata::*; +pub(crate) use icondata::*; use leptos::*; /// The Icon component. diff --git a/src/lib.rs b/src/lib.rs index 88e53b1..58082cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ mod input; mod input_number; mod layout; mod menu; +mod message; pub mod mobile; mod modal; mod progress; @@ -46,6 +47,7 @@ pub use input::*; pub use input_number::*; pub use layout::*; pub use menu::*; +pub use message::*; pub use modal::*; pub use progress::*; pub use select::*; diff --git a/src/message/message.css b/src/message/message.css new file mode 100644 index 0000000..92047e1 --- /dev/null +++ b/src/message/message.css @@ -0,0 +1,44 @@ +.melt-message-container { + z-index: 6000; + position: fixed; + top: 12px; + left: 0; + right: 0; + height: 0; + overflow: visible; + display: flex; + flex-direction: column; + align-items: center; +} + +.melt-message-wrapper { + margin-bottom: 8px; +} + +.melt-message { + box-sizing: border-box; + display: flex; + align-items: center; + flex-wrap: nowrap; + overflow: hidden; + + padding: 10px 20px; + max-width: 75vh; + background: #fff; + + font-size: 14px; + border-radius: 3px; + box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), + 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); +} + +.melt-message__icon { + width: 20px; + height: 20px; + margin-right: 10px; + font-size: 20px; +} + +.melt-message__content { + line-height: 1.6; +} diff --git a/src/message/message.rs b/src/message/message.rs new file mode 100644 index 0000000..1abd940 --- /dev/null +++ b/src/message/message.rs @@ -0,0 +1,44 @@ +use crate::{theme::use_theme, Icon, Theme}; +use icondata::*; +use leptos::*; + +#[derive(Default, Clone)] +pub enum MessageVariant { + #[default] + Success, + Warning, + Error, +} + +impl MessageVariant { + fn icon(&self) -> Icon { + match self { + MessageVariant::Success => icondata::Icon::Ai(AiCloseCircleFilled), + MessageVariant::Warning => icondata::Icon::Ai(AiExclamationCircleFilled), + MessageVariant::Error => icondata::Icon::Ai(AiCheckCircleFilled), + } + } + fn theme_color(&self, theme: &Theme) -> String { + match self { + MessageVariant::Success => theme.common.color_success.clone(), + MessageVariant::Warning => theme.common.color_warning.clone(), + MessageVariant::Error => theme.common.color_error.clone(), + } + } +} + +#[component] +pub(crate) fn Message(variant: MessageVariant, content: String) -> impl IntoView { + let theme = use_theme(Theme::light); + let style = theme.with_untracked(|theme| format!("color: {};", variant.theme_color(theme))); + view! { +
+
+
+ +
+
{content}
+
+
+ } +} diff --git a/src/message/message_environment.rs b/src/message/message_environment.rs new file mode 100644 index 0000000..5533cdd --- /dev/null +++ b/src/message/message_environment.rs @@ -0,0 +1,19 @@ +use super::{message::Message, message_provider::MessageType}; +use leptos::*; +use uuid::Uuid; + +#[component] +pub fn MessageEnvironment( + message: MessageType, + #[prop(into)] on_internal_after_leave: Callback, +) -> impl IntoView { + let (id, content, variant, options) = message; + set_timeout( + move || { + on_internal_after_leave.call(id); + }, + options.duration, + ); + + view! { } +} diff --git a/src/message/message_provider.rs b/src/message/message_provider.rs new file mode 100644 index 0000000..8f26bc7 --- /dev/null +++ b/src/message/message_provider.rs @@ -0,0 +1,78 @@ +use std::time::Duration; + +use super::{message::MessageVariant, message_environment::MessageEnvironment}; +use crate::{mount_style, teleport::Teleport}; +use leptos::*; +use uuid::Uuid; + +#[component] +pub fn MessageProvider(children: Children) -> impl IntoView { + mount_style("message", include_str!("./message.css")); + + let message_list = create_rw_signal::>(vec![]); + provide_context(MessageInjection::new(message_list)); + + let handle_after_leave = move |id| { + message_list.update(move |message_list| { + let Some(index) = message_list.iter().position(|message| id == message.0) else { + return; + }; + message_list.remove(index); + }); + }; + + view! { + {children()} + +
+ + } + } + /> +
+
+ } +} + +pub(crate) type MessageType = (Uuid, String, MessageVariant, MessageOptions); + +#[derive(Clone)] +pub struct MessageOptions { + pub duration: Duration, +} + +impl Default for MessageOptions { + fn default() -> Self { + Self { + duration: Duration::from_secs(3), + } + } +} + +#[derive(Clone)] +pub struct MessageInjection { + message_list: RwSignal>, +} +impl Copy for MessageInjection {} + +impl MessageInjection { + fn new(message_list: RwSignal>) -> Self { + Self { message_list } + } + + pub fn create(&self, content: String, variant: MessageVariant, options: MessageOptions) { + self.message_list.update(move |message_list| { + let id = uuid::Uuid::new_v4(); + message_list.push((id, content, variant, options)); + }); + } +} + +pub fn use_message() -> MessageInjection { + expect_context::() +} diff --git a/src/message/mod.rs b/src/message/mod.rs new file mode 100644 index 0000000..b01d37c --- /dev/null +++ b/src/message/mod.rs @@ -0,0 +1,6 @@ +mod message; +mod message_environment; +mod message_provider; + +pub use message::*; +pub use message_provider::*;