dioxus_ui_system/molecules/
input_group.rs1use dioxus::prelude::*;
6use crate::atoms::{Input, InputType, Label, TextSize, TextColor};
7use crate::theme::use_style;
8use crate::styles::Style;
9
10#[derive(Props, Clone, PartialEq)]
12pub struct InputGroupProps {
13 pub label: String,
15 #[props(default)]
17 pub value: String,
18 #[props(default)]
20 pub placeholder: Option<String>,
21 #[props(default)]
23 pub input_type: InputType,
24 #[props(default)]
26 pub error: Option<String>,
27 #[props(default)]
29 pub hint: Option<String>,
30 #[props(default)]
32 pub required: bool,
33 #[props(default)]
35 pub disabled: bool,
36 #[props(default)]
38 pub onchange: Option<EventHandler<String>>,
39 #[props(default)]
41 pub leading_icon: Option<Element>,
42 #[props(default)]
44 pub trailing_icon: Option<Element>,
45 #[props(default)]
47 pub style: Option<String>,
48}
49
50#[component]
69pub fn InputGroup(props: InputGroupProps) -> Element {
70 let label = props.label.clone();
71 let value = props.value.clone();
72 let placeholder = props.placeholder.clone();
73 let input_type = props.input_type.clone();
74 let error = props.error.clone();
75 let hint = props.hint.clone();
76 let required = props.required;
77 let disabled = props.disabled;
78
79 let container_style = use_style(|t| {
80 Style::new()
81 .flex()
82 .flex_col()
83 .gap(&t.spacing, "xs")
84 .w_full()
85 .build()
86 });
87
88 let input_wrapper_style = use_style(|_| {
89 Style::new()
90 .flex()
91 .items_center()
92 .relative()
93 .build()
94 });
95
96 let final_style = if let Some(custom) = &props.style {
97 format!("{} {}", container_style(), custom)
98 } else {
99 container_style()
100 };
101
102 let label_element = if required {
103 rsx! {
104 Label {
105 size: TextSize::Small,
106 weight: crate::atoms::label::TextWeight::Medium,
107 "{label}"
108 Label {
109 size: TextSize::Small,
110 color: TextColor::Destructive,
111 " *"
112 }
113 }
114 }
115 } else {
116 rsx! {
117 Label {
118 size: TextSize::Small,
119 weight: crate::atoms::label::TextWeight::Medium,
120 "{label}"
121 }
122 }
123 };
124
125 let helper_text = if let Some(err) = error {
126 Some(rsx! {
127 Label {
128 size: TextSize::ExtraSmall,
129 color: TextColor::Destructive,
130 "{err}"
131 }
132 })
133 } else if let Some(h) = hint {
134 Some(rsx! {
135 Label {
136 size: TextSize::ExtraSmall,
137 color: TextColor::Muted,
138 "{h}"
139 }
140 })
141 } else {
142 None
143 };
144
145 rsx! {
146 div {
147 style: "{final_style}",
148 {label_element}
149
150 div {
151 style: "{input_wrapper_style}",
152
153 if props.leading_icon.is_some() {
154 div {
155 style: "position: absolute; left: 12px; z-index: 1;",
156 {props.leading_icon.unwrap()}
157 }
158 }
159
160 Input {
161 value: value,
162 placeholder: placeholder,
163 input_type: input_type,
164 disabled: disabled,
165 onchange: props.onchange.clone(),
166 }
167
168 if props.trailing_icon.is_some() {
169 div {
170 style: "position: absolute; right: 12px; z-index: 1;",
171 {props.trailing_icon.unwrap()}
172 }
173 }
174 }
175
176 {helper_text}
177 }
178 }
179}