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}