dioxus_ui_system/molecules/
alert.rs1use crate::atoms::Box;
6use crate::styles::Style;
7use crate::theme::{use_style, use_theme};
8use dioxus::prelude::*;
9
10#[derive(Default, Clone, PartialEq)]
12pub enum AlertVariant {
13 #[default]
15 Default,
16 Destructive,
18 Success,
20 Warning,
22 Info,
24}
25
26#[derive(Props, Clone, PartialEq)]
28pub struct AlertProps {
29 pub children: Element,
31 #[props(default)]
33 pub variant: AlertVariant,
34 #[props(default)]
36 pub title: Option<String>,
37 #[props(default)]
39 pub icon: Option<String>,
40 #[props(default)]
42 pub dismissible: bool,
43 #[props(default)]
45 pub on_dismiss: Option<EventHandler<()>>,
46 #[props(default)]
48 pub style: Option<String>,
49 #[props(default)]
51 pub class: Option<String>,
52}
53
54#[component]
56pub fn Alert(props: AlertProps) -> Element {
57 let _theme = use_theme();
58 let mut is_visible = use_signal(|| true);
59
60 if !is_visible() {
61 return rsx! {};
62 }
63
64 let variant = props.variant.clone();
65
66 let alert_style = use_style(move |t| {
67 let (bg_color, border_color, _text_color) = match variant {
68 AlertVariant::Default => (&t.colors.background, &t.colors.border, &t.colors.foreground),
69 AlertVariant::Destructive => (
70 &t.colors.destructive.lighten(0.9),
71 &t.colors.destructive,
72 &t.colors.destructive,
73 ),
74 AlertVariant::Success => (
75 &t.colors.success.lighten(0.9),
76 &t.colors.success,
77 &t.colors.success.darken(0.2),
78 ),
79 AlertVariant::Warning => (
80 &t.colors.warning.lighten(0.9),
81 &t.colors.warning,
82 &t.colors.warning.darken(0.2),
83 ),
84 AlertVariant::Info => (
85 &t.colors.primary.lighten(0.9),
86 &t.colors.primary,
87 &t.colors.primary,
88 ),
89 };
90
91 Style::new()
92 .w_full()
93 .rounded(&t.radius, "lg")
94 .border(1, border_color)
95 .bg(bg_color)
96 .p(&t.spacing, "md")
97 .build()
98 });
99
100 let icon_style = use_style(|_t| Style::new().w_px(20).h_px(20).flex_shrink(0).build());
101
102 let mut handle_dismiss = move || {
103 is_visible.set(false);
104 if let Some(handler) = &props.on_dismiss {
105 handler.call(());
106 }
107 };
108
109 let default_icon = match props.variant {
111 AlertVariant::Default => None,
112 AlertVariant::Destructive => Some("alert-triangle"),
113 AlertVariant::Success => Some("check-circle"),
114 AlertVariant::Warning => Some("alert-triangle"),
115 AlertVariant::Info => Some("info"),
116 };
117
118 let icon_name = props.icon.as_deref().or(default_icon);
119 let custom_style = props.style.clone().unwrap_or_default();
120 let custom_class = props.class.clone().unwrap_or_default();
121
122 rsx! {
123 div {
124 role: "alert",
125 style: "{alert_style} {custom_style} display: flex; align-items: flex-start; gap: 12px;",
126 class: "{custom_class}",
127
128 if let Some(icon) = icon_name {
129 AlertIcon { name: icon.to_string(), style: icon_style() }
130 }
131
132 div {
133 style: "flex: 1;",
134
135 if let Some(title) = props.title {
136 h5 {
137 style: "margin: 0 0 4px 0; font-size: 14px; font-weight: 600;",
138 "{title}"
139 }
140 }
141
142 Box {
143 style: "font-size: 14px; line-height: 1.5;",
144 {props.children}
145 }
146 }
147
148 if props.dismissible {
149 button {
150 style: "background: none; border: none; cursor: pointer; padding: 4px; opacity: 0.5; transition: opacity 150ms;",
151 onmouseenter: move |e| e.stop_propagation(),
152 onclick: move |_| handle_dismiss(),
153 "✕"
154 }
155 }
156 }
157 }
158}
159
160#[derive(Props, Clone, PartialEq)]
161struct AlertIconProps {
162 name: String,
163 style: String,
164}
165
166#[component]
167fn AlertIcon(props: AlertIconProps) -> Element {
168 let svg_content = match props.name.as_str() {
170 "alert-triangle" => rsx! {
171 svg {
172 view_box: "0 0 24 24",
173 fill: "none",
174 stroke: "currentColor",
175 stroke_width: "2",
176 stroke_linecap: "round",
177 stroke_linejoin: "round",
178 style: "{props.style}",
179 path { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }
180 line { x1: "12", y1: "9", x2: "12", y2: "13" }
181 line { x1: "12", y1: "17", x2: "12.01", y2: "17" }
182 }
183 },
184 "check-circle" => rsx! {
185 svg {
186 view_box: "0 0 24 24",
187 fill: "none",
188 stroke: "currentColor",
189 stroke_width: "2",
190 stroke_linecap: "round",
191 stroke_linejoin: "round",
192 style: "{props.style}",
193 path { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }
194 polyline { points: "22 4 12 14.01 9 11.01" }
195 }
196 },
197 "info" => rsx! {
198 svg {
199 view_box: "0 0 24 24",
200 fill: "none",
201 stroke: "currentColor",
202 stroke_width: "2",
203 stroke_linecap: "round",
204 stroke_linejoin: "round",
205 style: "{props.style}",
206 circle { cx: "12", cy: "12", r: "10" }
207 line { x1: "12", y1: "16", x2: "12", y2: "12" }
208 line { x1: "12", y1: "8", x2: "12.01", y2: "8" }
209 }
210 },
211 _ => rsx! {},
212 };
213
214 svg_content
215}