mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19:22 -05:00
feat: add message component
This commit is contained in:
parent
92165edebf
commit
05bd1f3e5f
12 changed files with 316 additions and 35 deletions
|
@ -29,6 +29,7 @@ icondata = { version = "0.1.0", features = [
|
||||||
"AiMinusOutlined",
|
"AiMinusOutlined",
|
||||||
] }
|
] }
|
||||||
icondata_core = "0.0.2"
|
icondata_core = "0.0.2"
|
||||||
|
uuid = { version = "1.5.0", features = ["v4"] }
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["demo"]
|
members = ["demo"]
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::pages::*;
|
use crate::pages::*;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
use melt_ui::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn App() -> impl IntoView {
|
pub fn App() -> impl IntoView {
|
||||||
view! {
|
view! {
|
||||||
|
<MessageProvider>
|
||||||
<Router base="/melt-ui">
|
<Router base="/melt-ui">
|
||||||
<Routes base="/melt-ui".to_string()>
|
<Routes base="/melt-ui".to_string()>
|
||||||
<Route path="/" view=Home/>
|
<Route path="/" view=Home/>
|
||||||
|
@ -33,11 +35,13 @@ pub fn App() -> impl IntoView {
|
||||||
<Route path="/divider" view=DividerPage/>
|
<Route path="/divider" view=DividerPage/>
|
||||||
<Route path="/input-number" view=InputNumberPage/>
|
<Route path="/input-number" view=InputNumberPage/>
|
||||||
<Route path="/icon" view=IconPage/>
|
<Route path="/icon" view=IconPage/>
|
||||||
|
<Route path="/message" view=MessagePage/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||||
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||||
<Route path="/mobile/toast" view=ToastDemoPage/>
|
<Route path="/mobile/toast" view=ToastDemoPage/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
</MessageProvider>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,10 @@ fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||||
value: "badge".into(),
|
value: "badge".into(),
|
||||||
label: "Badge".into(),
|
label: "Badge".into(),
|
||||||
},
|
},
|
||||||
|
MenuItemOption {
|
||||||
|
value: "message".into(),
|
||||||
|
label: "Message".into(),
|
||||||
|
},
|
||||||
MenuItemOption {
|
MenuItemOption {
|
||||||
value: "modal".into(),
|
value: "modal".into(),
|
||||||
label: "Modal".into(),
|
label: "Modal".into(),
|
||||||
|
|
77
demo/src/pages/message/mod.rs
Normal file
77
demo/src/pages/message/mod.rs
Normal file
|
@ -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! {
|
||||||
|
<div style="width: 896px; margin: 0 auto;">
|
||||||
|
<h1>"Message"</h1>
|
||||||
|
<Alert variant=AlertVariant::Warning title="Prerequisite">
|
||||||
|
"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."
|
||||||
|
</Alert>
|
||||||
|
<Demo>
|
||||||
|
<Space>
|
||||||
|
<Button on:click=success>"Success"</Button>
|
||||||
|
<Button on:click=warning>"Warning"</Button>
|
||||||
|
<Button on:click=error>"Error"</Button>
|
||||||
|
</Space>
|
||||||
|
<DemoCode
|
||||||
|
slot
|
||||||
|
html=highlight_str!(
|
||||||
|
r#"
|
||||||
|
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! {
|
||||||
|
<Space>
|
||||||
|
<Button on:click=success>"Success"</Button>
|
||||||
|
<Button on:click=warning>"Warning"</Button>
|
||||||
|
<Button on:click=error>"Error"</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"rust"
|
||||||
|
)
|
||||||
|
>
|
||||||
|
|
||||||
|
""
|
||||||
|
</DemoCode>
|
||||||
|
</Demo>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ mod image;
|
||||||
mod input;
|
mod input;
|
||||||
mod input_number;
|
mod input_number;
|
||||||
mod menu;
|
mod menu;
|
||||||
|
mod message;
|
||||||
mod mobile;
|
mod mobile;
|
||||||
mod modal;
|
mod modal;
|
||||||
mod nav_bar;
|
mod nav_bar;
|
||||||
|
@ -43,6 +44,7 @@ pub use image::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use input_number::*;
|
pub use input_number::*;
|
||||||
pub use menu::*;
|
pub use menu::*;
|
||||||
|
pub use message::*;
|
||||||
pub use mobile::*;
|
pub use mobile::*;
|
||||||
pub use modal::*;
|
pub use modal::*;
|
||||||
pub use nav_bar::*;
|
pub use nav_bar::*;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// copy https://github.com/Carlosted/leptos-icons
|
// copy https://github.com/Carlosted/leptos-icons
|
||||||
// leptos updated version causes leptos_icons error
|
// leptos updated version causes leptos_icons error
|
||||||
pub use icondata::*;
|
pub(crate) use icondata::*;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
/// The Icon component.
|
/// The Icon component.
|
||||||
|
|
|
@ -16,6 +16,7 @@ mod input;
|
||||||
mod input_number;
|
mod input_number;
|
||||||
mod layout;
|
mod layout;
|
||||||
mod menu;
|
mod menu;
|
||||||
|
mod message;
|
||||||
pub mod mobile;
|
pub mod mobile;
|
||||||
mod modal;
|
mod modal;
|
||||||
mod progress;
|
mod progress;
|
||||||
|
@ -46,6 +47,7 @@ pub use input::*;
|
||||||
pub use input_number::*;
|
pub use input_number::*;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
pub use menu::*;
|
pub use menu::*;
|
||||||
|
pub use message::*;
|
||||||
pub use modal::*;
|
pub use modal::*;
|
||||||
pub use progress::*;
|
pub use progress::*;
|
||||||
pub use select::*;
|
pub use select::*;
|
||||||
|
|
44
src/message/message.css
Normal file
44
src/message/message.css
Normal file
|
@ -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;
|
||||||
|
}
|
44
src/message/message.rs
Normal file
44
src/message/message.rs
Normal file
|
@ -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! {
|
||||||
|
<div class="melt-message-wrapper">
|
||||||
|
<div class="melt-message">
|
||||||
|
<div class="melt-message__icon">
|
||||||
|
<Icon icon=variant.icon() style/>
|
||||||
|
</div>
|
||||||
|
<div class="melt-message__content">{content}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
19
src/message/message_environment.rs
Normal file
19
src/message/message_environment.rs
Normal file
|
@ -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<Uuid>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let (id, content, variant, options) = message;
|
||||||
|
set_timeout(
|
||||||
|
move || {
|
||||||
|
on_internal_after_leave.call(id);
|
||||||
|
},
|
||||||
|
options.duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
view! { <Message content variant/> }
|
||||||
|
}
|
78
src/message/message_provider.rs
Normal file
78
src/message/message_provider.rs
Normal file
|
@ -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<MessageType>>(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()}
|
||||||
|
<Teleport>
|
||||||
|
<div class="melt-message-container">
|
||||||
|
<For
|
||||||
|
each=move || message_list.get()
|
||||||
|
key=|message| message.0
|
||||||
|
children=move |message| {
|
||||||
|
view! {
|
||||||
|
<MessageEnvironment message on_internal_after_leave=handle_after_leave/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Vec<MessageType>>,
|
||||||
|
}
|
||||||
|
impl Copy for MessageInjection {}
|
||||||
|
|
||||||
|
impl MessageInjection {
|
||||||
|
fn new(message_list: RwSignal<Vec<MessageType>>) -> 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::<MessageInjection>()
|
||||||
|
}
|
6
src/message/mod.rs
Normal file
6
src/message/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
mod message;
|
||||||
|
mod message_environment;
|
||||||
|
mod message_provider;
|
||||||
|
|
||||||
|
pub use message::*;
|
||||||
|
pub use message_provider::*;
|
Loading…
Add table
Reference in a new issue