1use gpui::{
16 div, px, AnyElement, App, ElementId, InteractiveElement, IntoElement, ParentElement, Pixels,
17 RenderOnce, SharedString, StatefulInteractiveElement, Styled, Window,
18};
19
20use crate::a11y::{self, A11y as _};
21use herogpui_theme::ActiveTheme;
22
23#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
29#[derive(IntoElement)]
30pub struct Label {
31 text: SharedString,
32 is_required: bool,
33 is_disabled: bool,
34 is_invalid: bool,
35 label_for: Option<(ElementId, gpui::FocusHandle)>,
37}
38
39impl Label {
40 pub fn new(text: impl Into<SharedString>) -> Self {
42 Self {
43 text: text.into(),
44 is_required: false,
45 is_disabled: false,
46 is_invalid: false,
47 label_for: None,
48 }
49 }
50
51 pub fn label_for(mut self, id: impl Into<ElementId>, handle: gpui::FocusHandle) -> Self {
58 self.label_for = Some((id.into(), handle));
59 self
60 }
61
62 pub fn is_required(mut self, v: bool) -> Self {
64 self.is_required = v;
65 self
66 }
67
68 pub fn is_disabled(mut self, v: bool) -> Self {
70 self.is_disabled = v;
71 self
72 }
73
74 pub fn is_invalid(mut self, v: bool) -> Self {
76 self.is_invalid = v;
77 self
78 }
79}
80
81impl RenderOnce for Label {
82 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
83 let colors = cx.colors();
84 let mut el = div()
85 .flex()
86 .items_center()
87 .gap(px(2.))
88 .text_size(px(14.))
89 .line_height(px(20.))
90 .font_weight(gpui::FontWeight::MEDIUM)
91 .text_color(if self.is_invalid {
92 colors.danger.color
93 } else {
94 colors.foreground
95 })
96 .child(self.text.to_string());
97
98 if self.is_required {
99 el = el.child(div().text_color(colors.danger.color).child("*".to_owned()));
100 }
101
102 if self.is_disabled {
103 el = el.opacity(cx.layout().disabled_opacity);
104 }
105
106 match self.label_for {
108 Some((id, handle)) if !self.is_disabled => el
109 .id(id)
110 .cursor(crate::util::interactive_cursor(cx))
111 .on_click(move |_: &gpui::ClickEvent, window: &mut Window, cx| {
112 window.focus(&handle, cx);
113 })
114 .into_any_element(),
115 _ => el.into_any_element(),
116 }
117 }
118}
119
120#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
123#[derive(IntoElement)]
124pub struct Description {
125 text: SharedString,
126}
127
128impl Description {
129 pub fn new(text: impl Into<SharedString>) -> Self {
131 Self { text: text.into() }
132 }
133}
134
135impl RenderOnce for Description {
136 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
137 div()
138 .text_size(px(12.))
139 .line_height(px(16.))
140 .text_color(cx.colors().muted)
141 .child(self.text.to_string())
142 }
143}
144
145#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
147#[derive(IntoElement)]
148pub struct ErrorMessage {
149 text: SharedString,
150}
151
152impl ErrorMessage {
153 pub fn new(text: impl Into<SharedString>) -> Self {
155 Self { text: text.into() }
156 }
157}
158
159impl RenderOnce for ErrorMessage {
160 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
161 div()
162 .text_size(px(12.))
163 .line_height(px(16.))
164 .text_color(cx.colors().danger.color)
165 .child(self.text.to_string())
166 }
167}
168
169#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
175#[derive(IntoElement)]
176pub struct FieldError {
177 text: Option<SharedString>,
178 is_invalid: bool,
179 id: Option<ElementId>,
183}
184
185impl FieldError {
186 pub fn new() -> Self {
188 Self {
189 text: None,
190 is_invalid: false,
191 id: None,
192 }
193 }
194
195 pub fn message(mut self, text: impl Into<SharedString>) -> Self {
198 self.text = Some(text.into());
199 self.is_invalid = true;
200 self
201 }
202
203 pub fn is_invalid(mut self, v: bool) -> Self {
205 self.is_invalid = v;
206 self
207 }
208
209 pub fn id(mut self, id: impl Into<ElementId>) -> Self {
212 self.id = Some(id.into());
213 self
214 }
215}
216
217impl Default for FieldError {
218 fn default() -> Self {
219 Self::new()
220 }
221}
222
223impl RenderOnce for FieldError {
224 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
225 let message = self.text.clone().filter(|_| self.is_invalid);
226 if let Some(id) = self.id {
227 return crate::anim::field_error_panel(&id, message, window, cx)
228 .unwrap_or_else(|| div().into_any_element());
229 }
230 match (self.is_invalid, self.text) {
231 (true, Some(text)) => div()
233 .px(px(4.))
234 .child(ErrorMessage::new(text))
235 .into_any_element(),
236 _ => div().into_any_element(),
237 }
238 }
239}
240
241#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
246#[derive(IntoElement)]
247pub struct Fieldset {
248 gap: Pixels,
249 children: Vec<AnyElement>,
250 id: Option<ElementId>,
251}
252
253impl Fieldset {
254 pub fn new() -> Self {
256 Self {
257 gap: px(24.),
258 children: Vec::new(),
259 id: None,
260 }
261 }
262
263 pub fn id(mut self, id: impl Into<ElementId>) -> Self {
266 self.id = Some(id.into());
267 self
268 }
269
270 pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
272 self.gap = gap.into();
273 self
274 }
275}
276
277impl Default for Fieldset {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282
283impl ParentElement for Fieldset {
284 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
285 self.children.extend(elements);
286 }
287}
288
289impl RenderOnce for Fieldset {
290 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
291 let el = div()
292 .flex()
293 .flex_col()
294 .gap(self.gap)
295 .flex_shrink(1.)
298 .flex_grow(1.)
299 .flex_basis(px(0.))
300 .text_color(cx.colors().foreground)
301 .children(self.children);
302 match self.id {
303 Some(id) => el.id(id).a11y(a11y::Role::Group).into_any_element(),
304 None => el.into_any_element(),
305 }
306 }
307}
308
309#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
311#[derive(IntoElement)]
312pub struct FieldsetLegend {
313 text: SharedString,
314}
315
316impl FieldsetLegend {
317 pub fn new(text: impl Into<SharedString>) -> Self {
319 Self { text: text.into() }
320 }
321}
322
323impl RenderOnce for FieldsetLegend {
324 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
325 div()
326 .text_size(px(16.))
327 .line_height(px(24.))
328 .font_weight(gpui::FontWeight::MEDIUM)
330 .text_color(cx.colors().foreground)
331 .child(self.text.to_string())
332 }
333}
334
335#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
337#[derive(IntoElement)]
338pub struct FieldGroup {
339 gap: Pixels,
340 children: Vec<AnyElement>,
341}
342
343impl FieldGroup {
344 pub fn new() -> Self {
346 Self {
347 gap: px(16.),
348 children: Vec::new(),
349 }
350 }
351
352 pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
354 self.gap = gap.into();
355 self
356 }
357}
358
359impl Default for FieldGroup {
360 fn default() -> Self {
361 Self::new()
362 }
363}
364
365impl ParentElement for FieldGroup {
366 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
367 self.children.extend(elements);
368 }
369}
370
371impl RenderOnce for FieldGroup {
374 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
375 div()
376 .flex()
377 .flex_col()
378 .w_full()
379 .gap(self.gap)
380 .children(self.children)
381 }
382}
383
384#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
386#[derive(IntoElement)]
387pub struct FieldsetActions {
388 gap: Pixels,
389 children: Vec<AnyElement>,
390}
391
392impl FieldsetActions {
393 pub fn new() -> Self {
395 Self {
396 gap: px(8.),
397 children: Vec::new(),
398 }
399 }
400
401 pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
403 self.gap = gap.into();
404 self
405 }
406}
407
408impl Default for FieldsetActions {
409 fn default() -> Self {
410 Self::new()
411 }
412}
413
414impl ParentElement for FieldsetActions {
415 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
416 self.children.extend(elements);
417 }
418}
419
420impl RenderOnce for FieldsetActions {
421 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
422 div()
423 .flex()
424 .flex_row()
425 .items_center()
426 .pt(px(4.))
428 .gap(self.gap)
429 .children(self.children)
430 }
431}