dioxus_ui_system/molecules/
input_group.rs1use crate::atoms::{AlignItems, Input, InputType, Label, SpacingSize, TextColor, TextSize, VStack};
6use crate::styles::Style;
7use crate::theme::use_style;
8use dioxus::prelude::*;
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| Style::new().w_full().build());
80
81 let input_wrapper_style = use_style(|_| Style::new().relative().build());
82
83 let label_element = if required {
84 rsx! {
85 Label {
86 size: TextSize::Small,
87 weight: crate::atoms::label::TextWeight::Medium,
88 "{label}"
89 Label {
90 size: TextSize::Small,
91 color: TextColor::Destructive,
92 " *"
93 }
94 }
95 }
96 } else {
97 rsx! {
98 Label {
99 size: TextSize::Small,
100 weight: crate::atoms::label::TextWeight::Medium,
101 "{label}"
102 }
103 }
104 };
105
106 let helper_text = if let Some(err) = error {
107 Some(rsx! {
108 Label {
109 size: TextSize::ExtraSmall,
110 color: TextColor::Destructive,
111 "{err}"
112 }
113 })
114 } else if let Some(h) = hint {
115 Some(rsx! {
116 Label {
117 size: TextSize::ExtraSmall,
118 color: TextColor::Muted,
119 "{h}"
120 }
121 })
122 } else {
123 None
124 };
125
126 let custom_style = props.style.clone().unwrap_or_default();
127
128 rsx! {
129 div {
130 style: "{container_style} {custom_style}",
131
132 VStack {
133 gap: SpacingSize::Xs,
134 align: AlignItems::Stretch,
135
136 {label_element}
137
138 div {
139 style: "{input_wrapper_style} display: flex; align-items: center;",
140
141 if props.leading_icon.is_some() {
142 div {
143 style: "position: absolute; left: 12px; z-index: 1;",
144 {props.leading_icon.unwrap()}
145 }
146 }
147
148 Input {
149 value: value,
150 placeholder: placeholder,
151 input_type: input_type,
152 disabled: disabled,
153 onchange: props.onchange.clone(),
154 }
155
156 if props.trailing_icon.is_some() {
157 div {
158 style: "position: absolute; right: 12px; z-index: 1;",
159 {props.trailing_icon.unwrap()}
160 }
161 }
162 }
163
164 {helper_text}
165 }
166 }
167 }
168}