Skip to main content

ui/components/
alert.rs

1use gpui::Hsla;
2
3use crate::prelude::*;
4
5fn role(severity: Severity, step: u16) -> Hsla {
6    match severity {
7        Severity::Info => palette::info(step),
8        Severity::Success => palette::success(step),
9        Severity::Warning => palette::warning(step),
10        Severity::Error => palette::danger(step),
11    }
12}
13
14fn icon_for(severity: Severity) -> IconName {
15    match severity {
16        Severity::Info => IconName::Info,
17        Severity::Success => IconName::CheckCircle,
18        Severity::Warning => IconName::ExclamationTriangle,
19        Severity::Error => IconName::XCircle,
20    }
21}
22
23/// A tinted, icon-led message box for one of four severities.
24#[derive(IntoElement, RegisterComponent)]
25pub struct Alert {
26    severity: Severity,
27    title: SharedString,
28    message: Option<SharedString>,
29}
30
31impl Alert {
32    pub fn new(severity: Severity, title: impl Into<SharedString>) -> Self {
33        Self {
34            severity,
35            title: title.into(),
36            message: None,
37        }
38    }
39
40    pub fn info(title: impl Into<SharedString>) -> Self {
41        Self::new(Severity::Info, title)
42    }
43
44    pub fn success(title: impl Into<SharedString>) -> Self {
45        Self::new(Severity::Success, title)
46    }
47
48    pub fn warning(title: impl Into<SharedString>) -> Self {
49        Self::new(Severity::Warning, title)
50    }
51
52    pub fn error(title: impl Into<SharedString>) -> Self {
53        Self::new(Severity::Error, title)
54    }
55
56    pub fn message(mut self, message: impl Into<SharedString>) -> Self {
57        self.message = Some(message.into());
58        self
59    }
60}
61
62impl RenderOnce for Alert {
63    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
64        h_flex()
65            .items_start()
66            .gap_3()
67            .w_full()
68            .p_4()
69            .rounded_md()
70            .border_l_4()
71            .border_color(role(self.severity, 500))
72            .bg(role(self.severity, 50))
73            .child(
74                Icon::new(icon_for(self.severity))
75                    .size(IconSize::Small)
76                    .color(Color::Custom(role(self.severity, 500))),
77            )
78            .child(
79                v_flex()
80                    .gap_0p5()
81                    .child(
82                        Label::new(self.title)
83                            .size(LabelSize::Small)
84                            .color(Color::Custom(role(self.severity, 800))),
85                    )
86                    .children(self.message.map(|m| {
87                        Label::new(m)
88                            .size(LabelSize::XSmall)
89                            .color(Color::Custom(role(self.severity, 700)))
90                    })),
91            )
92    }
93}
94
95impl Component for Alert {
96    fn scope() -> ComponentScope {
97        ComponentScope::Notification
98    }
99
100    fn description() -> Option<&'static str> {
101        Some("A tinted, icon-led message box for info/success/warning/error.")
102    }
103
104    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
105        Some(
106            v_flex()
107                .gap_3()
108                .w(px(420.))
109                .child(Alert::info("Heads up").message("This is an informational alert."))
110                .child(
111                    Alert::success("Saved").message("Your changes to \"Q3 Roadmap\" were saved."),
112                )
113                .child(Alert::warning("Careful").message("This action needs review."))
114                .child(
115                    Alert::error("Failed").message("Payment could not be processed. Please retry."),
116                )
117                .child(Alert::info("Maintenance scheduled for Sunday, 2 AM UTC."))
118                .into_any_element(),
119        )
120    }
121}