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