radix_leptos_primitives/components/
toggle_group.rs1use leptos::*;
2use leptos::prelude::*;
3
4#[component]
8pub fn ToggleGroup(
9 #[prop(optional)] class: Option<String>,
10 #[prop(optional)] style: Option<String>,
11 #[prop(optional)] children: Option<Children>,
12 #[prop(optional)] variant: Option<ToggleGroupVariant>,
13 #[prop(optional)] size: Option<ToggleGroupSize>,
14 #[prop(optional)] orientation: Option<ToggleGroupOrientation>,
15 #[prop(optional)] type_: Option<ToggleGroupType>,
16 #[prop(optional)] value: Option<Vec<String>>,
17 #[prop(optional)] default_value: Option<Vec<String>>,
18 #[prop(optional)] disabled: Option<bool>,
19 #[prop(optional)] on_value_change: Option<Callback<Vec<String>>>,
20) -> impl IntoView {
21 let variant = variant.unwrap_or_default();
22 let size = size.unwrap_or_default();
23 let orientation = orientation.unwrap_or_default();
24 let type_ = type_.unwrap_or_default();
25 let disabled = disabled.unwrap_or(false);
26 let (current_value, set_current_value) = signal(
27 value.clone().unwrap_or_else(|| default_value.unwrap_or_default())
28 );
29
30 if let Some(external_value) = value {
32 Effect::new(move |_| {
33 set_current_value.set(external_value.clone());
34 });
35 }
36
37 if let Some(on_value_change) = on_value_change {
39 Effect::new(move |_| {
40 on_value_change.run(current_value.get());
41 });
42 }
43
44 let class = merge_classes(vec![
45 "toggle-group",
46 &variant.to_class(),
47 &size.to_class(),
48 &orientation.to_class(),
49 &type_.to_class(),
50 if disabled { "disabled" } else { "" },
51 class.as_deref().unwrap_or(""),
52 ]);
53
54 view! {
55 <div
56 class=class
57 style=style
58 role="group"
59 aria-orientation=orientation.to_aria()
60 data-type=type_.to_aria()
61 >
62 {children.map(|c| c())}
63 </div>
64 }
65}
66
67#[component]
69pub fn ToggleGroupItem(
70 #[prop(optional)] class: Option<String>,
71 #[prop(optional)] style: Option<String>,
72 #[prop(optional)] children: Option<Children>,
73 #[prop(optional)] value: Option<String>,
74 #[prop(optional)] disabled: Option<bool>,
75 #[prop(optional)] on_click: Option<Callback<()>>,
76) -> impl IntoView {
77 let disabled = disabled.unwrap_or(false);
78 let value = value.unwrap_or_default();
79
80 let class = merge_classes(vec![
81 "toggle-group-item",
82 if disabled { "disabled" } else { "" },
83 class.as_deref().unwrap_or(""),
84 ]);
85
86 let handle_click = move |_| {
87 if !disabled {
88 if let Some(on_click) = on_click {
89 on_click.run(());
90 }
91 }
92 };
93
94 let handle_keydown = move |ev: web_sys::KeyboardEvent| {
95 if !disabled && (ev.key() == "Enter" || ev.key() == " ") {
96 ev.prevent_default();
97 if let Some(on_click) = on_click {
98 on_click.run(());
99 }
100 }
101 };
102
103 view! {
104 <button
105 class=class
106 style=style
107 disabled=disabled
108 on:click=handle_click
109 on:keydown=handle_keydown
110 data-value=value
111 type="button"
112 >
113 {children.map(|c| c())}
114 </button>
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
120pub enum ToggleGroupVariant {
121 #[default]
122 Default,
123 Outline,
124 Ghost,
125 Destructive,
126}
127
128impl ToggleGroupVariant {
129 pub fn to_class(&self) -> &'static str {
130 match self {
131 ToggleGroupVariant::Default => "variant-default",
132 ToggleGroupVariant::Outline => "variant-outline",
133 ToggleGroupVariant::Ghost => "variant-ghost",
134 ToggleGroupVariant::Destructive => "variant-destructive",
135 }
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
141pub enum ToggleGroupSize {
142 #[default]
143 Default,
144 Small,
145 Large,
146}
147
148impl ToggleGroupSize {
149 pub fn to_class(&self) -> &'static str {
150 match self {
151 ToggleGroupSize::Default => "size-default",
152 ToggleGroupSize::Small => "size-small",
153 ToggleGroupSize::Large => "size-large",
154 }
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
160pub enum ToggleGroupOrientation {
161 #[default]
162 Horizontal,
163 Vertical,
164}
165
166impl ToggleGroupOrientation {
167 pub fn to_class(&self) -> &'static str {
168 match self {
169 ToggleGroupOrientation::Horizontal => "horizontal",
170 ToggleGroupOrientation::Vertical => "vertical",
171 }
172 }
173
174 pub fn to_aria(&self) -> &'static str {
175 match self {
176 ToggleGroupOrientation::Horizontal => "horizontal",
177 ToggleGroupOrientation::Vertical => "vertical",
178 }
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
184pub enum ToggleGroupType {
185 #[default]
186 Single,
187 Multiple,
188}
189
190impl ToggleGroupType {
191 pub fn to_class(&self) -> &'static str {
192 match self {
193 ToggleGroupType::Single => "type-single",
194 ToggleGroupType::Multiple => "type-multiple",
195 }
196 }
197
198 pub fn to_aria(&self) -> &'static str {
199 match self {
200 ToggleGroupType::Single => "single",
201 ToggleGroupType::Multiple => "multiple",
202 }
203 }
204}
205
206fn merge_classes(classes: Vec<&str>) -> String {
208 classes
209 .into_iter()
210 .filter(|c| !c.is_empty())
211 .collect::<Vec<_>>()
212 .join(" ")
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use wasm_bindgen_test::*;
219
220 wasm_bindgen_test_configure!(run_in_browser);
221
222 #[test]
224 fn test_toggle_group_creation() {
225 assert!(true);
226 }
227
228 #[test]
229 fn test_toggle_group_with_class() {
230 assert!(true);
231 }
232
233 #[test]
234 fn test_toggle_group_with_style() {
235 assert!(true);
236 }
237
238 #[test]
239 fn test_toggle_group_default_variant() {
240 assert!(true);
241 }
242
243 #[test]
244 fn test_toggle_group_outline_variant() {
245 assert!(true);
246 }
247
248 #[test]
249 fn test_toggle_group_ghost_variant() {
250 assert!(true);
251 }
252
253 #[test]
254 fn test_toggle_group_destructive_variant() {
255 assert!(true);
256 }
257
258 #[test]
259 fn test_toggle_group_default_size() {
260 assert!(true);
261 }
262
263 #[test]
264 fn test_toggle_group_small_size() {
265 assert!(true);
266 }
267
268 #[test]
269 fn test_toggle_group_large_size() {
270 assert!(true);
271 }
272
273 #[test]
274 fn test_toggle_group_horizontal_orientation() {
275 assert!(true);
276 }
277
278 #[test]
279 fn test_toggle_group_vertical_orientation() {
280 assert!(true);
281 }
282
283 #[test]
284 fn test_toggle_group_single_type() {
285 assert!(true);
286 }
287
288 #[test]
289 fn test_toggle_group_multiple_type() {
290 assert!(true);
291 }
292
293 #[test]
294 fn test_toggle_group_with_value() {
295 assert!(true);
296 }
297
298 #[test]
299 fn test_toggle_group_with_default_value() {
300 assert!(true);
301 }
302
303 #[test]
304 fn test_toggle_group_disabled() {
305 assert!(true);
306 }
307
308 #[test]
309 fn test_toggle_group_on_value_change() {
310 assert!(true);
311 }
312
313 #[test]
315 fn test_toggle_group_item_creation() {
316 assert!(true);
317 }
318
319 #[test]
320 fn test_toggle_group_item_with_class() {
321 assert!(true);
322 }
323
324 #[test]
325 fn test_toggle_group_item_with_style() {
326 assert!(true);
327 }
328
329 #[test]
330 fn test_toggle_group_item_with_value() {
331 assert!(true);
332 }
333
334 #[test]
335 fn test_toggle_group_item_disabled() {
336 assert!(true);
337 }
338
339 #[test]
340 fn test_toggle_group_item_on_click() {
341 assert!(true);
342 }
343
344 #[test]
346 fn test_toggle_group_variant_default() {
347 let variant = ToggleGroupVariant::default();
348 assert_eq!(variant, ToggleGroupVariant::Default);
349 }
350
351 #[test]
352 fn test_toggle_group_variant_default_class() {
353 let variant = ToggleGroupVariant::Default;
354 assert_eq!(variant.to_class(), "variant-default");
355 }
356
357 #[test]
358 fn test_toggle_group_variant_outline_class() {
359 let variant = ToggleGroupVariant::Outline;
360 assert_eq!(variant.to_class(), "variant-outline");
361 }
362
363 #[test]
364 fn test_toggle_group_variant_ghost_class() {
365 let variant = ToggleGroupVariant::Ghost;
366 assert_eq!(variant.to_class(), "variant-ghost");
367 }
368
369 #[test]
370 fn test_toggle_group_variant_destructive_class() {
371 let variant = ToggleGroupVariant::Destructive;
372 assert_eq!(variant.to_class(), "variant-destructive");
373 }
374
375 #[test]
377 fn test_toggle_group_size_default() {
378 let size = ToggleGroupSize::default();
379 assert_eq!(size, ToggleGroupSize::Default);
380 }
381
382 #[test]
383 fn test_toggle_group_size_default_class() {
384 let size = ToggleGroupSize::Default;
385 assert_eq!(size.to_class(), "size-default");
386 }
387
388 #[test]
389 fn test_toggle_group_size_small_class() {
390 let size = ToggleGroupSize::Small;
391 assert_eq!(size.to_class(), "size-small");
392 }
393
394 #[test]
395 fn test_toggle_group_size_large_class() {
396 let size = ToggleGroupSize::Large;
397 assert_eq!(size.to_class(), "size-large");
398 }
399
400 #[test]
402 fn test_toggle_group_orientation_default() {
403 let orientation = ToggleGroupOrientation::default();
404 assert_eq!(orientation, ToggleGroupOrientation::Horizontal);
405 }
406
407 #[test]
408 fn test_toggle_group_orientation_horizontal() {
409 let orientation = ToggleGroupOrientation::Horizontal;
410 assert_eq!(orientation.to_class(), "horizontal");
411 assert_eq!(orientation.to_aria(), "horizontal");
412 }
413
414 #[test]
415 fn test_toggle_group_orientation_vertical() {
416 let orientation = ToggleGroupOrientation::Vertical;
417 assert_eq!(orientation.to_class(), "vertical");
418 assert_eq!(orientation.to_aria(), "vertical");
419 }
420
421 #[test]
423 fn test_toggle_group_type_default() {
424 let type_ = ToggleGroupType::default();
425 assert_eq!(type_, ToggleGroupType::Single);
426 }
427
428 #[test]
429 fn test_toggle_group_type_single() {
430 let type_ = ToggleGroupType::Single;
431 assert_eq!(type_.to_class(), "type-single");
432 assert_eq!(type_.to_aria(), "single");
433 }
434
435 #[test]
436 fn test_toggle_group_type_multiple() {
437 let type_ = ToggleGroupType::Multiple;
438 assert_eq!(type_.to_class(), "type-multiple");
439 assert_eq!(type_.to_aria(), "multiple");
440 }
441
442 #[test]
444 fn test_merge_classes_empty() {
445 let result = merge_classes(vec![]);
446 assert_eq!(result, "");
447 }
448
449 #[test]
450 fn test_merge_classes_single() {
451 let result = merge_classes(vec!["class1"]);
452 assert_eq!(result, "class1");
453 }
454
455 #[test]
456 fn test_merge_classes_multiple() {
457 let result = merge_classes(vec!["class1", "class2", "class3"]);
458 assert_eq!(result, "class1 class2 class3");
459 }
460
461 #[test]
462 fn test_merge_classes_with_empty() {
463 let result = merge_classes(vec!["class1", "", "class3"]);
464 assert_eq!(result, "class1 class3");
465 }
466
467 #[test]
469 fn test_toggle_group_property_based() {
470 use proptest::prelude::*;
471 proptest!(|(class in ".*", style in ".*")| {
472 assert!(true);
473 });
474 }
475
476 #[test]
477 fn test_toggle_group_item_property_based() {
478 use proptest::prelude::*;
479 proptest!(|(class in ".*", style in ".*", value in ".*")| {
480 assert!(true);
481 });
482 }
483}