shadow_design/
notification.rs1use crate::icon::{Icon, IconKind};
2use dioxus::prelude::*;
3
4#[derive(Clone, Copy, PartialEq, Debug, Default)]
5pub enum NotificationVariant {
6 #[default]
7 Info,
8 Success,
9 Warning,
10 Danger,
11}
12
13impl NotificationVariant {
14 pub fn scss_class(&self) -> &'static str {
15 match self {
16 NotificationVariant::Info => "sd-toast--info",
17 NotificationVariant::Success => "sd-toast--success",
18 NotificationVariant::Warning => "sd-toast--warning",
19 NotificationVariant::Danger => "sd-toast--danger",
20 }
21 }
22
23 pub fn glow_class(&self) -> &'static str {
24 match self {
25 NotificationVariant::Info => "sd-toast--glow-info",
26 NotificationVariant::Success => "sd-toast--glow-success",
27 NotificationVariant::Warning => "sd-toast--glow-warning",
28 NotificationVariant::Danger => "sd-toast--glow-danger",
29 }
30 }
31
32 pub fn icon_kind(&self) -> IconKind {
33 match self {
34 NotificationVariant::Info => IconKind::Info,
35 NotificationVariant::Success => IconKind::CheckCircle,
36 NotificationVariant::Warning => IconKind::AlertTriangle,
37 NotificationVariant::Danger => IconKind::XCircle,
38 }
39 }
40
41 pub fn icon_color_class(&self) -> &'static str {
42 match self {
43 NotificationVariant::Info => "sd-toast__icon--info",
44 NotificationVariant::Success => "sd-toast__icon--success",
45 NotificationVariant::Warning => "sd-toast__icon--warning",
46 NotificationVariant::Danger => "sd-toast__icon--danger",
47 }
48 }
49}
50
51#[derive(Clone, PartialEq, Debug)]
52pub struct NotificationItem {
53 pub id: usize,
54 pub title: Option<String>,
55 pub message: String,
56 pub variant: NotificationVariant,
57}
58
59#[derive(Clone, Copy, PartialEq)]
60pub struct NotificationHandle {
61 items: Signal<Vec<NotificationItem>>,
62 next_id: Signal<usize>,
63}
64
65impl NotificationHandle {
66 pub fn info(&mut self, message: impl Into<String>) -> usize {
67 self.show(message, NotificationVariant::Info)
68 }
69
70 pub fn success(&mut self, message: impl Into<String>) -> usize {
71 self.show(message, NotificationVariant::Success)
72 }
73
74 pub fn warning(&mut self, message: impl Into<String>) -> usize {
75 self.show(message, NotificationVariant::Warning)
76 }
77
78 pub fn danger(&mut self, message: impl Into<String>) -> usize {
79 self.show(message, NotificationVariant::Danger)
80 }
81
82 pub fn show(&mut self, message: impl Into<String>, variant: NotificationVariant) -> usize {
83 let id = *self.next_id.read();
84 *self.next_id.write() += 1;
85
86 let item = NotificationItem {
87 id,
88 title: None,
89 message: message.into(),
90 variant,
91 };
92
93 self.items.write().push(item);
94 id
95 }
96
97 pub fn show_with_title(
98 &mut self,
99 title: impl Into<String>,
100 message: impl Into<String>,
101 variant: NotificationVariant,
102 ) -> usize {
103 let id = *self.next_id.read();
104 *self.next_id.write() += 1;
105
106 let item = NotificationItem {
107 id,
108 title: Some(title.into()),
109 message: message.into(),
110 variant,
111 };
112
113 self.items.write().push(item);
114 id
115 }
116
117 pub fn dismiss(&mut self, id: usize) {
118 self.items.write().retain(|item| item.id != id);
119 }
120
121 pub fn clear(&mut self) {
122 self.items.write().clear();
123 }
124}
125
126#[component]
127pub fn NotificationProvider(children: Element) -> Element {
128 let items = use_signal(Vec::<NotificationItem>::new);
129 let next_id = use_signal(|| 1usize);
130 let handle = NotificationHandle { items, next_id };
131 use_context_provider(|| handle);
132
133 rsx! {
134 {children}
135 NotificationContainer { items }
136 }
137}
138
139pub fn use_notification() -> NotificationHandle {
140 match try_use_context::<NotificationHandle>() {
141 Some(handle) => handle,
142 None => {
143 let items = use_signal(Vec::<NotificationItem>::new);
144 let next_id = use_signal(|| 1usize);
145 let handle = NotificationHandle { items, next_id };
146 use_context_provider(|| handle)
147 }
148 }
149}
150
151#[component]
152fn NotificationContainer(mut items: Signal<Vec<NotificationItem>>) -> Element {
153 rsx! {
154 div {
155 class: "sd-notification-container",
156 for item in items.read().iter().cloned() {
157 NotificationToast {
158 key: "{item.id}",
159 item: item.clone(),
160 on_dismiss: move |_| {
161 items.write().retain(|i| i.id != item.id);
162 }
163 }
164 }
165 }
166 }
167}
168
169#[component]
170fn NotificationToast(item: NotificationItem, on_dismiss: EventHandler<MouseEvent>) -> Element {
171 let variant_class = item.variant.scss_class();
172 let icon_color_class = item.variant.icon_color_class();
173 let icon_kind = item.variant.icon_kind();
174
175 #[cfg(feature = "theme")]
176 let theme = crate::theme::use_theme();
177 #[cfg(feature = "theme")]
178 let has_glow = theme().is_component_glow_enabled("notification", None);
179 #[cfg(not(feature = "theme"))]
180 let has_glow = true;
181
182 let glow_class = if has_glow {
183 item.variant.glow_class()
184 } else {
185 ""
186 };
187
188 #[cfg(feature = "animation")]
189 let anim_class = crate::animation::Animations::TOAST_ENTER;
190 #[cfg(not(feature = "animation"))]
191 let anim_class = "animate-toast-enter";
192
193 rsx! {
194 div {
195 class: "sd-toast {variant_class} {glow_class} {anim_class}",
196 span { class: "sd-toast__icon {icon_color_class}",
197 Icon { kind: icon_kind, size: 16 }
198 }
199 div { class: "sd-toast__content",
200 if let Some(ref title) = item.title {
201 h5 { class: "sd-toast__title", "{title}" }
202 }
203 p { class: "sd-toast__body", "{item.message}" }
204 }
205 button {
206 r#type: "button",
207 class: "sd-toast__close",
208 onclick: move |e| on_dismiss.call(e),
209 Icon { kind: IconKind::X, size: 13 }
210 }
211 }
212 }
213}