impulse_thaw/radio/
radio_group.rs

1use crate::{FieldInjection, FieldValidationState, Rule};
2use leptos::{context::Provider, prelude::*};
3use std::ops::Deref;
4use thaw_utils::{class_list, OptionModel};
5
6#[component]
7pub fn RadioGroup(
8    #[prop(optional, into)] class: MaybeProp<String>,
9    #[prop(optional, into)] id: MaybeProp<String>,
10    #[prop(optional, into)] rules: Vec<RadioGroupRule>,
11    /// The selected Radio item in this group.
12    #[prop(optional, into)]
13    value: OptionModel<String>,
14    /// The name of this radio group.
15    #[prop(optional, into)]
16    name: MaybeProp<String>,
17    children: Children,
18) -> impl IntoView {
19    let (id, name) = FieldInjection::use_id_and_name(id, name);
20    let validate = Rule::validate(rules, value, name);
21
22    Effect::new(move |prev: Option<()>| {
23        value.with(|_| {});
24        if prev.is_some() {
25            validate.run(Some(RadioGroupRuleTrigger::Change));
26        }
27    });
28
29    let name = Signal::derive(move || {
30        name.get()
31            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string())
32    });
33
34    view! {
35        <Provider value=RadioGroupInjection { value, name }>
36            <div class=class_list!["thaw-radio-group", class] id=id role="radiogroup">
37                {children()}
38            </div>
39        </Provider>
40    }
41}
42
43#[derive(Clone)]
44pub(crate) struct RadioGroupInjection {
45    pub value: OptionModel<String>,
46    pub name: Signal<String>,
47}
48
49impl RadioGroupInjection {
50    pub fn expect_context() -> Self {
51        expect_context()
52    }
53}
54
55#[derive(Debug, Default, PartialEq, Clone, Copy)]
56pub enum RadioGroupRuleTrigger {
57    #[default]
58    Change,
59}
60
61pub struct RadioGroupRule(Rule<Option<String>, RadioGroupRuleTrigger>);
62
63impl RadioGroupRule {
64    pub fn required(required: Signal<bool>) -> Self {
65        Self::validator(move |value, name| {
66            if required.get_untracked() && value.is_none() {
67                let message = name.get_untracked().map_or_else(
68                    || String::from("Please select!"),
69                    |name| format!("Please select {name}!"),
70                );
71                Err(FieldValidationState::Error(message))
72            } else {
73                Ok(())
74            }
75        })
76    }
77
78    pub fn required_with_message(required: Signal<bool>, message: Signal<String>) -> Self {
79        Self::validator(move |value, _| {
80            if required.get_untracked() && value.is_none() {
81                Err(FieldValidationState::Error(message.get_untracked()))
82            } else {
83                Ok(())
84            }
85        })
86    }
87
88    pub fn validator(
89        f: impl Fn(&Option<String>, Signal<Option<String>>) -> Result<(), FieldValidationState>
90            + Send
91            + Sync
92            + 'static,
93    ) -> Self {
94        Self(Rule::validator(f))
95    }
96
97    pub fn with_trigger(self, trigger: RadioGroupRuleTrigger) -> Self {
98        Self(Rule::with_trigger(self.0, trigger))
99    }
100}
101
102impl Deref for RadioGroupRule {
103    type Target = Rule<Option<String>, RadioGroupRuleTrigger>;
104
105    fn deref(&self) -> &Self::Target {
106        &self.0
107    }
108}