feat: optimize Toast component

This commit is contained in:
luoxiao 2024-07-11 17:37:21 +08:00
parent 8b122d0986
commit ca13969a01
4 changed files with 160 additions and 29 deletions

View file

@ -126,11 +126,11 @@ fn TheProvider(children: Children) -> impl IntoView {
view! { view! {
<ConfigProvider> <ConfigProvider>
// <ToasterProvider> <ToasterProvider>
<LoadingBarProvider> <LoadingBarProvider>
{children()} {children()}
</LoadingBarProvider> </LoadingBarProvider>
// </ToasterProvider> </ToasterProvider>
</ConfigProvider> </ConfigProvider>
} }
} }

View file

@ -1,4 +1,5 @@
use leptos::prelude::*; use leptos::prelude::*;
use std::time::Duration;
#[component] #[component]
pub fn Toast(children: Children) -> impl IntoView { 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 { pub enum ToastPosition {
Top, Top,
TopStart, TopStart,
#[default]
TopEnd, TopEnd,
Bottom, Bottom,
BottomStart, BottomStart,
#[default]
BottomEnd, BottomEnd,
} }
@ -35,15 +36,17 @@ impl ToastPosition {
#[derive(Clone)] #[derive(Clone)]
pub struct ToastOptions { pub struct ToastOptions {
pub id: uuid::Uuid, pub(crate) id: uuid::Uuid,
pub postition: Option<ToastPosition>, pub(crate) position: Option<ToastPosition>,
pub(crate) timeout: Option<Duration>,
} }
impl Default for ToastOptions { impl Default for ToastOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {
id: uuid::Uuid::new_v4(), id: uuid::Uuid::new_v4(),
postition: None, position: None,
timeout: None,
} }
} }
} }

View file

@ -1,4 +1,4 @@
div.thaw-toaster-container { div.thaw-toaster {
z-index: 1000000; z-index: 1000000;
position: absolute; position: absolute;
top: 0px; top: 0px;
@ -13,15 +13,45 @@ div.thaw-toaster-container {
color: var(--colorNeutralForeground1); color: var(--colorNeutralForeground1);
} }
.thaw-toaster { .thaw-toaster-container {
bottom: 16px; bottom: 16px;
right: 20px; right: 20px;
position: fixed; position: fixed;
width: 292px; width: 292px;
pointer-events: none; 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 { .thaw-toast {
display: grid; display: grid;
grid-template-columns: auto 1fr auto; grid-template-columns: auto 1fr auto;

View file

@ -1,36 +1,134 @@
use super::{ToastPosition, ToasterReceiver}; use super::{ToastOptions, ToastPosition, ToasterReceiver};
use leptos::prelude::*; use leptos::{either::Either, html, prelude::*, tachys::view::any_view::AnyView};
use thaw_components::Teleport; use send_wrapper::SendWrapper;
use thaw_utils::{class_list, mount_style}; use std::{collections::HashMap, time::Duration};
use thaw_components::{CSSTransition, Teleport};
use thaw_utils::mount_style;
#[component] #[component]
pub fn Toaster( pub fn Toaster(
receiver: ToasterReceiver, receiver: ToasterReceiver,
#[prop(optional)] position: ToastPosition, #[prop(optional)] position: ToastPosition,
#[prop(default = Duration::from_secs(3))] timeout: Duration,
) -> impl IntoView { ) -> impl IntoView {
mount_style("toaster", include_str!("./toaster.css")); mount_style("toaster", include_str!("./toaster.css"));
// let toast_list = RwSignal::new(vec![]); let bottom_start_id_list = RwSignal::<Vec<uuid::Uuid>>::new(Default::default());
let toasts = StoredValue::<HashMap<uuid::Uuid, (SendWrapper<AnyView<Dom>>, ToastOptions)>>::new(
Default::default(),
);
Effect::new(move |_| { Effect::new(move |_| {
for view in receiver.try_recv() { for (view, mut options) in receiver.try_recv() {
// toast_list.update(move |list| { if options.position.is_none() {
// list.push(view.0); 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! { view! {
<Teleport> <Teleport>
<div class="thaw-config-provider thaw-toaster-container"> <div class="thaw-config-provider thaw-toaster">
// <For <For
// each=move || toast_list.get() each=move || bottom_start_id_list.get()
// key=|toast| toast.1.id key=|id| id.clone()
// let:toast let:id
// > >
// <div class=class_list!["thaw-toaster", "thaw-toaster"]> {
// {toast.0} if let Some((view, options)) = toasts.try_update_value(|map| { map.remove(&id) }).flatten() {
// </div> Either::Left(view! { <ToasterContainer on_close view=view.take() options/> })
// </For> } else {
Either::Right(())
}
}
</For>
</div> </div>
</Teleport> </Teleport>
} }
} }
#[component]
fn ToasterContainer(
view: AnyView<Dom>,
options: ToastOptions,
#[prop(into)] on_close: Callback<(uuid::Uuid, ToastPosition)>,
) -> impl IntoView {
let container_ref = NodeRef::<html::Div>::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! {
<CSSTransition
node_ref=container_ref
name="fade-in-height-expand-transition"
show=is_show
appear=true
on_before_leave=on_before_leave
on_after_leave=on_after_leave
let:_
>
<div class="thaw-toaster-container" node_ref=container_ref>
{view}
</div>
</CSSTransition>
}
}