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}