mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09: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",
|
||||
] }
|
||||
icondata_core = "0.0.2"
|
||||
uuid = { version = "1.5.0", features = ["v4"] }
|
||||
|
||||
[workspace]
|
||||
members = ["demo"]
|
||||
|
|
|
@ -1,43 +1,47 @@
|
|||
use crate::pages::*;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use melt_ui::*;
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<Router base="/melt-ui">
|
||||
<Routes base="/melt-ui".to_string()>
|
||||
<Route path="/" view=Home/>
|
||||
<Route path="/components" view=ComponentsPage>
|
||||
<Route path="/menu" view=MenuPage/>
|
||||
<Route path="/slider" view=SliderPage/>
|
||||
<Route path="/tabbar" view=TabbarPage/>
|
||||
<Route path="/nav-bar" view=NavBarPage/>
|
||||
<Route path="/input" view=InputPage/>
|
||||
<Route path="/image" view=ImagePage/>
|
||||
<Route path="/modal" view=ModalPage/>
|
||||
<Route path="/button" view=ButtonPage/>
|
||||
<Route path="/checkbox" view=CheckboxPage/>
|
||||
<Route path="/toast" view=ToastPage/>
|
||||
<Route path="/tabs" view=TabsPage/>
|
||||
<Route path="/select" view=SelectPage/>
|
||||
<Route path="/space" view=SpacePage/>
|
||||
<Route path="/table" view=TablePage/>
|
||||
<Route path="/color-picker" view=ColorPickerPage/>
|
||||
<Route path="/alert" view=AlertPage/>
|
||||
<Route path="/grid" view=GridPage/>
|
||||
<Route path="/auto-complete" view=AutoCompletePage/>
|
||||
<Route path="/avatar" view=AvatarPage/>
|
||||
<Route path="/badge" view=BadgePage/>
|
||||
<Route path="/card" view=CardPage/>
|
||||
<Route path="/divider" view=DividerPage/>
|
||||
<Route path="/input-number" view=InputNumberPage/>
|
||||
<Route path="/icon" view=IconPage/>
|
||||
</Route>
|
||||
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||
<Route path="/mobile/toast" view=ToastDemoPage/>
|
||||
</Routes>
|
||||
</Router>
|
||||
<MessageProvider>
|
||||
<Router base="/melt-ui">
|
||||
<Routes base="/melt-ui".to_string()>
|
||||
<Route path="/" view=Home/>
|
||||
<Route path="/components" view=ComponentsPage>
|
||||
<Route path="/menu" view=MenuPage/>
|
||||
<Route path="/slider" view=SliderPage/>
|
||||
<Route path="/tabbar" view=TabbarPage/>
|
||||
<Route path="/nav-bar" view=NavBarPage/>
|
||||
<Route path="/input" view=InputPage/>
|
||||
<Route path="/image" view=ImagePage/>
|
||||
<Route path="/modal" view=ModalPage/>
|
||||
<Route path="/button" view=ButtonPage/>
|
||||
<Route path="/checkbox" view=CheckboxPage/>
|
||||
<Route path="/toast" view=ToastPage/>
|
||||
<Route path="/tabs" view=TabsPage/>
|
||||
<Route path="/select" view=SelectPage/>
|
||||
<Route path="/space" view=SpacePage/>
|
||||
<Route path="/table" view=TablePage/>
|
||||
<Route path="/color-picker" view=ColorPickerPage/>
|
||||
<Route path="/alert" view=AlertPage/>
|
||||
<Route path="/grid" view=GridPage/>
|
||||
<Route path="/auto-complete" view=AutoCompletePage/>
|
||||
<Route path="/avatar" view=AvatarPage/>
|
||||
<Route path="/badge" view=BadgePage/>
|
||||
<Route path="/card" view=CardPage/>
|
||||
<Route path="/divider" view=DividerPage/>
|
||||
<Route path="/input-number" view=InputNumberPage/>
|
||||
<Route path="/icon" view=IconPage/>
|
||||
<Route path="/message" view=MessagePage/>
|
||||
</Route>
|
||||
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||
<Route path="/mobile/toast" view=ToastDemoPage/>
|
||||
</Routes>
|
||||
</Router>
|
||||
</MessageProvider>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,6 +172,10 @@ fn gen_menu_data() -> Vec<MenuGroupOption> {
|
|||
value: "badge".into(),
|
||||
label: "Badge".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "message".into(),
|
||||
label: "Message".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "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_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::*;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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::*;
|
||||
|
|
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