From ca13969a015872b12f9f6294c6314ef2765931ac Mon Sep 17 00:00:00 2001 From: luoxiao Date: Thu, 11 Jul 2024 17:37:21 +0800 Subject: [PATCH] feat: optimize Toast component --- demo/src/app.rs | 4 +- thaw/src/toast/toast.rs | 13 ++-- thaw/src/toast/toaster.css | 36 +++++++++- thaw/src/toast/toaster.rs | 136 +++++++++++++++++++++++++++++++------ 4 files changed, 160 insertions(+), 29 deletions(-) diff --git a/demo/src/app.rs b/demo/src/app.rs index ace30e5..fbdf4e8 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -126,11 +126,11 @@ fn TheProvider(children: Children) -> impl IntoView { view! { - // + {children()} - // + } } diff --git a/thaw/src/toast/toast.rs b/thaw/src/toast/toast.rs index 9e80f02..efa65b9 100644 --- a/thaw/src/toast/toast.rs +++ b/thaw/src/toast/toast.rs @@ -1,4 +1,5 @@ use leptos::prelude::*; +use std::time::Duration; #[component] pub fn Toast(children: Children) -> impl IntoView { @@ -9,14 +10,14 @@ pub fn Toast(children: Children) -> impl IntoView { } } -#[derive(Default, Clone)] +#[derive(Default, Clone, Copy)] pub enum ToastPosition { Top, TopStart, - #[default] TopEnd, Bottom, BottomStart, + #[default] BottomEnd, } @@ -35,15 +36,17 @@ impl ToastPosition { #[derive(Clone)] pub struct ToastOptions { - pub id: uuid::Uuid, - pub postition: Option, + pub(crate) id: uuid::Uuid, + pub(crate) position: Option, + pub(crate) timeout: Option, } impl Default for ToastOptions { fn default() -> Self { Self { id: uuid::Uuid::new_v4(), - postition: None, + position: None, + timeout: None, } } } diff --git a/thaw/src/toast/toaster.css b/thaw/src/toast/toaster.css index d323a47..d34fa77 100644 --- a/thaw/src/toast/toaster.css +++ b/thaw/src/toast/toaster.css @@ -1,4 +1,4 @@ -div.thaw-toaster-container { +div.thaw-toaster { z-index: 1000000; position: absolute; top: 0px; @@ -13,15 +13,45 @@ div.thaw-toaster-container { color: var(--colorNeutralForeground1); } -.thaw-toaster { +.thaw-toaster-container { bottom: 16px; right: 20px; - + position: fixed; width: 292px; pointer-events: none; } +.thaw-toaster-container.fade-in-height-expand-transition-leave-from, +.thaw-toaster-container.fade-in-height-expand-transition-enter-to { + transform: scale(1); + opacity: 1; +} + +.thaw-toaster-container.fade-in-height-expand-transition-leave-to, +.thaw-toaster-container.fade-in-height-expand-transition-enter-from { + transform: scale(0.85); + opacity: 0; + margin-bottom: 0 !important; + max-height: 0 !important; +} + +.thaw-toaster-container.fade-in-height-expand-transition-leave-active { + overflow: visible; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0s, + opacity 0.3s cubic-bezier(0, 0, 0.2, 1) 0s, + margin-bottom 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0s, + transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.thaw-toaster-container.fade-in-height-expand-transition-enter-active { + overflow: visible; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.3s cubic-bezier(0.4, 0, 1, 1), + margin-bottom 0.3s cubic-bezier(0.4, 0, 0.2, 1), + transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + .thaw-toast { display: grid; grid-template-columns: auto 1fr auto; diff --git a/thaw/src/toast/toaster.rs b/thaw/src/toast/toaster.rs index 3f054c8..79bba4f 100644 --- a/thaw/src/toast/toaster.rs +++ b/thaw/src/toast/toaster.rs @@ -1,36 +1,134 @@ -use super::{ToastPosition, ToasterReceiver}; -use leptos::prelude::*; -use thaw_components::Teleport; -use thaw_utils::{class_list, mount_style}; +use super::{ToastOptions, ToastPosition, ToasterReceiver}; +use leptos::{either::Either, html, prelude::*, tachys::view::any_view::AnyView}; +use send_wrapper::SendWrapper; +use std::{collections::HashMap, time::Duration}; +use thaw_components::{CSSTransition, Teleport}; +use thaw_utils::mount_style; #[component] pub fn Toaster( receiver: ToasterReceiver, #[prop(optional)] position: ToastPosition, + #[prop(default = Duration::from_secs(3))] timeout: Duration, ) -> impl IntoView { mount_style("toaster", include_str!("./toaster.css")); - // let toast_list = RwSignal::new(vec![]); + let bottom_start_id_list = RwSignal::>::new(Default::default()); + let toasts = StoredValue::>, ToastOptions)>>::new( + Default::default(), + ); Effect::new(move |_| { - for view in receiver.try_recv() { - // toast_list.update(move |list| { - // list.push(view.0); - // }); + for (view, mut options) in receiver.try_recv() { + if options.position.is_none() { + options.position = Some(position); + } + 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 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!(), + }; + view! { -
- // - //
- // {toast.0} - //
- //
+
+ + { + if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() { + Either::Left(view! { }) + } else { + Either::Right(()) + } + } +
} } + +#[component] +fn ToasterContainer( + view: AnyView, + options: ToastOptions, + #[prop(into)] on_close: Callback<(uuid::Uuid, ToastPosition)>, +) -> impl IntoView { + let container_ref = NodeRef::::new(); + let is_show = RwSignal::new(true); + let ToastOptions { + id, + timeout, + position, + } = options; + let timeout = timeout.unwrap(); + let position = position.unwrap(); + + if !timeout.is_zero() { + set_timeout( + move || { + is_show.set(false); + }, + timeout, + ); + } + + let on_before_leave = move |_| { + let Some(el) = container_ref.get_untracked() else { + return; + }; + el.style(("max-height", format!("{}px", el.offset_height()))); + }; + let on_after_leave = move |_| { + request_animation_frame(move || on_close.call((id, position))); + }; + + view! { + +
+ {view} +
+
+ } +}