1use gpui::prelude::*;
6use gpui::*;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum InputSize {
11 Sm,
13 #[default]
15 Md,
16 Lg,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum InputVariant {
23 #[default]
25 Default,
26 Filled,
28 Flushed,
30}
31
32pub 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 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 pub fn value(mut self, value: impl Into<SharedString>) -> Self {
75 self.value = value.into();
76 self
77 }
78
79 pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
81 self.placeholder = Some(placeholder.into());
82 self
83 }
84
85 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
87 self.label = Some(label.into());
88 self
89 }
90
91 pub fn size(mut self, size: InputSize) -> Self {
93 self.size = size;
94 self
95 }
96
97 pub fn variant(mut self, variant: InputVariant) -> Self {
99 self.variant = variant;
100 self
101 }
102
103 pub fn disabled(mut self, disabled: bool) -> Self {
105 self.disabled = disabled;
106 self
107 }
108
109 pub fn readonly(mut self, readonly: bool) -> Self {
111 self.readonly = readonly;
112 self
113 }
114
115 pub fn error(mut self, error: impl Into<SharedString>) -> Self {
117 self.error = Some(error.into());
118 self
119 }
120
121 pub fn icon_left(mut self, icon: impl Into<SharedString>) -> Self {
123 self.icon_left = Some(icon.into());
124 self
125 }
126
127 pub fn icon_right(mut self, icon: impl Into<SharedString>) -> Self {
129 self.icon_right = Some(icon.into());
130 self
131 }
132
133 pub fn bg_color(mut self, color: impl Into<Rgba>) -> Self {
135 self.bg_color = Some(color.into());
136 self
137 }
138
139 pub fn text_color(mut self, color: impl Into<Rgba>) -> Self {
141 self.text_color = Some(color.into());
142 self
143 }
144
145 pub fn border_color(mut self, color: impl Into<Rgba>) -> Self {
147 self.border_color = Some(color.into());
148 self
149 }
150
151 pub fn placeholder_color(mut self, color: impl Into<Rgba>) -> Self {
153 self.placeholder_color = Some(color.into());
154 self
155 }
156
157 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 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 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 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 if let Some(icon) = self.icon_left {
229 input_wrapper = input_wrapper.child(div().text_color(placeholder_color).child(icon));
230 }
231
232 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 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 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 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}