singlestage/components/switch/
mod.rs

1use crate::{CheckboxGroupContext, Reactive};
2use leptos::prelude::*;
3
4#[component]
5pub fn Switch(
6    #[prop(optional)] children: Option<Children>,
7
8    // GLOBAL ATTRIBUTES
9    //
10    /// A space separated list of keys to focus this element. The first key available on the user's
11    /// keyboard layout is used.
12    #[prop(optional, into)]
13    accesskey: MaybeProp<String>,
14    /// Sets whether the input value should be capitalized and how. If a parent `<form>` has
15    /// `autocapitalize` rules set, it will override any rules set here.
16    ///
17    /// Accepted values: "none" or "off" | "sentences" or "on" | "words" | "characters".
18    #[prop(optional, into)]
19    autocapitalize: MaybeProp<String>,
20    /// Grabs focus once the page has finished loading. Only one element on the page can be focused
21    /// at a time.
22    #[prop(optional, into)]
23    autofocus: MaybeProp<bool>,
24    /// Apply classes to the element.
25    #[prop(optional, into)]
26    class: MaybeProp<String>,
27    /// Allows client-side editing of the element by the user.
28    ///
29    /// Accepted values: "true" | "false" | "plaintext-only"
30    #[prop(optional, into)]
31    contenteditable: MaybeProp<String>,
32    /// Indicate directionality of the element's text.
33    ///
34    /// Accepted values: "ltr" | "rtl" | "auto"
35    #[prop(optional, into)]
36    dir: MaybeProp<String>,
37    /// Toggle whether the element can be dragged.
38    #[prop(optional, into)]
39    draggable: MaybeProp<bool>,
40    /// Modifies the appearance of the enter key on virtual keyboards.
41    #[prop(optional, into)]
42    enterkeyhint: MaybeProp<String>,
43    /// Expose elements in the shadow DOM to be manipulated by the DOM.
44    #[prop(optional, into)]
45    exportparts: MaybeProp<String>,
46    /// Controls hidden status of the element.
47    #[prop(optional, into)]
48    hidden: MaybeProp<String>,
49    /// Set the id of this element.
50    #[prop(optional, into)]
51    id: MaybeProp<String>,
52    /// Toggle if the browser reacts to input events from this element.
53    #[prop(optional, into)]
54    inert: MaybeProp<bool>,
55    /// Hints to the browser of what type of virtual keyboard to display when editing this element
56    /// or its children.
57    #[prop(optional, into)]
58    inputmode: MaybeProp<String>,
59    /// Used to render a standard element as a custom element.
60    #[prop(optional, into)]
61    is: MaybeProp<String>,
62    /// Unique global identifier of an item.
63    #[prop(optional, into)]
64    itemid: MaybeProp<String>,
65    /// Used to add properties to an item.
66    #[prop(optional, into)]
67    itemprop: MaybeProp<String>,
68    /// Used to associate an item with a related non-parent element that's using `itemscope`.
69    #[prop(optional, into)]
70    itemref: MaybeProp<String>,
71    /// Used to declare that children elements are related to a particular item.
72    #[prop(optional, into)]
73    itemscope: MaybeProp<String>,
74    /// URL of data used to define `itemprops`.
75    #[prop(optional, into)]
76    itemtype: MaybeProp<String>,
77    /// Defines the language of an element.
78    #[prop(optional, into)]
79    lang: MaybeProp<String>,
80    /// Cryptographic "number used once".
81    #[prop(optional, into)]
82    nonce: MaybeProp<String>,
83    /// List of the part names of the element.
84    #[prop(optional, into)]
85    part: MaybeProp<String>,
86    /// Designate an element as a popover element.
87    #[prop(optional, into)]
88    popover: MaybeProp<String>,
89    // /// Define the semantic meaning of content.
90    // #[prop(optional, into)]
91    // role: MaybeProp<String>,
92    /// Assigns a slot to an element.
93    #[prop(optional, into)]
94    slot: MaybeProp<String>,
95    /// Toggle spellcheck for this input.
96    ///
97    /// Accepted values: "default" | "true" | "false".
98    #[prop(optional, into)]
99    spellcheck: MaybeProp<String>,
100    /// Define CSS to be applied to the element.
101    #[prop(optional, into)]
102    style: MaybeProp<String>,
103    /// Controls how an element behaves when a user navigates using the tab key.
104    #[prop(optional, into)]
105    tabindex: MaybeProp<usize>,
106    /// Describes the content of the element to screen readers.
107    #[prop(optional, into)]
108    title: MaybeProp<String>,
109    /// Defines localization behavior for the element.
110    #[prop(optional, into)]
111    translate: MaybeProp<String>,
112
113    // CHECKBOX ATTRIBUTES
114    //
115    /// Whether the command or control is checked
116    #[prop(optional, into)]
117    checked: Reactive<bool>,
118    /// Associate this element with a form element that may not be its parent by its `id`.
119    #[prop(optional, into)]
120    form: MaybeProp<String>,
121    /// Name of this element. Submitted with the form as part of a name/value pair.
122    #[prop(optional, into)]
123    name: MaybeProp<String>,
124    /// Toggle whether or not the user can modify the value of this element.
125    #[prop(optional, into)]
126    readonly: MaybeProp<bool>,
127    /// Toggle whether or not this element requires a value for form submission.
128    #[prop(optional, into)]
129    required: MaybeProp<bool>,
130    /// Whether the form control is disabled
131    #[prop(optional, into)]
132    disabled: MaybeProp<bool>,
133    /// The value of the control. When specified in the HTML, corresponds to the initial value
134    #[prop(optional, into)]
135    value: MaybeProp<String>,
136) -> impl IntoView {
137    let switch_ref = NodeRef::<leptos::html::Input>::new();
138
139    let on_change = move |ev| {
140        let switch_checked = event_target_checked(&ev);
141
142        checked.set(switch_checked);
143
144        if let Some(checkbox_value) = value.get_untracked()
145            && let Some(checkbox_group) = use_context::<CheckboxGroupContext>()
146        {
147            match switch_checked {
148                true => checkbox_group.value.update(|group_value| {
149                    group_value.push(checkbox_value);
150                }),
151                false => checkbox_group.value.update(|group_value| {
152                    if let Some(index) = group_value.iter().position(|el| *el == checkbox_value) {
153                        group_value.swap_remove(index);
154                    }
155                }),
156            }
157        }
158    };
159
160    if let Some(value) = value.get_untracked()
161        && let Some(checkbox_group) = use_context::<CheckboxGroupContext>()
162        && checkbox_group.value.get_untracked().contains(&value)
163        && let Some(switch) = switch_ref.get_untracked()
164    {
165        switch.set_checked(true)
166    }
167
168    Effect::new(move || {
169        if let Some(checkbox_group) = use_context::<CheckboxGroupContext>()
170            && let Some(value) = value.get_untracked()
171        {
172            if checkbox_group.value.get().contains(&value) {
173                checked.set(true);
174            } else {
175                checked.set(false);
176            }
177        }
178    });
179
180    Effect::new(move || {
181        if let Some(switch) = switch_ref.get_untracked() {
182            switch.set_checked(checked.get());
183        }
184    });
185
186    Effect::new(move || {
187        if let Some(switch) = switch_ref.get_untracked()
188            && let Some(disabled) = disabled.get()
189        {
190            switch.set_disabled(disabled);
191        }
192    });
193
194    let global_attrs_1 = view! {
195        <{..}
196            accesskey=move || accesskey.get()
197            autocapitalize=move || autocapitalize.get()
198            autofocus=move || autofocus.get()
199            // class=move || class.get()
200            contenteditable=move || contenteditable.get()
201            dir=move || dir.get()
202            draggable=move || draggable.get()
203            enterkeyhint=move || enterkeyhint.get()
204            exportparts=move || exportparts.get()
205            hidden=move || hidden.get()
206            id=move || id.get()
207            inert=move || inert.get()
208            inputmode=move || inputmode.get()
209            is=move || is.get()
210            itemid=move || itemid.get()
211        />
212    };
213
214    let global_attrs_2 = view! {
215        <{..}
216            itemprop=move || itemprop.get()
217            itemref=move || itemref.get()
218            itemscope=move || itemscope.get()
219            itemtype=move || itemtype.get()
220            lang=move || lang.get()
221            nonce=move || nonce.get()
222            part=move || part.get()
223            popover=move || popover.get()
224            // role=move || role.get()
225            slot=move || slot.get()
226            spellcheck=move || spellcheck.get()
227            style=move || style.get()
228            tabindex=move || tabindex.get()
229            title=move || title.get()
230            translate=move || translate.get()
231        />
232    };
233
234    view! {
235        {if let Some(children) = children {
236            view! {
237                <label class="singlestage-label">
238                    <input
239                        checked=checked.get_untracked()
240                        form=move || form.get()
241                        name=move || name.get()
242                        readonly=move || readonly.get()
243                        required=move || required.get()
244                        disabled=disabled.get_untracked()
245                        class=move || {
246                            format!("singlestage-input {}", class.get().unwrap_or_default())
247                        }
248                        node_ref=switch_ref
249                        on:change=on_change
250                        role="switch"
251                        type="checkbox"
252                        value=move || value.get()
253
254                        {..global_attrs_1}
255                        {..global_attrs_2}
256                    />
257                    {children()}
258                </label>
259            }
260                .into_any()
261        } else {
262            view! {
263                <input
264                    checked=checked.get_untracked()
265                    form=move || form.get()
266                    name=move || name.get()
267                    readonly=move || readonly.get()
268                    required=move || required.get()
269                    disabled=disabled.get_untracked()
270                    class=move || format!("singlestage-input {}", class.get().unwrap_or_default())
271                    node_ref=switch_ref
272                    on:change=on_change
273                    role="switch"
274                    type="checkbox"
275                    value=move || value.get()
276
277                    {..global_attrs_1}
278                    {..global_attrs_2}
279                />
280            }
281                .into_any()
282        }}
283    }
284}