nova_forms/components/
select.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 a select field from an enum.
8#[component]
9pub fn Select<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        raw_value,
31        ..
32    } = FieldWiring::<T>::wire(bind, value, change, error, label.clone());
33 
34    let select_elem = view! {
35        <select
36            id=qs.to_string()
37            name=qs.to_string()
38            autocomplete=false
39            prop:value=move || raw_value.get()
40            on:input=move |ev| { set_raw_value.call(event_target_value(&ev));}
41        >
42            <For
43                each={move || T::iter()}
44                key={|item| *item}
45                children={move |item| {
46                    let option_elem = html::option()
47                        .attr("id", format!("{}({})", qs.to_string(), item.into()))
48                        .attr("selected", move || value.get() == Ok(item))
49                        .attr("value", move || item.into())
50                        .child(use_translation(item));
51                    
52                    view! {
53                        {option_elem}
54                    }
55                }}
56            />
57        </select>
58    };
59
60    view! {
61        <div
62            class="field select"
63            class:error=move || error.get().is_some()
64            class:ok=move || error.get().is_none()
65        >
66            {move || {
67                if render_mode.get() {
68                    if let Ok(value) = value.get() {
69                        view!{
70                            <span class="label">{label.clone()}</span>
71                            <span class="value">{use_translation(value)}</span>
72                        }.into_view()
73                    } else {
74                        view!{
75                            <span class="label">{label.clone()}</span>
76                            <span class="value"></span>
77                        }.into_view()
78                    }
79                } else {
80                    view! {
81                        <label for=qs.to_string()>{label.clone()}</label>
82                        {select_elem.clone()}
83                        {move || {
84                            if let Some(error) = error.get() {
85                                view! { <span class="error-message">{error}</span> }
86                                    .into_view()
87                            } else {
88                                View::default()
89                            }
90                        }}
91                    }.into_view()
92                }
93            }}
94        </div>
95    }
96}