Skip to main content

dioxus_element_plug/components/
input.rs

1use dioxus::prelude::*;
2// Use string literals for CSS classes
3
4// Input CSS class constants
5pub const INPUT: &str = "el-input";
6pub const INPUT_INNER: &str = "el-input__inner";
7pub const INPUT_WRAPPER: &str = "el-input__wrapper";
8pub const INPUT_PREFIX: &str = "el-input__prefix";
9pub const INPUT_SUFFIX: &str = "el-input__suffix";
10pub const INPUT_DISABLED: &str = "is-disabled";
11pub const INPUT_LARGE: &str = "el-input--large";
12pub const INPUT_MEDIUM: &str = "el-input--medium";
13pub const INPUT_SMALL: &str = "el-input--small";
14pub const INPUT_MINI: &str = "el-input--mini";
15pub const INPUT_ERROR: &str = "is-error";
16pub const INPUT_GROUP: &str = "el-input-group";
17pub const INPUT_GROUP_APPEND: &str = "el-input-group__append";
18pub const INPUT_GROUP_PREPEND: &str = "el-input-group__prepend";
19
20/// Input sizes matching theme-chalk
21#[derive(Clone, PartialEq)]
22pub enum InputSize {
23    Large,
24    Medium,
25    Small,
26    Mini,
27}
28
29impl InputSize {
30    pub fn as_class(&self) -> &'static str {
31        match self {
32            InputSize::Large => "el-input--large",
33            InputSize::Medium => "el-input--medium",
34            InputSize::Small => "el-input--small",
35            InputSize::Mini => "el-input--mini",
36        }
37    }
38}
39
40/// Input types
41#[derive(Clone, PartialEq)]
42pub enum InputType {
43    Text,
44    Password,
45    Email,
46    Url,
47    Number,
48    Tel,
49    Search,
50    Textarea,
51}
52
53impl InputType {
54    pub fn as_str(&self) -> &'static str {
55        match self {
56            InputType::Text => "text",
57            InputType::Password => "password",
58            InputType::Email => "email",
59            InputType::Url => "url",
60            InputType::Number => "number",
61            InputType::Tel => "tel",
62            InputType::Search => "search",
63            InputType::Textarea => "textarea",
64        }
65    }
66}
67
68/// Input props for the theme-chalk styled input component
69#[derive(Props, Clone, PartialEq)]
70pub struct InputProps {
71    /// Input value
72    #[props(default)]
73    pub value: Option<String>,
74
75    /// Input placeholder
76    #[props(default)]
77    pub placeholder: Option<String>,
78
79    /// Input type
80    #[props(default = InputType::Text)]
81    pub input_type: InputType,
82
83    /// Input size
84    #[props(default)]
85    pub size: Option<InputSize>,
86
87    /// Whether the input is disabled
88    #[props(default = false)]
89    pub disabled: bool,
90
91    /// Whether the input is readonly
92    #[props(default = false)]
93    pub readonly: bool,
94
95    /// Whether to show clear button
96    #[props(default = false)]
97    pub clearable: bool,
98
99    /// Whether to show password toggle (for password inputs)
100    #[props(default = false)]
101    pub show_password: bool,
102
103    /// Whether the input has error state
104    #[props(default = false)]
105    pub error: bool,
106
107    /// Input label
108    #[props(default)]
109    pub label: Option<String>,
110
111    /// Input prefix icon (CSS class)
112    #[props(default)]
113    pub prefix_icon: Option<String>,
114
115    /// Input suffix icon (CSS class)
116    #[props(default)]
117    pub suffix_icon: Option<String>,
118
119    /// Maximum length
120    #[props(default)]
121    pub maxlength: Option<u32>,
122
123    /// Minimum length
124    #[props(default)]
125    pub minlength: Option<u32>,
126
127    /// Whether to auto focus
128    #[props(default = false)]
129    pub autofocus: bool,
130
131    /// Name attribute
132    #[props(default)]
133    pub name: Option<String>,
134
135    /// Id attribute
136    #[props(default)]
137    pub id: Option<String>,
138
139    /// Input event handler
140    #[props(default)]
141    pub on_input: Option<EventHandler<FormEvent>>,
142
143    /// Change event handler
144    #[props(default)]
145    pub on_change: Option<EventHandler<FormEvent>>,
146
147    /// Focus event handler
148    #[props(default)]
149    pub on_focus: Option<EventHandler<FocusEvent>>,
150
151    /// Blur event handler
152    #[props(default)]
153    pub on_blur: Option<EventHandler<FocusEvent>>,
154
155    /// Key down event handler
156    #[props(default)]
157    pub on_keydown: Option<EventHandler<KeyboardEvent>>,
158
159    /// Additional CSS classes
160    #[props(default)]
161    pub class: Option<String>,
162
163    /// Inline styles
164    #[props(default)]
165    pub style: Option<String>,
166}
167
168/// An input component styled with theme-chalk CSS
169///
170/// This component wraps the native HTML input/textarea element and applies
171/// theme-chalk CSS classes for consistent styling.
172///
173/// ## Example
174///
175/// ```rust,ignore
176/// use dioxus_theme_chalk::components::input::{Input, InputType, InputSize};
177///
178/// rsx! {
179///     Input {
180///         input_type: InputType::Text,
181///         placeholder: "Enter your name",
182///         size: Some(InputSize::Medium),
183///         on_input: move |evt| println!("Input: {}", evt.value()),
184///     }
185/// }
186/// ```
187#[component]
188pub fn Input(props: InputProps) -> Element {
189    let base_class = "el-input";
190    let mut wrapper_classes = vec![base_class];
191
192    if let Some(size) = &props.size {
193        wrapper_classes.push(size.as_class());
194    }
195
196    if props.disabled {
197        wrapper_classes.push("is-disabled");
198    }
199
200    if props.error {
201        wrapper_classes.push("is-error");
202    }
203
204    let wrapper_class_string = wrapper_classes.join(" ");
205
206    let input_class = "el-input__inner";
207
208    let input_element = if props.input_type == InputType::Textarea {
209        rsx! {
210            textarea {
211                class: "{input_class}",
212                value: props.value.unwrap_or_default(),
213                placeholder: props.placeholder.unwrap_or_default(),
214                disabled: props.disabled,
215                readonly: props.readonly,
216                maxlength: props.maxlength.map(|n| n.to_string()).unwrap_or_default(),
217                minlength: props.minlength.map(|n| n.to_string()).unwrap_or_default(),
218                autofocus: props.autofocus,
219                name: props.name.unwrap_or_default(),
220                id: props.id.unwrap_or_default(),
221                style: props.style.unwrap_or_default(),
222                oninput: move |event| {
223                    if let Some(handler) = props.on_input {
224                        handler.call(event);
225                    }
226                },
227                onchange: move |event| {
228                    if let Some(handler) = props.on_change {
229                        handler.call(event);
230                    }
231                },
232                onfocus: move |event| {
233                    if let Some(handler) = props.on_focus {
234                        handler.call(event);
235                    }
236                },
237                onblur: move |event| {
238                    if let Some(handler) = props.on_blur {
239                        handler.call(event);
240                    }
241                },
242                onkeydown: move |event| {
243                    if let Some(handler) = props.on_keydown {
244                        handler.call(event);
245                    }
246                },
247            }
248        }
249    } else {
250        rsx! {
251            input {
252                class: "{input_class}",
253                r#type: props.input_type.as_str(),
254                value: props.value.unwrap_or_default(),
255                placeholder: props.placeholder.unwrap_or_default(),
256                disabled: props.disabled,
257                readonly: props.readonly,
258                maxlength: props.maxlength.map(|n| n.to_string()).unwrap_or_default(),
259                minlength: props.minlength.map(|n| n.to_string()).unwrap_or_default(),
260                autofocus: props.autofocus,
261                name: props.name.unwrap_or_default(),
262                id: props.id.unwrap_or_default(),
263                style: props.style.unwrap_or_default(),
264                oninput: move |event| {
265                    if let Some(handler) = props.on_input {
266                        handler.call(event);
267                    }
268                },
269                onchange: move |event| {
270                    if let Some(handler) = props.on_change {
271                        handler.call(event);
272                    }
273                },
274                onfocus: move |event| {
275                    if let Some(handler) = props.on_focus {
276                        handler.call(event);
277                    }
278                },
279                onblur: move |event| {
280                    if let Some(handler) = props.on_blur {
281                        handler.call(event);
282                    }
283                },
284                onkeydown: move |event| {
285                    if let Some(handler) = props.on_keydown {
286                        handler.call(event);
287                    }
288                },
289            }
290        }
291    };
292
293    rsx! {
294        div {
295            class: "{wrapper_class_string}",
296
297            if let Some(ref label) = props.label {
298                label {
299                    class: "el-form-item__label",
300                    "{label}"
301                }
302            }
303
304            div {
305                class: "el-input__wrapper",
306
307                if let Some(ref prefix_icon) = props.prefix_icon {
308                    span {
309                        class: "el-input__prefix",
310                        i {
311                            class: "{prefix_icon} el-input__icon"
312                        }
313                    }
314                }
315
316                {input_element}
317
318                if props.clearable {
319                    span {
320                        class: "el-input__suffix",
321                        i {
322                            class: "el-input__icon el-icon-circle-close"
323                        }
324                    }
325                }
326
327                if props.show_password && props.input_type == InputType::Password {
328                    span {
329                        class: "el-input__suffix",
330                        i {
331                            class: "el-input__icon el-icon-view"
332                        }
333                    }
334                }
335
336                if let Some(ref suffix_icon) = props.suffix_icon {
337                    span {
338                        class: "el-input__suffix",
339                        i {
340                            class: "{suffix_icon} el-input__icon"
341                        }
342                    }
343                }
344            }
345        }
346    }
347}
348
349/// Search input with built-in search icon
350#[component]
351pub fn SearchInput(props: InputProps) -> Element {
352    let mut props = props;
353    if props.suffix_icon.is_none() {
354        props.suffix_icon = Some("el-icon-search".to_string());
355    }
356    Input(props)
357}
358
359/// Password input with toggle visibility
360#[component]
361pub fn PasswordInput(props: InputProps) -> Element {
362    let mut props = props;
363    props.input_type = InputType::Password;
364    props.show_password = true;
365    Input(props)
366}