1use crate::{FieldInjection, FieldValidationState, Rule};
2use leptos::{html, prelude::*};
3use std::ops::Deref;
4use thaw_utils::{class_list, mount_style, Model};
5
6#[component]
7pub fn Switch(
8 #[prop(optional, into)] class: MaybeProp<String>,
9 #[prop(optional, into)] id: MaybeProp<String>,
10 #[prop(optional, into)]
13 name: MaybeProp<String>,
14 #[prop(optional, into)]
16 value: MaybeProp<String>,
17 #[prop(optional, into)]
19 rules: Vec<SwitchRule>,
20 #[prop(optional, into)]
22 checked: Model<bool>,
23 #[prop(optional, into)]
25 label: MaybeProp<String>,
26) -> impl IntoView {
27 mount_style("switch", include_str!("./switch.css"));
28 let (id, name) = FieldInjection::use_id_and_name(id, name);
29 let validate = Rule::validate(rules, checked, name);
30 let id = Signal::derive(move || id.get().unwrap_or_else(|| uuid::Uuid::new_v4().to_string()));
31 let input_ref = NodeRef::<html::Input>::new();
32 let on_change = move |_| {
33 let input = input_ref.get_untracked().unwrap();
34 let did_update = checked.try_maybe_update(|checked| {
35 if *checked != input.checked() {
36 *checked = input.checked();
37 (true, true)
38 } else {
39 (false, false)
40 }
41 });
42 if did_update.unwrap_or_default() {
43 validate.run(Some(SwitchRuleTrigger::Change));
44 }
45 };
46
47 view! {
48 <div class=class_list!["thaw-switch", class]>
49 <input
50 class="thaw-switch__input"
51 role="switch"
52 type="checkbox"
53 id=id
54 name=name
55 value=move || value.get()
56 checked=checked.get_untracked()
57 prop:checked=move || { checked.get() }
58 node_ref=input_ref
59 on:change=on_change
60 />
61 <div aria-hidden="true" class="thaw-switch__indicator thaw-switch__button">
62 <svg
63 fill="currentColor"
64 aria-hidden="true"
65 width="1em"
66 height="1em"
67 viewBox="0 0 20 20"
68 >
69 <path d="M10 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16Z" fill="currentColor"></path>
70 </svg>
71 </div>
72 {move || {
73 if let Some(label) = label.get() {
74 view! {
75 <label class="thaw-switch__label" for=id>
76 {label}
77 </label>
78 }
79 .into()
80 } else {
81 None
82 }
83 }}
84 </div>
85 }
86}
87
88#[derive(Debug, Default, PartialEq, Clone, Copy)]
89pub enum SwitchRuleTrigger {
90 #[default]
91 Change,
92}
93
94pub struct SwitchRule(Rule<bool, SwitchRuleTrigger>);
95
96impl SwitchRule {
97 pub fn validator(
98 f: impl Fn(&bool, Signal<Option<String>>) -> Result<(), FieldValidationState>
99 + Send
100 + Sync
101 + 'static,
102 ) -> Self {
103 Self(Rule::validator(f))
104 }
105
106 pub fn with_trigger(self, trigger: SwitchRuleTrigger) -> Self {
107 Self(Rule::with_trigger(self.0, trigger))
108 }
109}
110
111impl Deref for SwitchRule {
112 type Target = Rule<bool, SwitchRuleTrigger>;
113
114 fn deref(&self) -> &Self::Target {
115 &self.0
116 }
117}