Toast intent (#269)

* add toast intent support

* toast intent

* toast intent

* fix CI
This commit is contained in:
kandrelczyk 2024-09-24 01:14:11 +02:00 committed by GitHub
parent 3545744335
commit a03226f202
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 154 additions and 31 deletions

View file

@ -79,7 +79,9 @@ pub fn SiteHeader() -> impl IntoView {
{
use leptos::ev;
let handle = window_event_listener(ev::keydown, move |e| {
if js_sys::Reflect::has(&e, &js_sys::wasm_bindgen::JsValue::from_str("key")).unwrap_or_default() {
if js_sys::Reflect::has(&e, &js_sys::wasm_bindgen::JsValue::from_str("key"))
.unwrap_or_default()
{
let key = e.key();
if key == *"/" {
if let Some(auto_complete_ref) = auto_complete_ref.get_untracked() {
@ -88,7 +90,6 @@ pub fn SiteHeader() -> impl IntoView {
}
}
}
});
on_cleanup(move || handle.remove());
}

View file

@ -8,6 +8,6 @@ use leptos::prelude::*;
fn main() {
let _ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to_body(App)
}

View file

@ -1,8 +1,8 @@
use crate::components::SiteHeader;
use leptos::prelude::*;
use leptos_meta::Style;
use leptos_router::hooks::{use_navigate, use_query_map};
use thaw::*;
use leptos_meta::Style;
#[component]
pub fn Home() -> impl IntoView {

View file

@ -158,7 +158,7 @@ fn iter_nodes<'a>(
NodeValue::Superscript => quote!("Superscript todo!!!"),
NodeValue::Link(node_link) => {
let NodeLink { url, title } = node_link;
quote!(
<Link href=#url attr:title=#title>
#(#children)*

View file

@ -63,11 +63,67 @@ view! {
}
```
### Toast Intent
```rust demo
let toaster = ToasterInjection::expect_context();
fn dispatch_toast(toaster: ToasterInjection, intent: ToastIntent) {
toaster.dispatch_toast(move || view! {
<Toast>
<ToastTitle>"Email sent"</ToastTitle>
<ToastBody>
"This is a toast body"
<ToastBodySubtitle slot>
"Subtitle"
</ToastBodySubtitle>
</ToastBody>
<ToastFooter>
"Footer"
</ToastFooter>
</Toast>
}, ToastOptions::default().with_intent(intent));
};
view! {
<Space>
<Button on_click=move |_| dispatch_toast(toaster, ToastIntent::Info)>"Info"</Button>
<Button on_click=move |_| dispatch_toast(toaster, ToastIntent::Success)>"Success"</Button>
<Button on_click=move |_| dispatch_toast(toaster, ToastIntent::Warning)>"Warning"</Button>
<Button on_click=move |_| dispatch_toast(toaster, ToastIntent::Error)>"Error"</Button>
</Space>
}
```
### Toast Title Media
```rust demo
let toaster = ToasterInjection::expect_context();
let on_click = move |_| {
toaster.dispatch_toast(move || view! {
<Toast>
<ToastTitle>
"Loading"
<ToastTitleMedia slot>
<Spinner size=SpinnerSize::Tiny/>
</ToastTitleMedia>
</ToastTitle>
</Toast>
}, Default::default());
};
view! {
<Button on_click=on_click>"Make toast"</Button>
}
```
### ToasterProvider Props
| Name | Type | Default | Description |
| -------- | --------------- | -------------------------- | ------------------------------------- |
| position | `ToastPosition` | `ToastPosition::BottomEnd` | The position the toast should render. |
| intent | `ToastIntent ` | `ToastPosition::Info` | The intent of the toast. |
| children | `Children` | | |
### ToastOptions Props
@ -76,7 +132,7 @@ view! {
| ------------- | --------------------------------------- | ------------------------------------- |
| with_position | `Fn(mut self, position: ToastPosition)` | The position the toast should render. |
| with_timeout | `Fn(mut self, timeout: Duration)` | Auto dismiss timeout in milliseconds. |
| with_intent | `Fn(mut self, intent: ToastIntent)` | Intent. |
| with_intent | `Fn(mut self, intent: ToastIntent)` | The intent of the toast. |
### Toast & ToastFooter Props

View file

@ -34,9 +34,10 @@ impl ToastPosition {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone, Copy)]
pub enum ToastIntent {
Success,
#[default]
Info,
Warning,
Error,

View file

@ -1,5 +1,8 @@
use leptos::{either::Either, prelude::*};
use thaw_components::OptionComp;
use thaw_utils::class_list;
use crate::ToastIntent;
#[component]
pub fn ToastTitle(
@ -7,27 +10,63 @@ pub fn ToastTitle(
children: Children,
#[prop(optional)] toast_title_action: Option<ToastTitleAction>,
) -> impl IntoView {
let intent: ToastIntent = expect_context();
view! {
<div class="thaw-toast-title__media">
<div class=class_list![
"thaw-toast-title__media", format!("thaw-toast-title__{:?}", intent).to_lowercase()
]>
{if let Some(media) = toast_title_media {
Either::Left((media.children)())
} else {
Either::Right(
view! {
<svg
fill="currentColor"
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
>
<path
d="M18 10a8 8 0 1 0-16 0 8 8 0 0 0 16 0ZM9.5 8.91a.5.5 0 0 1 1 0V13.6a.5.5 0 0 1-1 0V8.9Zm-.25-2.16a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0Z"
{
Either::Right(
view! {
<svg
fill="currentColor"
></path>
</svg>
},
)
aria-hidden="true"
width="1em"
height="1em"
viewBox="0 0 20 20"
>
{match intent {
ToastIntent::Info => {
view! {
<path
d="M18 10a8 8 0 1 0-16 0 8 8 0 0 0 16 0ZM9.5 8.91a.5.5 0 0 1 1 0V13.6a.5.5 0 0 1-1 0V8.9Zm-.25-2.16a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0Z"
fill="currentColor"
></path>
}.into_any()
},
ToastIntent::Success => {
view! {
<path
d="M10 2a8 8 0 1 1 0 16 8 8 0 0 1 0-16Zm3.36 5.65a.5.5 0 0 0-.64-.06l-.07.06L9 11.3 7.35 9.65l-.07-.06a.5.5 0 0 0-.7.7l.07.07 2 2 .07.06c.17.11.4.11.56 0l.07-.06 4-4 .07-.08a.5.5 0 0 0-.06-.63Z"
fill="currentColor"
></path>
}.into_any()
},
ToastIntent::Warning => {
view! {
<path
d="M8.68 2.79a1.5 1.5 0 0 1 2.64 0l6.5 12A1.5 1.5 0 0 1 16.5 17h-13a1.5 1.5 0 0 1-1.32-2.21l6.5-12ZM10.5 7.5a.5.5 0 0 0-1 0v4a.5.5 0 0 0 1 0v-4Zm.25 6.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"
fill="currentColor"
></path>
}.into_any()
},
ToastIntent::Error => {
view! {
<path
d="M10 2a8 8 0 1 1 0 16 8 8 0 0 1 0-16ZM7.8 7.11a.5.5 0 0 0-.63.06l-.06.07a.5.5 0 0 0 .06.64L9.3 10l-2.12 2.12-.06.07a.5.5 0 0 0 .06.64l.07.06c.2.13.47.11.64-.06L10 10.7l2.12 2.12.07.06c.2.13.46.11.64-.06l.06-.07a.5.5 0 0 0-.06-.64L10.7 10l2.12-2.12.06-.07a.5.5 0 0 0-.06-.64l-.07-.06a.5.5 0 0 0-.64.06L10 9.3 7.88 7.17l-.07-.06Z"
fill="currentColor"
></path>
}.into_any()
}
}}
</svg>
},
)
}
}}
</div>
<div class="thaw-toast-title">{children()}</div>

View file

@ -108,10 +108,25 @@ div.thaw-toaster-wrapper {
grid-column-end: 2;
padding-right: 8px;
font-size: 16px;
color: var(--colorNeutralForeground1);
color: var(--colorNeutralForeground2);
}
.thaw-toast-title__info {
color: var(--colorNeutralForeground2);
}
.thaw-toast-title__success {
color: var(--colorStatusSuccessForeground1);
}
.thaw-toast-title__warning {
color: var(--colorStatusWarningForeground3);
}
.thaw-toast-title__error {
color: var(--colorStatusDangerForeground1);
}
.thaw-toast-title__media > svg {
display: inline;
line-height: 0;

View file

@ -1,6 +1,6 @@
use super::{ToastOptions, ToastPosition, ToasterReceiver};
use super::{ToastIntent, ToastOptions, ToastPosition, ToasterReceiver};
use crate::ConfigInjection;
use leptos::{either::Either, html, prelude::*};
use leptos::{context::Provider, either::Either, html, prelude::*};
use send_wrapper::SendWrapper;
use std::{collections::HashMap, time::Duration};
use thaw_components::{CSSTransition, Teleport};
@ -11,6 +11,7 @@ use wasm_bindgen::UnwrapThrowExt;
pub fn Toaster(
receiver: ToasterReceiver,
#[prop(optional)] position: ToastPosition,
#[prop(optional)] intent: ToastIntent,
#[prop(default = Duration::from_secs(3))] timeout: Duration,
) -> impl IntoView {
mount_style("toaster", include_str!("./toaster.css"));
@ -42,6 +43,9 @@ pub fn Toaster(
if options.timeout.is_none() {
options.timeout = Some(timeout);
}
if options.intent.is_none() {
options.intent = Some(intent);
}
let list = id_list(&options.position.unwrap_throw());
let id = options.id;
@ -171,10 +175,12 @@ fn ToasterContainer(
id,
timeout,
position,
intent,
..
} = options;
let timeout = timeout.unwrap_throw();
let position = position.unwrap_throw();
let intent = intent.unwrap_throw();
if !timeout.is_zero() {
set_timeout(
@ -209,9 +215,11 @@ fn ToasterContainer(
on_after_leave=on_after_leave
let:_
>
<div class="thaw-toaster-container" node_ref=container_ref>
{children()}
</div>
<Provider value=intent>
<div class="thaw-toaster-container" node_ref=container_ref>
{children()}
</div>
</Provider>
</CSSTransition>
}
}

View file

@ -1,4 +1,4 @@
use super::{toaster::Toaster, ToastPosition, ToasterInjection};
use super::{toaster::Toaster, ToastIntent, ToastPosition, ToasterInjection};
use leptos::{context::Provider, prelude::*};
#[component]
@ -6,11 +6,14 @@ pub fn ToasterProvider(
/// The position the toast should render.
#[prop(optional)]
position: ToastPosition,
/// The intent of the toasts
#[prop(optional)]
intent: ToastIntent,
children: Children,
) -> impl IntoView {
let (injection, receiver) = ToasterInjection::channel();
view! {
<Toaster receiver position />
<Toaster receiver position intent />
<Provider value=injection>{children()}</Provider>
}
}