Skip to repository content192 lines · 6.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:40:16.233Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
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 description() -> &'static str {
138 "A non-blocking, severity-aware message strip used to surface informative, \
139 success, warning, or error messages without interrupting the user."
140 }
141
142 fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
143 let severity_examples = vec![
144 single_example(
145 "Default",
146 Banner::new()
147 .child(Label::new("This is a default banner with no customization"))
148 .into_any_element(),
149 ),
150 single_example(
151 "Info",
152 Banner::new()
153 .severity(Severity::Info)
154 .child(Label::new("This is an informational message"))
155 .action_slot(
156 Button::new("learn-more", "Learn More")
157 .end_icon(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
158 )
159 .into_any_element(),
160 ),
161 single_example(
162 "Success",
163 Banner::new()
164 .severity(Severity::Success)
165 .child(Label::new("Operation completed successfully"))
166 .action_slot(Button::new("dismiss", "Dismiss"))
167 .into_any_element(),
168 ),
169 single_example(
170 "Warning",
171 Banner::new()
172 .severity(Severity::Warning)
173 .child(Label::new("Your settings file uses deprecated settings"))
174 .action_slot(Button::new("update", "Update Settings"))
175 .into_any_element(),
176 ),
177 single_example(
178 "Error",
179 Banner::new()
180 .severity(Severity::Error)
181 .child(Label::new("Connection error: unable to connect to server"))
182 .action_slot(Button::new("reconnect", "Retry"))
183 .into_any_element(),
184 ),
185 ];
186
187 example_group(severity_examples)
188 .vertical()
189 .into_any_element()
190 }
191}
192