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}