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}