gpui_ui_kit/
alert.rs

1//! Alert component
2//!
3//! Contextual feedback messages.
4
5use gpui::prelude::*;
6use gpui::*;
7
8/// Alert variant
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum AlertVariant {
11    /// Informational (default)
12    #[default]
13    Info,
14    /// Success message
15    Success,
16    /// Warning message
17    Warning,
18    /// Error message
19    Error,
20}
21
22impl AlertVariant {
23    fn colors(&self) -> (Rgba, Rgba, Rgba) {
24        // Returns (background, border, icon_color)
25        match self {
26            AlertVariant::Info => (rgb(0x1a2a3a), rgb(0x007acc), rgb(0x007acc)),
27            AlertVariant::Success => (rgb(0x1a3a1a), rgb(0x2da44e), rgb(0x2da44e)),
28            AlertVariant::Warning => (rgb(0x3a3a1a), rgb(0xd29922), rgb(0xd29922)),
29            AlertVariant::Error => (rgb(0x3a1a1a), rgb(0xcc3333), rgb(0xcc3333)),
30        }
31    }
32
33    fn icon(&self) -> &'static str {
34        match self {
35            AlertVariant::Info => "ℹ",
36            AlertVariant::Success => "✓",
37            AlertVariant::Warning => "⚠",
38            AlertVariant::Error => "✕",
39        }
40    }
41}
42
43/// An alert component
44pub struct Alert {
45    id: ElementId,
46    title: Option<SharedString>,
47    message: SharedString,
48    variant: AlertVariant,
49    closeable: bool,
50    icon: Option<SharedString>,
51    on_close: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
52}
53
54impl Alert {
55    /// Create a new alert
56    pub fn new(id: impl Into<ElementId>, message: impl Into<SharedString>) -> Self {
57        Self {
58            id: id.into(),
59            title: None,
60            message: message.into(),
61            variant: AlertVariant::default(),
62            closeable: false,
63            icon: None,
64            on_close: None,
65        }
66    }
67
68    /// Set title
69    pub fn title(mut self, title: impl Into<SharedString>) -> Self {
70        self.title = Some(title.into());
71        self
72    }
73
74    /// Set variant
75    pub fn variant(mut self, variant: AlertVariant) -> Self {
76        self.variant = variant;
77        self
78    }
79
80    /// Make closeable
81    pub fn closeable(mut self, closeable: bool) -> Self {
82        self.closeable = closeable;
83        self
84    }
85
86    /// Set custom icon
87    pub fn icon(mut self, icon: impl Into<SharedString>) -> Self {
88        self.icon = Some(icon.into());
89        self
90    }
91
92    /// Set close handler
93    pub fn on_close(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
94        self.on_close = Some(Box::new(handler));
95        self
96    }
97
98    /// Build into element
99    pub fn build(self) -> Stateful<Div> {
100        let (bg, border, icon_color) = self.variant.colors();
101        let default_icon = self.variant.icon();
102
103        let mut alert = div()
104            .id(self.id)
105            .flex()
106            .items_start()
107            .gap_3()
108            .p_4()
109            .bg(bg)
110            .border_1()
111            .border_color(border)
112            .rounded_lg();
113
114        // Icon
115        let icon = self.icon.unwrap_or_else(|| default_icon.into());
116        alert = alert.child(div().text_lg().text_color(icon_color).child(icon));
117
118        // Content
119        let mut content = div().flex_1().flex().flex_col().gap_1();
120
121        if let Some(title) = self.title {
122            content = content.child(
123                div()
124                    .text_sm()
125                    .font_weight(FontWeight::SEMIBOLD)
126                    .text_color(rgb(0xffffff))
127                    .child(title),
128            );
129        }
130
131        content = content.child(
132            div()
133                .text_sm()
134                .text_color(rgb(0xcccccc))
135                .child(self.message),
136        );
137
138        alert = alert.child(content);
139
140        // Close button
141        if self.closeable {
142            let mut close_btn = div()
143                .id("alert-close")
144                .text_sm()
145                .text_color(rgb(0x888888))
146                .cursor_pointer()
147                .hover(|s| s.text_color(rgb(0xffffff)));
148
149            if let Some(handler) = self.on_close {
150                let handler_ptr: *const dyn Fn(&mut Window, &mut App) = handler.as_ref();
151                close_btn =
152                    close_btn.on_mouse_up(MouseButton::Left, move |_event, window, cx| unsafe {
153                        (*handler_ptr)(window, cx);
154                    });
155                std::mem::forget(handler);
156            }
157
158            alert = alert.child(close_btn.child("×"));
159        }
160
161        alert
162    }
163}
164
165impl IntoElement for Alert {
166    type Element = Stateful<Div>;
167
168    fn into_element(self) -> Self::Element {
169        self.build()
170    }
171}
172
173/// A simple inline alert (no close button)
174pub struct InlineAlert {
175    message: SharedString,
176    variant: AlertVariant,
177}
178
179impl InlineAlert {
180    /// Create a new inline alert
181    pub fn new(message: impl Into<SharedString>) -> Self {
182        Self {
183            message: message.into(),
184            variant: AlertVariant::default(),
185        }
186    }
187
188    /// Set variant
189    pub fn variant(mut self, variant: AlertVariant) -> Self {
190        self.variant = variant;
191        self
192    }
193
194    /// Build into element
195    pub fn build(self) -> Div {
196        let (_, _border, icon_color) = self.variant.colors();
197        let icon = self.variant.icon();
198
199        div()
200            .flex()
201            .items_center()
202            .gap_2()
203            .text_sm()
204            .text_color(icon_color)
205            .child(div().child(icon))
206            .child(self.message)
207    }
208}
209
210impl IntoElement for InlineAlert {
211    type Element = Div;
212
213    fn into_element(self) -> Self::Element {
214        self.build()
215    }
216}