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