adui_dioxus/components/
alert.rs1use crate::components::icon::{Icon, IconKind};
2use dioxus::prelude::*;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum AlertType {
7 Success,
8 Info,
9 Warning,
10 Error,
11}
12
13impl AlertType {
14 fn as_class(&self) -> &'static str {
15 match self {
16 AlertType::Success => "adui-alert-success",
17 AlertType::Info => "adui-alert-info",
18 AlertType::Warning => "adui-alert-warning",
19 AlertType::Error => "adui-alert-error",
20 }
21 }
22
23 fn icon_kind(&self) -> IconKind {
24 match self {
25 AlertType::Success => IconKind::Check,
26 AlertType::Info => IconKind::Info,
27 AlertType::Warning => IconKind::Info,
28 AlertType::Error => IconKind::Close,
29 }
30 }
31}
32
33#[derive(Props, Clone, PartialEq)]
35pub struct AlertProps {
36 #[props(default = AlertType::Info)]
38 pub r#type: AlertType,
39 pub message: Element,
41 #[props(optional)]
43 pub description: Option<Element>,
44 #[props(default = true)]
46 pub show_icon: bool,
47 #[props(default)]
49 pub closable: bool,
50 #[props(optional)]
52 pub on_close: Option<EventHandler<()>>,
53 #[props(optional)]
55 pub icon: Option<Element>,
56 #[props(default)]
58 pub banner: bool,
59 #[props(optional)]
61 pub class: Option<String>,
62 #[props(optional)]
64 pub style: Option<String>,
65}
66
67#[component]
69pub fn Alert(props: AlertProps) -> Element {
70 let AlertProps {
71 r#type,
72 message,
73 description,
74 show_icon,
75 closable,
76 on_close,
77 icon,
78 banner,
79 class,
80 style,
81 } = props;
82
83 let mut class_list = vec!["adui-alert".to_string(), r#type.as_class().to_string()];
84 if banner {
85 class_list.push("adui-alert-banner".into());
86 }
87 if let Some(extra) = class {
88 class_list.push(extra);
89 }
90 let class_attr = class_list.join(" ");
91 let style_attr = style.unwrap_or_default();
92
93 let on_close_cb = on_close;
94
95 let visible = use_signal(|| true);
98
99 if !*visible.read() {
100 return VNode::empty();
101 }
102
103 rsx! {
104 div { class: "{class_attr}", style: "{style_attr}",
105 if show_icon {
106 div { class: "adui-alert-icon",
107 if let Some(custom) = icon.clone() {
108 {custom}
109 } else {
110 Icon { kind: r#type.icon_kind(), size: 16.0 }
111 }
112 }
113 }
114 div { class: "adui-alert-content",
115 div { class: "adui-alert-message", {message} }
116 if let Some(desc) = description {
117 div { class: "adui-alert-description", {desc} }
118 }
119 }
120 if closable {
121 button {
122 r#type: "button",
123 class: "adui-alert-close-icon",
124 onclick: move |_| {
125 if let Some(cb) = on_close_cb {
126 cb.call(());
127 }
128 let mut v = visible;
129 v.set(false);
130 },
131 Icon { kind: IconKind::Close, size: 12.0 }
132 }
133 }
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn alert_type_class_mapping_is_stable() {
144 assert_eq!(AlertType::Success.as_class(), "adui-alert-success");
145 assert_eq!(AlertType::Info.as_class(), "adui-alert-info");
146 assert_eq!(AlertType::Warning.as_class(), "adui-alert-warning");
147 assert_eq!(AlertType::Error.as_class(), "adui-alert-error");
148 }
149
150 #[test]
151 fn alert_type_icon_mapping() {
152 assert_eq!(AlertType::Success.icon_kind(), IconKind::Check);
153 assert_eq!(AlertType::Info.icon_kind(), IconKind::Info);
154 assert_eq!(AlertType::Warning.icon_kind(), IconKind::Info);
155 assert_eq!(AlertType::Error.icon_kind(), IconKind::Close);
156 }
157
158 #[test]
159 fn alert_type_all_variants() {
160 let variants = [
161 AlertType::Success,
162 AlertType::Info,
163 AlertType::Warning,
164 AlertType::Error,
165 ];
166 for variant in variants.iter() {
167 let class = variant.as_class();
168 assert!(!class.is_empty());
169 assert!(class.starts_with("adui-alert-"));
170 let icon = variant.icon_kind();
171 let _ = format!("{:?}", icon);
173 }
174 }
175
176 #[test]
177 fn alert_type_equality() {
178 assert_eq!(AlertType::Success, AlertType::Success);
179 assert_eq!(AlertType::Info, AlertType::Info);
180 assert_ne!(AlertType::Success, AlertType::Error);
181 }
182
183 #[test]
184 fn alert_type_clone() {
185 let original = AlertType::Warning;
186 let cloned = original;
187 assert_eq!(original, cloned);
188 assert_eq!(original.as_class(), cloned.as_class());
189 assert_eq!(original.icon_kind(), cloned.icon_kind());
190 }
191
192 #[test]
193 fn alert_props_defaults() {
194 }
201
202 #[test]
203 fn alert_type_debug() {
204 let alert_type = AlertType::Error;
205 let debug_str = format!("{:?}", alert_type);
206 assert!(debug_str.contains("Error"));
207 }
208
209 #[test]
210 fn alert_type_all_icon_kinds() {
211 let success_icon = AlertType::Success.icon_kind();
213 let info_icon = AlertType::Info.icon_kind();
214 let warning_icon = AlertType::Warning.icon_kind();
215 let error_icon = AlertType::Error.icon_kind();
216
217 assert_eq!(success_icon, IconKind::Check);
218 assert_eq!(info_icon, IconKind::Info);
219 assert_eq!(warning_icon, IconKind::Info);
220 assert_eq!(error_icon, IconKind::Close);
221 }
222
223 #[test]
224 fn alert_type_class_prefix() {
225 assert!(AlertType::Success.as_class().starts_with("adui-alert-"));
227 assert!(AlertType::Info.as_class().starts_with("adui-alert-"));
228 assert!(AlertType::Warning.as_class().starts_with("adui-alert-"));
229 assert!(AlertType::Error.as_class().starts_with("adui-alert-"));
230 }
231
232 #[test]
233 fn alert_type_unique_classes() {
234 let classes: Vec<&str> = vec![
236 AlertType::Success.as_class(),
237 AlertType::Info.as_class(),
238 AlertType::Warning.as_class(),
239 AlertType::Error.as_class(),
240 ];
241 for (i, class1) in classes.iter().enumerate() {
242 for (j, class2) in classes.iter().enumerate() {
243 if i != j {
244 assert_ne!(class1, class2);
245 }
246 }
247 }
248 }
249
250 #[test]
251 fn alert_type_copy_semantics() {
252 let alert_type = AlertType::Warning;
254 let class1 = alert_type.as_class();
255 let class2 = alert_type.as_class();
256 let icon1 = alert_type.icon_kind();
257 let icon2 = alert_type.icon_kind();
258 assert_eq!(class1, class2);
259 assert_eq!(icon1, icon2);
260 }
261}