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::prelude::*;
 11/// use ui::{Banner, Button, Icon, IconName, IconSize, Label, Severity};
 12///
 13/// Banner::new()
 14///     .severity(Severity::Success)
 15///     .children([Label::new("This is a success message")])
 16///     .action_slot(
 17///         Button::new("learn-more", "Learn More")
 18///             .end_icon(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
 19///     );
 20/// ```
 21#[derive(IntoElement, RegisterComponent)]
 22pub struct Banner {
 23    severity: Severity,
 24    children: Vec<AnyElement>,
 25    action_slot: Option<AnyElement>,
 26    wrap_content: bool,
 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            wrap_content: false,
 37        }
 38    }
 39
 40    /// Sets the severity of the banner.
 41    pub fn severity(mut self, severity: Severity) -> Self {
 42        self.severity = severity;
 43        self
 44    }
 45
 46    /// A slot for actions, such as CTA or dismissal buttons.
 47    pub fn action_slot(mut self, element: impl IntoElement) -> Self {
 48        self.action_slot = Some(element.into_any_element());
 49        self
 50    }
 51
 52    /// Sets whether the banner content should wrap.
 53    pub fn wrap_content(mut self, wrap: bool) -> Self {
 54        self.wrap_content = wrap;
 55        self
 56    }
 57}
 58
 59impl ParentElement for Banner {
 60    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
 61        self.children.extend(elements)
 62    }
 63}
 64
 65impl RenderOnce for Banner {
 66    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
 67        let banner = h_flex()
 68            .min_w_0()
 69            .py_0p5()
 70            .gap_1p5()
 71            .when(self.wrap_content, |this| this.flex_wrap())
 72            .justify_between()
 73            .rounded_sm()
 74            .border_1();
 75
 76        let (icon, icon_color, bg_color, border_color) = match self.severity {
 77            Severity::Info => (
 78                IconName::Info,
 79                Color::Muted,
 80                cx.theme().status().info_background.opacity(0.5),
 81                cx.theme().colors().border.opacity(0.5),
 82            ),
 83            Severity::Success => (
 84                IconName::Check,
 85                Color::Success,
 86                cx.theme().status().success.opacity(0.1),
 87                cx.theme().status().success.opacity(0.2),
 88            ),
 89            Severity::Warning => (
 90                IconName::Warning,
 91                Color::Warning,
 92                cx.theme().status().warning_background.opacity(0.5),
 93                cx.theme().status().warning_border.opacity(0.4),
 94            ),
 95            Severity::Error => (
 96                IconName::XCircle,
 97                Color::Error,
 98                cx.theme().status().error.opacity(0.1),
 99                cx.theme().status().error.opacity(0.2),
100            ),
101        };
102
103        let mut banner = banner.bg(bg_color).border_color(border_color);
104
105        let icon_and_child = h_flex()
106            .items_start()
107            .min_w_0()
108            .flex_1()
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().flex_1().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                            .end_icon(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
153                    )
154                    .into_any_element(),
155            ),
156            single_example(
157                "Success",
158                Banner::new()
159                    .severity(Severity::Success)
160                    .child(Label::new("Operation completed successfully"))
161                    .action_slot(Button::new("dismiss", "Dismiss"))
162                    .into_any_element(),
163            ),
164            single_example(
165                "Warning",
166                Banner::new()
167                    .severity(Severity::Warning)
168                    .child(Label::new("Your settings file uses deprecated settings"))
169                    .action_slot(Button::new("update", "Update Settings"))
170                    .into_any_element(),
171            ),
172            single_example(
173                "Error",
174                Banner::new()
175                    .severity(Severity::Error)
176                    .child(Label::new("Connection error: unable to connect to server"))
177                    .action_slot(Button::new("reconnect", "Retry"))
178                    .into_any_element(),
179            ),
180        ];
181
182        Some(
183            example_group(severity_examples)
184                .vertical()
185                .into_any_element(),
186        )
187    }
188}