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().gap(px(4.0));
70        if let Some(label) = self.label {
71            column = column.child(div().text_size(px(font_sm)).text_color(text).child(label));
72        }
73        if let Some(child) = self.child {
74            column = column.child(child);
75        }
76        if let Some(error) = self.error {
77            column = column.child(
78                div()
79                    .text_size(px(font_xs))
80                    .text_color(error_color)
81                    .child(error),
82            );
83        } else if let Some(description) = self.description {
84            column = column.child(
85                div()
86                    .text_size(px(font_xs))
87                    .text_color(dimmed)
88                    .child(description),
89            );
90        }
91        column.probe("Field")
92    }
93}