mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
Toast dismiss (#270)
* broken toaster * toast dismiss * toaster test * fixed dismiss * cargo fmt * dipose signal
This commit is contained in:
parent
a03226f202
commit
b831008e09
6 changed files with 159 additions and 41 deletions
|
@ -18,6 +18,7 @@ chrono = "0.4.38"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
# leptos-use = "0.10.10"
|
# leptos-use = "0.10.10"
|
||||||
send_wrapper = "0.6"
|
send_wrapper = "0.6"
|
||||||
|
uuid = { version = "1.10.0", features = ["v4", "js"] }
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
console_log = "1"
|
console_log = "1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::components::{Demo, DemoCode};
|
use crate::components::{Demo, DemoCode};
|
||||||
use leptos::{ev, prelude::*};
|
use leptos::{ev, prelude::*};
|
||||||
use thaw::*;
|
use thaw::*;
|
||||||
|
use uuid;
|
||||||
|
|
||||||
demo_markdown::include_md! {}
|
demo_markdown::include_md! {}
|
||||||
|
|
|
@ -95,6 +95,38 @@ view! {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dismiss Toast
|
||||||
|
|
||||||
|
```rust demo
|
||||||
|
let toaster = ToasterInjection::expect_context();
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
|
|
||||||
|
let dispatch = move |_| {
|
||||||
|
toaster.dispatch_toast(move || view! {
|
||||||
|
<Toast>
|
||||||
|
<ToastTitle>"Email sent"</ToastTitle>
|
||||||
|
<ToastBody>
|
||||||
|
"This is a toast body"
|
||||||
|
<ToastBodySubtitle slot>
|
||||||
|
"Subtitle"
|
||||||
|
</ToastBodySubtitle>
|
||||||
|
</ToastBody>
|
||||||
|
</Toast>
|
||||||
|
},ToastOptions::default().with_id(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
let dismiss = move |_| {
|
||||||
|
toaster.dismiss_toast(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Button on_click=dispatch>"Show toast"</Button>
|
||||||
|
<Button on_click=dismiss>"Hide toast"</Button>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Toast Title Media
|
### Toast Title Media
|
||||||
|
|
||||||
```rust demo
|
```rust demo
|
||||||
|
|
|
@ -17,17 +17,22 @@ use wasm_bindgen::UnwrapThrowExt;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct ToasterInjection {
|
pub struct ToasterInjection {
|
||||||
sender: StoredValue<Sender<(Children, ToastOptions)>>,
|
sender: StoredValue<Sender<ToasterMessage>>,
|
||||||
trigger: StoredValue<ArcTrigger>,
|
trigger: StoredValue<ArcTrigger>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ToasterMessage {
|
||||||
|
Dispatch(Children, ToastOptions),
|
||||||
|
Dismiss(uuid::Uuid),
|
||||||
|
}
|
||||||
|
|
||||||
impl ToasterInjection {
|
impl ToasterInjection {
|
||||||
pub fn expect_context() -> Self {
|
pub fn expect_context() -> Self {
|
||||||
expect_context()
|
expect_context()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel() -> (Self, ToasterReceiver) {
|
pub fn channel() -> (Self, ToasterReceiver) {
|
||||||
let (sender, receiver) = channel::<(Children, ToastOptions)>();
|
let (sender, receiver) = channel::<ToasterMessage>();
|
||||||
let trigger = ArcTrigger::new();
|
let trigger = ArcTrigger::new();
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -39,6 +44,15 @@ impl ToasterInjection {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dismiss_toast(&self, toast_id: uuid::Uuid) {
|
||||||
|
self.sender.with_value(|sender| {
|
||||||
|
sender
|
||||||
|
.send(ToasterMessage::Dismiss(toast_id))
|
||||||
|
.unwrap_throw()
|
||||||
|
});
|
||||||
|
self.trigger.with_value(|trigger| trigger.notify());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dispatch_toast<C, IV>(&self, children: C, options: ToastOptions)
|
pub fn dispatch_toast<C, IV>(&self, children: C, options: ToastOptions)
|
||||||
where
|
where
|
||||||
C: FnOnce() -> IV + Send + 'static,
|
C: FnOnce() -> IV + Send + 'static,
|
||||||
|
@ -46,7 +60,10 @@ impl ToasterInjection {
|
||||||
{
|
{
|
||||||
self.sender.with_value(|sender| {
|
self.sender.with_value(|sender| {
|
||||||
sender
|
sender
|
||||||
.send((Box::new(move || children().into_any()), options))
|
.send(ToasterMessage::Dispatch(
|
||||||
|
Box::new(move || children().into_any()),
|
||||||
|
options,
|
||||||
|
))
|
||||||
.unwrap_throw()
|
.unwrap_throw()
|
||||||
});
|
});
|
||||||
self.trigger.with_value(|trigger| trigger.notify());
|
self.trigger.with_value(|trigger| trigger.notify());
|
||||||
|
@ -54,16 +71,16 @@ impl ToasterInjection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ToasterReceiver {
|
pub struct ToasterReceiver {
|
||||||
receiver: Receiver<(Children, ToastOptions)>,
|
receiver: Receiver<ToasterMessage>,
|
||||||
trigger: ArcTrigger,
|
trigger: ArcTrigger,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToasterReceiver {
|
impl ToasterReceiver {
|
||||||
pub fn new(receiver: Receiver<(Children, ToastOptions)>, trigger: ArcTrigger) -> Self {
|
fn new(receiver: Receiver<ToasterMessage>, trigger: ArcTrigger) -> Self {
|
||||||
Self { receiver, trigger }
|
Self { receiver, trigger }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_recv(&self) -> TryIter<'_, (Children, ToastOptions)> {
|
fn try_recv(&self) -> TryIter<'_, ToasterMessage> {
|
||||||
self.trigger.track();
|
self.trigger.track();
|
||||||
self.receiver.try_iter()
|
self.receiver.try_iter()
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,12 @@ impl Default for ToastOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToastOptions {
|
impl ToastOptions {
|
||||||
|
/// The id that will be assigned to this toast.
|
||||||
|
pub fn with_id(mut self, id: uuid::Uuid) -> Self {
|
||||||
|
self.id = id;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// The position the toast should render.
|
/// The position the toast should render.
|
||||||
pub fn with_position(mut self, position: ToastPosition) -> Self {
|
pub fn with_position(mut self, position: ToastPosition) -> Self {
|
||||||
self.position = Some(position);
|
self.position = Some(position);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{ToastIntent, ToastOptions, ToastPosition, ToasterReceiver};
|
use super::{ToastIntent, ToastOptions, ToastPosition, ToasterReceiver};
|
||||||
use crate::ConfigInjection;
|
use crate::{toast::ToasterMessage, ConfigInjection};
|
||||||
use leptos::{context::Provider, either::Either, html, prelude::*};
|
use leptos::{context::Provider, either::Either, html, prelude::*};
|
||||||
use send_wrapper::SendWrapper;
|
use send_wrapper::SendWrapper;
|
||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
@ -22,9 +22,11 @@ pub fn Toaster(
|
||||||
let bottom_id_list = RwSignal::<Vec<uuid::Uuid>>::new(Default::default());
|
let bottom_id_list = RwSignal::<Vec<uuid::Uuid>>::new(Default::default());
|
||||||
let bottom_start_id_list = RwSignal::<Vec<uuid::Uuid>>::new(Default::default());
|
let bottom_start_id_list = RwSignal::<Vec<uuid::Uuid>>::new(Default::default());
|
||||||
let bottom_end_id_list = RwSignal::<Vec<uuid::Uuid>>::new(Default::default());
|
let bottom_end_id_list = RwSignal::<Vec<uuid::Uuid>>::new(Default::default());
|
||||||
let toasts = StoredValue::<HashMap<uuid::Uuid, (SendWrapper<Children>, ToastOptions)>>::new(
|
let toasts = StoredValue::<
|
||||||
Default::default(),
|
HashMap<uuid::Uuid, (SendWrapper<Children>, ToastOptions, RwSignal<bool>)>,
|
||||||
);
|
>::new(Default::default());
|
||||||
|
let toast_show_list =
|
||||||
|
StoredValue::<HashMap<uuid::Uuid, RwSignal<bool>>>::new(Default::default());
|
||||||
|
|
||||||
let id_list = move |position: &ToastPosition| match position {
|
let id_list = move |position: &ToastPosition| match position {
|
||||||
ToastPosition::Top => top_id_list,
|
ToastPosition::Top => top_id_list,
|
||||||
|
@ -35,26 +37,38 @@ pub fn Toaster(
|
||||||
ToastPosition::BottomEnd => bottom_end_id_list,
|
ToastPosition::BottomEnd => bottom_end_id_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let owner = Owner::current().unwrap();
|
||||||
Effect::new(move |_| {
|
Effect::new(move |_| {
|
||||||
for (view, mut options) in receiver.try_recv() {
|
for message in receiver.try_recv() {
|
||||||
if options.position.is_none() {
|
match message {
|
||||||
options.position = Some(position);
|
ToasterMessage::Dispatch(view, mut options) => {
|
||||||
}
|
if options.position.is_none() {
|
||||||
if options.timeout.is_none() {
|
options.position = Some(position);
|
||||||
options.timeout = Some(timeout);
|
}
|
||||||
}
|
if options.timeout.is_none() {
|
||||||
if options.intent.is_none() {
|
options.timeout = Some(timeout);
|
||||||
options.intent = Some(intent);
|
}
|
||||||
}
|
if options.intent.is_none() {
|
||||||
|
options.intent = Some(intent);
|
||||||
|
}
|
||||||
|
|
||||||
let list = id_list(&options.position.unwrap_throw());
|
let list = id_list(&options.position.unwrap_throw());
|
||||||
let id = options.id;
|
let id = options.id;
|
||||||
toasts.update_value(|map| {
|
let is_show = owner.with(|| RwSignal::new(true));
|
||||||
map.insert(id, (SendWrapper::new(view), options));
|
toasts.update_value(|map| {
|
||||||
});
|
map.insert(id, (SendWrapper::new(view), options, is_show));
|
||||||
list.update(|list| {
|
});
|
||||||
list.push(id);
|
toast_show_list.update_value(|map| {
|
||||||
});
|
map.insert(id, is_show);
|
||||||
|
});
|
||||||
|
list.update(|list| {
|
||||||
|
list.push(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ToasterMessage::Dismiss(toast_id) => {
|
||||||
|
toast_show_list.with_value(|map| map.get(&toast_id).unwrap_throw().set(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -66,6 +80,10 @@ pub fn Toaster(
|
||||||
};
|
};
|
||||||
list.remove(index);
|
list.remove(index);
|
||||||
});
|
});
|
||||||
|
let is_show = toast_show_list.try_update_value(|map| { map.remove(&id) } ).flatten();
|
||||||
|
if let Some(is_show) = is_show {
|
||||||
|
is_show.dispose();
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
@ -76,12 +94,19 @@ pub fn Toaster(
|
||||||
>
|
>
|
||||||
<div class="thaw-toaster thaw-toaster--top">
|
<div class="thaw-toaster thaw-toaster--top">
|
||||||
<For each=move || top_id_list.get() key=|id| id.clone() let:id>
|
<For each=move || top_id_list.get() key=|id| id.clone() let:id>
|
||||||
{if let Some((view, options)) = toasts
|
{if let Some((view, options, is_show)) = toasts
|
||||||
.try_update_value(|map| { map.remove(&id) })
|
.try_update_value(|map| { map.remove(&id) })
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
Either::Left(
|
Either::Left(
|
||||||
view! { <ToasterContainer on_close children=view.take() options /> },
|
view! {
|
||||||
|
<ToasterContainer
|
||||||
|
on_close
|
||||||
|
children=view.take()
|
||||||
|
options
|
||||||
|
is_show
|
||||||
|
/>
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Either::Right(())
|
Either::Right(())
|
||||||
|
@ -90,12 +115,19 @@ pub fn Toaster(
|
||||||
</div>
|
</div>
|
||||||
<div class="thaw-toaster thaw-toaster--top-start">
|
<div class="thaw-toaster thaw-toaster--top-start">
|
||||||
<For each=move || top_start_id_list.get() key=|id| id.clone() let:id>
|
<For each=move || top_start_id_list.get() key=|id| id.clone() let:id>
|
||||||
{if let Some((view, options)) = toasts
|
{if let Some((view, options, is_show)) = toasts
|
||||||
.try_update_value(|map| { map.remove(&id) })
|
.try_update_value(|map| { map.remove(&id) })
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
Either::Left(
|
Either::Left(
|
||||||
view! { <ToasterContainer on_close children=view.take() options /> },
|
view! {
|
||||||
|
<ToasterContainer
|
||||||
|
on_close
|
||||||
|
children=view.take()
|
||||||
|
options
|
||||||
|
is_show
|
||||||
|
/>
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Either::Right(())
|
Either::Right(())
|
||||||
|
@ -104,12 +136,19 @@ pub fn Toaster(
|
||||||
</div>
|
</div>
|
||||||
<div class="thaw-toaster thaw-toaster--top-end">
|
<div class="thaw-toaster thaw-toaster--top-end">
|
||||||
<For each=move || top_end_id_list.get() key=|id| id.clone() let:id>
|
<For each=move || top_end_id_list.get() key=|id| id.clone() let:id>
|
||||||
{if let Some((view, options)) = toasts
|
{if let Some((view, options, is_show)) = toasts
|
||||||
.try_update_value(|map| { map.remove(&id) })
|
.try_update_value(|map| { map.remove(&id) })
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
Either::Left(
|
Either::Left(
|
||||||
view! { <ToasterContainer on_close children=view.take() options /> },
|
view! {
|
||||||
|
<ToasterContainer
|
||||||
|
on_close
|
||||||
|
children=view.take()
|
||||||
|
options
|
||||||
|
is_show
|
||||||
|
/>
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Either::Right(())
|
Either::Right(())
|
||||||
|
@ -118,12 +157,19 @@ pub fn Toaster(
|
||||||
</div>
|
</div>
|
||||||
<div class="thaw-toaster thaw-toaster--bottom">
|
<div class="thaw-toaster thaw-toaster--bottom">
|
||||||
<For each=move || bottom_id_list.get() key=|id| id.clone() let:id>
|
<For each=move || bottom_id_list.get() key=|id| id.clone() let:id>
|
||||||
{if let Some((view, options)) = toasts
|
{if let Some((view, options, is_show)) = toasts
|
||||||
.try_update_value(|map| { map.remove(&id) })
|
.try_update_value(|map| { map.remove(&id) })
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
Either::Left(
|
Either::Left(
|
||||||
view! { <ToasterContainer on_close children=view.take() options /> },
|
view! {
|
||||||
|
<ToasterContainer
|
||||||
|
on_close
|
||||||
|
children=view.take()
|
||||||
|
options
|
||||||
|
is_show
|
||||||
|
/>
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Either::Right(())
|
Either::Right(())
|
||||||
|
@ -132,12 +178,19 @@ pub fn Toaster(
|
||||||
</div>
|
</div>
|
||||||
<div class="thaw-toaster thaw-toaster--bottom-start">
|
<div class="thaw-toaster thaw-toaster--bottom-start">
|
||||||
<For each=move || bottom_start_id_list.get() key=|id| id.clone() let:id>
|
<For each=move || bottom_start_id_list.get() key=|id| id.clone() let:id>
|
||||||
{if let Some((view, options)) = toasts
|
{if let Some((view, options, is_show)) = toasts
|
||||||
.try_update_value(|map| { map.remove(&id) })
|
.try_update_value(|map| { map.remove(&id) })
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
Either::Left(
|
Either::Left(
|
||||||
view! { <ToasterContainer on_close children=view.take() options /> },
|
view! {
|
||||||
|
<ToasterContainer
|
||||||
|
on_close
|
||||||
|
children=view.take()
|
||||||
|
options
|
||||||
|
is_show
|
||||||
|
/>
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Either::Right(())
|
Either::Right(())
|
||||||
|
@ -146,12 +199,19 @@ pub fn Toaster(
|
||||||
</div>
|
</div>
|
||||||
<div class="thaw-toaster thaw-toaster--bottom-end">
|
<div class="thaw-toaster thaw-toaster--bottom-end">
|
||||||
<For each=move || bottom_end_id_list.get() key=|id| id.clone() let:id>
|
<For each=move || bottom_end_id_list.get() key=|id| id.clone() let:id>
|
||||||
{if let Some((view, options)) = toasts
|
{if let Some((view, options, is_show)) = toasts
|
||||||
.try_update_value(|map| { map.remove(&id) })
|
.try_update_value(|map| { map.remove(&id) })
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
Either::Left(
|
Either::Left(
|
||||||
view! { <ToasterContainer on_close children=view.take() options /> },
|
view! {
|
||||||
|
<ToasterContainer
|
||||||
|
on_close
|
||||||
|
children=view.take()
|
||||||
|
options
|
||||||
|
is_show
|
||||||
|
/>
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Either::Right(())
|
Either::Right(())
|
||||||
|
@ -168,9 +228,9 @@ fn ToasterContainer(
|
||||||
options: ToastOptions,
|
options: ToastOptions,
|
||||||
#[prop(into)] on_close: StoredValue<ArcTwoCallback<uuid::Uuid, ToastPosition>>,
|
#[prop(into)] on_close: StoredValue<ArcTwoCallback<uuid::Uuid, ToastPosition>>,
|
||||||
children: Children,
|
children: Children,
|
||||||
|
is_show: RwSignal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let container_ref = NodeRef::<html::Div>::new();
|
let container_ref = NodeRef::<html::Div>::new();
|
||||||
let is_show = RwSignal::new(true);
|
|
||||||
let ToastOptions {
|
let ToastOptions {
|
||||||
id,
|
id,
|
||||||
timeout,
|
timeout,
|
||||||
|
@ -178,6 +238,7 @@ fn ToasterContainer(
|
||||||
intent,
|
intent,
|
||||||
..
|
..
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
let timeout = timeout.unwrap_throw();
|
let timeout = timeout.unwrap_throw();
|
||||||
let position = position.unwrap_throw();
|
let position = position.unwrap_throw();
|
||||||
let intent = intent.unwrap_throw();
|
let intent = intent.unwrap_throw();
|
||||||
|
|
Loading…
Add table
Reference in a new issue