Skip to main content

liora_components/
alert.rs

1use gpui::{App, Component, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
2use liora_core::Config;
3use liora_icons::Icon;
4use liora_icons_lucide::IconName;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum AlertType {
8    #[default]
9    Info,
10    Success,
11    Warning,
12    Error,
13}
14
15pub struct Alert {
16    title: SharedString,
17    description: Option<SharedString>,
18    alert_type: AlertType,
19    closable: bool,
20    show_icon: bool,
21    on_close: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
22}
23
24impl Alert {
25    pub fn new(title: impl Into<SharedString>) -> Self {
26        Self {
27            title: title.into(),
28            description: None,
29            alert_type: AlertType::Info,
30            closable: false,
31            show_icon: true,
32            on_close: None,
33        }
34    }
35
36    pub fn description(mut self, desc: impl Into<SharedString>) -> Self {
37        self.description = Some(desc.into());
38        self
39    }
40
41    pub fn alert_type(mut self, t: AlertType) -> Self {
42        self.alert_type = t;
43        self
44    }
45
46    pub fn closable(mut self, c: bool) -> Self {
47        self.closable = c;
48        self
49    }
50
51    pub fn show_icon(mut self, s: bool) -> Self {
52        self.show_icon = s;
53        self
54    }
55
56    pub fn on_close(mut self, f: impl Fn(&mut Window, &mut App) + 'static) -> Self {
57        self.on_close = Some(Box::new(f));
58        self
59    }
60}
61
62impl RenderOnce for Alert {
63    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
64        let theme = cx.global::<Config>().theme.clone();
65
66        let (color, icon_name) = match self.alert_type {
67            AlertType::Info => (theme.primary.base, IconName::Info),
68            AlertType::Success => (theme.success.base, IconName::Check),
69            AlertType::Warning => (theme.warning.base, IconName::TriangleAlert),
70            AlertType::Error => (theme.danger.base, IconName::CircleX),
71        };
72
73        let bg = color.opacity(0.1);
74
75        div()
76            .flex()
77            .flex_row()
78            .items_center()
79            .gap_3()
80            .p_3()
81            .bg(bg)
82            .border_1()
83            .border_color(color)
84            .rounded(px(theme.radius.md))
85            .child(div().flex().items_center().when(self.show_icon, |s| {
86                s.child(Icon::new(icon_name).size(px(20.0)).color(color))
87            }))
88            .child(
89                div()
90                    .flex_1()
91                    .flex()
92                    .flex_col()
93                    .gap_1()
94                    .child(
95                        div()
96                            .flex()
97                            .items_center()
98                            .min_h(px(20.0))
99                            .font_weight(gpui::FontWeight::BOLD)
100                            .text_color(color)
101                            .child(self.title),
102                    )
103                    .when_some(self.description, |s, d| {
104                        s.child(div().text_sm().text_color(color).child(d))
105                    }),
106            )
107            .child(div().flex().items_center().when(self.closable, |s| {
108                s.child(
109                    div()
110                        .id("close-btn")
111                        .cursor_pointer()
112                        .child(Icon::new(IconName::X).size(px(14.0)).color(color))
113                        .on_click(|_, _window, _cx| {
114                            // Notify parent
115                        }),
116                )
117            }))
118    }
119}
120
121impl IntoElement for Alert {
122    type Element = Component<Self>;
123    fn into_element(self) -> Self::Element {
124        Component::new(self)
125    }
126}