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 #[props(extends = GlobalAttributes)]
34 attributes: Vec<Attribute>,
35 pub children: Element,
37}
38
39#[component]
40pub fn FormGroup(props: FormGroupProps) -> Element {
41 let full_class = if props.class.is_empty() {
42 "mb-3".to_string()
43 } else {
44 format!("mb-3 {}", props.class)
45 };
46
47 rsx! {
48 div { class: "{full_class}",
49 ..props.attributes,
50 if !props.label.is_empty() {
51 label { class: "form-label", "{props.label}" }
52 }
53 {props.children}
54 }
55 }
56}
57
58#[derive(Clone, PartialEq, Props)]
76pub struct InputProps {
77 #[props(default = "text".to_string())]
79 pub r#type: String,
80 #[props(default)]
82 pub value: String,
83 #[props(default)]
85 pub placeholder: String,
86 #[props(default)]
88 pub size: Size,
89 #[props(default)]
91 pub disabled: bool,
92 #[props(default)]
94 pub readonly: bool,
95 #[props(default)]
97 pub oninput: Option<EventHandler<FormEvent>>,
98 #[props(default)]
100 pub class: String,
101 #[props(extends = GlobalAttributes)]
103 attributes: Vec<Attribute>,
104}
105
106#[component]
107pub fn Input(props: InputProps) -> Element {
108 let size_class = match props.size {
109 Size::Md => String::new(),
110 s => format!(" form-control-{s}"),
111 };
112
113 let full_class = if props.class.is_empty() {
114 format!("form-control{size_class}")
115 } else {
116 format!("form-control{size_class} {}", props.class)
117 };
118
119 rsx! {
120 input {
121 class: "{full_class}",
122 r#type: "{props.r#type}",
123 value: "{props.value}",
124 placeholder: "{props.placeholder}",
125 disabled: props.disabled,
126 readonly: props.readonly,
127 oninput: move |evt| {
128 if let Some(handler) = &props.oninput {
129 handler.call(evt);
130 }
131 },
132 ..props.attributes,
133 }
134 }
135}
136
137#[derive(Clone, PartialEq, Props)]
158pub struct SelectProps {
159 #[props(default)]
161 pub value: String,
162 #[props(default)]
164 pub size: Size,
165 #[props(default)]
167 pub disabled: bool,
168 #[props(default)]
170 pub onchange: Option<EventHandler<FormEvent>>,
171 #[props(default)]
173 pub class: String,
174 #[props(extends = GlobalAttributes)]
176 attributes: Vec<Attribute>,
177 pub children: Element,
179}
180
181#[component]
182pub fn Select(props: SelectProps) -> Element {
183 let size_class = match props.size {
184 Size::Md => String::new(),
185 s => format!(" form-select-{s}"),
186 };
187
188 let full_class = if props.class.is_empty() {
189 format!("form-select{size_class}")
190 } else {
191 format!("form-select{size_class} {}", props.class)
192 };
193
194 rsx! {
195 select {
196 class: "{full_class}",
197 value: "{props.value}",
198 disabled: props.disabled,
199 onchange: move |evt| {
200 if let Some(handler) = &props.onchange {
201 handler.call(evt);
202 }
203 },
204 ..props.attributes,
205 {props.children}
206 }
207 }
208}
209
210#[derive(Clone, PartialEq, Props)]
218pub struct TextareaProps {
219 #[props(default)]
221 pub value: String,
222 #[props(default = 3)]
224 pub rows: u32,
225 #[props(default)]
227 pub placeholder: String,
228 #[props(default)]
230 pub disabled: bool,
231 #[props(default)]
233 pub readonly: bool,
234 #[props(default)]
236 pub oninput: Option<EventHandler<FormEvent>>,
237 #[props(default)]
239 pub class: String,
240 #[props(extends = GlobalAttributes)]
242 attributes: Vec<Attribute>,
243}
244
245#[component]
246pub fn Textarea(props: TextareaProps) -> Element {
247 let full_class = if props.class.is_empty() {
248 "form-control".to_string()
249 } else {
250 format!("form-control {}", props.class)
251 };
252
253 rsx! {
254 textarea {
255 class: "{full_class}",
256 rows: "{props.rows}",
257 placeholder: "{props.placeholder}",
258 disabled: props.disabled,
259 readonly: props.readonly,
260 value: "{props.value}",
261 oninput: move |evt| {
262 if let Some(handler) = &props.oninput {
263 handler.call(evt);
264 }
265 },
266 ..props.attributes,
267 }
268 }
269}
270
271#[derive(Clone, PartialEq, Props)]
291pub struct CheckboxProps {
292 #[props(default)]
294 pub checked: bool,
295 #[props(default)]
297 pub label: String,
298 #[props(default)]
300 pub disabled: bool,
301 #[props(default)]
303 pub onchange: Option<EventHandler<FormEvent>>,
304 #[props(default)]
306 pub class: String,
307 #[props(extends = GlobalAttributes)]
309 attributes: Vec<Attribute>,
310}
311
312#[component]
313pub fn Checkbox(props: CheckboxProps) -> Element {
314 let full_class = if props.class.is_empty() {
315 "form-check".to_string()
316 } else {
317 format!("form-check {}", props.class)
318 };
319
320 rsx! {
321 div { class: "{full_class}",
322 ..props.attributes,
323 input {
324 class: "form-check-input",
325 r#type: "checkbox",
326 checked: props.checked,
327 disabled: props.disabled,
328 onchange: move |evt| {
329 if let Some(handler) = &props.onchange {
330 handler.call(evt);
331 }
332 },
333 }
334 if !props.label.is_empty() {
335 label { class: "form-check-label", "{props.label}" }
336 }
337 }
338 }
339}
340
341#[derive(Clone, PartialEq, Props)]
361pub struct SwitchProps {
362 #[props(default)]
364 pub checked: bool,
365 #[props(default)]
367 pub label: String,
368 #[props(default)]
370 pub disabled: bool,
371 #[props(default)]
373 pub onchange: Option<EventHandler<FormEvent>>,
374 #[props(default)]
376 pub class: String,
377 #[props(extends = GlobalAttributes)]
379 attributes: Vec<Attribute>,
380}
381
382#[component]
383pub fn Switch(props: SwitchProps) -> Element {
384 let full_class = if props.class.is_empty() {
385 "form-check form-switch".to_string()
386 } else {
387 format!("form-check form-switch {}", props.class)
388 };
389
390 rsx! {
391 div { class: "{full_class}",
392 ..props.attributes,
393 input {
394 class: "form-check-input",
395 r#type: "checkbox",
396 role: "switch",
397 checked: props.checked,
398 disabled: props.disabled,
399 onchange: move |evt| {
400 if let Some(handler) = &props.onchange {
401 handler.call(evt);
402 }
403 },
404 }
405 if !props.label.is_empty() {
406 label { class: "form-check-label", "{props.label}" }
407 }
408 }
409 }
410}
411
412#[derive(Clone, PartialEq, Props)]
420pub struct RangeProps {
421 #[props(default)]
423 pub value: String,
424 #[props(default = "0".to_string())]
426 pub min: String,
427 #[props(default = "100".to_string())]
429 pub max: String,
430 #[props(default)]
432 pub step: String,
433 #[props(default)]
435 pub disabled: bool,
436 #[props(default)]
438 pub oninput: Option<EventHandler<FormEvent>>,
439 #[props(default)]
441 pub class: String,
442 #[props(extends = GlobalAttributes)]
444 attributes: Vec<Attribute>,
445}
446
447#[component]
448pub fn Range(props: RangeProps) -> Element {
449 let full_class = if props.class.is_empty() {
450 "form-range".to_string()
451 } else {
452 format!("form-range {}", props.class)
453 };
454
455 rsx! {
456 input {
457 class: "{full_class}",
458 r#type: "range",
459 value: "{props.value}",
460 min: "{props.min}",
461 max: "{props.max}",
462 step: if props.step.is_empty() { None } else { Some(props.step.clone()) },
463 disabled: props.disabled,
464 oninput: move |evt| {
465 if let Some(handler) = &props.oninput {
466 handler.call(evt);
467 }
468 },
469 ..props.attributes,
470 }
471 }
472}
473
474#[derive(Clone, PartialEq, Props)]
487pub struct FloatingLabelProps {
488 pub label: String,
490 #[props(default)]
492 pub class: String,
493 #[props(extends = GlobalAttributes)]
495 attributes: Vec<Attribute>,
496 pub children: Element,
498}
499
500#[component]
501pub fn FloatingLabel(props: FloatingLabelProps) -> Element {
502 let full_class = if props.class.is_empty() {
503 "form-floating".to_string()
504 } else {
505 format!("form-floating {}", props.class)
506 };
507
508 rsx! {
509 div { class: "{full_class}",
510 ..props.attributes,
511 {props.children}
512 label { "{props.label}" }
513 }
514 }
515}
516
517#[derive(Clone, PartialEq, Props)]
526pub struct FormFeedbackProps {
527 #[props(default)]
529 pub valid: bool,
530 #[props(default)]
532 pub class: String,
533 #[props(extends = GlobalAttributes)]
535 attributes: Vec<Attribute>,
536 pub children: Element,
538}
539
540#[component]
541pub fn FormFeedback(props: FormFeedbackProps) -> Element {
542 let base = if props.valid {
543 "valid-feedback"
544 } else {
545 "invalid-feedback"
546 };
547 let full_class = if props.class.is_empty() {
548 base.to_string()
549 } else {
550 format!("{base} {}", props.class)
551 };
552
553 rsx! {
554 div { class: "{full_class}", ..props.attributes, {props.children} }
555 }
556}
557
558#[derive(Clone, PartialEq, Props)]
567pub struct FormTextProps {
568 #[props(default)]
570 pub class: String,
571 #[props(extends = GlobalAttributes)]
573 attributes: Vec<Attribute>,
574 pub children: Element,
576}
577
578#[component]
579pub fn FormText(props: FormTextProps) -> Element {
580 let full_class = if props.class.is_empty() {
581 "form-text".to_string()
582 } else {
583 format!("form-text {}", props.class)
584 };
585
586 rsx! {
587 div { class: "{full_class}", ..props.attributes, {props.children} }
588 }
589}
590
591#[derive(Clone, PartialEq, Props)]
614pub struct RadioProps {
615 pub name: String,
617 #[props(default)]
619 pub checked: bool,
620 #[props(default)]
622 pub label: String,
623 #[props(default)]
625 pub disabled: bool,
626 #[props(default)]
628 pub onchange: Option<EventHandler<FormEvent>>,
629 #[props(default)]
631 pub class: String,
632 #[props(extends = GlobalAttributes)]
634 attributes: Vec<Attribute>,
635}
636
637#[component]
638pub fn Radio(props: RadioProps) -> Element {
639 let full_class = if props.class.is_empty() {
640 "form-check".to_string()
641 } else {
642 format!("form-check {}", props.class)
643 };
644
645 rsx! {
646 div { class: "{full_class}",
647 ..props.attributes,
648 input {
649 class: "form-check-input",
650 r#type: "radio",
651 name: "{props.name}",
652 checked: props.checked,
653 disabled: props.disabled,
654 onchange: move |evt| {
655 if let Some(handler) = &props.onchange {
656 handler.call(evt);
657 }
658 },
659 }
660 if !props.label.is_empty() {
661 label { class: "form-check-label", "{props.label}" }
662 }
663 }
664 }
665}