1use std::{cell::Cell, rc::Rc, time::Duration};
2
3use gpui::{
4 AnyElement, App, Axis, Corners, Edges, ElementId, InteractiveElement, IntoElement,
5 ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,
6 Window, prelude::FluentBuilder as _, relative,
7};
8use gpui_base::{Toggle as BaseToggle, ToggleGroup as BaseToggleGroup};
9use smallvec::{SmallVec, smallvec};
10
11use crate::{ActiveTheme, Disableable, Icon, Sizable, Size, StyledExt, tooltip::ComponentTooltip};
12
13#[derive(Default, Copy, Debug, Clone, PartialEq, Eq, Hash)]
14pub enum ToggleVariant {
15 #[default]
16 Ghost,
17 Outline,
18}
19
20pub trait ToggleVariants: Sized {
21 fn with_variant(self, variant: ToggleVariant) -> Self;
23 fn ghost(self) -> Self {
25 self.with_variant(ToggleVariant::Ghost)
26 }
27 fn outline(self) -> Self {
29 self.with_variant(ToggleVariant::Outline)
30 }
31}
32
33#[derive(IntoElement)]
34pub struct Toggle {
35 id: ElementId,
36 style: StyleRefinement,
37 checked: bool,
38 size: Size,
39 variant: ToggleVariant,
40 disabled: bool,
41 border_corners: Corners<bool>,
42 border_edges: Edges<bool>,
43 children: SmallVec<[AnyElement; 1]>,
44 on_click: Option<Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
45 tooltip: ComponentTooltip,
46}
47
48impl Toggle {
49 pub fn new(id: impl Into<ElementId>) -> Self {
51 Self {
52 id: id.into(),
53 style: StyleRefinement::default(),
54 checked: false,
55 size: Size::default(),
56 variant: ToggleVariant::default(),
57 disabled: false,
58 border_corners: Corners {
59 top_left: true,
60 top_right: true,
61 bottom_left: true,
62 bottom_right: true,
63 },
64 border_edges: Edges::all(true),
65 children: smallvec![],
66 on_click: None,
67 tooltip: ComponentTooltip::default(),
68 }
69 }
70
71 pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
73 self.tooltip.text = Some((tooltip.into(), None));
74 self
75 }
76
77 pub fn tooltip_show_delay(mut self, delay: Duration) -> Self {
81 self.tooltip.show_delay = Some(delay);
82 self
83 }
84
85 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
87 let label: SharedString = label.into();
88 self.children.push(label.into_any_element());
89 self
90 }
91
92 pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
94 let icon: Icon = icon.into();
95 self.children.push(icon.into());
96 self
97 }
98
99 pub fn checked(mut self, checked: bool) -> Self {
101 self.checked = checked;
102 self
103 }
104
105 pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
109 self.on_click = Some(Box::new(handler));
110 self
111 }
112
113 pub(crate) fn border_corners(mut self, corners: impl Into<Corners<bool>>) -> Self {
114 self.border_corners = corners.into();
115 self
116 }
117
118 pub(crate) fn border_edges(mut self, edges: impl Into<Edges<bool>>) -> Self {
119 self.border_edges = edges.into();
120 self
121 }
122}
123
124impl ToggleVariants for Toggle {
125 fn with_variant(mut self, variant: ToggleVariant) -> Self {
126 self.variant = variant;
127 self
128 }
129}
130
131impl ParentElement for Toggle {
132 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
133 self.children.extend(elements);
134 }
135}
136
137impl Disableable for Toggle {
138 fn disabled(mut self, disabled: bool) -> Self {
139 self.disabled = disabled;
140 self
141 }
142}
143
144impl Sizable for Toggle {
145 fn with_size(mut self, size: impl Into<Size>) -> Self {
146 self.size = size.into();
147 self
148 }
149}
150
151impl Styled for Toggle {
152 fn style(&mut self) -> &mut StyleRefinement {
153 &mut self.style
154 }
155}
156
157impl RenderOnce for Toggle {
158 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
159 let checked = self.checked;
160 let disabled = self.disabled;
161 let hoverable = !disabled && !checked;
162 let rounding = cx.theme().radius;
163 let pressed_background = cx.theme().tokens.accent;
164 let pressed_foreground = cx.theme().accent_foreground;
165 let instance_style = self.style.clone();
166
167 BaseToggle::new(self.id)
168 .pressed(checked)
169 .disabled(disabled)
170 .when_some(
171 self.tooltip.text.as_ref().map(|(text, _)| text.clone()),
172 |this, label| this.accessibility_label(label),
173 )
174 .when_some(self.on_click, |this, on_click| {
175 this.on_change(move |next, _, window, cx| on_click(&next, window, cx))
176 })
177 .flex()
178 .flex_row()
179 .line_height(relative(1.25))
180 .items_center()
181 .justify_center()
182 .map(|this| match self.size {
183 Size::XSmall => this.min_w_5().h_5().px_0p5().text_xs(),
184 Size::Small => this.min_w_6().h_6().px_1().text_sm(),
185 Size::Large => this.min_w_9().h_9().px_3().text_base(),
186 _ => this.min_w_8().h_8().px_2().text_sm(),
187 })
188 .when(self.border_corners.top_left, |this| {
189 this.rounded_tl(rounding)
190 })
191 .when(self.border_corners.top_right, |this| {
192 this.rounded_tr(rounding)
193 })
194 .when(self.border_corners.bottom_left, |this| {
195 this.rounded_bl(rounding)
196 })
197 .when(self.border_corners.bottom_right, |this| {
198 this.rounded_br(rounding)
199 })
200 .when(self.variant == ToggleVariant::Outline, |this| {
201 this.when(self.border_edges.left, |this| this.border_l_1())
202 .when(self.border_edges.right, |this| this.border_r_1())
203 .when(self.border_edges.top, |this| this.border_t_1())
204 .when(self.border_edges.bottom, |this| this.border_b_1())
205 .border_color(cx.theme().border)
206 .bg(cx.theme().tokens.background)
207 })
208 .hover(|this| {
213 if !hoverable {
214 return this;
215 }
216 this.bg(cx.theme().tokens.accent)
217 .text_color(cx.theme().accent_foreground)
218 })
219 .styles(|styles| {
220 styles.pressed(|style| {
221 style
222 .bg(pressed_background)
223 .text_color(pressed_foreground)
224 .refine_style(&instance_style)
225 })
226 })
227 .refine_style(&self.style)
228 .children(self.children)
229 .map(|this| self.tooltip.apply(this))
230 }
231}
232
233#[derive(IntoElement)]
235pub struct ToggleGroup {
236 id: ElementId,
237 style: StyleRefinement,
238 size: Size,
239 variant: ToggleVariant,
240 disabled: bool,
241 segmented: bool,
242 single: bool,
243 items: Vec<Toggle>,
244 on_click: Option<Rc<dyn Fn(&Vec<bool>, &mut Window, &mut App) + 'static>>,
245}
246
247impl ToggleGroup {
248 pub fn new(id: impl Into<ElementId>) -> Self {
250 Self {
251 id: id.into(),
252 style: StyleRefinement::default(),
253 size: Size::default(),
254 variant: ToggleVariant::default(),
255 disabled: false,
256 segmented: false,
257 single: false,
258 items: Vec::new(),
259 on_click: None,
260 }
261 }
262
263 pub fn child(mut self, toggle: impl Into<Toggle>) -> Self {
265 self.items.push(toggle.into());
266 self
267 }
268
269 pub fn children(mut self, children: impl IntoIterator<Item = impl Into<Toggle>>) -> Self {
271 self.items.extend(children.into_iter().map(Into::into));
272 self
273 }
274
275 pub fn on_click(
280 mut self,
281 on_click: impl Fn(&Vec<bool>, &mut Window, &mut App) + 'static,
282 ) -> Self {
283 self.on_click = Some(Rc::new(on_click));
284 self
285 }
286
287 pub fn segmented(mut self) -> Self {
293 self.segmented = true;
294 self
295 }
296
297 pub fn single(mut self) -> Self {
304 self.single = true;
305 self
306 }
307}
308
309impl Sizable for ToggleGroup {
310 fn with_size(mut self, size: impl Into<Size>) -> Self {
311 self.size = size.into();
312 self
313 }
314}
315
316impl ToggleVariants for ToggleGroup {
317 fn with_variant(mut self, variant: ToggleVariant) -> Self {
318 self.variant = variant;
319 self
320 }
321}
322
323impl Disableable for ToggleGroup {
324 fn disabled(mut self, disabled: bool) -> Self {
325 self.disabled = disabled;
326 self
327 }
328}
329
330impl Styled for ToggleGroup {
331 fn style(&mut self) -> &mut StyleRefinement {
332 &mut self.style
333 }
334}
335
336impl RenderOnce for ToggleGroup {
337 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
338 let disabled = self.disabled;
339 let single = self.single;
340 let items_len = self.items.len();
341 let checks = self
342 .items
343 .iter()
344 .map(|item| item.checked)
345 .collect::<Vec<bool>>();
346 let clicked_index = Rc::new(Cell::new(None));
347
348 BaseToggleGroup::new(self.id)
349 .axis(Axis::Horizontal)
350 .child(
351 crate::h_flex()
352 .items_center()
353 .when(!self.segmented, |this| this.gap_2())
354 .refine_style(&self.style)
355 .children(self.items.into_iter().enumerate().map({
356 {
357 let clicked_index = clicked_index.clone();
358 move |(ix, item)| {
359 let item = if !self.segmented || items_len == 1 {
360 item
361 } else if ix == 0 {
362 item.border_corners(Corners {
363 top_left: true,
364 top_right: false,
365 bottom_left: true,
366 bottom_right: false,
367 })
368 .border_edges(Edges {
369 left: true,
370 top: true,
371 right: true,
372 bottom: true,
373 })
374 } else if ix == items_len - 1 {
375 item.border_corners(Corners {
376 top_left: false,
377 top_right: true,
378 bottom_left: false,
379 bottom_right: true,
380 })
381 .border_edges(Edges {
382 left: false,
383 top: true,
384 right: true,
385 bottom: true,
386 })
387 } else {
388 item.border_corners(Corners {
389 top_left: false,
390 top_right: false,
391 bottom_left: false,
392 bottom_right: false,
393 })
394 .border_edges(Edges {
395 left: false,
396 top: true,
397 right: true,
398 bottom: true,
399 })
400 };
401
402 let effective_disabled = disabled || item.disabled;
403 let clicked_index = clicked_index.clone();
404 item.disabled(effective_disabled)
405 .with_size(self.size)
406 .with_variant(self.variant)
407 .on_click(move |_, _, cx| {
408 clicked_index.set(Some(ix));
409 cx.propagate();
410 })
411 }
412 }
413 })),
414 )
415 .when_some(
416 (!disabled).then_some(self.on_click).flatten(),
417 |this, on_click| {
418 this.on_click(move |_, window, cx| {
419 let Some(ix) = clicked_index.take() else {
420 return;
421 };
422 let mut next = if single {
425 vec![false; checks.len()]
426 } else {
427 checks.clone()
428 };
429 next[ix] = !checks[ix];
430 on_click(&next, window, cx);
431 })
432 },
433 )
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440 use crate::{IconName, Theme, h_flex};
441 use gpui::{
442 Context, Hsla, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, Render,
443 StatefulInteractiveElement, TestAppContext, VisualTestContext, div, point, px,
444 };
445 use std::cell::{Cell, RefCell};
446
447 struct ToggleHarness {
448 disabled: bool,
449 changes: Rc<RefCell<Vec<bool>>>,
450 parent_clicks: Rc<Cell<usize>>,
451 }
452
453 impl Render for ToggleHarness {
454 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
455 let changes = self.changes.clone();
456 let parent_clicks = self.parent_clicks.clone();
457 h_flex()
458 .id("toggle-parent")
459 .tab_group()
460 .size(px(100.))
461 .on_click(move |_, _, _| parent_clicks.set(parent_clicks.get() + 1))
462 .child(
463 Toggle::new("toggle")
464 .label("Bold")
465 .disabled(self.disabled)
466 .size_full()
467 .on_click(move |next, _, _| changes.borrow_mut().push(*next)),
468 )
469 }
470 }
471
472 fn harness(
473 cx: &mut TestAppContext,
474 disabled: bool,
475 ) -> (
476 &mut VisualTestContext,
477 Rc<RefCell<Vec<bool>>>,
478 Rc<Cell<usize>>,
479 ) {
480 cx.update(crate::init);
481 let changes = Rc::new(RefCell::new(Vec::new()));
482 let parent_clicks = Rc::new(Cell::new(0));
483 let (_, cx) = cx.add_window_view({
484 let changes = changes.clone();
485 let parent_clicks = parent_clicks.clone();
486 move |_, _| ToggleHarness {
487 disabled,
488 changes,
489 parent_clicks,
490 }
491 });
492 cx.update(|window, cx| window.draw(cx).clear(cx));
493 (cx, changes, parent_clicks)
494 }
495
496 fn activate_key(cx: &mut VisualTestContext, key: &str) {
497 let keystroke =
498 cx.update(|_, cx| Keystroke::parse_with_os(key, cx.platform_info().os).unwrap());
499 cx.simulate_event(KeyDownEvent {
500 keystroke: keystroke.clone(),
501 is_held: false,
502 prefer_character_input: false,
503 });
504 cx.simulate_event(KeyUpEvent { keystroke });
505 }
506
507 #[gpui::test]
508 fn canonical_pointer_activation_fires_once_and_focuses(cx: &mut TestAppContext) {
509 let (cx, changes, _) = harness(cx, false);
510 cx.simulate_click(point(px(10.), px(10.)), Modifiers::default());
511 assert_eq!(changes.borrow().as_slice(), &[true]);
512 cx.update(|window, cx| assert!(window.focused(cx).is_some()));
513 }
514
515 #[gpui::test]
516 fn canonical_toggle_supports_tab_enter_and_space(cx: &mut TestAppContext) {
517 let (cx, changes, _) = harness(cx, false);
518 cx.update(|window, cx| window.focus_next(cx));
519 cx.update(|window, cx| assert!(window.focused(cx).is_some()));
520 activate_key(cx, "enter");
521 activate_key(cx, "space");
522 assert_eq!(changes.borrow().as_slice(), &[true, true]);
523 }
524
525 #[gpui::test]
526 fn canonical_disabled_toggle_is_inert_and_blocks_parent(cx: &mut TestAppContext) {
527 let (cx, changes, parent_clicks) = harness(cx, true);
528 cx.simulate_click(point(px(10.), px(10.)), Modifiers::default());
529 assert!(changes.borrow().is_empty());
530 assert_eq!(parent_clicks.get(), 0);
531 }
532
533 struct ToggleGroupHarness {
534 install_group_callback: bool,
535 child_clicks: Rc<Cell<usize>>,
536 group_changes: Rc<RefCell<Vec<Vec<bool>>>>,
537 }
538
539 impl Render for ToggleGroupHarness {
540 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
541 let child_clicks = self.child_clicks.clone();
542 let mut group = ToggleGroup::new("toggle-group")
543 .w(px(120.))
544 .child(
545 Toggle::new("one")
546 .label("One")
547 .checked(true)
548 .on_click(move |_, _, _| child_clicks.set(child_clicks.get() + 1)),
549 )
550 .child(Toggle::new("two").label("Two"));
551 if self.install_group_callback {
552 let changes = self.group_changes.clone();
553 group = group.on_click(move |next, _, _| changes.borrow_mut().push(next.clone()));
554 }
555 group
556 }
557 }
558
559 fn toggle_group_harness(
560 cx: &mut TestAppContext,
561 install_group_callback: bool,
562 ) -> (
563 &mut VisualTestContext,
564 Rc<Cell<usize>>,
565 Rc<RefCell<Vec<Vec<bool>>>>,
566 ) {
567 cx.update(crate::init);
568 let child_clicks = Rc::new(Cell::new(0));
569 let group_changes = Rc::new(RefCell::new(Vec::new()));
570 let (_, cx) = cx.add_window_view({
571 let child_clicks = child_clicks.clone();
572 let group_changes = group_changes.clone();
573 move |_, _| ToggleGroupHarness {
574 install_group_callback,
575 child_clicks,
576 group_changes,
577 }
578 });
579 cx.update(|window, cx| window.draw(cx).clear(cx));
580 (cx, child_clicks, group_changes)
581 }
582
583 #[gpui::test]
584 fn legacy_toggle_group_overrides_child_callback_even_without_group_callback(
585 cx: &mut TestAppContext,
586 ) {
587 let (cx, child_clicks, changes) = toggle_group_harness(cx, false);
588 cx.simulate_click(point(px(10.), px(10.)), Modifiers::default());
589 assert_eq!(child_clicks.get(), 0);
590 assert!(changes.borrow().is_empty());
591 }
592
593 #[gpui::test]
594 fn legacy_toggle_group_pointer_flips_only_the_clicked_rendered_value(cx: &mut TestAppContext) {
595 let (cx, child_clicks, changes) = toggle_group_harness(cx, true);
596 cx.simulate_click(point(px(10.), px(10.)), Modifiers::default());
597 assert_eq!(child_clicks.get(), 0);
598 assert_eq!(changes.borrow().as_slice(), &[vec![false, false]]);
599 }
600
601 #[gpui::test]
602 fn legacy_toggle_group_keyboard_click_does_not_reach_the_group_callback(
603 cx: &mut TestAppContext,
604 ) {
605 let (cx, child_clicks, changes) = toggle_group_harness(cx, true);
606 cx.update(|window, cx| window.focus_next(cx));
607 activate_key(cx, "enter");
608 assert_eq!(child_clicks.get(), 0);
609 assert!(changes.borrow().is_empty());
610 }
611
612 struct SelectionHarness {
615 single: bool,
616 changes: Rc<RefCell<Vec<Vec<bool>>>>,
617 }
618
619 impl Render for SelectionHarness {
620 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
621 let changes = self.changes.clone();
622 ToggleGroup::new("selection")
623 .segmented()
624 .when(self.single, ToggleGroup::single)
625 .child(Toggle::new("one").label("One").w(px(60.)).checked(true))
626 .child(Toggle::new("two").label("Two").w(px(60.)))
627 .child(Toggle::new("three").label("Three").w(px(60.)))
628 .on_click(move |next, _, _| changes.borrow_mut().push(next.clone()))
629 }
630 }
631
632 fn selection_harness(
633 cx: &mut TestAppContext,
634 single: bool,
635 ) -> (&mut VisualTestContext, Rc<RefCell<Vec<Vec<bool>>>>) {
636 cx.update(crate::init);
637 let changes = Rc::new(RefCell::new(Vec::new()));
638 let (_, cx) = cx.add_window_view({
639 let changes = changes.clone();
640 move |_, _| SelectionHarness { single, changes }
641 });
642 cx.update(|window, cx| window.draw(cx).clear(cx));
643 (cx, changes)
644 }
645
646 #[gpui::test]
647 fn single_toggle_group_moves_the_check_to_the_clicked_item(cx: &mut TestAppContext) {
648 let (cx, changes) = selection_harness(cx, true);
649 cx.simulate_click(point(px(90.), px(16.)), Modifiers::default());
650 assert_eq!(changes.borrow().as_slice(), &[vec![false, true, false]]);
651 }
652
653 #[gpui::test]
654 fn single_toggle_group_clears_the_checked_item_when_it_is_clicked(cx: &mut TestAppContext) {
655 let (cx, changes) = selection_harness(cx, true);
656 cx.simulate_click(point(px(30.), px(16.)), Modifiers::default());
657 assert_eq!(changes.borrow().as_slice(), &[vec![false, false, false]]);
658 }
659
660 #[gpui::test]
661 fn multi_toggle_group_keeps_the_other_checked_items(cx: &mut TestAppContext) {
662 let (cx, changes) = selection_harness(cx, false);
663 cx.simulate_click(point(px(90.), px(16.)), Modifiers::default());
664 assert_eq!(changes.borrow().as_slice(), &[vec![true, true, false]]);
665 }
666
667 #[derive(IntoElement)]
669 struct ContentColor(Rc<Cell<Option<Hsla>>>);
670
671 impl RenderOnce for ContentColor {
672 fn render(self, window: &mut Window, _: &mut App) -> impl IntoElement {
673 self.0.set(Some(window.text_style().color));
674 div().w(px(40.)).h(px(16.))
675 }
676 }
677
678 struct ContentColorHarness {
681 checked: Option<usize>,
682 color: Rc<Cell<Option<Hsla>>>,
683 }
684
685 impl Render for ContentColorHarness {
686 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
687 ToggleGroup::new("selection")
688 .segmented()
689 .single()
690 .child(
691 Toggle::new("one")
692 .w(px(60.))
693 .checked(self.checked == Some(0))
694 .child(ContentColor(self.color.clone())),
695 )
696 .child(
697 Toggle::new("two")
698 .label("Two")
699 .w(px(60.))
700 .checked(self.checked == Some(1)),
701 )
702 .on_click(cx.listener(|this, next: &Vec<bool>, _, cx| {
703 this.checked = next.iter().position(|checked| *checked);
704 cx.notify();
705 }))
706 }
707 }
708
709 #[gpui::test]
710 fn an_item_unchecked_from_elsewhere_drops_its_hover_color(cx: &mut TestAppContext) {
711 let (foreground, accent_foreground) = cx.update(|cx| {
712 crate::init(cx);
713 let theme = Theme::global_mut(cx);
714 theme.foreground = gpui::black();
715 theme.accent_foreground = gpui::white();
716 (theme.foreground, theme.accent_foreground)
717 });
718 let color = Rc::new(Cell::new(None));
719 let (_, cx) = cx.add_window_view({
720 let color = color.clone();
721 move |_, _| ContentColorHarness {
722 checked: None,
723 color,
724 }
725 });
726 let draw = |cx: &mut VisualTestContext| cx.update(|window, cx| window.draw(cx).clear(cx));
727 draw(cx);
728 assert_eq!(color.get(), Some(foreground));
729
730 cx.simulate_mouse_move(point(px(30.), px(16.)), None, Modifiers::default());
732 draw(cx);
733 assert_eq!(color.get(), Some(accent_foreground));
734 cx.simulate_click(point(px(30.), px(16.)), Modifiers::default());
735 draw(cx);
736 assert_eq!(color.get(), Some(accent_foreground));
737 cx.simulate_mouse_move(point(px(90.), px(16.)), None, Modifiers::default());
738 draw(cx);
739 assert_eq!(color.get(), Some(accent_foreground));
740
741 cx.simulate_click(point(px(90.), px(16.)), Modifiers::default());
744 draw(cx);
745 assert_eq!(color.get(), Some(foreground));
746 }
747
748 #[test]
749 fn instance_style_remains_the_final_visual_override() {
750 let toggle = Toggle::new("styled").checked(true).opacity(0.37);
751 assert_eq!(toggle.style.opacity, Some(0.37));
752 }
753
754 #[gpui::test]
755 fn test_toggle_builder(_cx: &mut gpui::TestAppContext) {
756 let toggle = Toggle::new("complex-toggle")
757 .label("Enable Feature")
758 .icon(IconName::Check)
759 .checked(true)
760 .outline()
761 .large()
762 .disabled(false)
763 .on_click(|_, _, _| {});
764
765 assert_eq!(toggle.children.len(), 2); assert!(toggle.checked);
767 assert_eq!(toggle.variant, ToggleVariant::Outline);
768 assert_eq!(toggle.size, Size::Large);
769 assert!(!toggle.disabled);
770 assert!(toggle.on_click.is_some());
771 }
772
773 #[gpui::test]
774 fn test_toggle_group_builder(_cx: &mut gpui::TestAppContext) {
775 let group = ToggleGroup::new("complex-group")
776 .child(Toggle::new("toggle1").label("Option 1"))
777 .child(Toggle::new("toggle2").label("Option 2").checked(true))
778 .child(Toggle::new("toggle3").label("Option 3"))
779 .outline()
780 .large()
781 .segmented()
782 .single()
783 .disabled(false)
784 .on_click(|_, _, _| {});
785
786 assert_eq!(group.items.len(), 3);
787 assert_eq!(group.variant, ToggleVariant::Outline);
788 assert_eq!(group.size, Size::Large);
789 assert!(group.segmented);
790 assert!(group.single);
791 assert!(!group.disabled);
792 assert!(group.on_click.is_some());
793 }
794}