1use dioxus::prelude::*;
2
3use crate::types::Size;
4
5#[derive(Clone, PartialEq, Props)]
25pub struct FormGroupProps {
26 #[props(default)]
28 pub label: String,
29 #[props(default)]
31 pub class: String,
32 pub children: Element,
34}
35
36#[component]
37pub fn FormGroup(props: FormGroupProps) -> Element {
38 let full_class = if props.class.is_empty() {
39 "mb-3".to_string()
40 } else {
41 format!("mb-3 {}", props.class)
42 };
43
44 rsx! {
45 div { class: "{full_class}",
46 if !props.label.is_empty() {
47 label { class: "form-label", "{props.label}" }
48 }
49 {props.children}
50 }
51 }
52}
53
54#[derive(Clone, PartialEq, Props)]
72pub struct InputProps {
73 #[props(default = "text".to_string())]
75 pub r#type: String,
76 #[props(default)]
78 pub value: String,
79 #[props(default)]
81 pub placeholder: String,
82 #[props(default)]
84 pub size: Size,
85 #[props(default)]
87 pub disabled: bool,
88 #[props(default)]
90 pub readonly: bool,
91 #[props(default)]
93 pub oninput: Option<EventHandler<FormEvent>>,
94 #[props(default)]
96 pub class: String,
97}
98
99#[component]
100pub fn Input(props: InputProps) -> Element {
101 let size_class = match props.size {
102 Size::Md => String::new(),
103 s => format!(" form-control-{s}"),
104 };
105
106 let full_class = if props.class.is_empty() {
107 format!("form-control{size_class}")
108 } else {
109 format!("form-control{size_class} {}", props.class)
110 };
111
112 rsx! {
113 input {
114 class: "{full_class}",
115 r#type: "{props.r#type}",
116 value: "{props.value}",
117 placeholder: "{props.placeholder}",
118 disabled: props.disabled,
119 readonly: props.readonly,
120 oninput: move |evt| {
121 if let Some(handler) = &props.oninput {
122 handler.call(evt);
123 }
124 },
125 }
126 }
127}
128
129#[derive(Clone, PartialEq, Props)]
150pub struct SelectProps {
151 #[props(default)]
153 pub value: String,
154 #[props(default)]
156 pub size: Size,
157 #[props(default)]
159 pub disabled: bool,
160 #[props(default)]
162 pub onchange: Option<EventHandler<FormEvent>>,
163 #[props(default)]
165 pub class: String,
166 pub children: Element,
168}
169
170#[component]
171pub fn Select(props: SelectProps) -> Element {
172 let size_class = match props.size {
173 Size::Md => String::new(),
174 s => format!(" form-select-{s}"),
175 };
176
177 let full_class = if props.class.is_empty() {
178 format!("form-select{size_class}")
179 } else {
180 format!("form-select{size_class} {}", props.class)
181 };
182
183 rsx! {
184 select {
185 class: "{full_class}",
186 value: "{props.value}",
187 disabled: props.disabled,
188 onchange: move |evt| {
189 if let Some(handler) = &props.onchange {
190 handler.call(evt);
191 }
192 },
193 {props.children}
194 }
195 }
196}
197
198#[derive(Clone, PartialEq, Props)]
206pub struct TextareaProps {
207 #[props(default)]
209 pub value: String,
210 #[props(default = 3)]
212 pub rows: u32,
213 #[props(default)]
215 pub placeholder: String,
216 #[props(default)]
218 pub disabled: bool,
219 #[props(default)]
221 pub readonly: bool,
222 #[props(default)]
224 pub oninput: Option<EventHandler<FormEvent>>,
225 #[props(default)]
227 pub class: String,
228}
229
230#[component]
231pub fn Textarea(props: TextareaProps) -> Element {
232 let full_class = if props.class.is_empty() {
233 "form-control".to_string()
234 } else {
235 format!("form-control {}", props.class)
236 };
237
238 rsx! {
239 textarea {
240 class: "{full_class}",
241 rows: "{props.rows}",
242 placeholder: "{props.placeholder}",
243 disabled: props.disabled,
244 readonly: props.readonly,
245 value: "{props.value}",
246 oninput: move |evt| {
247 if let Some(handler) = &props.oninput {
248 handler.call(evt);
249 }
250 },
251 }
252 }
253}
254
255#[derive(Clone, PartialEq, Props)]
275pub struct CheckboxProps {
276 #[props(default)]
278 pub checked: bool,
279 #[props(default)]
281 pub label: String,
282 #[props(default)]
284 pub disabled: bool,
285 #[props(default)]
287 pub onchange: Option<EventHandler<FormEvent>>,
288 #[props(default)]
290 pub class: String,
291}
292
293#[component]
294pub fn Checkbox(props: CheckboxProps) -> Element {
295 let full_class = if props.class.is_empty() {
296 "form-check".to_string()
297 } else {
298 format!("form-check {}", props.class)
299 };
300
301 rsx! {
302 div { class: "{full_class}",
303 input {
304 class: "form-check-input",
305 r#type: "checkbox",
306 checked: props.checked,
307 disabled: props.disabled,
308 onchange: move |evt| {
309 if let Some(handler) = &props.onchange {
310 handler.call(evt);
311 }
312 },
313 }
314 if !props.label.is_empty() {
315 label { class: "form-check-label", "{props.label}" }
316 }
317 }
318 }
319}
320
321#[derive(Clone, PartialEq, Props)]
341pub struct SwitchProps {
342 #[props(default)]
344 pub checked: bool,
345 #[props(default)]
347 pub label: String,
348 #[props(default)]
350 pub disabled: bool,
351 #[props(default)]
353 pub onchange: Option<EventHandler<FormEvent>>,
354 #[props(default)]
356 pub class: String,
357}
358
359#[component]
360pub fn Switch(props: SwitchProps) -> Element {
361 let full_class = if props.class.is_empty() {
362 "form-check form-switch".to_string()
363 } else {
364 format!("form-check form-switch {}", props.class)
365 };
366
367 rsx! {
368 div { class: "{full_class}",
369 input {
370 class: "form-check-input",
371 r#type: "checkbox",
372 role: "switch",
373 checked: props.checked,
374 disabled: props.disabled,
375 onchange: move |evt| {
376 if let Some(handler) = &props.onchange {
377 handler.call(evt);
378 }
379 },
380 }
381 if !props.label.is_empty() {
382 label { class: "form-check-label", "{props.label}" }
383 }
384 }
385 }
386}
387
388#[derive(Clone, PartialEq, Props)]
396pub struct RangeProps {
397 #[props(default)]
399 pub value: String,
400 #[props(default = "0".to_string())]
402 pub min: String,
403 #[props(default = "100".to_string())]
405 pub max: String,
406 #[props(default)]
408 pub step: String,
409 #[props(default)]
411 pub disabled: bool,
412 #[props(default)]
414 pub oninput: Option<EventHandler<FormEvent>>,
415 #[props(default)]
417 pub class: String,
418}
419
420#[component]
421pub fn Range(props: RangeProps) -> Element {
422 let full_class = if props.class.is_empty() {
423 "form-range".to_string()
424 } else {
425 format!("form-range {}", props.class)
426 };
427
428 rsx! {
429 input {
430 class: "{full_class}",
431 r#type: "range",
432 value: "{props.value}",
433 min: "{props.min}",
434 max: "{props.max}",
435 step: if props.step.is_empty() { None } else { Some(props.step.clone()) },
436 disabled: props.disabled,
437 oninput: move |evt| {
438 if let Some(handler) = &props.oninput {
439 handler.call(evt);
440 }
441 },
442 }
443 }
444}
445
446#[derive(Clone, PartialEq, Props)]
459pub struct FloatingLabelProps {
460 pub label: String,
462 #[props(default)]
464 pub class: String,
465 pub children: Element,
467}
468
469#[component]
470pub fn FloatingLabel(props: FloatingLabelProps) -> Element {
471 let full_class = if props.class.is_empty() {
472 "form-floating".to_string()
473 } else {
474 format!("form-floating {}", props.class)
475 };
476
477 rsx! {
478 div { class: "{full_class}",
479 {props.children}
480 label { "{props.label}" }
481 }
482 }
483}
484
485#[derive(Clone, PartialEq, Props)]
494pub struct FormFeedbackProps {
495 #[props(default)]
497 pub valid: bool,
498 #[props(default)]
500 pub class: String,
501 pub children: Element,
503}
504
505#[component]
506pub fn FormFeedback(props: FormFeedbackProps) -> Element {
507 let base = if props.valid {
508 "valid-feedback"
509 } else {
510 "invalid-feedback"
511 };
512 let full_class = if props.class.is_empty() {
513 base.to_string()
514 } else {
515 format!("{base} {}", props.class)
516 };
517
518 rsx! {
519 div { class: "{full_class}", {props.children} }
520 }
521}
522
523#[derive(Clone, PartialEq, Props)]
532pub struct FormTextProps {
533 #[props(default)]
535 pub class: String,
536 pub children: Element,
538}
539
540#[component]
541pub fn FormText(props: FormTextProps) -> Element {
542 let full_class = if props.class.is_empty() {
543 "form-text".to_string()
544 } else {
545 format!("form-text {}", props.class)
546 };
547
548 rsx! {
549 div { class: "{full_class}", {props.children} }
550 }
551}
552
553#[derive(Clone, PartialEq, Props)]
576pub struct RadioProps {
577 pub name: String,
579 #[props(default)]
581 pub checked: bool,
582 #[props(default)]
584 pub label: String,
585 #[props(default)]
587 pub disabled: bool,
588 #[props(default)]
590 pub onchange: Option<EventHandler<FormEvent>>,
591 #[props(default)]
593 pub class: String,
594}
595
596#[component]
597pub fn Radio(props: RadioProps) -> Element {
598 let full_class = if props.class.is_empty() {
599 "form-check".to_string()
600 } else {
601 format!("form-check {}", props.class)
602 };
603
604 rsx! {
605 div { class: "{full_class}",
606 input {
607 class: "form-check-input",
608 r#type: "radio",
609 name: "{props.name}",
610 checked: props.checked,
611 disabled: props.disabled,
612 onchange: move |evt| {
613 if let Some(handler) = &props.onchange {
614 handler.call(evt);
615 }
616 },
617 }
618 if !props.label.is_empty() {
619 label { class: "form-check-label", "{props.label}" }
620 }
621 }
622 }
623}