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_val = crate::theme::use_theme()();
25    #[cfg(feature = "theme")]
26    let radius_class = theme_val.radius_scale.md();
27    #[cfg(feature = "theme")]
28    let base_override = theme_val.resolve_component_base("input").unwrap_or("");
29    #[cfg(feature = "theme")]
30    let class_modifiers = theme_val.resolve_class_modifiers("input", &class);
31    #[cfg(feature = "theme")]
32    let glow_class = theme_val.resolve_elevation_class("input", glow);
33
34    #[cfg(not(feature = "theme"))]
35    let base_override = "";
36    #[cfg(not(feature = "theme"))]
37    let class_modifiers = String::new();
38    #[cfg(not(feature = "theme"))]
39    let radius_class = "";
40    #[cfg(not(feature = "theme"))]
41    let glow_class = if glow.unwrap_or(false) {
42        "sd-input--glow".to_string()
43    } else {
44        String::new()
45    };
46
47    let has_clear = clearable && !value().is_empty() && !disabled;
48    let has_left = left_icon.is_some();
49    let has_right = right_icon.is_some() || has_clear;
50    let is_left_custom = matches!(&left_icon, Some(SvgIcon::Custom(_)));
51    let is_right_custom = matches!(&right_icon, Some(SvgIcon::Custom(_)));
52    let left_pointer_style = if is_left_custom {
53        "pointer-events: auto;"
54    } else {
55        "pointer-events: none;"
56    };
57    let right_pointer_style = if is_right_custom {
58        "pointer-events: auto;"
59    } else {
60        "pointer-events: none;"
61    };
62
63    let icon_class = match (has_left, has_right) {
64        (true, true) => "sd-input--has-both-icons",
65        (true, false) => "sd-input--has-left-icon",
66        (false, true) => "sd-input--has-right-icon",
67        (false, false) => "",
68    };
69
70    let error_class = if error.is_some() {
71        "sd-input--error"
72    } else {
73        ""
74    };
75
76    rsx! {
77        div {
78            class: "sd-input-wrapper {class_modifiers} {class}",
79            if let Some(lbl) = label {
80                label { class: "sd-input-label", "{lbl}" }
81            }
82            div {
83                class: "sd-input-container",
84                if let Some(licon) = left_icon {
85                    if let Some(handler) = on_left_icon_click {
86                        button {
87                            r#type: "button",
88                            class: "sd-input-icon sd-input-icon--left sd-input-icon--interactive",
89                            onclick: move |e| {
90                                e.stop_propagation();
91                                handler.call(e);
92                            },
93                            RenderSvgIcon { icon: licon, size: 16 }
94                        }
95                    } else {
96                        div {
97                            class: "sd-input-icon sd-input-icon--left",
98                            style: "{left_pointer_style}",
99                            RenderSvgIcon { icon: licon, size: 16 }
100                        }
101                    }
102                }
103                input {
104                    r#type: "{itype}",
105                    value: "{value}",
106                    placeholder: placeholder.as_deref().unwrap_or_default(),
107                    disabled,
108                    class: "sd-input {base_override} {icon_class} {error_class} {radius_class} {glow_class}",
109                    oninput: move |e| {
110                        *value.write() = e.value();
111                    },
112                }
113                if has_clear {
114                    button {
115                        r#type: "button",
116                        class: "sd-input-icon sd-input-icon--right sd-input-icon--interactive",
117                        title: "Cancella testo",
118                        onclick: move |e| {
119                            e.stop_propagation();
120                            *value.write() = String::new();
121                        },
122                        Icon { kind: IconKind::X, size: 14 }
123                    }
124                } else if let Some(ricon) = right_icon {
125                    if let Some(handler) = on_right_icon_click {
126                        button {
127                            r#type: "button",
128                            class: "sd-input-icon sd-input-icon--right sd-input-icon--interactive",
129                            onclick: move |e| {
130                                e.stop_propagation();
131                                handler.call(e);
132                            },
133                            RenderSvgIcon { icon: ricon, size: 16 }
134                        }
135                    } else {
136                        div {
137                            class: "sd-input-icon sd-input-icon--right",
138                            style: "{right_pointer_style}",
139                            RenderSvgIcon { icon: ricon, size: 16 }
140                        }
141                    }
142                }
143            }
144            if let Some(err) = error {
145                span { class: "sd-input-error", "{err}" }
146            } else if let Some(help) = help_text {
147                span { class: "sd-input-help", "{help}" }
148            }
149        }
150    }
151}