dioxus_bootstrap_css/form.rs
1use dioxus::prelude::*;
2
3use crate::types::Size;
4
5/// Bootstrap FormGroup — label + control wrapper.
6///
7/// # Bootstrap HTML → Dioxus
8///
9/// ```html
10/// <!-- Bootstrap HTML -->
11/// <div class="mb-3">
12/// <label class="form-label">Email</label>
13/// <input type="email" class="form-control" placeholder="you@example.com">
14/// </div>
15/// ```
16///
17/// ```rust,no_run
18/// # use dioxus::prelude::*;
19/// # use dioxus_bootstrap_css::prelude::*;
20/// # fn _doctest() -> Element {
21/// rsx! {
22/// FormGroup { label: "Email",
23/// Input { r#type: "email", placeholder: "you@example.com" }
24/// }
25/// }
26/// # }
27/// ```
28#[derive(Clone, PartialEq, Props)]
29pub struct FormGroupProps {
30 /// Label text.
31 #[props(default)]
32 pub label: String,
33 /// Additional CSS classes for the wrapper div.
34 #[props(default)]
35 pub class: String,
36 /// Any additional HTML attributes.
37 #[props(extends = GlobalAttributes)]
38 attributes: Vec<Attribute>,
39 /// Child elements (form control).
40 pub children: Element,
41}
42
43#[component]
44pub fn FormGroup(props: FormGroupProps) -> Element {
45 let full_class = if props.class.is_empty() {
46 "mb-3".to_string()
47 } else {
48 format!("mb-3 {}", props.class)
49 };
50
51 rsx! {
52 div { class: "{full_class}",
53 ..props.attributes,
54 if !props.label.is_empty() {
55 label { class: "form-label", "{props.label}" }
56 }
57 {props.children}
58 }
59 }
60}
61
62/// Bootstrap Input component.
63///
64/// # Bootstrap HTML → Dioxus
65///
66/// | HTML | Dioxus |
67/// |---|---|
68/// | `<input class="form-control" type="text">` | `Input { r#type: "text" }` |
69/// | `<input class="form-control form-control-sm" type="email">` | `Input { r#type: "email", size: Size::Sm }` |
70/// | `<input class="form-control" disabled>` | `Input { disabled: true }` |
71///
72/// ```rust,no_run
73/// # use dioxus::prelude::*;
74/// # use dioxus_bootstrap_css::prelude::*;
75/// # fn _doctest() -> Element {
76/// rsx! {
77/// Input { r#type: "text", value: "hello", placeholder: "Enter text" }
78/// Input { r#type: "email", size: Size::Sm, oninput: move |evt| { /* handle */ } }
79/// Input { r#type: "password", disabled: true }
80/// }
81/// # }
82/// ```
83#[derive(Clone, PartialEq, Props)]
84pub struct InputProps {
85 /// Input type (text, email, password, number, etc.).
86 #[props(default = "text".to_string())]
87 pub r#type: String,
88 /// Current value.
89 #[props(default)]
90 pub value: String,
91 /// When `true`, the `value` attribute is omitted so the field is
92 /// *uncontrolled*: the DOM keeps whatever value the user or an external
93 /// script writes, instead of Dioxus forcing it back to `value` on every
94 /// render. Use for a field another script streams into (e.g. a live
95 /// transcript box).
96 #[props(default)]
97 pub uncontrolled: bool,
98 /// Placeholder text.
99 #[props(default)]
100 pub placeholder: String,
101 /// Minimum value for numeric/date inputs.
102 #[props(default)]
103 pub min: Option<String>,
104 /// Maximum value for numeric/date inputs.
105 #[props(default)]
106 pub max: Option<String>,
107 /// Browser autocomplete hint.
108 #[props(default)]
109 pub autocomplete: Option<String>,
110 /// Input size.
111 #[props(default)]
112 pub size: Size,
113 /// Disabled state.
114 #[props(default)]
115 pub disabled: bool,
116 /// Readonly state.
117 #[props(default)]
118 pub readonly: bool,
119 /// Input event handler.
120 #[props(default)]
121 pub oninput: Option<EventHandler<FormEvent>>,
122 /// Change event handler.
123 #[props(default)]
124 pub onchange: Option<EventHandler<FormEvent>>,
125 /// Key down event handler.
126 #[props(default)]
127 pub onkeydown: Option<EventHandler<KeyboardEvent>>,
128 /// Key up event handler.
129 #[props(default)]
130 pub onkeyup: Option<EventHandler<KeyboardEvent>>,
131 /// Additional CSS classes.
132 #[props(default)]
133 pub class: String,
134 /// Any additional HTML attributes.
135 #[props(extends = GlobalAttributes)]
136 attributes: Vec<Attribute>,
137}
138
139#[component]
140pub fn Input(props: InputProps) -> Element {
141 let size_class = match props.size {
142 Size::Md => String::new(),
143 s => format!(" form-control-{s}"),
144 };
145
146 let full_class = if props.class.is_empty() {
147 format!("form-control{size_class}")
148 } else {
149 format!("form-control{size_class} {}", props.class)
150 };
151
152 rsx! {
153 input {
154 class: "{full_class}",
155 r#type: "{props.r#type}",
156 value: if props.uncontrolled { None } else { Some(props.value.clone()) },
157 placeholder: "{props.placeholder}",
158 min: props.min.clone(),
159 max: props.max.clone(),
160 autocomplete: props.autocomplete.clone(),
161 disabled: props.disabled,
162 readonly: props.readonly,
163 oninput: move |evt| {
164 if let Some(handler) = &props.oninput {
165 handler.call(evt);
166 }
167 },
168 onchange: move |evt| {
169 if let Some(handler) = &props.onchange {
170 handler.call(evt);
171 }
172 },
173 onkeydown: move |evt| {
174 if let Some(handler) = &props.onkeydown {
175 handler.call(evt);
176 }
177 },
178 onkeyup: move |evt| {
179 if let Some(handler) = &props.onkeyup {
180 handler.call(evt);
181 }
182 },
183 ..props.attributes,
184 }
185 }
186}
187
188/// Bootstrap Select (dropdown) component.
189///
190/// # Bootstrap HTML → Dioxus
191///
192/// ```html
193/// <!-- Bootstrap HTML -->
194/// <select class="form-select">
195/// <option value="opt1">Option 1</option>
196/// <option value="opt2" selected>Option 2</option>
197/// </select>
198/// ```
199///
200/// ```rust,no_run
201/// # use dioxus::prelude::*;
202/// # use dioxus_bootstrap_css::prelude::*;
203/// # fn _doctest() -> Element {
204/// rsx! {
205/// Select { value: "opt2", onchange: move |evt| { /* handle */ },
206/// option { value: "opt1", "Option 1" }
207/// option { value: "opt2", "Option 2" }
208/// }
209/// }
210/// # }
211/// ```
212#[derive(Clone, PartialEq, Props)]
213pub struct SelectProps {
214 /// Current selected value.
215 #[props(default)]
216 pub value: String,
217 /// Select size.
218 #[props(default)]
219 pub size: Size,
220 /// Disabled state.
221 #[props(default)]
222 pub disabled: bool,
223 /// Change event handler.
224 #[props(default)]
225 pub onchange: Option<EventHandler<FormEvent>>,
226 /// Additional CSS classes.
227 #[props(default)]
228 pub class: String,
229 /// Any additional HTML attributes.
230 #[props(extends = GlobalAttributes)]
231 attributes: Vec<Attribute>,
232 /// Child elements (option elements).
233 pub children: Element,
234}
235
236#[component]
237pub fn Select(props: SelectProps) -> Element {
238 let size_class = match props.size {
239 Size::Md => String::new(),
240 s => format!(" form-select-{s}"),
241 };
242
243 let full_class = if props.class.is_empty() {
244 format!("form-select{size_class}")
245 } else {
246 format!("form-select{size_class} {}", props.class)
247 };
248
249 rsx! {
250 select {
251 class: "{full_class}",
252 value: "{props.value}",
253 disabled: props.disabled,
254 onchange: move |evt| {
255 if let Some(handler) = &props.onchange {
256 handler.call(evt);
257 }
258 },
259 ..props.attributes,
260 {props.children}
261 }
262 }
263}
264
265/// Bootstrap Textarea component.
266///
267/// # Bootstrap HTML → Dioxus
268///
269/// | HTML | Dioxus |
270/// |---|---|
271/// | `<textarea class="form-control" rows="5">` | `Textarea { rows: 5 }` |
272/// | `<textarea class="form-control form-control-sm">` | `Textarea { size: Size::Sm }` |
273/// | `<textarea class="form-control" placeholder="..." disabled>` | `Textarea { placeholder: "...", disabled: true }` |
274///
275/// ```rust,no_run
276/// # use dioxus::prelude::*;
277/// # use dioxus_bootstrap_css::prelude::*;
278/// # fn _doctest() -> Element {
279/// rsx! {
280/// Textarea { rows: 5, placeholder: "Enter description..." }
281/// }
282/// # }
283/// ```
284#[derive(Clone, PartialEq, Props)]
285pub struct TextareaProps {
286 /// Current value.
287 #[props(default)]
288 pub value: String,
289 /// When `true`, the `value` attribute is omitted so the field is
290 /// *uncontrolled*: the DOM keeps whatever value the user or an external
291 /// script writes, instead of Dioxus forcing it back to `value` on every
292 /// render. Use for a field another script streams into (e.g. a live
293 /// transcript box).
294 #[props(default)]
295 pub uncontrolled: bool,
296 /// Number of visible rows.
297 #[props(default = 3)]
298 pub rows: u32,
299 /// Placeholder text.
300 #[props(default)]
301 pub placeholder: String,
302 /// Textarea size.
303 #[props(default)]
304 pub size: Size,
305 /// Disabled state.
306 #[props(default)]
307 pub disabled: bool,
308 /// Readonly state.
309 #[props(default)]
310 pub readonly: bool,
311 /// Input event handler.
312 #[props(default)]
313 pub oninput: Option<EventHandler<FormEvent>>,
314 /// Change event handler.
315 #[props(default)]
316 pub onchange: Option<EventHandler<FormEvent>>,
317 /// Key down event handler.
318 #[props(default)]
319 pub onkeydown: Option<EventHandler<KeyboardEvent>>,
320 /// Key up event handler.
321 #[props(default)]
322 pub onkeyup: Option<EventHandler<KeyboardEvent>>,
323 /// Additional CSS classes.
324 #[props(default)]
325 pub class: String,
326 /// Any additional HTML attributes.
327 #[props(extends = GlobalAttributes)]
328 attributes: Vec<Attribute>,
329}
330
331#[component]
332pub fn Textarea(props: TextareaProps) -> Element {
333 let size_class = match props.size {
334 Size::Md => String::new(),
335 s => format!(" form-control-{s}"),
336 };
337 let full_class = if props.class.is_empty() {
338 format!("form-control{size_class}")
339 } else {
340 format!("form-control{size_class} {}", props.class)
341 };
342
343 rsx! {
344 textarea {
345 class: "{full_class}",
346 rows: "{props.rows}",
347 placeholder: "{props.placeholder}",
348 disabled: props.disabled,
349 readonly: props.readonly,
350 value: if props.uncontrolled { None } else { Some(props.value.clone()) },
351 oninput: move |evt| {
352 if let Some(handler) = &props.oninput {
353 handler.call(evt);
354 }
355 },
356 onchange: move |evt| {
357 if let Some(handler) = &props.onchange {
358 handler.call(evt);
359 }
360 },
361 onkeydown: move |evt| {
362 if let Some(handler) = &props.onkeydown {
363 handler.call(evt);
364 }
365 },
366 onkeyup: move |evt| {
367 if let Some(handler) = &props.onkeyup {
368 handler.call(evt);
369 }
370 },
371 ..props.attributes,
372 }
373 }
374}
375
376/// Bootstrap Checkbox component.
377///
378/// # Bootstrap HTML → Dioxus
379///
380/// ```html
381/// <!-- Bootstrap HTML -->
382/// <div class="form-check">
383/// <input class="form-check-input" type="checkbox" checked>
384/// <label class="form-check-label">Accept terms</label>
385/// </div>
386/// ```
387///
388/// ```rust,no_run
389/// # use dioxus::prelude::*;
390/// # use dioxus_bootstrap_css::prelude::*;
391/// # fn _doctest() -> Element {
392/// rsx! {
393/// Checkbox { checked: true, label: "Accept terms",
394/// onchange: move |evt| { /* handle */ },
395/// }
396/// }
397/// # }
398/// ```
399#[derive(Clone, PartialEq, Props)]
400pub struct CheckboxProps {
401 /// Whether the checkbox is checked.
402 #[props(default)]
403 pub checked: bool,
404 /// Optional id applied to the checkbox input.
405 #[props(default)]
406 pub input_id: Option<String>,
407 /// Label text.
408 #[props(default)]
409 pub label: String,
410 /// Disabled state.
411 #[props(default)]
412 pub disabled: bool,
413 /// Change event handler.
414 #[props(default)]
415 pub onchange: Option<EventHandler<FormEvent>>,
416 /// Click event handler for the checkbox input.
417 #[props(default)]
418 pub onclick: Option<EventHandler<MouseEvent>>,
419 /// Additional CSS classes for the wrapper.
420 #[props(default)]
421 pub class: String,
422 /// Any additional HTML attributes.
423 #[props(extends = GlobalAttributes)]
424 attributes: Vec<Attribute>,
425}
426
427#[component]
428pub fn Checkbox(props: CheckboxProps) -> Element {
429 let full_class = if props.class.is_empty() {
430 "form-check".to_string()
431 } else {
432 format!("form-check {}", props.class)
433 };
434 let label_for = props.input_id.clone().unwrap_or_default();
435
436 rsx! {
437 div { class: "{full_class}",
438 ..props.attributes,
439 input {
440 class: "form-check-input",
441 r#type: "checkbox",
442 id: props.input_id.unwrap_or_default(),
443 checked: props.checked,
444 disabled: props.disabled,
445 onclick: move |evt| {
446 if let Some(handler) = &props.onclick {
447 handler.call(evt);
448 }
449 },
450 onchange: move |evt| {
451 if let Some(handler) = &props.onchange {
452 handler.call(evt);
453 }
454 },
455 }
456 if !props.label.is_empty() {
457 label { class: "form-check-label", r#for: "{label_for}", "{props.label}" }
458 }
459 }
460 }
461}
462
463/// Bootstrap Switch (toggle) component.
464///
465/// # Bootstrap HTML → Dioxus
466///
467/// ```html
468/// <!-- Bootstrap HTML -->
469/// <div class="form-check form-switch">
470/// <input class="form-check-input" type="checkbox" role="switch" checked>
471/// <label class="form-check-label">Enable notifications</label>
472/// </div>
473/// ```
474///
475/// ```rust,no_run
476/// # use dioxus::prelude::*;
477/// # use dioxus_bootstrap_css::prelude::*;
478/// # fn _doctest() -> Element {
479/// rsx! {
480/// Switch { checked: true, label: "Enable notifications",
481/// onchange: move |evt| { /* handle */ },
482/// }
483/// }
484/// # }
485/// ```
486#[derive(Clone, PartialEq, Props)]
487pub struct SwitchProps {
488 /// Whether the switch is on.
489 #[props(default)]
490 pub checked: bool,
491 /// Label text.
492 #[props(default)]
493 pub label: String,
494 /// Disabled state.
495 #[props(default)]
496 pub disabled: bool,
497 /// Change event handler.
498 #[props(default)]
499 pub onchange: Option<EventHandler<FormEvent>>,
500 /// Additional CSS classes for the wrapper.
501 #[props(default)]
502 pub class: String,
503 /// Any additional HTML attributes.
504 #[props(extends = GlobalAttributes)]
505 attributes: Vec<Attribute>,
506}
507
508#[component]
509pub fn Switch(props: SwitchProps) -> Element {
510 let full_class = if props.class.is_empty() {
511 "form-check form-switch".to_string()
512 } else {
513 format!("form-check form-switch {}", props.class)
514 };
515
516 rsx! {
517 div { class: "{full_class}",
518 ..props.attributes,
519 input {
520 class: "form-check-input",
521 r#type: "checkbox",
522 role: "switch",
523 checked: props.checked,
524 disabled: props.disabled,
525 onchange: move |evt| {
526 if let Some(handler) = &props.onchange {
527 handler.call(evt);
528 }
529 },
530 }
531 if !props.label.is_empty() {
532 label { class: "form-check-label", "{props.label}" }
533 }
534 }
535 }
536}
537
538/// Bootstrap Range (slider) input.
539///
540/// # Bootstrap HTML → Dioxus
541///
542/// | HTML | Dioxus |
543/// |---|---|
544/// | `<input type="range" class="form-range" min="0" max="100">` | `Range { min: "0", max: "100" }` |
545/// | `<input type="range" class="form-range" step="5" disabled>` | `Range { step: "5".into(), disabled: true }` |
546///
547/// ```rust,no_run
548/// # use dioxus::prelude::*;
549/// # use dioxus_bootstrap_css::prelude::*;
550/// # fn _doctest() -> Element {
551/// rsx! {
552/// Range { value: "50", min: "0", max: "100" }
553/// }
554/// # }
555/// ```
556#[derive(Clone, PartialEq, Props)]
557pub struct RangeProps {
558 /// Current value.
559 #[props(default)]
560 pub value: String,
561 /// Minimum value.
562 #[props(default = "0".to_string())]
563 pub min: String,
564 /// Maximum value.
565 #[props(default = "100".to_string())]
566 pub max: String,
567 /// Step increment.
568 #[props(default)]
569 pub step: String,
570 /// Disabled state.
571 #[props(default)]
572 pub disabled: bool,
573 /// Input event handler.
574 #[props(default)]
575 pub oninput: Option<EventHandler<FormEvent>>,
576 /// Additional CSS classes.
577 #[props(default)]
578 pub class: String,
579 /// Any additional HTML attributes.
580 #[props(extends = GlobalAttributes)]
581 attributes: Vec<Attribute>,
582}
583
584#[component]
585pub fn Range(props: RangeProps) -> Element {
586 let full_class = if props.class.is_empty() {
587 "form-range".to_string()
588 } else {
589 format!("form-range {}", props.class)
590 };
591
592 rsx! {
593 input {
594 class: "{full_class}",
595 r#type: "range",
596 value: "{props.value}",
597 min: "{props.min}",
598 max: "{props.max}",
599 step: if props.step.is_empty() { None } else { Some(props.step.clone()) },
600 disabled: props.disabled,
601 oninput: move |evt| {
602 if let Some(handler) = &props.oninput {
603 handler.call(evt);
604 }
605 },
606 ..props.attributes,
607 }
608 }
609}
610
611/// Bootstrap Floating Label wrapper.
612///
613/// Wraps an Input or Textarea with a floating label that moves
614/// above the control when focused or filled.
615///
616/// # Bootstrap HTML → Dioxus
617///
618/// | HTML | Dioxus |
619/// |---|---|
620/// | `<div class="form-floating"><input class="form-control" placeholder="..."><label>Email</label></div>` | `FloatingLabel { label: "Email", Input { placeholder: "..." } }` |
621///
622/// ```rust,no_run
623/// # use dioxus::prelude::*;
624/// # use dioxus_bootstrap_css::prelude::*;
625/// # fn _doctest() -> Element {
626/// rsx! {
627/// FloatingLabel { label: "Email address",
628/// Input { r#type: "email", placeholder: "name@example.com" }
629/// }
630/// }
631/// # }
632/// ```
633#[derive(Clone, PartialEq, Props)]
634pub struct FloatingLabelProps {
635 /// Label text.
636 pub label: String,
637 /// Additional CSS classes.
638 #[props(default)]
639 pub class: String,
640 /// Any additional HTML attributes.
641 #[props(extends = GlobalAttributes)]
642 attributes: Vec<Attribute>,
643 /// Child element (Input or Textarea).
644 pub children: Element,
645}
646
647#[component]
648pub fn FloatingLabel(props: FloatingLabelProps) -> Element {
649 let full_class = if props.class.is_empty() {
650 "form-floating".to_string()
651 } else {
652 format!("form-floating {}", props.class)
653 };
654
655 rsx! {
656 div { class: "{full_class}",
657 ..props.attributes,
658 {props.children}
659 label { "{props.label}" }
660 }
661 }
662}
663
664/// Bootstrap form validation feedback text.
665///
666/// # Bootstrap HTML → Dioxus
667///
668/// | HTML | Dioxus |
669/// |---|---|
670/// | `<div class="valid-feedback">Looks good!</div>` | `FormFeedback { valid: true, "Looks good!" }` |
671/// | `<div class="invalid-feedback">Required.</div>` | `FormFeedback { "Required." }` |
672///
673/// ```rust,no_run
674/// # use dioxus::prelude::*;
675/// # use dioxus_bootstrap_css::prelude::*;
676/// # fn _doctest() -> Element {
677/// rsx! {
678/// Input { class: "is-valid".to_string(), value: "correct" }
679/// FormFeedback { valid: true, "Looks good!" }
680/// }
681/// # }
682/// ```
683#[derive(Clone, PartialEq, Props)]
684pub struct FormFeedbackProps {
685 /// True for valid feedback, false for invalid.
686 #[props(default)]
687 pub valid: bool,
688 /// Additional CSS classes.
689 #[props(default)]
690 pub class: String,
691 /// Any additional HTML attributes.
692 #[props(extends = GlobalAttributes)]
693 attributes: Vec<Attribute>,
694 /// Feedback text.
695 pub children: Element,
696}
697
698#[component]
699pub fn FormFeedback(props: FormFeedbackProps) -> Element {
700 let base = if props.valid {
701 "valid-feedback"
702 } else {
703 "invalid-feedback"
704 };
705 let full_class = if props.class.is_empty() {
706 base.to_string()
707 } else {
708 format!("{base} {}", props.class)
709 };
710
711 rsx! {
712 div { class: "{full_class}", ..props.attributes, {props.children} }
713 }
714}
715
716/// Bootstrap form text (help text below a control).
717///
718/// # Bootstrap HTML → Dioxus
719///
720/// | HTML | Dioxus |
721/// |---|---|
722/// | `<div class="form-text">Must be 8-20 characters.</div>` | `FormText { "Must be 8-20 characters." }` |
723///
724/// ```rust,no_run
725/// # use dioxus::prelude::*;
726/// # use dioxus_bootstrap_css::prelude::*;
727/// # fn _doctest() -> Element {
728/// rsx! {
729/// Input { r#type: "password" }
730/// FormText { "Must be 8-20 characters long." }
731/// }
732/// # }
733/// ```
734#[derive(Clone, PartialEq, Props)]
735pub struct FormTextProps {
736 /// Additional CSS classes.
737 #[props(default)]
738 pub class: String,
739 /// Any additional HTML attributes.
740 #[props(extends = GlobalAttributes)]
741 attributes: Vec<Attribute>,
742 /// Help text content.
743 pub children: Element,
744}
745
746#[component]
747pub fn FormText(props: FormTextProps) -> Element {
748 let full_class = if props.class.is_empty() {
749 "form-text".to_string()
750 } else {
751 format!("form-text {}", props.class)
752 };
753
754 rsx! {
755 div { class: "{full_class}", ..props.attributes, {props.children} }
756 }
757}
758
759/// Bootstrap Radio button component.
760///
761/// # Bootstrap HTML → Dioxus
762///
763/// ```html
764/// <!-- Bootstrap HTML -->
765/// <div class="form-check">
766/// <input class="form-check-input" type="radio" name="color" checked>
767/// <label class="form-check-label">Red</label>
768/// </div>
769/// <div class="form-check">
770/// <input class="form-check-input" type="radio" name="color">
771/// <label class="form-check-label">Blue</label>
772/// </div>
773/// ```
774///
775/// ```rust,no_run
776/// # use dioxus::prelude::*;
777/// # use dioxus_bootstrap_css::prelude::*;
778/// # fn _doctest() -> Element {
779/// rsx! {
780/// Radio { name: "color", label: "Red", checked: true }
781/// Radio { name: "color", label: "Blue" }
782/// }
783/// # }
784/// ```
785#[derive(Clone, PartialEq, Props)]
786pub struct RadioProps {
787 /// Radio group name.
788 pub name: String,
789 /// Whether the radio is checked.
790 #[props(default)]
791 pub checked: bool,
792 /// Label text.
793 #[props(default)]
794 pub label: String,
795 /// Disabled state.
796 #[props(default)]
797 pub disabled: bool,
798 /// Change event handler.
799 #[props(default)]
800 pub onchange: Option<EventHandler<FormEvent>>,
801 /// Additional CSS classes for the wrapper.
802 #[props(default)]
803 pub class: String,
804 /// Any additional HTML attributes.
805 #[props(extends = GlobalAttributes)]
806 attributes: Vec<Attribute>,
807}
808
809#[component]
810pub fn Radio(props: RadioProps) -> Element {
811 let full_class = if props.class.is_empty() {
812 "form-check".to_string()
813 } else {
814 format!("form-check {}", props.class)
815 };
816
817 rsx! {
818 div { class: "{full_class}",
819 ..props.attributes,
820 input {
821 class: "form-check-input",
822 r#type: "radio",
823 name: "{props.name}",
824 checked: props.checked,
825 disabled: props.disabled,
826 onchange: move |evt| {
827 if let Some(handler) = &props.onchange {
828 handler.call(evt);
829 }
830 },
831 }
832 if !props.label.is_empty() {
833 label { class: "form-check-label", "{props.label}" }
834 }
835 }
836 }
837}