liora_components/
alert.rs1use 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 }),
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}