gpui_ui_kit/
input.rs

1//! Input component
2//!
3//! Text input field with optional label, placeholder, and validation.
4
5use gpui::prelude::*;
6use gpui::*;
7
8/// Input size variants
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum InputSize {
11    /// Small input
12    Sm,
13    /// Medium input (default)
14    #[default]
15    Md,
16    /// Large input
17    Lg,
18}
19
20/// Input visual variant
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum InputVariant {
23    /// Default input style
24    #[default]
25    Default,
26    /// Filled background
27    Filled,
28    /// Flushed (bottom border only)
29    Flushed,
30}
31
32/// A text input component
33pub struct Input {
34    id: ElementId,
35    value: SharedString,
36    placeholder: Option<SharedString>,
37    label: Option<SharedString>,
38    size: InputSize,
39    variant: InputVariant,
40    disabled: bool,
41    readonly: bool,
42    error: Option<SharedString>,
43    icon_left: Option<SharedString>,
44    icon_right: Option<SharedString>,
45    bg_color: Option<Rgba>,
46    text_color: Option<Rgba>,
47    border_color: Option<Rgba>,
48    placeholder_color: Option<Rgba>,
49}
50
51impl Input {
52    /// Create a new input
53    pub fn new(id: impl Into<ElementId>) -> Self {
54        Self {
55            id: id.into(),
56            value: "".into(),
57            placeholder: None,
58            label: None,
59            size: InputSize::default(),
60            variant: InputVariant::default(),
61            disabled: false,
62            readonly: false,
63            error: None,
64            icon_left: None,
65            icon_right: None,
66            bg_color: None,
67            text_color: None,
68            border_color: None,
69            placeholder_color: None,
70        }
71    }
72
73    /// Set the input value
74    pub fn value(mut self, value: impl Into<SharedString>) -> Self {
75        self.value = value.into();
76        self
77    }
78
79    /// Set placeholder text
80    pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
81        self.placeholder = Some(placeholder.into());
82        self
83    }
84
85    /// Set label text
86    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
87        self.label = Some(label.into());
88        self
89    }
90
91    /// Set input size
92    pub fn size(mut self, size: InputSize) -> Self {
93        self.size = size;
94        self
95    }
96
97    /// Set input variant
98    pub fn variant(mut self, variant: InputVariant) -> Self {
99        self.variant = variant;
100        self
101    }
102
103    /// Set disabled state
104    pub fn disabled(mut self, disabled: bool) -> Self {
105        self.disabled = disabled;
106        self
107    }
108
109    /// Set readonly state
110    pub fn readonly(mut self, readonly: bool) -> Self {
111        self.readonly = readonly;
112        self
113    }
114
115    /// Set error message
116    pub fn error(mut self, error: impl Into<SharedString>) -> Self {
117        self.error = Some(error.into());
118        self
119    }
120
121    /// Set left icon
122    pub fn icon_left(mut self, icon: impl Into<SharedString>) -> Self {
123        self.icon_left = Some(icon.into());
124        self
125    }
126
127    /// Set right icon
128    pub fn icon_right(mut self, icon: impl Into<SharedString>) -> Self {
129        self.icon_right = Some(icon.into());
130        self
131    }
132
133    /// Set background color
134    pub fn bg_color(mut self, color: impl Into<Rgba>) -> Self {
135        self.bg_color = Some(color.into());
136        self
137    }
138
139    /// Set text color
140    pub fn text_color(mut self, color: impl Into<Rgba>) -> Self {
141        self.text_color = Some(color.into());
142        self
143    }
144
145    /// Set border color
146    pub fn border_color(mut self, color: impl Into<Rgba>) -> Self {
147        self.border_color = Some(color.into());
148        self
149    }
150
151    /// Set placeholder color
152    pub fn placeholder_color(mut self, color: impl Into<Rgba>) -> Self {
153        self.placeholder_color = Some(color.into());
154        self
155    }
156
157    /// Build into element
158    pub fn build(self) -> Div {
159        let (py, _text_size) = match self.size {
160            InputSize::Sm => (px(4.0), "text_xs"),
161            InputSize::Md => (px(8.0), "text_sm"),
162            InputSize::Lg => (px(12.0), "text_base"),
163        };
164
165        let has_error = self.error.is_some();
166        let default_border = rgb(0x3a3a3a);
167        let border_color = if has_error {
168            rgb(0xcc3333)
169        } else {
170            self.border_color.unwrap_or(default_border)
171        };
172
173        let mut container = div().flex().flex_col().gap_1();
174
175        // Label
176        if let Some(label) = self.label {
177            container = container.child(
178                div()
179                    .text_sm()
180                    .text_color(rgb(0xcccccc))
181                    .font_weight(FontWeight::MEDIUM)
182                    .child(label),
183            );
184        }
185
186        // Input wrapper
187        let mut input_wrapper = div()
188            .id(self.id)
189            .flex()
190            .items_center()
191            .gap_2()
192            .px_3()
193            .py(py)
194            .rounded_md()
195            .border_1()
196            .border_color(border_color);
197
198        // Apply variant styling
199        match self.variant {
200            InputVariant::Default => {
201                input_wrapper = input_wrapper.bg(self.bg_color.unwrap_or(rgb(0x1e1e1e)));
202            }
203            InputVariant::Filled => {
204                input_wrapper = input_wrapper
205                    .bg(self.bg_color.unwrap_or(rgb(0x2a2a2a)))
206                    .border_color(rgba(0x00000000));
207            }
208            InputVariant::Flushed => {
209                input_wrapper = input_wrapper
210                    .bg(rgba(0x00000000))
211                    .border_0()
212                    .border_b_1()
213                    .border_color(border_color)
214                    .rounded_none();
215            }
216        }
217
218        if self.disabled {
219            input_wrapper = input_wrapper.opacity(0.5).cursor_not_allowed();
220        } else if !self.readonly {
221            input_wrapper = input_wrapper.hover(|s| s.border_color(rgb(0x007acc)));
222        }
223
224        let placeholder_color = self.placeholder_color.unwrap_or(rgb(0x666666));
225        let text_color = self.text_color.unwrap_or(rgb(0xffffff));
226
227        // Left icon
228        if let Some(icon) = self.icon_left {
229            input_wrapper = input_wrapper.child(div().text_color(placeholder_color).child(icon));
230        }
231
232        // Input text/placeholder
233        let text_el = if self.value.is_empty() {
234            if let Some(placeholder) = self.placeholder {
235                div()
236                    .flex_1()
237                    .text_color(placeholder_color)
238                    .child(placeholder)
239            } else {
240                div().flex_1()
241            }
242        } else {
243            div().flex_1().text_color(text_color).child(self.value)
244        };
245
246        // Apply text size
247        let text_el = match self.size {
248            InputSize::Sm => text_el.text_xs(),
249            InputSize::Md => text_el.text_sm(),
250            InputSize::Lg => text_el,
251        };
252
253        input_wrapper = input_wrapper.child(text_el);
254
255        // Right icon
256        if let Some(icon) = self.icon_right {
257            input_wrapper = input_wrapper.child(div().text_color(rgb(0x666666)).child(icon));
258        }
259
260        container = container.child(input_wrapper);
261
262        // Error message
263        if let Some(error) = self.error {
264            container = container.child(div().text_xs().text_color(rgb(0xcc3333)).child(error));
265        }
266
267        container
268    }
269}
270
271impl IntoElement for Input {
272    type Element = Div;
273
274    fn into_element(self) -> Self::Element {
275        self.build()
276    }
277}