diff --git a/demo_markdown/docs/toast/mod.md b/demo_markdown/docs/toast/mod.md index 8bda118..4234fbe 100644 --- a/demo_markdown/docs/toast/mod.md +++ b/demo_markdown/docs/toast/mod.md @@ -1,14 +1,64 @@ # Toast ```rust demo -let toaster = ToasterInjection::use_(); +let toaster = ToasterInjection::expect_context(); let on_click = move |_| { - toaster.dispatch_toast(view! { "Hello" }.into_any(), Default::default()); + toaster.dispatch_toast(view! { + + "Email sent" + + "This is a toast body" + + "Subtitle" + + + + "Footer" + // Action + // Action + + + }.into_any(), Default::default()); }; view! { } +``` -``` \ No newline at end of file +### Toast Positions + +```rust demo +let toaster = ToasterInjection::expect_context(); + +let dispatch_toast = Callback::new(move |position| { + toaster.dispatch_toast(view! { + + "Email sent" + + "This is a toast body" + + "Subtitle" + + + + "Footer" + // Action + // Action + + + }.into_any(), ToastOptions::default().with_position(position)); +}); + +view! { + + + + + + + + +} +``` diff --git a/thaw/src/config_provider/mod.rs b/thaw/src/config_provider/mod.rs index be5f50f..5e6dcc4 100644 --- a/thaw/src/config_provider/mod.rs +++ b/thaw/src/config_provider/mod.rs @@ -71,6 +71,10 @@ impl ConfigInjection { pub fn use_() -> ConfigInjection { expect_context() } + + pub fn expect_context() -> Self { + expect_context() + } } #[derive(Clone)] diff --git a/thaw/src/theme/color.rs b/thaw/src/theme/color.rs index f34b108..75253a6 100644 --- a/thaw/src/theme/color.rs +++ b/thaw/src/theme/color.rs @@ -109,6 +109,7 @@ pub struct ColorTheme { pub color_transparent_stroke: String, pub shadow4: String, + pub shadow8: String, pub shadow16: String, } @@ -223,6 +224,7 @@ impl ColorTheme { color_transparent_stroke: "transparent".into(), shadow4: "0 0 2px rgba(0,0,0,0.12), 0 2px 4px rgba(0,0,0,0.14)".into(), + shadow8: "0 0 2px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.14)".into(), shadow16: "0 0 2px rgba(0,0,0,0.12), 0 8px 16px rgba(0,0,0,0.14)".into(), } } @@ -337,6 +339,7 @@ impl ColorTheme { color_transparent_stroke: "transparent".into(), shadow4: "0 0 2px rgba(0,0,0,0.24), 0 2px 4px rgba(0,0,0,0.28)".into(), + shadow8: "0 0 2px rgba(0,0,0,0.24), 0 4px 8px rgba(0,0,0,0.28)".into(), shadow16: "0 0 2px rgba(0,0,0,0.24), 0 8px 16px rgba(0,0,0,0.28)".into(), } } diff --git a/thaw/src/toast/mod.rs b/thaw/src/toast/mod.rs index b975189..6652abd 100644 --- a/thaw/src/toast/mod.rs +++ b/thaw/src/toast/mod.rs @@ -22,7 +22,7 @@ pub struct ToasterInjection { } impl ToasterInjection { - pub fn use_() -> Self { + pub fn expect_context() -> Self { expect_context() } diff --git a/thaw/src/toast/toast.rs b/thaw/src/toast/toast.rs index efa65b9..8a1de06 100644 --- a/thaw/src/toast/toast.rs +++ b/thaw/src/toast/toast.rs @@ -25,20 +25,29 @@ impl ToastPosition { pub fn as_str(&self) -> &'static str { match self { Self::Top => "top", - Self::TopStart => "top-left", - Self::TopEnd => "top-right", + Self::TopStart => "top-start", + Self::TopEnd => "top-dnc", Self::Bottom => "bottom", - Self::BottomStart => "bottom-left", - Self::BottomEnd => "bottom-right", + Self::BottomStart => "bottom-start", + Self::BottomEnd => "bottom-end", } } } +#[derive(Debug, Clone)] +pub enum ToastIntent { + Success, + Info, + Warning, + Error, +} + #[derive(Clone)] pub struct ToastOptions { pub(crate) id: uuid::Uuid, pub(crate) position: Option, pub(crate) timeout: Option, + pub(crate) intent: Option, } impl Default for ToastOptions { @@ -47,6 +56,24 @@ impl Default for ToastOptions { id: uuid::Uuid::new_v4(), position: None, timeout: None, + intent: None, } } } + +impl ToastOptions { + pub fn with_position(mut self, position: ToastPosition) -> Self { + self.position = Some(position); + self + } + + pub fn with_timeout(mut self, timeout: Duration) -> Self { + self.timeout = Some(timeout); + self + } + + pub fn with_intent(mut self, intent: ToastIntent) -> Self { + self.intent = Some(intent); + self + } +} diff --git a/thaw/src/toast/toast_body.rs b/thaw/src/toast/toast_body.rs index f2d5d93..34ba062 100644 --- a/thaw/src/toast/toast_body.rs +++ b/thaw/src/toast/toast_body.rs @@ -7,9 +7,13 @@ pub fn ToastBody( children: Children, ) -> impl IntoView { view! { - {children()} +
+ {children()} +
- {(subtitle.children)()} +
+ {(subtitle.children)()} +
} } diff --git a/thaw/src/toast/toast_footer.rs b/thaw/src/toast/toast_footer.rs index a84e131..83e4315 100644 --- a/thaw/src/toast/toast_footer.rs +++ b/thaw/src/toast/toast_footer.rs @@ -2,5 +2,9 @@ use leptos::prelude::*; #[component] pub fn ToastFooter(children: Children) -> impl IntoView { - children() + view! { + + } } diff --git a/thaw/src/toast/toast_title.rs b/thaw/src/toast/toast_title.rs index ffc08a4..cb0aebd 100644 --- a/thaw/src/toast/toast_title.rs +++ b/thaw/src/toast/toast_title.rs @@ -1,4 +1,4 @@ -use leptos::prelude::*; +use leptos::{either::Either, prelude::*}; use thaw_components::OptionComp; #[component] @@ -9,13 +9,25 @@ pub fn ToastTitle( ) -> impl IntoView { view! {
- + { + if let Some(media) = toast_title_media { + Either::Left((media.children)()) + } else { + Either::Right(view! { + + }) + } + } +
+
+ {children()}
- {children()} - {(action.children)()} +
+ {(action.children)()} +
} } diff --git a/thaw/src/toast/toaster.css b/thaw/src/toast/toaster.css index d34fa77..cf3b453 100644 --- a/thaw/src/toast/toaster.css +++ b/thaw/src/toast/toaster.css @@ -1,4 +1,4 @@ -div.thaw-toaster { +div.thaw-toaster-wrapper { z-index: 1000000; position: absolute; top: 0px; @@ -13,15 +13,51 @@ div.thaw-toaster { color: var(--colorNeutralForeground1); } -.thaw-toaster-container { - bottom: 16px; - right: 20px; - +.thaw-toaster { position: fixed; width: 292px; pointer-events: none; } +.thaw-toaster--top { + top: 16px; + left: calc(50% + 20px); + transform: translateX(-50%); +} + +.thaw-toaster--top-start { + top: 16px; + left: 20px; +} + +.thaw-toaster--top-end { + top: 16px; + right: 20px; +} + +.thaw-toaster--bottom { + bottom: 16px; + left: calc(50% + 20px); + transform: translateX(-50%); +} + +.thaw-toaster--bottom-start { + bottom: 16px; + left: 20px; +} + +.thaw-toaster--bottom-end { + bottom: 16px; + right: 20px; +} + +.thaw-toaster-container { + box-sizing: border-box; + margin-top: 16px; + pointer-events: all; + border-radius: var(--borderRadiusMedium); +} + .thaw-toaster-container.fade-in-height-expand-transition-leave-from, .thaw-toaster-container.fade-in-height-expand-transition-enter-to { transform: scale(1); @@ -95,3 +131,33 @@ div.thaw-toaster { grid-column-end: -1; color: var(--colorBrandForeground1); } + +.thaw-toast-body { + grid-column-start: 2; + grid-column-end: 3; + padding-top: 6px; + font-size: var(--fontSizeBase300); + line-height: var(--fontSizeBase300); + font-weight: var(--fontWeightRegular); + color: var(--colorNeutralForeground1); + word-break: break-word; +} + +.thaw-toast-body__subtitle { + padding-top: 4px; + grid-column-start: 2; + grid-column-end: 3; + font-size: var(--fontSizeBase200); + line-height: var(--fontSizeBase200); + font-weight: var(--fontWeightRegular); + color: var(--colorNeutralForeground2); +} + +.thaw-toast-footer { + padding-top: 16px; + grid-column-start: 2; + grid-column-end: 3; + display: flex; + align-items: center; + gap: 14px; +} diff --git a/thaw/src/toast/toaster.rs b/thaw/src/toast/toaster.rs index 79bba4f..63ae801 100644 --- a/thaw/src/toast/toaster.rs +++ b/thaw/src/toast/toaster.rs @@ -1,4 +1,5 @@ use super::{ToastOptions, ToastPosition, ToasterReceiver}; +use crate::ConfigInjection; use leptos::{either::Either, html, prelude::*, tachys::view::any_view::AnyView}; use send_wrapper::SendWrapper; use std::{collections::HashMap, time::Duration}; @@ -9,14 +10,29 @@ use thaw_utils::mount_style; pub fn Toaster( receiver: ToasterReceiver, #[prop(optional)] position: ToastPosition, - #[prop(default = Duration::from_secs(3))] timeout: Duration, + #[prop(default = Duration::from_secs(3000))] timeout: Duration, ) -> impl IntoView { mount_style("toaster", include_str!("./toaster.css")); + let config_provider = ConfigInjection::expect_context(); + let top_id_list = RwSignal::>::new(Default::default()); + let top_start_id_list = RwSignal::>::new(Default::default()); + let top_end_id_list = RwSignal::>::new(Default::default()); + let bottom_id_list = RwSignal::>::new(Default::default()); let bottom_start_id_list = RwSignal::>::new(Default::default()); + let bottom_end_id_list = RwSignal::>::new(Default::default()); let toasts = StoredValue::>, ToastOptions)>>::new( Default::default(), ); + let id_list = move |position: &ToastPosition| match position { + ToastPosition::Top => top_id_list, + ToastPosition::TopStart => top_start_id_list, + ToastPosition::TopEnd => top_end_id_list, + ToastPosition::Bottom => bottom_id_list, + ToastPosition::BottomStart => bottom_start_id_list, + ToastPosition::BottomEnd => bottom_end_id_list, + }; + Effect::new(move |_| { for (view, mut options) in receiver.try_recv() { if options.position.is_none() { @@ -25,57 +41,124 @@ pub fn Toaster( if options.timeout.is_none() { options.timeout = Some(timeout); } - match options.position.unwrap() { - ToastPosition::Top => todo!(), - ToastPosition::TopStart => todo!(), - ToastPosition::TopEnd => todo!(), - ToastPosition::Bottom => todo!(), - ToastPosition::BottomStart => { - let id = options.id; - toasts.update_value(|map| { - map.insert(id, (SendWrapper::new(view), options)); - }); - bottom_start_id_list.update(|list| { - list.push(id); - }); - } - ToastPosition::BottomEnd => todo!(), - } + + let list = id_list(&options.position.unwrap()); + let id = options.id; + toasts.update_value(|map| { + map.insert(id, (SendWrapper::new(view), options)); + }); + list.update(|list| { + list.push(id); + }); } }); - let on_close = move |(id, position)| match position { - ToastPosition::Top => todo!(), - ToastPosition::TopStart => todo!(), - ToastPosition::TopEnd => todo!(), - ToastPosition::Bottom => todo!(), - ToastPosition::BottomStart => { - bottom_start_id_list.update(move |list| { - let Some(index) = list.iter().position(|item_id| &id == item_id) else { - return; - }; - list.remove(index); - }); - } - ToastPosition::BottomEnd => todo!(), + let on_close = move |(id, position)| { + let list = id_list(&position); + list.update(move |list| { + let Some(index) = list.iter().position(|item_id| &id == item_id) else { + return; + }; + list.remove(index); + }); }; view! { -
- - { - if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { - Either::Left(view! { }) - } else { - Either::Right(()) +
+
+ + { + if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { + Either::Left(view! { }) + } else { + Either::Right(()) + } } - } - + +
+
+ + { + if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { + Either::Left(view! { }) + } else { + Either::Right(()) + } + } + +
+
+ + { + if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { + Either::Left(view! { }) + } else { + Either::Right(()) + } + } + +
+
+ + { + if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { + Either::Left(view! { }) + } else { + Either::Right(()) + } + } + +
+
+ + { + if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { + Either::Left(view! { }) + } else { + Either::Right(()) + } + } + +
+
+ + { + if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { + Either::Left(view! { }) + } else { + Either::Right(()) + } + } + +
} @@ -93,6 +176,7 @@ fn ToasterContainer( id, timeout, position, + .. } = options; let timeout = timeout.unwrap(); let position = position.unwrap();