toast.rs
| 1 | use std::time::Duration; |
| 2 | |
| 3 | use leptos::prelude::*; |
| 4 | use wasm_bindgen_futures::spawn_local; |
| 5 | |
| 6 | /// Context type: read/write access to the current toast. |
| 7 | /// |
| 8 | /// The state is `(message, is_error)`: errors get their own styling so they |
| 9 | /// do not read like routine confirmations. |
| 10 | #[derive(Copy, Clone)] |
| 11 | pub struct ToastMsg(pub RwSignal<Option<(String, bool)>>); |
| 12 | |
| 13 | pub fn provide_toast() -> ToastMsg { |
| 14 | let toast = RwSignal::new(Option::<(String, bool)>::None); |
| 15 | let t = ToastMsg(toast); |
| 16 | provide_context(t); |
| 17 | t |
| 18 | } |
| 19 | |
| 20 | /// Show a toast that disappears after ~3 seconds. |
| 21 | pub fn show(toast: ToastMsg, msg: impl Into<String>) { |
| 22 | set(toast, msg.into(), false); |
| 23 | } |
| 24 | |
| 25 | /// Show a failure toast (danger styling). |
| 26 | pub fn show_error(toast: ToastMsg, msg: impl Into<String>) { |
| 27 | set(toast, msg.into(), true); |
| 28 | } |
| 29 | |
| 30 | fn set(toast: ToastMsg, msg: String, is_error: bool) { |
| 31 | toast.0.set(Some((msg.clone(), is_error))); |
| 32 | spawn_local(async move { |
| 33 | gloo_timers::future::sleep(Duration::from_secs(3)).await; |
| 34 | if toast.0.get_untracked().as_ref().map(|(m, _)| m.as_str()) == Some(msg.as_str()) { |
| 35 | toast.0.set(None); |
| 36 | } |
| 37 | }); |
| 38 | } |
| 39 | |
| 40 | #[component] |
| 41 | pub fn ToastView(toast: ToastMsg) -> impl IntoView { |
| 42 | view! { |
| 43 | <Show when=move || toast.0.get().is_some()> |
| 44 | <div class="toast" class:toast-error=move || toast.0.get().is_some_and(|(_, e)| e)> |
| 45 | {move || toast.0.get().map(|(m, _)| m).unwrap_or_default()} |
| 46 | </div> |
| 47 | </Show> |
| 48 | } |
| 49 | } |
| 50 |