impulse_thaw/checkbox/
checkbox_group.rs

1use crate::{FieldInjection, FieldValidationState, Rule};
2use leptos::{context::Provider, prelude::*};
3use std::{collections::HashSet, ops::Deref};
4use thaw_utils::{class_list, Model};
5
6#[component]
7pub fn CheckboxGroup(
8    #[prop(optional, into)] class: MaybeProp<String>,
9    #[prop(optional, into)] id: MaybeProp<String>,
10    /// A string specifying a name for the input control.
11    /// This name is submitted along with the control's value when the form data is submitted.
12    #[prop(optional, into)]
13    name: MaybeProp<String>,
14    #[prop(optional, into)] rules: Vec<CheckboxGroupRule>,
15    /// Sets the value of the checkbox group.
16    #[prop(optional, into)]
17    value: Model<HashSet<String>>,
18    children: Children,
19) -> impl IntoView {
20    let (id, name) = FieldInjection::use_id_and_name(id, name);
21    let validate = Rule::validate(rules, value, name);
22    Effect::new(move |prev: Option<()>| {
23        value.with(|_| {});
24        if prev.is_some() {
25            validate.run(Some(CheckboxGroupRuleTrigger::Change));
26        }
27    });
28
29    view! {
30        <Provider value=CheckboxGroupInjection {
31            value,
32            name,
33        }>
34            <div class=class_list!["thaw-checkbox-group", class] id=id role="group">
35                {children()}
36            </div>
37        </Provider>
38    }
39}
40
41#[derive(Clone)]
42pub(crate) struct CheckboxGroupInjection {
43    pub value: Model<HashSet<String>>,
44    pub name: Signal<Option<String>>,
45}
46
47impl Copy for CheckboxGroupInjection {}
48
49impl CheckboxGroupInjection {
50    pub fn use_context() -> Option<Self> {
51        use_context()
52    }
53}
54
55#[derive(Debug, Default, PartialEq, Clone, Copy)]
56pub enum CheckboxGroupRuleTrigger {
57    #[default]
58    Change,
59}
60
61pub struct CheckboxGroupRule(Rule<HashSet<String>, CheckboxGroupRuleTrigger>);
62
63impl CheckboxGroupRule {
64    pub fn required(required: Signal<bool>) -> Self {
65        Self::validator(move |value, name| {
66            if required.get_untracked() && value.is_empty() {
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_empty() {
81                Err(FieldValidationState::Error(message.get_untracked()))
82            } else {
83                Ok(())
84            }
85        })
86    }
87
88    pub fn validator(
89        f: impl Fn(&HashSet<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: CheckboxGroupRuleTrigger) -> Self {
98        Self(Rule::with_trigger(self.0, trigger))
99    }
100}
101
102impl Deref for CheckboxGroupRule {
103    type Target = Rule<HashSet<String>, CheckboxGroupRuleTrigger>;
104
105    fn deref(&self) -> &Self::Target {
106        &self.0
107    }
108}