1use gpui::{
2 Animation, AnimationExt, AnyElement, AnyView, ClickEvent, ElementId, Hsla, IntoElement,
3 KeybindingKeystroke, Keystroke, Styled, Window, div, ease_out_quint, hsla, prelude::*, white,
4};
5use std::{rc::Rc, sync::Arc};
6
7use crate::{Color, Icon, IconName, ToggleState, Tooltip};
8use crate::{ElevationIndex, KeyBinding, prelude::*};
9
10pub fn checkbox(id: impl Into<ElementId>, toggle_state: ToggleState) -> Checkbox {
15 Checkbox::new(id, toggle_state)
16}
17
18pub fn switch(id: impl Into<ElementId>, toggle_state: ToggleState) -> Switch {
20 Switch::new(id, toggle_state)
21}
22
23#[derive(Debug, Default, Clone, PartialEq, Eq)]
25pub enum ToggleStyle {
26 #[default]
28 Ghost,
29 ElevationBased(ElevationIndex),
32 Custom(Hsla),
34}
35
36#[derive(IntoElement, RegisterComponent)]
42pub struct Checkbox {
43 id: ElementId,
44 toggle_state: ToggleState,
45 style: ToggleStyle,
46 disabled: bool,
47 placeholder: bool,
48 filled: bool,
49 visualization: bool,
50 label: Option<SharedString>,
51 label_size: LabelSize,
52 label_color: Color,
53 tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
54 on_click: Option<Box<dyn Fn(&ToggleState, &ClickEvent, &mut Window, &mut App) + 'static>>,
55 tab_index: Option<isize>,
56}
57
58impl Checkbox {
59 pub fn new(id: impl Into<ElementId>, checked: ToggleState) -> Self {
61 Self {
62 id: id.into(),
63 toggle_state: checked,
64 style: ToggleStyle::default(),
65 disabled: false,
66 placeholder: false,
67 filled: false,
68 visualization: false,
69 label: None,
70 label_size: LabelSize::Default,
71 label_color: Color::Muted,
72 tooltip: None,
73 on_click: None,
74 tab_index: None,
75 }
76 }
77
78 pub fn tab_index(mut self, tab_index: isize) -> Self {
82 self.tab_index = Some(tab_index);
83 self
84 }
85
86 pub fn disabled(mut self, disabled: bool) -> Self {
88 self.disabled = disabled;
89 self
90 }
91
92 pub fn placeholder(mut self, placeholder: bool) -> Self {
94 self.placeholder = placeholder;
95 self
96 }
97
98 pub fn on_click(
100 mut self,
101 handler: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
102 ) -> Self {
103 self.on_click = Some(Box::new(move |state, _, window, cx| {
104 handler(state, window, cx)
105 }));
106 self
107 }
108
109 pub fn on_click_ext(
110 mut self,
111 handler: impl Fn(&ToggleState, &ClickEvent, &mut Window, &mut App) + 'static,
112 ) -> Self {
113 self.on_click = Some(Box::new(handler));
114 self
115 }
116
117 pub fn fill(mut self) -> Self {
119 self.filled = true;
120 self
121 }
122
123 pub fn visualization_only(mut self, visualization: bool) -> Self {
126 self.visualization = visualization;
127 self
128 }
129
130 pub fn style(mut self, style: ToggleStyle) -> Self {
132 self.style = style;
133 self
134 }
135
136 pub fn elevation(mut self, elevation: ElevationIndex) -> Self {
138 self.style = ToggleStyle::ElevationBased(elevation);
139 self
140 }
141
142 pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
144 self.tooltip = Some(Box::new(tooltip));
145 self
146 }
147
148 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
150 self.label = Some(label.into());
151 self
152 }
153
154 pub fn label_size(mut self, size: LabelSize) -> Self {
155 self.label_size = size;
156 self
157 }
158
159 pub fn label_color(mut self, color: Color) -> Self {
160 self.label_color = color;
161 self
162 }
163}
164
165impl Checkbox {
166 fn checked(&self) -> bool {
167 matches!(
168 self.toggle_state,
169 ToggleState::Selected | ToggleState::Indeterminate
170 )
171 }
172
173 fn bg_color(&self, cx: &App) -> Hsla {
174 if self.disabled {
175 return semantic::border_muted(cx).opacity(0.6);
176 }
177
178 let checked = self.checked();
179 match self.style.clone() {
180 ToggleStyle::Ghost if checked => palette::primary(600),
181 ToggleStyle::Ghost if self.filled => semantic::surface(cx),
182 ToggleStyle::Ghost => gpui::transparent_black(),
183 ToggleStyle::ElevationBased(_) if checked => palette::primary(600),
184 ToggleStyle::ElevationBased(elevation) if self.filled => elevation.darker_bg(cx),
185 ToggleStyle::ElevationBased(_) => gpui::transparent_black(),
186 ToggleStyle::Custom(color) if checked => color,
187 ToggleStyle::Custom(color) if self.filled => color.opacity(0.2),
188 ToggleStyle::Custom(_) => gpui::transparent_black(),
189 }
190 }
191
192 fn border_color(&self, cx: &App) -> Hsla {
193 if self.disabled {
194 return semantic::border_muted(cx);
195 }
196
197 match self.style.clone() {
198 ToggleStyle::Ghost | ToggleStyle::ElevationBased(_) if self.checked() => {
199 palette::primary(600)
200 }
201 ToggleStyle::Ghost | ToggleStyle::ElevationBased(_) => semantic::border(cx),
202 ToggleStyle::Custom(color) => color.opacity(0.3),
203 }
204 }
205
206 pub fn container_size() -> Pixels {
207 px(20.0)
208 }
209}
210
211impl RenderOnce for Checkbox {
212 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
213 let group_id = format!("checkbox_group_{:?}", self.id);
214 let is_solid_style = matches!(
215 self.style,
216 ToggleStyle::Ghost | ToggleStyle::ElevationBased(_)
217 );
218 let color = if self.disabled {
219 Color::Disabled
220 } else if self.checked() && is_solid_style {
221 Color::Custom(white())
224 } else {
225 Color::Selected
226 };
227
228 let icon = match self.toggle_state {
229 ToggleState::Selected => {
230 if self.placeholder {
231 None
232 } else {
233 Some(
234 Icon::new(IconName::Check)
235 .size(IconSize::Small)
236 .color(color),
237 )
238 }
239 }
240 ToggleState::Indeterminate => {
241 Some(Icon::new(IconName::Dash).size(IconSize::Small).color(color))
242 }
243 ToggleState::Unselected => None,
244 };
245
246 let bg_color = self.bg_color(cx);
247 let border_color = self.border_color(cx);
248 let hover_border_color = border_color.alpha(0.7);
249
250 let size = Self::container_size();
251
252 let tab_index = self.tab_index;
253 let checkbox = h_flex()
254 .group(group_id.clone())
255 .id(self.id.clone())
256 .size(size)
257 .justify_center()
258 .child(
259 div()
260 .id((self.id.clone(), "box"))
261 .flex()
262 .flex_none()
263 .justify_center()
264 .items_center()
265 .m_1()
266 .size_4()
267 .rounded_xs()
268 .bg(bg_color)
269 .border_1()
270 .border_color(border_color)
271 .when(self.disabled, |this| this.cursor_not_allowed())
272 .when(!self.disabled && !self.visualization, |this| {
273 this.group_hover(group_id.clone(), |el| el.border_color(hover_border_color))
274 })
275 .when_some(tab_index.filter(|_| !self.disabled), |this, tab_index| {
276 this.tab_index(tab_index).focus_visible(|mut style| {
277 style.border_color = Some(palette::primary(500));
278 style
279 })
280 })
281 .when(self.placeholder, |this| {
282 this.child(
283 div()
284 .flex_none()
285 .rounded_full()
286 .bg(color.color(cx).alpha(0.5))
287 .size(px(4.)),
288 )
289 })
290 .children(icon),
291 );
292
293 h_flex()
294 .id(self.id)
295 .map(|this| {
296 if self.disabled {
297 this.cursor_not_allowed()
298 } else if self.visualization {
299 this.cursor_default()
300 } else {
301 this.cursor_pointer()
302 }
303 })
304 .gap(DynamicSpacing::Base06.rems(cx))
305 .child(checkbox)
306 .when_some(self.label, |this, label| {
307 this.child(
308 Label::new(label)
309 .color(self.label_color)
310 .size(self.label_size),
311 )
312 })
313 .when_some(self.tooltip, |this, tooltip| {
314 this.tooltip(move |window, cx| tooltip(window, cx))
315 })
316 .when_some(
317 self.on_click.filter(|_| !self.disabled),
318 |this, on_click| {
319 this.on_click(move |click, window, cx| {
320 on_click(&self.toggle_state.inverse(), click, window, cx)
321 })
322 },
323 )
324 }
325}
326
327#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
329pub enum SwitchColor {
330 #[default]
331 Accent,
332 Custom(Hsla),
333}
334
335impl SwitchColor {
336 fn get_colors(&self, is_on: bool, cx: &App) -> (Hsla, Hsla) {
340 if !is_on {
341 return (semantic::border_muted(cx), semantic::border(cx));
342 }
343
344 match self {
345 SwitchColor::Accent => (palette::primary(600), palette::primary(600)),
346 SwitchColor::Custom(color) => (*color, color.opacity(0.6)),
347 }
348 }
349}
350
351impl From<SwitchColor> for Color {
352 fn from(color: SwitchColor) -> Self {
353 match color {
354 SwitchColor::Accent => Color::Accent,
355 SwitchColor::Custom(_) => Color::Default,
356 }
357 }
358}
359
360#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
362pub enum SwitchLabelPosition {
363 Start,
364 #[default]
365 End,
366}
367
368#[derive(IntoElement, RegisterComponent)]
372pub struct Switch {
373 id: ElementId,
374 toggle_state: ToggleState,
375 disabled: bool,
376 on_click: Option<Rc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>>,
377 label: Option<SharedString>,
378 label_position: Option<SwitchLabelPosition>,
379 label_size: LabelSize,
380 full_width: bool,
381 key_binding: Option<KeyBinding>,
382 color: SwitchColor,
383 tab_index: Option<isize>,
384}
385
386impl Switch {
387 pub fn new(id: impl Into<ElementId>, state: ToggleState) -> Self {
389 Self {
390 id: id.into(),
391 toggle_state: state,
392 disabled: false,
393 on_click: None,
394 label: None,
395 label_position: None,
396 label_size: LabelSize::Small,
397 full_width: false,
398 key_binding: None,
399 color: SwitchColor::default(),
400 tab_index: None,
401 }
402 }
403
404 pub fn color(mut self, color: SwitchColor) -> Self {
406 self.color = color;
407 self
408 }
409
410 pub fn disabled(mut self, disabled: bool) -> Self {
412 self.disabled = disabled;
413 self
414 }
415
416 pub fn on_click(
418 mut self,
419 handler: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
420 ) -> Self {
421 self.on_click = Some(Rc::new(handler));
422 self
423 }
424
425 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
427 self.label = Some(label.into());
428 self
429 }
430
431 pub fn label_position(
432 mut self,
433 label_position: impl Into<Option<SwitchLabelPosition>>,
434 ) -> Self {
435 self.label_position = label_position.into();
436 self
437 }
438
439 pub fn label_size(mut self, size: LabelSize) -> Self {
440 self.label_size = size;
441 self
442 }
443
444 pub fn full_width(mut self, full_width: bool) -> Self {
445 self.full_width = full_width;
446 self
447 }
448
449 pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
451 self.key_binding = key_binding.into();
452 self
453 }
454
455 pub fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
456 self.tab_index = Some(tab_index.into());
457 self
458 }
459}
460
461const SWITCH_TRACK_W: f32 = 44.0;
464const SWITCH_TRACK_H: f32 = 24.0;
465const SWITCH_THUMB_SIZE: f32 = 20.0;
466const SWITCH_THUMB_INSET: f32 = 2.0;
467
468impl RenderOnce for Switch {
469 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
470 let is_on = self.toggle_state == ToggleState::Selected;
471 let (bg_color, border_color) = self.color.get_colors(is_on, cx);
472
473 let bg_hover_color = if is_on {
474 palette::primary(700)
475 } else {
476 semantic::hover_bg(cx)
477 };
478
479 let thumb_opacity = if self.disabled { 0.5 } else { 1.0 };
480
481 let group_id = format!("switch_group_{:?}", self.id);
482 let label = self.label;
483
484 let thumb_off_pos = SWITCH_THUMB_INSET;
488 let thumb_on_pos = SWITCH_TRACK_W - SWITCH_THUMB_SIZE - SWITCH_THUMB_INSET;
489 let thumb_key = format!("switch-thumb-{:?}-{is_on}", self.id);
490 let thumb = div()
491 .absolute()
492 .top(px(SWITCH_THUMB_INSET))
493 .size(px(SWITCH_THUMB_SIZE))
494 .rounded_full()
495 .bg(white())
496 .opacity(thumb_opacity)
497 .with_animation(
498 thumb_key,
499 Animation::new(AnimationDuration::Fast.duration()).with_easing(ease_out_quint()),
500 move |thumb, delta| {
501 let (start, end) = if is_on {
502 (thumb_off_pos, thumb_on_pos)
503 } else {
504 (thumb_on_pos, thumb_off_pos)
505 };
506 thumb.left(px(start + delta * (end - start)))
507 },
508 );
509
510 let switch = div()
511 .id((self.id.clone(), "switch"))
512 .p(px(1.0))
513 .border_2()
514 .border_color(gpui::transparent_black())
515 .rounded_full()
516 .when_some(
517 self.tab_index.filter(|_| !self.disabled),
518 |this, tab_index| {
519 this.tab_index(tab_index)
520 .focus_visible(|mut style| {
521 style.border_color = Some(palette::primary(500));
522 style
523 })
524 .when_some(self.on_click.clone(), |this, on_click| {
525 this.on_click(move |_, window, cx| {
526 on_click(&self.toggle_state.inverse(), window, cx)
527 })
528 })
529 },
530 )
531 .child(
532 h_flex()
533 .relative()
534 .w(px(SWITCH_TRACK_W))
535 .h(px(SWITCH_TRACK_H))
536 .group(group_id.clone())
537 .rounded_full()
538 .bg(bg_color)
539 .when(!self.disabled, |this| {
540 this.group_hover(group_id.clone(), |el| el.bg(bg_hover_color))
541 })
542 .border_1()
543 .border_color(border_color)
544 .child(thumb),
545 );
546
547 h_flex()
548 .id(self.id)
549 .cursor_pointer()
550 .gap(DynamicSpacing::Base06.rems(cx))
551 .when(self.full_width, |this| this.w_full().justify_between())
552 .when(
553 self.label_position == Some(SwitchLabelPosition::Start),
554 |this| {
555 this.when_some(label.clone(), |this, label| {
556 this.child(Label::new(label).size(self.label_size))
557 })
558 },
559 )
560 .child(switch)
561 .when(
562 self.label_position == Some(SwitchLabelPosition::End),
563 |this| {
564 this.when_some(label, |this, label| {
565 this.child(Label::new(label).size(self.label_size))
566 })
567 },
568 )
569 .children(self.key_binding)
570 .when_some(
571 self.on_click.filter(|_| !self.disabled),
572 |this, on_click| {
573 this.on_click(move |_, window, cx| {
574 on_click(&self.toggle_state.inverse(), window, cx)
575 })
576 },
577 )
578 }
579}
580
581#[derive(IntoElement, RegisterComponent)]
602pub struct SwitchField {
603 id: ElementId,
604 label: Option<SharedString>,
605 description: Option<SharedString>,
606 toggle_state: ToggleState,
607 on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
608 disabled: bool,
609 color: SwitchColor,
610 tooltip: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyView>>,
611 tab_index: Option<isize>,
612}
613
614impl SwitchField {
615 pub fn new(
616 id: impl Into<ElementId>,
617 label: Option<impl Into<SharedString>>,
618 description: Option<SharedString>,
619 toggle_state: impl Into<ToggleState>,
620 on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
621 ) -> Self {
622 Self {
623 id: id.into(),
624 label: label.map(Into::into),
625 description,
626 toggle_state: toggle_state.into(),
627 on_click: Arc::new(on_click),
628 disabled: false,
629 color: SwitchColor::Accent,
630 tooltip: None,
631 tab_index: None,
632 }
633 }
634
635 pub fn description(mut self, description: impl Into<SharedString>) -> Self {
636 self.description = Some(description.into());
637 self
638 }
639
640 pub fn disabled(mut self, disabled: bool) -> Self {
641 self.disabled = disabled;
642 self
643 }
644
645 pub fn color(mut self, color: SwitchColor) -> Self {
648 self.color = color;
649 self
650 }
651
652 pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
653 self.tooltip = Some(Rc::new(tooltip));
654 self
655 }
656
657 pub fn tab_index(mut self, tab_index: isize) -> Self {
658 self.tab_index = Some(tab_index);
659 self
660 }
661}
662
663impl RenderOnce for SwitchField {
664 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
665 let tooltip = self
666 .tooltip
667 .zip(self.label.clone())
668 .map(|(tooltip_fn, label)| {
669 h_flex().gap_0p5().child(Label::new(label)).child(
670 IconButton::new("tooltip_button", IconName::Info)
671 .icon_size(IconSize::XSmall)
672 .icon_color(Color::Muted)
673 .shape(crate::IconButtonShape::Square)
674 .style(ButtonStyle::Transparent)
675 .tooltip({
676 let tooltip = tooltip_fn.clone();
677 move |window, cx| tooltip(window, cx)
678 })
679 .on_click(|_, _, _| {}), )
681 });
682
683 h_flex()
684 .id((self.id.clone(), "container"))
685 .when(!self.disabled, |this| {
686 this.hover(|this| this.cursor_pointer())
687 })
688 .w_full()
689 .gap_4()
690 .justify_between()
691 .flex_wrap()
692 .child(match (&self.description, tooltip) {
693 (Some(description), Some(tooltip)) => v_flex()
694 .gap_0p5()
695 .max_w_5_6()
696 .child(tooltip)
697 .child(Label::new(description.clone()).color(Color::Muted))
698 .into_any_element(),
699 (Some(description), None) => v_flex()
700 .gap_0p5()
701 .max_w_5_6()
702 .when_some(self.label, |this, label| this.child(Label::new(label)))
703 .child(Label::new(description.clone()).color(Color::Muted))
704 .into_any_element(),
705 (None, Some(tooltip)) => tooltip.into_any_element(),
706 (None, None) => {
707 if let Some(label) = self.label.clone() {
708 Label::new(label).into_any_element()
709 } else {
710 gpui::Empty.into_any_element()
711 }
712 }
713 })
714 .child(
715 Switch::new((self.id.clone(), "switch"), self.toggle_state)
716 .color(self.color)
717 .disabled(self.disabled)
718 .when_some(
719 self.tab_index.filter(|_| !self.disabled),
720 |this, tab_index| this.tab_index(tab_index),
721 )
722 .on_click({
723 let on_click = self.on_click.clone();
724 move |state, window, cx| {
725 (on_click)(state, window, cx);
726 }
727 }),
728 )
729 .when(!self.disabled, |this| {
730 this.on_click({
731 let on_click = self.on_click.clone();
732 let toggle_state = self.toggle_state;
733 move |_click, window, cx| {
734 (on_click)(&toggle_state.inverse(), window, cx);
735 }
736 })
737 })
738 }
739}
740
741impl Component for SwitchField {
742 fn scope() -> ComponentScope {
743 ComponentScope::Input
744 }
745
746 fn description() -> Option<&'static str> {
747 Some("A field component that combines a label, description, and switch")
748 }
749
750 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
751 Some(
752 v_flex()
753 .gap_6()
754 .children(vec![
755 example_group_with_title(
756 "States",
757 vec![
758 single_example(
759 "Unselected",
760 SwitchField::new(
761 "switch_field_unselected",
762 Some("Enable notifications"),
763 Some("Receive notifications when new messages arrive.".into()),
764 ToggleState::Unselected,
765 |_, _, _| {},
766 )
767 .into_any_element(),
768 ),
769 single_example(
770 "Selected",
771 SwitchField::new(
772 "switch_field_selected",
773 Some("Enable notifications"),
774 Some("Receive notifications when new messages arrive.".into()),
775 ToggleState::Selected,
776 |_, _, _| {},
777 )
778 .into_any_element(),
779 ),
780 ],
781 ),
782 example_group_with_title(
783 "Colors",
784 vec![
785 single_example(
786 "Default",
787 SwitchField::new(
788 "switch_field_default",
789 Some("Default color"),
790 Some("This uses the default switch color.".into()),
791 ToggleState::Selected,
792 |_, _, _| {},
793 )
794 .into_any_element(),
795 ),
796 single_example(
797 "Accent",
798 SwitchField::new(
799 "switch_field_accent",
800 Some("Accent color"),
801 Some("This uses the accent color scheme.".into()),
802 ToggleState::Selected,
803 |_, _, _| {},
804 )
805 .color(SwitchColor::Accent)
806 .into_any_element(),
807 ),
808 ],
809 ),
810 example_group_with_title(
811 "Disabled",
812 vec![single_example(
813 "Disabled",
814 SwitchField::new(
815 "switch_field_disabled",
816 Some("Disabled field"),
817 Some("This field is disabled and cannot be toggled.".into()),
818 ToggleState::Selected,
819 |_, _, _| {},
820 )
821 .disabled(true)
822 .into_any_element(),
823 )],
824 ),
825 example_group_with_title(
826 "No Description",
827 vec![single_example(
828 "No Description",
829 SwitchField::new(
830 "switch_field_disabled",
831 Some("Disabled field"),
832 None,
833 ToggleState::Selected,
834 |_, _, _| {},
835 )
836 .into_any_element(),
837 )],
838 ),
839 example_group_with_title(
840 "With Tooltip",
841 vec![
842 single_example(
843 "Tooltip with Description",
844 SwitchField::new(
845 "switch_field_tooltip_with_desc",
846 Some("Nice Feature"),
847 Some("Enable advanced configuration options.".into()),
848 ToggleState::Unselected,
849 |_, _, _| {},
850 )
851 .tooltip(Tooltip::text("This is content for this tooltip!"))
852 .into_any_element(),
853 ),
854 single_example(
855 "Tooltip without Description",
856 SwitchField::new(
857 "switch_field_tooltip_no_desc",
858 Some("Nice Feature"),
859 None,
860 ToggleState::Selected,
861 |_, _, _| {},
862 )
863 .tooltip(Tooltip::text("This is content for this tooltip!"))
864 .into_any_element(),
865 ),
866 ],
867 ),
868 ])
869 .into_any_element(),
870 )
871 }
872}
873
874impl Component for Checkbox {
875 fn scope() -> ComponentScope {
876 ComponentScope::Input
877 }
878
879 fn description() -> Option<&'static str> {
880 Some("A checkbox component that can be used for multiple choice selections")
881 }
882
883 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
884 Some(
885 v_flex()
886 .gap_6()
887 .children(vec![
888 example_group_with_title(
889 "States",
890 vec![
891 single_example(
892 "Unselected",
893 Checkbox::new("checkbox_unselected", ToggleState::Unselected)
894 .into_any_element(),
895 ),
896 single_example(
897 "Placeholder",
898 Checkbox::new("checkbox_indeterminate", ToggleState::Selected)
899 .placeholder(true)
900 .into_any_element(),
901 ),
902 single_example(
903 "Indeterminate",
904 Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate)
905 .into_any_element(),
906 ),
907 single_example(
908 "Selected",
909 Checkbox::new("checkbox_selected", ToggleState::Selected)
910 .into_any_element(),
911 ),
912 ],
913 ),
914 example_group_with_title(
915 "Styles",
916 vec![
917 single_example(
918 "Default",
919 Checkbox::new("checkbox_default", ToggleState::Selected)
920 .into_any_element(),
921 ),
922 single_example(
923 "Filled",
924 Checkbox::new("checkbox_filled", ToggleState::Selected)
925 .fill()
926 .into_any_element(),
927 ),
928 single_example(
929 "ElevationBased",
930 Checkbox::new("checkbox_elevation", ToggleState::Selected)
931 .style(ToggleStyle::ElevationBased(
932 ElevationIndex::EditorSurface,
933 ))
934 .into_any_element(),
935 ),
936 single_example(
937 "Custom Color",
938 Checkbox::new("checkbox_custom", ToggleState::Selected)
939 .style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7)))
940 .into_any_element(),
941 ),
942 ],
943 ),
944 example_group_with_title(
945 "Disabled",
946 vec![
947 single_example(
948 "Unselected",
949 Checkbox::new(
950 "checkbox_disabled_unselected",
951 ToggleState::Unselected,
952 )
953 .disabled(true)
954 .into_any_element(),
955 ),
956 single_example(
957 "Selected",
958 Checkbox::new("checkbox_disabled_selected", ToggleState::Selected)
959 .disabled(true)
960 .into_any_element(),
961 ),
962 ],
963 ),
964 example_group_with_title(
965 "With Label",
966 vec![single_example(
967 "Default",
968 Checkbox::new("checkbox_with_label", ToggleState::Selected)
969 .label("Always save on quit")
970 .into_any_element(),
971 )],
972 ),
973 example_group_with_title(
974 "Extra",
975 vec![single_example(
976 "Visualization-Only",
977 Checkbox::new("viz_only", ToggleState::Selected)
978 .visualization_only(true)
979 .into_any_element(),
980 )],
981 ),
982 ])
983 .into_any_element(),
984 )
985 }
986}
987
988impl Component for Switch {
989 fn scope() -> ComponentScope {
990 ComponentScope::Input
991 }
992
993 fn description() -> Option<&'static str> {
994 Some("A switch component that represents binary states like on/off")
995 }
996
997 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
998 Some(
999 v_flex()
1000 .gap_6()
1001 .children(vec![
1002 example_group_with_title(
1003 "States",
1004 vec![
1005 single_example(
1006 "Off",
1007 Switch::new("switch_off", ToggleState::Unselected)
1008 .on_click(|_, _, _cx| {})
1009 .into_any_element(),
1010 ),
1011 single_example(
1012 "On",
1013 Switch::new("switch_on", ToggleState::Selected)
1014 .on_click(|_, _, _cx| {})
1015 .into_any_element(),
1016 ),
1017 ],
1018 ),
1019 example_group_with_title(
1020 "Colors",
1021 vec![
1022 single_example(
1023 "Accent (Default)",
1024 Switch::new("switch_accent_style", ToggleState::Selected)
1025 .on_click(|_, _, _cx| {})
1026 .into_any_element(),
1027 ),
1028 single_example(
1029 "Custom",
1030 Switch::new("switch_custom_style", ToggleState::Selected)
1031 .color(SwitchColor::Custom(hsla(300.0 / 360.0, 0.6, 0.6, 1.0)))
1032 .on_click(|_, _, _cx| {})
1033 .into_any_element(),
1034 ),
1035 ],
1036 ),
1037 example_group_with_title(
1038 "Disabled",
1039 vec![
1040 single_example(
1041 "Off",
1042 Switch::new("switch_disabled_off", ToggleState::Unselected)
1043 .disabled(true)
1044 .into_any_element(),
1045 ),
1046 single_example(
1047 "On",
1048 Switch::new("switch_disabled_on", ToggleState::Selected)
1049 .disabled(true)
1050 .into_any_element(),
1051 ),
1052 ],
1053 ),
1054 example_group_with_title(
1055 "With Label",
1056 vec![
1057 single_example(
1058 "Start Label",
1059 Switch::new("switch_with_label_start", ToggleState::Selected)
1060 .label("Always save on quit")
1061 .label_position(SwitchLabelPosition::Start)
1062 .into_any_element(),
1063 ),
1064 single_example(
1065 "End Label",
1066 Switch::new("switch_with_label_end", ToggleState::Selected)
1067 .label("Always save on quit")
1068 .label_position(SwitchLabelPosition::End)
1069 .into_any_element(),
1070 ),
1071 single_example(
1072 "Default Size Label",
1073 Switch::new(
1074 "switch_with_label_default_size",
1075 ToggleState::Selected,
1076 )
1077 .label("Always save on quit")
1078 .label_size(LabelSize::Default)
1079 .into_any_element(),
1080 ),
1081 single_example(
1082 "Small Size Label",
1083 Switch::new("switch_with_label_small_size", ToggleState::Selected)
1084 .label("Always save on quit")
1085 .label_size(LabelSize::Small)
1086 .into_any_element(),
1087 ),
1088 ],
1089 ),
1090 example_group_with_title(
1091 "With Keybinding",
1092 vec![single_example(
1093 "Keybinding",
1094 Switch::new("switch_with_keybinding", ToggleState::Selected)
1095 .key_binding(Some(KeyBinding::from_keystrokes(
1096 vec![KeybindingKeystroke::from_keystroke(
1097 Keystroke::parse("cmd-s").unwrap(),
1098 )]
1099 .into(),
1100 false,
1101 )))
1102 .into_any_element(),
1103 )],
1104 ),
1105 ])
1106 .into_any_element(),
1107 )
1108 }
1109}