banner.rs

  1use crate::prelude::*;
  2use gpui::{AnyElement, IntoElement, ParentElement, Styled};
  3
  4/// Severity levels that determine the style of the banner.
  5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
  6pub enum Severity {
  7    Info,
  8    Success,
  9    Warning,
 10    Error,
 11}
 12
 13/// Banners provide informative and brief messages without interrupting the user.
 14/// This component offers four severity levels that can be used depending on the message.
 15///
 16/// # Usage Example
 17///
 18/// ```
 19/// use ui::{Banner};
 20///
 21///    Banner::new()
 22///     .severity(Severity::Success)
 23///     .children(Label::new("This is a success message"))
 24///     .action_slot(
 25///         Button::new("learn-more", "Learn More")
 26///             .icon(IconName::ArrowUpRight)
 27///             .icon_size(IconSize::Small)
 28///             .icon_position(IconPosition::End),
 29///     )
 30/// ```
 31#[derive(IntoElement, RegisterComponent)]
 32pub struct Banner {
 33    severity: Severity,
 34    children: Vec<AnyElement>,
 35    action_slot: Option<AnyElement>,
 36}
 37
 38impl Banner {
 39    /// Creates a new `Banner` component with default styling.
 40    pub fn new() -> Self {
 41        Self {
 42            severity: Severity::Info,
 43            children: Vec::new(),
 44            action_slot: None,
 45        }
 46    }
 47
 48    /// Sets the severity of the banner.
 49    pub fn severity(mut self, severity: Severity) -> Self {
 50        self.severity = severity;
 51        self
 52    }
 53
 54    /// A slot for actions, such as CTA or dismissal buttons.
 55    pub fn action_slot(mut self, element: impl IntoElement) -> Self {
 56        self.action_slot = Some(element.into_any_element());
 57        self
 58    }
 59}
 60
 61impl ParentElement for Banner {
 62    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
 63        self.children.extend(elements)
 64    }
 65}
 66
 67impl RenderOnce for Banner {
 68    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
 69        let banner = h_flex()
 70            .py_0p5()
 71            .gap_1p5()
 72            .flex_wrap()
 73            .justify_between()
 74            .rounded_sm()
 75            .border_1();
 76
 77        let (icon, icon_color, bg_color, border_color) = match self.severity {
 78            Severity::Info => (
 79                IconName::Info,
 80                Color::Muted,
 81                cx.theme().status().info_background.opacity(0.5),
 82                cx.theme().colors().border.opacity(0.5),
 83            ),
 84            Severity::Success => (
 85                IconName::Check,
 86                Color::Success,
 87                cx.theme().status().success.opacity(0.1),
 88                cx.theme().status().success.opacity(0.2),
 89            ),
 90            Severity::Warning => (
 91                IconName::Warning,
 92                Color::Warning,
 93                cx.theme().status().warning_background.opacity(0.5),
 94                cx.theme().status().warning_border.opacity(0.4),
 95            ),
 96            Severity::Error => (
 97                IconName::XCircle,
 98                Color::Error,
 99                cx.theme().status().error.opacity(0.1),
100                cx.theme().status().error.opacity(0.2),
101            ),
102        };
103
104        let mut banner = banner.bg(bg_color).border_color(border_color);
105
106        let icon_and_child = h_flex()
107            .items_start()
108            .min_w_0()
109            .gap_1p5()
110            .child(
111                h_flex()
112                    .h(window.line_height())
113                    .flex_shrink_0()
114                    .child(Icon::new(icon).size(IconSize::XSmall).color(icon_color)),
115            )
116            .child(div().min_w_0().children(self.children));
117
118        if let Some(action_slot) = self.action_slot {
119            banner = banner
120                .pl_2()
121                .pr_1()
122                .child(icon_and_child)
123                .child(action_slot);
124        } else {
125            banner = banner.px_2().child(icon_and_child);
126        }
127
128        banner
129    }
130}
131
132impl Component for Banner {
133    fn scope() -> ComponentScope {
134        ComponentScope::DataDisplay
135    }
136
137    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
138        let severity_examples = vec![
139            single_example(
140                "Default",
141                Banner::new()
142                    .child(Label::new("This is a default banner with no customization"))
143                    .into_any_element(),
144            ),
145            single_example(
146                "Info",
147                Banner::new()
148                    .severity(Severity::Info)
149                    .child(Label::new("This is an informational message"))
150                    .action_slot(
151                        Button::new("learn-more", "Learn More")
152                            .icon(IconName::ArrowUpRight)
153                            .icon_size(IconSize::Small)
154                            .icon_position(IconPosition::End),
155                    )
156                    .into_any_element(),
157            ),
158            single_example(
159                "Success",
160                Banner::new()
161                    .severity(Severity::Success)
162                    .child(Label::new("Operation completed successfully"))
163                    .action_slot(Button::new("dismiss", "Dismiss"))
164                    .into_any_element(),
165            ),
166            single_example(
167                "Warning",
168                Banner::new()
169                    .severity(Severity::Warning)
170                    .child(Label::new("Your settings file uses deprecated settings"))
171                    .action_slot(Button::new("update", "Update Settings"))
172                    .into_any_element(),
173            ),
174            single_example(
175                "Error",
176                Banner::new()
177                    .severity(Severity::Error)
178                    .child(Label::new("Connection error: unable to connect to server"))
179                    .action_slot(Button::new("reconnect", "Retry"))
180                    .into_any_element(),
181            ),
182        ];
183
184        Some(
185            example_group(severity_examples)
186                .vertical()
187                .into_any_element(),
188        )
189    }
190}