dioxus_element_plug/components/
input.rs1use dioxus::prelude::*;
2use crate::components::common::{ClassBuilder, style_str, fire_event};
3
4pub 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#[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#[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#[derive(Props, Clone, PartialEq)]
70pub struct InputProps {
71 #[props(default)]
73 pub value: Option<String>,
74
75 #[props(default)]
77 pub placeholder: Option<String>,
78
79 #[props(default = InputType::Text)]
81 pub input_type: InputType,
82
83 #[props(default)]
85 pub size: Option<InputSize>,
86
87 #[props(default = false)]
89 pub disabled: bool,
90
91 #[props(default = false)]
93 pub readonly: bool,
94
95 #[props(default = false)]
97 pub clearable: bool,
98
99 #[props(default = false)]
101 pub show_password: bool,
102
103 #[props(default = false)]
105 pub error: bool,
106
107 #[props(default)]
109 pub label: Option<String>,
110
111 #[props(default)]
113 pub prefix_icon: Option<String>,
114
115 #[props(default)]
117 pub suffix_icon: Option<String>,
118
119 #[props(default)]
121 pub maxlength: Option<u32>,
122
123 #[props(default)]
125 pub minlength: Option<u32>,
126
127 #[props(default = false)]
129 pub autofocus: bool,
130
131 #[props(default)]
133 pub name: Option<String>,
134
135 #[props(default)]
137 pub id: Option<String>,
138
139 #[props(default)]
141 pub on_input: Option<EventHandler<FormEvent>>,
142
143 #[props(default)]
145 pub on_change: Option<EventHandler<FormEvent>>,
146
147 #[props(default)]
149 pub on_focus: Option<EventHandler<FocusEvent>>,
150
151 #[props(default)]
153 pub on_blur: Option<EventHandler<FocusEvent>>,
154
155 #[props(default)]
157 pub on_keydown: Option<EventHandler<KeyboardEvent>>,
158
159 #[props(default)]
161 pub class: Option<String>,
162
163 #[props(default)]
165 pub style: Option<String>,
166}
167
168#[component]
188pub fn Input(props: InputProps) -> Element {
189 let mut focused = use_signal(|| false);
190
191 let mut outer_builder = ClassBuilder::new(INPUT);
192
193 if let Some(ref size) = props.size {
194 outer_builder = outer_builder.add_class(size.as_class());
195 }
196
197 let outer_class = outer_builder
198 .add_if("is-disabled", props.disabled)
199 .add_if("is-error", props.error)
200 .add_opt(props.class.as_ref())
201 .build();
202
203 let wrapper_class = ClassBuilder::new(INPUT_WRAPPER)
204 .add_if("is-focus", focused())
205 .add_if("is-disabled", props.disabled)
206 .build();
207
208 let inner_style = style_str(&props.style);
209 let value = props.value.clone().unwrap_or_default();
210 let placeholder = props.placeholder.clone().unwrap_or_default();
211 let name = props.name.clone().unwrap_or_default();
212 let id = props.id.clone().unwrap_or_default();
213 let maxlength = props.maxlength.map(|n| n.to_string()).unwrap_or_default();
214 let minlength = props.minlength.map(|n| n.to_string()).unwrap_or_default();
215
216 let on_input = props.on_input;
217 let on_change = props.on_change;
218 let on_focus = props.on_focus;
219 let on_blur = props.on_blur;
220 let on_keydown = props.on_keydown;
221
222 let input_element = if props.input_type == InputType::Textarea {
223 rsx! {
224 textarea {
225 class: INPUT_INNER,
226 value: "{value}",
227 placeholder: "{placeholder}",
228 disabled: props.disabled,
229 readonly: props.readonly,
230 maxlength: "{maxlength}",
231 minlength: "{minlength}",
232 autofocus: props.autofocus,
233 name: "{name}",
234 id: "{id}",
235 style: "{inner_style}",
236 oninput: move |event| fire_event(&on_input, event),
237 onchange: move |event| fire_event(&on_change, event),
238 onfocus: move |event| {
239 focused.set(true);
240 fire_event(&on_focus, event);
241 },
242 onblur: move |event| {
243 focused.set(false);
244 fire_event(&on_blur, event);
245 },
246 onkeydown: move |event| fire_event(&on_keydown, event),
247 }
248 }
249 } else {
250 rsx! {
251 input {
252 class: INPUT_INNER,
253 r#type: props.input_type.as_str(),
254 value: "{value}",
255 placeholder: "{placeholder}",
256 disabled: props.disabled,
257 readonly: props.readonly,
258 maxlength: "{maxlength}",
259 minlength: "{minlength}",
260 autofocus: props.autofocus,
261 name: "{name}",
262 id: "{id}",
263 style: "{inner_style}",
264 oninput: move |event| fire_event(&on_input, event),
265 onchange: move |event| fire_event(&on_change, event),
266 onfocus: move |event| {
267 focused.set(true);
268 fire_event(&on_focus, event);
269 },
270 onblur: move |event| {
271 focused.set(false);
272 fire_event(&on_blur, event);
273 },
274 onkeydown: move |event| fire_event(&on_keydown, event),
275 }
276 }
277 };
278
279 rsx! {
280 div {
281 class: "{outer_class}",
282
283 if let Some(ref label) = props.label {
284 label {
285 class: "el-form-item__label",
286 "{label}"
287 }
288 }
289
290 div {
291 class: "{wrapper_class}",
292
293 if let Some(ref prefix_icon) = props.prefix_icon {
294 span {
295 class: INPUT_PREFIX,
296 i { class: "{prefix_icon} el-input__icon" }
297 }
298 }
299
300 {input_element}
301
302 if props.clearable {
303 span {
304 class: INPUT_SUFFIX,
305 i { class: "el-input__icon el-icon-circle-close" }
306 }
307 }
308
309 if props.show_password && props.input_type == InputType::Password {
310 span {
311 class: INPUT_SUFFIX,
312 i { class: "el-input__icon el-icon-view" }
313 }
314 }
315
316 if let Some(ref suffix_icon) = props.suffix_icon {
317 span {
318 class: INPUT_SUFFIX,
319 i { class: "{suffix_icon} el-input__icon" }
320 }
321 }
322 }
323 }
324 }
325}
326
327#[component]
329pub fn SearchInput(props: InputProps) -> Element {
330 let mut props = props;
331 if props.suffix_icon.is_none() {
332 props.suffix_icon = Some("el-icon-search".to_string());
333 }
334 Input(props)
335}
336
337#[component]
339pub fn PasswordInput(props: InputProps) -> Element {
340 let mut props = props;
341 props.input_type = InputType::Password;
342 props.show_password = true;
343 Input(props)
344}