mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19:22 -05:00
feat: optimize Toast component
This commit is contained in:
parent
8b122d0986
commit
ca13969a01
4 changed files with 160 additions and 29 deletions
|
@ -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>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +13,7 @@ div.thaw-toaster-container {
|
||||||
color: var(--colorNeutralForeground1);
|
color: var(--colorNeutralForeground1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thaw-toaster {
|
.thaw-toaster-container {
|
||||||
bottom: 16px;
|
bottom: 16px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
|
|
||||||
|
@ -22,6 +22,36 @@ div.thaw-toaster-container {
|
||||||
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;
|
||||||
|
|
|
@ -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>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue