nova_forms/components/
radio.rs

1use std::{fmt::Display, hash::Hash, str::FromStr};
2
3use crate::{use_translation, QueryStringPart, FieldWiring};
4use leptos::*;
5use strum::{IntoEnumIterator, ParseError};
6
7/// A component that renders radio buttons from an enum.
8#[component]
9pub fn Radio<T>(
10    /// The label of the input field.
11    #[prop(into)] label: TextProp,
12    /// The query string that binds the input field to the form data.
13    #[prop(into)] bind: QueryStringPart,
14    /// The initial value of the input field.
15    #[prop(optional, into)] value: MaybeProp<T>,
16    /// A write signal that is updated with the parsed value of the input field.
17    #[prop(optional, into)] change: Option<Callback<Result<T, T::Err>, ()>>,
18    /// Set a custom error message for the input field.
19    #[prop(optional, into)] error: MaybeProp<TextProp>,
20) -> impl IntoView
21where
22    T: IntoEnumIterator + FromStr<Err = ParseError> + Into<&'static str> + Clone + Copy + Default + Eq + Hash + Display + 'static
23{    
24    let FieldWiring {
25        qs,
26        value,
27        error,
28        set_raw_value,
29        render_mode,
30        ..
31    } = FieldWiring::<T>::wire(bind, value, change, error, label.clone());
32 
33    view! {
34        <div
35            class="field radio"
36            class:error=move || error.get().is_some()
37            class:ok=move || error.get().is_none()
38        >
39        { move || {
40            if render_mode.get() {
41                if let Ok(value) = value.get() {
42                    view!{
43                        <span class="label">{label.clone()}</span>
44                        <span class="value">{use_translation(value)}</span>
45                    }.into_view()
46                } else {
47                    view!{
48                        <span class="label">{label.clone()}</span>
49                        <span class="value"></span>
50                    }.into_view()
51                }
52            } else {
53                view! {
54                    <fieldset>
55                        <legend>{label.clone()}</legend>
56                        <For
57                            each={move || T::iter()}
58                            key={|item| *item}
59                            children={move |item| {
60                                let input_elem = html::input()
61                                    .attr("type", "radio")
62                                    .attr("id", format!("{}({})", qs.to_string(), item.into()))
63                                    .attr("name", qs.to_string())
64                                    .attr("checked", move || value.get() == Ok(item))
65                                    .attr("value", move || item.into())
66                                    .on(ev::input, move |ev| {
67                                        set_raw_value.call(event_target_value(&ev));
68                                    });
69
70                                view! {
71                                    <label for=format!("{}({})", qs.to_string(), item.into())>
72                                        {input_elem}
73                                        <span class="custom-radio"></span>
74                                        <span class="custom-radio-label">{use_translation(item)}</span>
75                                    </label>
76                                }
77                            }}
78                        />
79                        {move || {
80                            if let Some(error) = error.get() {
81                                view! { <span class="error-message">{error}</span> }
82                                    .into_view()
83                            } else {
84                                View::default()
85                            }
86                        }}
87                    </fieldset>
88                }.into_view()
89            }
90        }}    
91        </div>
92    }
93}