Skip to main content

maud_ui/primitives/
switch.rs

1//! Switch component — maud-ui Wave 2
2use maud::{html, Markup};
3
4#[derive(Clone, Debug)]
5pub struct Props {
6    pub name: String,
7    pub id: String,
8    pub label: String,
9    pub checked: bool,
10    pub disabled: bool,
11    /// Explicit accessible name. Required when `label` is empty (e.g., when
12    /// the switch sits next to an external label or description block).
13    /// Falls back to `label` if not set.
14    pub aria_label: Option<String>,
15}
16
17impl Default for Props {
18    fn default() -> Self {
19        Self {
20            name: "switch".to_string(),
21            id: "switch".to_string(),
22            label: "Toggle".to_string(),
23            checked: false,
24            disabled: false,
25            aria_label: None,
26        }
27    }
28}
29
30pub fn render(props: Props) -> Markup {
31    let aria_checked = if props.checked { "true" } else { "false" };
32    let value = if props.checked { "true" } else { "false" };
33    let accessible_name = props
34        .aria_label
35        .clone()
36        .unwrap_or_else(|| props.label.clone());
37
38    html! {
39        span class="mui-switch-wrap" {
40            @if props.disabled {
41                button type="button" class="mui-switch" role="switch"
42                    aria-checked=(aria_checked)
43                    aria-label=(accessible_name)
44                    id=(props.id.clone())
45                    data-mui="switch"
46                    data-name=(props.name.clone())
47                    disabled {
48                    span class="mui-switch__thumb" aria-hidden="true";
49                }
50            } @else {
51                button type="button" class="mui-switch" role="switch"
52                    aria-checked=(aria_checked)
53                    aria-label=(accessible_name)
54                    id=(props.id.clone())
55                    data-mui="switch"
56                    data-name=(props.name.clone()) {
57                    span class="mui-switch__thumb" aria-hidden="true";
58                }
59            }
60            input type="hidden" name=(props.name.clone()) value=(value)
61                class="mui-switch__value" aria-hidden="true";
62            @if !props.label.is_empty() {
63                label for=(props.id) class="mui-switch__label" {
64                    (props.label)
65                }
66            }
67        }
68    }
69}
70
71pub fn showcase() -> Markup {
72    html! {
73        div.mui-showcase__grid {
74            // Realistic settings panel
75            section {
76                h2 { "Notification Settings" }
77                div style="display:flex;flex-direction:column;gap:1rem;max-width:28rem;" {
78                    // Marketing emails — off
79                    div style="display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;" {
80                        div {
81                            label for="sw-marketing" style="font-size:0.875rem;font-weight:500;color:var(--mui-text);display:block;" {
82                                "Marketing emails"
83                            }
84                            span style="font-size:0.8125rem;color:var(--mui-text-muted);" {
85                                "Receive emails about new products, features, and more."
86                            }
87                        }
88                        (render(Props {
89                            name: "marketing".to_string(),
90                            id: "sw-marketing".to_string(),
91                            label: String::new(),
92                            checked: false,
93                            disabled: false,
94                            aria_label: Some("Marketing emails".to_string()),
95                        }))
96                    }
97                    // Push notifications — on
98                    div style="display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;" {
99                        div {
100                            label for="sw-push" style="font-size:0.875rem;font-weight:500;color:var(--mui-text);display:block;" {
101                                "Push notifications"
102                            }
103                            span style="font-size:0.8125rem;color:var(--mui-text-muted);" {
104                                "Receive notifications directly on your device."
105                            }
106                        }
107                        (render(Props {
108                            name: "push".to_string(),
109                            id: "sw-push".to_string(),
110                            label: String::new(),
111                            checked: true,
112                            disabled: false,
113                            aria_label: Some("Push notifications".to_string()),
114                        }))
115                    }
116                    // Airplane mode — disabled
117                    div style="display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;opacity:0.6;" {
118                        div {
119                            label for="sw-airplane" style="font-size:0.875rem;font-weight:500;color:var(--mui-text);display:block;" {
120                                "Airplane mode"
121                            }
122                            span style="font-size:0.8125rem;color:var(--mui-text-muted);" {
123                                "Managed by your organization."
124                            }
125                        }
126                        (render(Props {
127                            name: "airplane".to_string(),
128                            id: "sw-airplane".to_string(),
129                            label: String::new(),
130                            checked: false,
131                            disabled: true,
132                            aria_label: Some("Airplane mode".to_string()),
133                        }))
134                    }
135                }
136            }
137
138            // Simple inline states
139            section {
140                h2 { "States" }
141                div.mui-showcase__row {
142                    (render(Props {
143                        name: "demo-off".to_string(),
144                        id: "demo-off".to_string(),
145                        label: "Off".to_string(),
146                        checked: false,
147                        disabled: false,
148                        aria_label: None,
149                    }))
150                    (render(Props {
151                        name: "demo-on".to_string(),
152                        id: "demo-on".to_string(),
153                        label: "On".to_string(),
154                        checked: true,
155                        disabled: false,
156                        aria_label: None,
157                    }))
158                    (render(Props {
159                        name: "demo-disabled".to_string(),
160                        id: "demo-disabled".to_string(),
161                        label: "Disabled".to_string(),
162                        checked: false,
163                        disabled: true,
164                        aria_label: None,
165                    }))
166                    (render(Props {
167                        name: "demo-locked".to_string(),
168                        id: "demo-locked".to_string(),
169                        label: "Locked on".to_string(),
170                        checked: true,
171                        disabled: true,
172                        aria_label: None,
173                    }))
174                }
175            }
176        }
177    }
178}