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::theme::{theme, ColorName, Size};
9
10/// Label / description / error wrapper around a single control.
11#[derive(IntoElement)]
12pub struct Field {
13    label: Option<SharedString>,
14    description: Option<SharedString>,
15    error: Option<SharedString>,
16    child: Option<AnyElement>,
17}
18
19impl Field {
20    pub fn new() -> Self {
21        Field {
22            label: None,
23            description: None,
24            error: None,
25            child: None,
26        }
27    }
28
29    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
30        self.label = Some(label.into());
31        self
32    }
33
34    pub fn description(mut self, description: impl Into<SharedString>) -> Self {
35        self.description = Some(description.into());
36        self
37    }
38
39    pub fn error(mut self, error: impl Into<SharedString>) -> Self {
40        self.error = Some(error.into());
41        self
42    }
43
44    /// The wrapped control.
45    pub fn child(mut self, child: impl IntoElement) -> Self {
46        self.child = Some(child.into_any_element());
47        self
48    }
49}
50
51impl Default for Field {
52    fn default() -> Self {
53        Field::new()
54    }
55}
56
57impl RenderOnce for Field {
58    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
59        let t = theme(cx);
60        let text = t.text().hsla();
61        let dimmed = t.dimmed().hsla();
62        let error_color = t
63            .color(ColorName::Red, if t.scheme.is_dark() { 5 } else { 7 })
64            .hsla();
65        let font_sm = t.font_size(Size::Sm);
66        let font_xs = t.font_size(Size::Xs);
67
68        let mut column = div().flex().flex_col().gap(px(4.0));
69        if let Some(label) = self.label {
70            column = column.child(div().text_size(px(font_sm)).text_color(text).child(label));
71        }
72        if let Some(child) = self.child {
73            column = column.child(child);
74        }
75        if let Some(error) = self.error {
76            column = column.child(
77                div()
78                    .text_size(px(font_xs))
79                    .text_color(error_color)
80                    .child(error),
81            );
82        } else if let Some(description) = self.description {
83            column = column.child(
84                div()
85                    .text_size(px(font_xs))
86                    .text_color(dimmed)
87                    .child(description),
88            );
89        }
90        column
91    }
92}