banner.rs

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