Skip to repository content251 lines · 9.1 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T06:49:37.304Z 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
alert_modal.rs
1use crate::component_prelude::*;
2use crate::prelude::*;
3use crate::{Checkbox, ListBulletItem, ToggleState};
4use gpui::Action;
5use gpui::FocusHandle;
6use gpui::IntoElement;
7use gpui::Stateful;
8use smallvec::{SmallVec, smallvec};
9use theme::ActiveTheme;
10
11type ActionHandler = Box<dyn FnOnce(Stateful<Div>) -> Stateful<Div>>;
12
13#[derive(IntoElement, RegisterComponent)]
14pub struct AlertModal {
15 id: ElementId,
16 header: Option<AnyElement>,
17 children: SmallVec<[AnyElement; 2]>,
18 footer: Option<AnyElement>,
19 title: Option<SharedString>,
20 primary_action: Option<SharedString>,
21 dismiss_label: Option<SharedString>,
22 width: Option<DefiniteLength>,
23 key_context: Option<String>,
24 action_handlers: Vec<ActionHandler>,
25 focus_handle: Option<FocusHandle>,
26}
27
28impl AlertModal {
29 pub fn new(id: impl Into<ElementId>) -> Self {
30 Self {
31 id: id.into(),
32 header: None,
33 children: smallvec![],
34 footer: None,
35 title: None,
36 primary_action: None,
37 dismiss_label: None,
38 width: None,
39 key_context: None,
40 action_handlers: Vec::new(),
41 focus_handle: None,
42 }
43 }
44
45 pub fn title(mut self, title: impl Into<SharedString>) -> Self {
46 self.title = Some(title.into());
47 self
48 }
49
50 pub fn header(mut self, header: impl IntoElement) -> Self {
51 self.header = Some(header.into_any_element());
52 self
53 }
54
55 pub fn footer(mut self, footer: impl IntoElement) -> Self {
56 self.footer = Some(footer.into_any_element());
57 self
58 }
59
60 pub fn primary_action(mut self, primary_action: impl Into<SharedString>) -> Self {
61 self.primary_action = Some(primary_action.into());
62 self
63 }
64
65 pub fn dismiss_label(mut self, dismiss_label: impl Into<SharedString>) -> Self {
66 self.dismiss_label = Some(dismiss_label.into());
67 self
68 }
69
70 pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
71 self.width = Some(width.into());
72 self
73 }
74
75 pub fn key_context(mut self, key_context: impl Into<String>) -> Self {
76 self.key_context = Some(key_context.into());
77 self
78 }
79
80 pub fn on_action<A: Action>(
81 mut self,
82 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
83 ) -> Self {
84 self.action_handlers
85 .push(Box::new(move |div| div.on_action(listener)));
86 self
87 }
88
89 pub fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
90 self.focus_handle = Some(focus_handle.clone());
91 self
92 }
93}
94
95impl RenderOnce for AlertModal {
96 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
97 let width = self.width.unwrap_or_else(|| px(440.).into());
98 let has_default_footer = self.primary_action.is_some() || self.dismiss_label.is_some();
99
100 let mut modal = v_flex()
101 .when_some(self.key_context, |this, key_context| {
102 this.key_context(key_context.as_str())
103 })
104 .when_some(self.focus_handle, |this, focus_handle| {
105 this.track_focus(&focus_handle)
106 })
107 .id(self.id)
108 .elevation_3(cx)
109 .w(width)
110 .bg(cx.theme().colors().elevated_surface_background)
111 .overflow_hidden();
112
113 for handler in self.action_handlers {
114 modal = handler(modal);
115 }
116
117 if let Some(header) = self.header {
118 modal = modal.child(header);
119 } else if let Some(title) = self.title {
120 modal = modal.child(
121 v_flex()
122 .pt_3()
123 .pr_3()
124 .pl_3()
125 .pb_1()
126 .child(Headline::new(title).size(HeadlineSize::Small)),
127 );
128 }
129
130 if !self.children.is_empty() {
131 modal = modal.child(
132 v_flex()
133 .p_3()
134 .text_ui(cx)
135 .text_color(Color::Muted.color(cx))
136 .gap_1()
137 .children(self.children),
138 );
139 }
140
141 if let Some(footer) = self.footer {
142 modal = modal.child(footer);
143 } else if has_default_footer {
144 let primary_action = self.primary_action.unwrap_or_else(|| "OK".into());
145 let dismiss_label = self.dismiss_label.unwrap_or_else(|| "Cancel".into());
146
147 modal = modal.child(
148 h_flex()
149 .p_3()
150 .items_center()
151 .justify_end()
152 .gap_1()
153 .child(Button::new(dismiss_label.clone(), dismiss_label).color(Color::Muted))
154 .child(Button::new(primary_action.clone(), primary_action)),
155 );
156 }
157
158 modal
159 }
160}
161
162impl ParentElement for AlertModal {
163 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
164 self.children.extend(elements)
165 }
166}
167
168impl Component for AlertModal {
169 fn scope() -> ComponentScope {
170 ComponentScope::Notification
171 }
172
173 fn status() -> ComponentStatus {
174 ComponentStatus::WorkInProgress
175 }
176
177 fn description() -> &'static str {
178 "A modal dialog that presents an alert message with primary and dismiss actions."
179 }
180
181 fn preview(_window: &mut Window, cx: &mut App) -> AnyElement {
182 v_flex()
183 .gap_6()
184 .p_4()
185 .children(vec![
186 example_group(vec![single_example(
187 "Basic Alert",
188 AlertModal::new("simple-modal")
189 .title("Do you want to leave the current call?")
190 .child(
191 "The current window will be closed, and connections to any shared projects will be terminated."
192 )
193 .primary_action("Leave Call")
194 .dismiss_label("Cancel")
195 .into_any_element(),
196 )]),
197 example_group(vec![single_example(
198 "Custom Header",
199 AlertModal::new("custom-header-modal")
200 .header(
201 v_flex()
202 .p_3()
203 .bg(cx.theme().colors().background)
204 .gap_1()
205 .child(
206 h_flex()
207 .gap_1()
208 .child(Icon::new(IconName::Warning).color(Color::Warning))
209 .child(Headline::new("Example Alert").size(HeadlineSize::Small))
210 )
211 .child(
212 h_flex()
213 .pl(IconSize::default().rems() + rems(0.5))
214 .child(Label::new("~/projects/my-project").color(Color::Muted))
215 )
216 )
217 .child(
218 "Sample body copy for the alert modal component gallery.
219This preview is design-system sample content and describes no real behaviour.",
220 )
221 .child(
222 v_flex()
223 .mt_1()
224 .child(Label::new("This example lists:").color(Color::Muted))
225 .child(ListBulletItem::new("A first bullet item"))
226 .child(ListBulletItem::new("A second bullet item"))
227 .child(ListBulletItem::new("A third bullet item"))
228 )
229 .footer(
230 h_flex()
231 .p_3()
232 .justify_between()
233 .child(
234 Checkbox::new("trust-parent", ToggleState::Unselected)
235 .label("An example checkbox option")
236 )
237 .child(
238 h_flex()
239 .gap_1()
240 .child(Button::new("secondary", "Secondary Action").color(Color::Muted))
241 .child(Button::new("trust", "Trust and Continue").style(ButtonStyle::Filled))
242 )
243 )
244 .width(rems(40.))
245 .into_any_element(),
246 )]),
247 ])
248 .into_any_element()
249 }
250}
251