radix_leptos_primitives/components/
toggle.rs1use leptos::*;
2use leptos::prelude::*;
3
4#[component]
8pub fn Toggle(
9 #[prop(optional)] class: Option<String>,
10 #[prop(optional)] style: Option<String>,
11 #[prop(optional)] children: Option<Children>,
12 #[prop(optional)] variant: Option<ToggleVariant>,
13 #[prop(optional)] size: Option<ToggleSize>,
14 #[prop(optional)] pressed: Option<bool>,
15 #[prop(optional)] default_pressed: Option<bool>,
16 #[prop(optional)] disabled: Option<bool>,
17 #[prop(optional)] on_pressed_change: Option<Callback<bool>>,
18 #[prop(optional)] on_click: Option<Callback<()>>,
19) -> impl IntoView {
20 let variant = variant.unwrap_or_default();
21 let size = size.unwrap_or_default();
22 let disabled = disabled.unwrap_or(false);
23 let (is_pressed, set_is_pressed) = signal(
24 pressed.unwrap_or_else(|| default_pressed.unwrap_or(false))
25 );
26
27 if let Some(external_pressed) = pressed {
29 Effect::new(move |_| {
30 set_is_pressed.set(external_pressed);
31 });
32 }
33
34 if let Some(on_pressed_change) = on_pressed_change {
36 Effect::new(move |_| {
37 on_pressed_change.run(is_pressed.get());
38 });
39 }
40
41 let class = merge_classes(vec![
42 "toggle",
43 &variant.to_class(),
44 &size.to_class(),
45 if is_pressed.get() { "pressed" } else { "" },
46 if disabled { "disabled" } else { "" },
47 class.as_deref().unwrap_or(""),
48 ]);
49
50 let handle_click = move |_| {
51 if !disabled {
52 set_is_pressed.update(|pressed| *pressed = !*pressed);
53 if let Some(on_click) = on_click {
54 on_click.run(());
55 }
56 }
57 };
58
59 let handle_keydown = move |ev: web_sys::KeyboardEvent| {
60 if !disabled && (ev.key() == "Enter" || ev.key() == " ") {
61 ev.prevent_default();
62 set_is_pressed.update(|pressed| *pressed = !*pressed);
63 if let Some(on_click) = on_click {
64 on_click.run(());
65 }
66 }
67 };
68
69 view! {
70 <button
71 class=class
72 style=style
73 disabled=disabled
74 on:click=handle_click
75 on:keydown=handle_keydown
76 aria-pressed=is_pressed.get()
77 type="button"
78 >
79 {children.map(|c| c())}
80 </button>
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
86pub enum ToggleVariant {
87 #[default]
88 Default,
89 Outline,
90 Ghost,
91 Destructive,
92}
93
94impl ToggleVariant {
95 pub fn to_class(&self) -> &'static str {
96 match self {
97 ToggleVariant::Default => "variant-default",
98 ToggleVariant::Outline => "variant-outline",
99 ToggleVariant::Ghost => "variant-ghost",
100 ToggleVariant::Destructive => "variant-destructive",
101 }
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
107pub enum ToggleSize {
108 #[default]
109 Default,
110 Small,
111 Large,
112}
113
114impl ToggleSize {
115 pub fn to_class(&self) -> &'static str {
116 match self {
117 ToggleSize::Default => "size-default",
118 ToggleSize::Small => "size-small",
119 ToggleSize::Large => "size-large",
120 }
121 }
122}
123
124fn merge_classes(classes: Vec<&str>) -> String {
126 classes
127 .into_iter()
128 .filter(|c| !c.is_empty())
129 .collect::<Vec<_>>()
130 .join(" ")
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use wasm_bindgen_test::*;
137
138 wasm_bindgen_test_configure!(run_in_browser);
139
140 #[test]
142 fn test_toggle_creation() {
143 assert!(true);
144 }
145
146 #[test]
147 fn test_toggle_with_class() {
148 assert!(true);
149 }
150
151 #[test]
152 fn test_toggle_with_style() {
153 assert!(true);
154 }
155
156 #[test]
157 fn test_toggle_default_variant() {
158 assert!(true);
159 }
160
161 #[test]
162 fn test_toggle_outline_variant() {
163 assert!(true);
164 }
165
166 #[test]
167 fn test_toggle_ghost_variant() {
168 assert!(true);
169 }
170
171 #[test]
172 fn test_toggle_destructive_variant() {
173 assert!(true);
174 }
175
176 #[test]
177 fn test_toggle_default_size() {
178 assert!(true);
179 }
180
181 #[test]
182 fn test_toggle_small_size() {
183 assert!(true);
184 }
185
186 #[test]
187 fn test_toggle_large_size() {
188 assert!(true);
189 }
190
191 #[test]
192 fn test_toggle_pressed() {
193 assert!(true);
194 }
195
196 #[test]
197 fn test_toggle_default_pressed() {
198 assert!(true);
199 }
200
201 #[test]
202 fn test_toggle_disabled() {
203 assert!(true);
204 }
205
206 #[test]
207 fn test_toggle_on_pressed_change() {
208 assert!(true);
209 }
210
211 #[test]
212 fn test_toggle_on_click() {
213 assert!(true);
214 }
215
216 #[test]
218 fn test_toggle_variant_default() {
219 let variant = ToggleVariant::default();
220 assert_eq!(variant, ToggleVariant::Default);
221 }
222
223 #[test]
224 fn test_toggle_variant_default_class() {
225 let variant = ToggleVariant::Default;
226 assert_eq!(variant.to_class(), "variant-default");
227 }
228
229 #[test]
230 fn test_toggle_variant_outline_class() {
231 let variant = ToggleVariant::Outline;
232 assert_eq!(variant.to_class(), "variant-outline");
233 }
234
235 #[test]
236 fn test_toggle_variant_ghost_class() {
237 let variant = ToggleVariant::Ghost;
238 assert_eq!(variant.to_class(), "variant-ghost");
239 }
240
241 #[test]
242 fn test_toggle_variant_destructive_class() {
243 let variant = ToggleVariant::Destructive;
244 assert_eq!(variant.to_class(), "variant-destructive");
245 }
246
247 #[test]
249 fn test_toggle_size_default() {
250 let size = ToggleSize::default();
251 assert_eq!(size, ToggleSize::Default);
252 }
253
254 #[test]
255 fn test_toggle_size_default_class() {
256 let size = ToggleSize::Default;
257 assert_eq!(size.to_class(), "size-default");
258 }
259
260 #[test]
261 fn test_toggle_size_small_class() {
262 let size = ToggleSize::Small;
263 assert_eq!(size.to_class(), "size-small");
264 }
265
266 #[test]
267 fn test_toggle_size_large_class() {
268 let size = ToggleSize::Large;
269 assert_eq!(size.to_class(), "size-large");
270 }
271
272 #[test]
274 fn test_merge_classes_empty() {
275 let result = merge_classes(vec![]);
276 assert_eq!(result, "");
277 }
278
279 #[test]
280 fn test_merge_classes_single() {
281 let result = merge_classes(vec!["class1"]);
282 assert_eq!(result, "class1");
283 }
284
285 #[test]
286 fn test_merge_classes_multiple() {
287 let result = merge_classes(vec!["class1", "class2", "class3"]);
288 assert_eq!(result, "class1 class2 class3");
289 }
290
291 #[test]
292 fn test_merge_classes_with_empty() {
293 let result = merge_classes(vec!["class1", "", "class3"]);
294 assert_eq!(result, "class1 class3");
295 }
296
297 #[test]
299 fn test_toggle_property_based() {
300 use proptest::prelude::*;
301 proptest!(|(class in ".*", style in ".*")| {
302 assert!(true);
303 });
304 }
305}