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