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}
46
47impl Input {
48 pub fn new(id: impl Into<ElementId>) -> Self {
50 Self {
51 id: id.into(),
52 value: "".into(),
53 placeholder: None,
54 label: None,
55 size: InputSize::default(),
56 variant: InputVariant::default(),
57 disabled: false,
58 readonly: false,
59 error: None,
60 icon_left: None,
61 icon_right: None,
62 }
63 }
64
65 pub fn value(mut self, value: impl Into<SharedString>) -> Self {
67 self.value = value.into();
68 self
69 }
70
71 pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
73 self.placeholder = Some(placeholder.into());
74 self
75 }
76
77 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
79 self.label = Some(label.into());
80 self
81 }
82
83 pub fn size(mut self, size: InputSize) -> Self {
85 self.size = size;
86 self
87 }
88
89 pub fn variant(mut self, variant: InputVariant) -> Self {
91 self.variant = variant;
92 self
93 }
94
95 pub fn disabled(mut self, disabled: bool) -> Self {
97 self.disabled = disabled;
98 self
99 }
100
101 pub fn readonly(mut self, readonly: bool) -> Self {
103 self.readonly = readonly;
104 self
105 }
106
107 pub fn error(mut self, error: impl Into<SharedString>) -> Self {
109 self.error = Some(error.into());
110 self
111 }
112
113 pub fn icon_left(mut self, icon: impl Into<SharedString>) -> Self {
115 self.icon_left = Some(icon.into());
116 self
117 }
118
119 pub fn icon_right(mut self, icon: impl Into<SharedString>) -> Self {
121 self.icon_right = Some(icon.into());
122 self
123 }
124
125 pub fn build(self) -> Div {
127 let (py, text_size) = match self.size {
128 InputSize::Sm => (px(4.0), "text_xs"),
129 InputSize::Md => (px(8.0), "text_sm"),
130 InputSize::Lg => (px(12.0), "text_base"),
131 };
132
133 let has_error = self.error.is_some();
134 let border_color = if has_error {
135 rgb(0xcc3333)
136 } else {
137 rgb(0x3a3a3a)
138 };
139
140 let mut container = div().flex().flex_col().gap_1();
141
142 if let Some(label) = self.label {
144 container = container.child(
145 div()
146 .text_sm()
147 .text_color(rgb(0xcccccc))
148 .font_weight(FontWeight::MEDIUM)
149 .child(label),
150 );
151 }
152
153 let mut input_wrapper = div()
155 .id(self.id)
156 .flex()
157 .items_center()
158 .gap_2()
159 .px_3()
160 .py(py)
161 .rounded_md()
162 .border_1()
163 .border_color(border_color);
164
165 match self.variant {
167 InputVariant::Default => {
168 input_wrapper = input_wrapper.bg(rgb(0x1e1e1e));
169 }
170 InputVariant::Filled => {
171 input_wrapper = input_wrapper
172 .bg(rgb(0x2a2a2a))
173 .border_color(rgba(0x00000000));
174 }
175 InputVariant::Flushed => {
176 input_wrapper = input_wrapper
177 .bg(rgba(0x00000000))
178 .border_0()
179 .border_b_1()
180 .border_color(border_color)
181 .rounded_none();
182 }
183 }
184
185 if self.disabled {
186 input_wrapper = input_wrapper.opacity(0.5).cursor_not_allowed();
187 } else if !self.readonly {
188 input_wrapper = input_wrapper.hover(|s| s.border_color(rgb(0x007acc)));
189 }
190
191 if let Some(icon) = self.icon_left {
193 input_wrapper = input_wrapper.child(div().text_color(rgb(0x666666)).child(icon));
194 }
195
196 let text_el = if self.value.is_empty() {
198 if let Some(placeholder) = self.placeholder {
199 div().flex_1().text_color(rgb(0x666666)).child(placeholder)
200 } else {
201 div().flex_1()
202 }
203 } else {
204 div().flex_1().text_color(rgb(0xffffff)).child(self.value)
205 };
206
207 let text_el = match self.size {
209 InputSize::Sm => text_el.text_xs(),
210 InputSize::Md => text_el.text_sm(),
211 InputSize::Lg => text_el,
212 };
213
214 input_wrapper = input_wrapper.child(text_el);
215
216 if let Some(icon) = self.icon_right {
218 input_wrapper = input_wrapper.child(div().text_color(rgb(0x666666)).child(icon));
219 }
220
221 container = container.child(input_wrapper);
222
223 if let Some(error) = self.error {
225 container = container.child(div().text_xs().text_color(rgb(0xcc3333)).child(error));
226 }
227
228 container
229 }
230}
231
232impl IntoElement for Input {
233 type Element = Div;
234
235 fn into_element(self) -> Self::Element {
236 self.build()
237 }
238}