impulse_thaw/radio/
radio_group.rs1use 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 #[prop(optional, into)]
13 value: OptionModel<String>,
14 #[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}