use leptos::prelude::*; use std::collections::HashMap; use uuid::Uuid; use shared::NotificationLevel; #[derive(Clone, Debug, PartialEq)] pub struct Toast { pub id: String, pub message: String, pub level: NotificationLevel, pub visible: RwSignal, } #[derive(Clone, Copy)] pub struct ToastContext { pub toasts: RwSignal>, } impl ToastContext { pub fn add(&self, message: impl Into, level: NotificationLevel) { let id = Uuid::new_v4().to_string(); let message = message.into(); let toast = Toast { id: id.clone(), message, level, visible: RwSignal::new(true), }; self.toasts.update(|m| { m.insert(id.clone(), toast); }); // Auto remove after 5 seconds let toasts = self.toasts; let id_clone = id.clone(); leptos::task::spawn_local(async move { gloo_timers::future::TimeoutFuture::new(5000).await; toasts.update(|m| { if let Some(t) = m.get(&id_clone) { t.visible.set(false); } }); // Wait for animation gloo_timers::future::TimeoutFuture::new(300).await; toasts.update(|m| { m.remove(&id_clone); }); }); } } pub fn provide_toast_context() { let toasts = RwSignal::new(HashMap::new()); provide_context(ToastContext { toasts }); } #[component] pub fn Toaster() -> impl IntoView { let context = expect_context::(); view! {
{move || { context.toasts.get().into_values().map(|toast| { view! { } }).collect::>() }}
} } #[component] fn ToastItem(toast: Toast) -> impl IntoView { let (visible, set_visible) = (toast.visible, toast.visible.write_only()); let base_classes = "pointer-events-auto relative w-full rounded-lg border p-4 shadow-lg transition-all duration-300 ease-in-out"; let color_classes = match toast.level { NotificationLevel::Success => "bg-green-50 text-green-900 border-green-200 dark:bg-green-900 dark:text-green-100 dark:border-green-800", NotificationLevel::Error => "bg-red-50 text-red-900 border-red-200 dark:bg-red-900 dark:text-red-100 dark:border-red-800", NotificationLevel::Warning => "bg-yellow-50 text-yellow-900 border-yellow-200 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-800", NotificationLevel::Info => "bg-blue-50 text-blue-900 border-blue-200 dark:bg-blue-900 dark:text-blue-100 dark:border-blue-800", }; view! { } }