Skip to main content

shadow_design/
input.rs

1use crate::icon::{Icon, IconKind, RenderSvgIcon, SvgIcon};
2use dioxus::prelude::*;
3
4#[component]
5pub fn TextInput(
6    value: Signal<String>,
7    #[props(default)] label: Option<String>,
8    #[props(default)] placeholder: Option<String>,
9    #[props(default)] help_text: Option<String>,
10    #[props(default)] error: Option<String>,
11    #[props(default)] disabled: bool,
12    #[props(default)] clearable: bool,
13    #[props(default)] glow: Option<bool>,
14    #[props(default)] left_icon: Option<SvgIcon>,
15    #[props(default)] right_icon: Option<SvgIcon>,
16    #[props(default)] on_left_icon_click: Option<EventHandler<MouseEvent>>,
17    #[props(default)] on_right_icon_click: Option<EventHandler<MouseEvent>>,
18    #[props(default)] input_type: Option<String>,
19    #[props(default)] class: String,
20) -> Element {
21    let itype = input_type.unwrap_or_else(|| "text".to_string());
22
23    #[cfg(feature = "theme")]
24    let theme = crate::theme::use_theme();
25    #[cfg(feature = "theme")]
26    let radius_class = theme().radius_scale.md();
27
28    #[cfg(not(feature = "theme"))]
29    let radius_class = "";
30
31    let has_clear = clearable && !value().is_empty() && !disabled;
32    let has_left = left_icon.is_some();
33    let has_right = right_icon.is_some() || has_clear;
34    let is_left_custom = matches!(&left_icon, Some(SvgIcon::Custom(_)));
35    let is_right_custom = matches!(&right_icon, Some(SvgIcon::Custom(_)));
36    let left_pointer_style = if is_left_custom {
37        "pointer-events: auto;"
38    } else {
39        "pointer-events: none;"
40    };
41    let right_pointer_style = if is_right_custom {
42        "pointer-events: auto;"
43    } else {
44        "pointer-events: none;"
45    };
46
47    let icon_class = match (has_left, has_right) {
48        (true, true) => "sd-input--has-both-icons",
49        (true, false) => "sd-input--has-left-icon",
50        (false, true) => "sd-input--has-right-icon",
51        (false, false) => "",
52    };
53
54    let error_class = if error.is_some() {
55        "sd-input--error"
56    } else {
57        ""
58    };
59
60    rsx! {
61        div {
62            class: "sd-input-wrapper {class}",
63            if let Some(lbl) = label {
64                label { class: "sd-input-label", "{lbl}" }
65            }
66            div {
67                class: "sd-input-container",
68                if let Some(licon) = left_icon {
69                    if let Some(handler) = on_left_icon_click {
70                        button {
71                            r#type: "button",
72                            class: "sd-input-icon sd-input-icon--left sd-input-icon--interactive",
73                            onclick: move |e| {
74                                e.stop_propagation();
75                                handler.call(e);
76                            },
77                            RenderSvgIcon { icon: licon, size: 16 }
78                        }
79                    } else {
80                        div {
81                            class: "sd-input-icon sd-input-icon--left",
82                            style: "{left_pointer_style}",
83                            RenderSvgIcon { icon: licon, size: 16 }
84                        }
85                    }
86                }
87                input {
88                    r#type: "{itype}",
89                    value: "{value}",
90                    placeholder: placeholder.as_deref().unwrap_or_default(),
91                    disabled,
92                    class: "sd-input {icon_class} {error_class} {radius_class}",
93                    oninput: move |e| {
94                        *value.write() = e.value();
95                    },
96                }
97                if has_clear {
98                    button {
99                        r#type: "button",
100                        class: "sd-input-icon sd-input-icon--right sd-input-icon--interactive",
101                        title: "Cancella testo",
102                        onclick: move |e| {
103                            e.stop_propagation();
104                            *value.write() = String::new();
105                        },
106                        Icon { kind: IconKind::X, size: 14 }
107                    }
108                } else if let Some(ricon) = right_icon {
109                    if let Some(handler) = on_right_icon_click {
110                        button {
111                            r#type: "button",
112                            class: "sd-input-icon sd-input-icon--right sd-input-icon--interactive",
113                            onclick: move |e| {
114                                e.stop_propagation();
115                                handler.call(e);
116                            },
117                            RenderSvgIcon { icon: ricon, size: 16 }
118                        }
119                    } else {
120                        div {
121                            class: "sd-input-icon sd-input-icon--right",
122                            style: "{right_pointer_style}",
123                            RenderSvgIcon { icon: ricon, size: 16 }
124                        }
125                    }
126                }
127            }
128            if let Some(err) = error {
129                span { class: "sd-input-error", "{err}" }
130            } else if let Some(help) = help_text {
131                span { class: "sd-input-help", "{help}" }
132            }
133        }
134    }
135}