Feature/toast on status change (#274)

* toast status change

* toast status change

* toast status change

* dismiss all
This commit is contained in:
kandrelczyk 2024-10-07 08:13:13 +02:00 committed by GitHub
parent e18bbff216
commit f9d0fdc2ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 102 additions and 12 deletions

View file

@ -102,6 +102,12 @@ let toaster = ToasterInjection::expect_context();
let id = uuid::Uuid::new_v4(); let id = uuid::Uuid::new_v4();
let mounted = RwSignal::new(false);
let on_status_change = move |status| {
mounted.set(status == ToastStatus::Mounted);
};
let dispatch = move |_| { let dispatch = move |_| {
toaster.dispatch_toast(move || view! { toaster.dispatch_toast(move || view! {
<Toast> <Toast>
@ -113,7 +119,7 @@ let dispatch = move |_| {
</ToastBodySubtitle> </ToastBodySubtitle>
</ToastBody> </ToastBody>
</Toast> </Toast>
},ToastOptions::default().with_id(id)); },ToastOptions::default().with_id(id).with_on_status_change(on_status_change))
}; };
let dismiss = move |_| { let dismiss = move |_| {
@ -121,8 +127,45 @@ let dismiss = move |_| {
}; };
view! { view! {
<Button on_click=dispatch>"Show toast"</Button> {move || {if !mounted.get() {
<Button on_click=dismiss>"Hide toast"</Button> view!{<Button on_click=dispatch>"Show toast"</Button>}
} else {
view!{<Button on_click=dismiss>"Hide toast"</Button>}
}
}}}
```
### Dismiss All
```rust demo
let toaster = ToasterInjection::expect_context();
fn dispatch_toast(toaster: ToasterInjection) {
toaster.dispatch_toast(move || view! {
<Toast>
<ToastTitle>"Email sent"</ToastTitle>
<ToastBody>
"This is a toast body"
<ToastBodySubtitle slot>
"Subtitle"
</ToastBodySubtitle>
</ToastBody>
<ToastFooter>
"Footer"
</ToastFooter>
</Toast>
}, ToastOptions::default());
};
let dismiss_all = move || {
toaster.dismiss_all();
};
view! {
<Space>
<Button on_click=move |_| dispatch_toast(toaster)>"Dispatch toast"</Button>
<Button on_click=move |_| dismiss_all()>"Dismiss all"</Button>
</Space>
} }
``` ```
@ -160,11 +203,12 @@ view! {
### ToastOptions Props ### ToastOptions Props
| Name | Type | Description | | Name | Type | Description |
| ------------- | --------------------------------------- | ------------------------------------- | | --------------------- | ----------------------------------------------------- | ------------------------------------- |
| with_position | `Fn(mut self, position: ToastPosition)` | The position the toast should render. | | with_position | `Fn(mut self, position: ToastPosition)` | The position the toast should render. |
| with_timeout | `Fn(mut self, timeout: Duration)` | Auto dismiss timeout in milliseconds. | | with_timeout | `Fn(mut self, timeout: Duration)` | Auto dismiss timeout in milliseconds. |
| with_intent | `Fn(mut self, intent: ToastIntent)` | The intent of the toast. | | with_intent | `Fn(mut self, intent: ToastIntent)` | The intent of the toast. |
| with_on_status_change | `Fn(mut self, on_status_change: Fn(ToastStatus))` | The intent of the toast. |
### Toast & ToastFooter Props ### Toast & ToastFooter Props

View file

@ -24,6 +24,7 @@ pub struct ToasterInjection {
enum ToasterMessage { enum ToasterMessage {
Dispatch(Children, ToastOptions), Dispatch(Children, ToastOptions),
Dismiss(uuid::Uuid), Dismiss(uuid::Uuid),
DismissAll,
} }
impl ToasterInjection { impl ToasterInjection {
@ -53,6 +54,15 @@ impl ToasterInjection {
self.trigger.with_value(|trigger| trigger.notify()); self.trigger.with_value(|trigger| trigger.notify());
} }
pub fn dismiss_all(&self) {
self.sender.with_value(|sender| {
sender
.send(ToasterMessage::DismissAll)
.unwrap_throw()
});
self.trigger.with_value(|trigger| trigger.notify());
}
pub fn dispatch_toast<C, IV>(&self, children: C, options: ToastOptions) pub fn dispatch_toast<C, IV>(&self, children: C, options: ToastOptions)
where where
C: FnOnce() -> IV + Send + 'static, C: FnOnce() -> IV + Send + 'static,

View file

@ -1,6 +1,6 @@
use leptos::prelude::*; use leptos::prelude::*;
use std::time::Duration; use std::time::Duration;
use thaw_utils::class_list; use thaw_utils::{class_list, ArcOneCallback};
#[component] #[component]
pub fn Toast( pub fn Toast(
@ -43,12 +43,19 @@ pub enum ToastIntent {
Error, Error,
} }
#[derive(Debug, Clone, PartialEq)]
pub enum ToastStatus {
Mounted,
Unmounted,
}
#[derive(Clone)] #[derive(Clone)]
pub struct ToastOptions { pub struct ToastOptions {
pub(crate) id: uuid::Uuid, pub(crate) id: uuid::Uuid,
pub(crate) position: Option<ToastPosition>, pub(crate) position: Option<ToastPosition>,
pub(crate) timeout: Option<Duration>, pub(crate) timeout: Option<Duration>,
pub(crate) intent: Option<ToastIntent>, pub(crate) intent: Option<ToastIntent>,
pub(crate) on_status_change: Option<ArcOneCallback<ToastStatus>>,
} }
impl Default for ToastOptions { impl Default for ToastOptions {
@ -58,6 +65,7 @@ impl Default for ToastOptions {
position: None, position: None,
timeout: None, timeout: None,
intent: None, intent: None,
on_status_change: None,
} }
} }
} }
@ -86,4 +94,13 @@ impl ToastOptions {
self.intent = Some(intent); self.intent = Some(intent);
self self
} }
/// Status change callback.
pub fn with_on_status_change(
mut self,
on_status_change: impl Fn(ToastStatus) + Send + Sync + 'static,
) -> Self {
self.on_status_change = Some(on_status_change.into());
self
}
} }

View file

@ -1,5 +1,5 @@
use super::{ToastIntent, ToastOptions, ToastPosition, ToasterReceiver}; use super::{ToastIntent, ToastOptions, ToastPosition, ToasterReceiver};
use crate::{toast::ToasterMessage, ConfigInjection}; use crate::{toast::ToasterMessage, ConfigInjection, ToastStatus};
use leptos::{context::Provider, either::Either, html, prelude::*}; use leptos::{context::Provider, either::Either, html, prelude::*};
use send_wrapper::SendWrapper; use send_wrapper::SendWrapper;
use std::{collections::HashMap, time::Duration}; use std::{collections::HashMap, time::Duration};
@ -51,10 +51,14 @@ pub fn Toaster(
if options.intent.is_none() { if options.intent.is_none() {
options.intent = Some(intent); options.intent = Some(intent);
} }
let list = id_list(&options.position.unwrap_throw()); let list = id_list(&options.position.unwrap_throw());
let id = options.id; let id = options.id;
let is_show = owner.with(|| RwSignal::new(true)); let is_show = owner.with(|| RwSignal::new(true));
if let Some(on_status_change) = options.on_status_change.clone() {
on_status_change(ToastStatus::Mounted)
}
toasts.update_value(|map| { toasts.update_value(|map| {
map.insert(id, (SendWrapper::new(view), options, is_show)); map.insert(id, (SendWrapper::new(view), options, is_show));
}); });
@ -66,7 +70,18 @@ pub fn Toaster(
}); });
} }
ToasterMessage::Dismiss(toast_id) => { ToasterMessage::Dismiss(toast_id) => {
toast_show_list.with_value(|map| map.get(&toast_id).unwrap_throw().set(false)); toast_show_list.with_value(|map| {
if let Some(is_show) = map.get(&toast_id) {
is_show.set(false)
}
});
},
ToasterMessage::DismissAll => {
toast_show_list.with_value(|map| {
for is_show in map.values() {
is_show.set(false)
}
});
} }
} }
} }
@ -238,6 +253,7 @@ fn ToasterContainer(
timeout, timeout,
position, position,
intent, intent,
on_status_change,
.. ..
} = options; } = options;
@ -266,6 +282,9 @@ fn ToasterContainer(
f(id, position); f(id, position);
} }
}); });
if let Some(on_status_change) = on_status_change.clone() {
on_status_change(ToastStatus::Unmounted);
}
}; };
view! { view! {