Skip to main content

maud_ui/primitives/
alert.rs

1//! Alert component — callout/banner for important messages.
2use maud::{html, Markup};
3
4/// Alert color variants
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Variant {
7    Default,
8    Info,
9    Success,
10    Warning,
11    Danger,
12}
13
14impl Variant {
15    fn class(&self) -> &'static str {
16        match self {
17            Self::Default => "mui-alert--default",
18            Self::Info => "mui-alert--info",
19            Self::Success => "mui-alert--success",
20            Self::Warning => "mui-alert--warning",
21            Self::Danger => "mui-alert--danger",
22        }
23    }
24
25    fn icon_char(&self) -> &'static str {
26        match self {
27            Self::Default => "\u{25cb}",  // circle
28            Self::Info => "\u{24d8}",     // circled i
29            Self::Success => "\u{2713}",  // checkmark
30            Self::Warning => "\u{26a0}",  // warning triangle
31            Self::Danger => "\u{26a0}",   // warning triangle (destructive)
32        }
33    }
34}
35
36/// Alert rendering properties
37#[derive(Debug, Clone)]
38pub struct Props {
39    /// Alert title text
40    pub title: String,
41    /// Optional description text
42    pub description: Option<String>,
43    /// Visual variant (color scheme)
44    pub variant: Variant,
45    /// Whether to show the icon (default true)
46    pub icon: bool,
47}
48
49impl Default for Props {
50    fn default() -> Self {
51        Self {
52            title: String::new(),
53            description: None,
54            variant: Variant::Default,
55            icon: true,
56        }
57    }
58}
59
60/// Render a single alert with the given properties
61pub fn render(props: Props) -> Markup {
62    html! {
63        div class={"mui-alert " (props.variant.class())} role="alert" {
64            @if props.icon {
65                div class="mui-alert__icon" aria-hidden="true" {
66                    (props.variant.icon_char())
67                }
68            }
69            div class="mui-alert__content" {
70                div class="mui-alert__title" {
71                    (props.title)
72                }
73                @if let Some(desc) = props.description {
74                    p class="mui-alert__description" {
75                        (desc)
76                    }
77                }
78            }
79        }
80    }
81}
82
83/// Showcase all alert variants and use cases
84pub fn showcase() -> Markup {
85    html! {
86        div.mui-showcase__grid {
87            div {
88                p.mui-showcase__caption { "Subscription expiring" }
89                div.mui-showcase__column {
90                    (render(Props {
91                        title: "Your Pro plan expires in 3 days".into(),
92                        description: Some("Renew by Apr 19 to keep unlimited builds and priority support. After expiry your workspace drops to the Free tier and projects over 3 will be archived.".into()),
93                        variant: Variant::Warning,
94                        icon: true,
95                    }))
96                }
97            }
98
99            div {
100                p.mui-showcase__caption { "Two-factor enabled" }
101                div.mui-showcase__column {
102                    (render(Props {
103                        title: "Two-factor authentication is on".into(),
104                        description: Some("Backup codes were emailed to invoice@geldentech.ca. Store them somewhere safe — you'll need one if you lose access to your authenticator.".into()),
105                        variant: Variant::Success,
106                        icon: true,
107                    }))
108                }
109            }
110
111            div {
112                p.mui-showcase__caption { "Destructive — API key revocation" }
113                div.mui-showcase__column {
114                    (render(Props {
115                        title: "Revoke key sk_live_\u{2026}A9f2?".into(),
116                        description: Some("Any services using this key will stop working immediately. This action cannot be undone — you'll need to issue a new key and redeploy.".into()),
117                        variant: Variant::Danger,
118                        icon: true,
119                    }))
120                }
121            }
122
123            div {
124                p.mui-showcase__caption { "Informational" }
125                div.mui-showcase__column {
126                    (render(Props {
127                        title: "Scheduled maintenance Sunday 02:00 UTC".into(),
128                        description: Some("The build pipeline will be paused for roughly 20 minutes. In-flight deploys will resume automatically once maintenance completes.".into()),
129                        variant: Variant::Info,
130                        icon: true,
131                    }))
132                }
133            }
134        }
135    }
136}