nova_forms/components/
choice.rs

1use std::{fmt::Debug, marker::PhantomData, str::FromStr};
2
3use crate::{BaseGroupContext, GroupContext, QueryString};
4use leptos::*;
5
6mod context {
7    use super::*;
8
9    #[derive(Debug, Clone)]
10    pub struct ChoicesContext<T: Clone + Eq + 'static> {
11        selected: Signal<T>,
12    }
13
14    impl<T> ChoicesContext<T>
15    where
16        T: Clone + Eq + 'static
17    {
18        pub fn new<F>(f: F) -> Self
19        where
20            F: Fn() -> T + 'static,
21        {
22            Self {
23                selected: Signal::derive(f)
24            }
25        }
26
27        pub fn selected(&self, discriminant: T) -> Signal<bool> {
28            let selected = self.selected;
29            Signal::derive(move || selected.get() == discriminant)
30        }
31    }
32}
33
34pub(crate) use context::ChoicesContext;
35
36/// A component that renders radio buttons from an enum.
37#[component]
38pub fn Choices<T>(
39    /// Tag to determine the type.
40    #[prop(into)] tag: QueryString,
41    #[prop(optional)] _phantom: PhantomData<T>,
42    /// Child components.
43    children: Children,
44) -> impl IntoView
45where
46    T: Clone + Eq + Default + Debug + FromStr + 'static,
47    T::Err: Clone + Debug + 'static,
48{   
49    let parent_group = expect_context::<GroupContext>();
50    let base_group = expect_context::<BaseGroupContext>();
51    let value: Signal<Result<T, <T as FromStr>::Err>> = Signal::derive(move || base_group
52        .get(parent_group.qs().join(tag))
53        .get()
54        .expect(&format!("tag {} not found", base_group.qs().join(tag)))
55        .as_input()
56        .expect("expected input, got group").value::<T>()
57        .get());
58    
59    provide_context(ChoicesContext::new(move || value.get().unwrap_or_default()));
60
61    let children = children();
62
63    view! {
64        <div class="choices">
65            {children}
66        </div>
67    }
68}
69
70#[component]
71pub fn Choice<T>(
72    /// The label of the input field.
73    #[prop(into)] discriminant: T,
74    #[prop(optional)] children: Option<Children>,
75) -> impl IntoView
76where
77    T: Copy + Eq + 'static
78{    
79    let context = expect_context::<ChoicesContext<T>>();
80
81    view! {
82        <div class=move || {
83            if context.selected(discriminant).get() { "choice selected" } else { "choice hidden" }
84        }>
85            {if let Some(children) = children { children().into_view() } else { View::default() }}
86        </div>
87    }
88}