adui_dioxus/components/
alert.rs

1use crate::components::icon::{Icon, IconKind};
2use dioxus::prelude::*;
3
4/// Semantic type of an Alert.
5#[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/// Props for the Alert component (MVP subset).
34#[derive(Props, Clone, PartialEq)]
35pub struct AlertProps {
36    /// Semantic type of the alert, controlling colors and default icon.
37    #[props(default = AlertType::Info)]
38    pub r#type: AlertType,
39    /// Main message content.
40    pub message: Element,
41    /// Optional detailed description.
42    #[props(optional)]
43    pub description: Option<Element>,
44    /// Whether to show the semantic icon.
45    #[props(default = true)]
46    pub show_icon: bool,
47    /// Whether the alert can be closed.
48    #[props(default)]
49    pub closable: bool,
50    /// Called when the close button is clicked.
51    #[props(optional)]
52    pub on_close: Option<EventHandler<()>>,
53    /// Optional custom icon element.
54    #[props(optional)]
55    pub icon: Option<Element>,
56    /// Whether the alert should be rendered as a banner (full width, compact).
57    #[props(default)]
58    pub banner: bool,
59    /// Extra class on the root element.
60    #[props(optional)]
61    pub class: Option<String>,
62    /// Inline style on the root element.
63    #[props(optional)]
64    pub style: Option<String>,
65}
66
67/// Ant Design flavored Alert (MVP: type + icon + closable).
68#[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    // The visible flag allows the alert to hide itself after close when
96    // used in uncontrolled mode.
97    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}