Skip to main content

guise/input/
field.rs

1//! `Field` — the shared label / description / error chrome that wraps a form
2//! control. Extracted so every input draws its surrounding text the same way;
3//! `NumberInput`, `TextArea`, and `Combobox` all compose it.
4
5use gpui::prelude::*;
6use gpui::{div, px, AnyElement, App, IntoElement, SharedString, Window};
7
8use crate::devtools::Probed;
9use crate::theme::{theme, ColorName, Size};
10
11/// Label / description / error wrapper around a single control.
12#[derive(IntoElement)]
13pub struct Field {
14  label: Option<SharedString>,
15  description: Option<SharedString>,
16  error: Option<SharedString>,
17  child: Option<AnyElement>,
18}
19
20impl Field {
21  pub fn new() -> Self {
22    Field {
23      label: None,
24      description: None,
25      error: None,
26      child: None,
27    }
28  }
29
30  pub fn label(mut self, label: impl Into<SharedString>) -> Self {
31    self.label = Some(label.into());
32    self
33  }
34
35  pub fn description(mut self, description: impl Into<SharedString>) -> Self {
36    self.description = Some(description.into());
37    self
38  }
39
40  pub fn error(mut self, error: impl Into<SharedString>) -> Self {
41    self.error = Some(error.into());
42    self
43  }
44
45  /// The wrapped control.
46  pub fn child(mut self, child: impl IntoElement) -> Self {
47    self.child = Some(child.into_any_element());
48    self
49  }
50}
51
52impl Default for Field {
53  fn default() -> Self {
54    Field::new()
55  }
56}
57
58impl RenderOnce for Field {
59  fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
60    let t = theme(cx);
61    let text = t.text().hsla();
62    let dimmed = t.dimmed().hsla();
63    let error_color = t
64      .color(ColorName::Red, if t.scheme.is_dark() { 5 } else { 7 })
65      .hsla();
66    let font_sm = t.font_size(Size::Sm);
67    let font_xs = t.font_size(Size::Xs);
68
69    let mut column = div().flex().flex_col().min_w(px(0.0)).gap(px(4.0));
70    if let Some(label) = self.label {
71      column = column.child(
72        div()
73          .min_w(px(0.0))
74          .text_size(px(font_sm))
75          .text_color(text)
76          .child(label),
77      );
78    }
79    if let Some(child) = self.child {
80      column = column.child(child);
81    }
82    if let Some(error) = self.error {
83      column = column.child(
84        div()
85          .min_w(px(0.0))
86          .text_size(px(font_xs))
87          .text_color(error_color)
88          .child(error),
89      );
90    } else if let Some(description) = self.description {
91      column = column.child(
92        div()
93          .min_w(px(0.0))
94          .text_size(px(font_xs))
95          .text_color(dimmed)
96          .child(description),
97      );
98    }
99    column.probe("Field")
100  }
101}