toggle.rs

  1use gpui::{
  2    AnyElement, AnyView, ElementId, Hsla, IntoElement, Styled, Window, div, hsla, prelude::*,
  3};
  4use std::sync::Arc;
  5
  6use crate::utils::is_light;
  7use crate::{Color, Icon, IconName, ToggleState};
  8use crate::{ElevationIndex, KeyBinding, prelude::*};
  9
 10// TODO: Checkbox, CheckboxWithLabel, and Switch could all be
 11// restructured to use a ToggleLike, similar to Button/Buttonlike, Label/Labellike
 12
 13/// Creates a new checkbox.
 14pub fn checkbox(id: impl Into<ElementId>, toggle_state: ToggleState) -> Checkbox {
 15    Checkbox::new(id, toggle_state)
 16}
 17
 18/// Creates a new switch.
 19pub fn switch(id: impl Into<ElementId>, toggle_state: ToggleState) -> Switch {
 20    Switch::new(id, toggle_state)
 21}
 22
 23/// The visual style of a toggle.
 24#[derive(Debug, Default, Clone, PartialEq, Eq)]
 25pub enum ToggleStyle {
 26    /// Toggle has a transparent background
 27    #[default]
 28    Ghost,
 29    /// Toggle has a filled background based on the
 30    /// elevation index of the parent container
 31    ElevationBased(ElevationIndex),
 32    /// A custom style using a color to tint the toggle
 33    Custom(Hsla),
 34}
 35
 36/// # Checkbox
 37///
 38/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
 39/// Each checkbox works independently from other checkboxes in the list,
 40/// therefore checking an additional box does not affect any other selections.
 41#[derive(IntoElement, RegisterComponent)]
 42pub struct Checkbox {
 43    id: ElementId,
 44    toggle_state: ToggleState,
 45    disabled: bool,
 46    placeholder: bool,
 47    on_click: Option<Box<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>>,
 48    filled: bool,
 49    style: ToggleStyle,
 50    tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
 51    label: Option<SharedString>,
 52}
 53
 54impl Checkbox {
 55    /// Creates a new [`Checkbox`].
 56    pub fn new(id: impl Into<ElementId>, checked: ToggleState) -> Self {
 57        Self {
 58            id: id.into(),
 59            toggle_state: checked,
 60            disabled: false,
 61            on_click: None,
 62            filled: false,
 63            style: ToggleStyle::default(),
 64            tooltip: None,
 65            label: None,
 66            placeholder: false,
 67        }
 68    }
 69
 70    /// Sets the disabled state of the [`Checkbox`].
 71    pub fn disabled(mut self, disabled: bool) -> Self {
 72        self.disabled = disabled;
 73        self
 74    }
 75
 76    /// Sets the disabled state of the [`Checkbox`].
 77    pub fn placeholder(mut self, placeholder: bool) -> Self {
 78        self.placeholder = placeholder;
 79        self
 80    }
 81
 82    /// Binds a handler to the [`Checkbox`] that will be called when clicked.
 83    pub fn on_click(
 84        mut self,
 85        handler: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
 86    ) -> Self {
 87        self.on_click = Some(Box::new(handler));
 88        self
 89    }
 90
 91    /// Sets the `fill` setting of the checkbox, indicating whether it should be filled.
 92    pub fn fill(mut self) -> Self {
 93        self.filled = true;
 94        self
 95    }
 96
 97    /// Sets the style of the checkbox using the specified [`ToggleStyle`].
 98    pub fn style(mut self, style: ToggleStyle) -> Self {
 99        self.style = style;
100        self
101    }
102
103    /// Match the style of the checkbox to the current elevation using [`ToggleStyle::ElevationBased`].
104    pub fn elevation(mut self, elevation: ElevationIndex) -> Self {
105        self.style = ToggleStyle::ElevationBased(elevation);
106        self
107    }
108
109    /// Sets the tooltip for the checkbox.
110    pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
111        self.tooltip = Some(Box::new(tooltip));
112        self
113    }
114
115    /// Set the label for the checkbox.
116    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
117        self.label = Some(label.into());
118        self
119    }
120}
121
122impl Checkbox {
123    fn bg_color(&self, cx: &App) -> Hsla {
124        let style = self.style.clone();
125        match (style, self.filled) {
126            (ToggleStyle::Ghost, false) => cx.theme().colors().ghost_element_background,
127            (ToggleStyle::Ghost, true) => cx.theme().colors().element_background,
128            (ToggleStyle::ElevationBased(_), false) => gpui::transparent_black(),
129            (ToggleStyle::ElevationBased(elevation), true) => elevation.darker_bg(cx),
130            (ToggleStyle::Custom(_), false) => gpui::transparent_black(),
131            (ToggleStyle::Custom(color), true) => color.opacity(0.2),
132        }
133    }
134
135    fn border_color(&self, cx: &App) -> Hsla {
136        if self.disabled {
137            return cx.theme().colors().border_variant;
138        }
139
140        match self.style.clone() {
141            ToggleStyle::Ghost => cx.theme().colors().border,
142            ToggleStyle::ElevationBased(_) => cx.theme().colors().border,
143            ToggleStyle::Custom(color) => color.opacity(0.3),
144        }
145    }
146
147    /// container size
148    pub fn container_size() -> Pixels {
149        px(20.0)
150    }
151}
152
153impl RenderOnce for Checkbox {
154    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
155        let group_id = format!("checkbox_group_{:?}", self.id);
156        let color = if self.disabled {
157            Color::Disabled
158        } else {
159            Color::Selected
160        };
161        let icon = match self.toggle_state {
162            ToggleState::Selected => {
163                if self.placeholder {
164                    None
165                } else {
166                    Some(
167                        Icon::new(IconName::Check)
168                            .size(IconSize::Small)
169                            .color(color),
170                    )
171                }
172            }
173            ToggleState::Indeterminate => {
174                Some(Icon::new(IconName::Dash).size(IconSize::Small).color(color))
175            }
176            ToggleState::Unselected => None,
177        };
178
179        let bg_color = self.bg_color(cx);
180        let border_color = self.border_color(cx);
181        let hover_border_color = border_color.alpha(0.7);
182
183        let size = Self::container_size();
184
185        let checkbox = h_flex()
186            .id(self.id.clone())
187            .justify_center()
188            .items_center()
189            .size(size)
190            .group(group_id.clone())
191            .child(
192                div()
193                    .flex()
194                    .flex_none()
195                    .justify_center()
196                    .items_center()
197                    .m_1()
198                    .size_4()
199                    .rounded_xs()
200                    .bg(bg_color)
201                    .border_1()
202                    .border_color(border_color)
203                    .when(self.disabled, |this| this.cursor_not_allowed())
204                    .when(self.disabled, |this| {
205                        this.bg(cx.theme().colors().element_disabled.opacity(0.6))
206                    })
207                    .when(!self.disabled, |this| {
208                        this.group_hover(group_id.clone(), |el| el.border_color(hover_border_color))
209                    })
210                    .when(self.placeholder, |this| {
211                        this.child(
212                            div()
213                                .flex_none()
214                                .rounded_full()
215                                .bg(color.color(cx).alpha(0.5))
216                                .size(px(4.)),
217                        )
218                    })
219                    .children(icon),
220            );
221
222        h_flex()
223            .id(self.id)
224            .gap(DynamicSpacing::Base06.rems(cx))
225            .child(checkbox)
226            .when_some(
227                self.on_click.filter(|_| !self.disabled),
228                |this, on_click| {
229                    this.on_click(move |_, window, cx| {
230                        on_click(&self.toggle_state.inverse(), window, cx)
231                    })
232                },
233            )
234            // TODO: Allow label size to be different from default.
235            // TODO: Allow label color to be different from muted.
236            .when_some(self.label, |this, label| {
237                this.child(Label::new(label).color(Color::Muted))
238            })
239            .when_some(self.tooltip, |this, tooltip| {
240                this.tooltip(move |window, cx| tooltip(window, cx))
241            })
242    }
243}
244
245/// A [`Checkbox`] that has a [`Label`].
246#[derive(IntoElement, RegisterComponent)]
247pub struct CheckboxWithLabel {
248    id: ElementId,
249    label: Label,
250    checked: ToggleState,
251    on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
252    filled: bool,
253    style: ToggleStyle,
254    checkbox_position: IconPosition,
255}
256
257// TODO: Remove `CheckboxWithLabel` now that `label` is a method of `Checkbox`.
258impl CheckboxWithLabel {
259    /// Creates a checkbox with an attached label.
260    pub fn new(
261        id: impl Into<ElementId>,
262        label: Label,
263        checked: ToggleState,
264        on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
265    ) -> Self {
266        Self {
267            id: id.into(),
268            label,
269            checked,
270            on_click: Arc::new(on_click),
271            filled: false,
272            style: ToggleStyle::default(),
273            checkbox_position: IconPosition::Start,
274        }
275    }
276
277    /// Sets the style of the checkbox using the specified [`ToggleStyle`].
278    pub fn style(mut self, style: ToggleStyle) -> Self {
279        self.style = style;
280        self
281    }
282
283    /// Match the style of the checkbox to the current elevation using [`ToggleStyle::ElevationBased`].
284    pub fn elevation(mut self, elevation: ElevationIndex) -> Self {
285        self.style = ToggleStyle::ElevationBased(elevation);
286        self
287    }
288
289    /// Sets the `fill` setting of the checkbox, indicating whether it should be filled.
290    pub fn fill(mut self) -> Self {
291        self.filled = true;
292        self
293    }
294
295    pub fn checkbox_position(mut self, position: IconPosition) -> Self {
296        self.checkbox_position = position;
297        self
298    }
299}
300
301impl RenderOnce for CheckboxWithLabel {
302    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
303        h_flex()
304            .gap(DynamicSpacing::Base08.rems(cx))
305            .when(self.checkbox_position == IconPosition::Start, |this| {
306                this.child(
307                    Checkbox::new(self.id.clone(), self.checked)
308                        .style(self.style.clone())
309                        .when(self.filled, Checkbox::fill)
310                        .on_click({
311                            let on_click = self.on_click.clone();
312                            move |checked, window, cx| {
313                                (on_click)(checked, window, cx);
314                            }
315                        }),
316                )
317            })
318            .child(
319                div()
320                    .id(SharedString::from(format!("{}-label", self.id)))
321                    .on_click({
322                        let on_click = self.on_click.clone();
323                        move |_event, window, cx| {
324                            (on_click)(&self.checked.inverse(), window, cx);
325                        }
326                    })
327                    .child(self.label),
328            )
329            .when(self.checkbox_position == IconPosition::End, |this| {
330                this.child(
331                    Checkbox::new(self.id.clone(), self.checked)
332                        .style(self.style)
333                        .when(self.filled, Checkbox::fill)
334                        .on_click(move |checked, window, cx| {
335                            (self.on_click)(checked, window, cx);
336                        }),
337                )
338            })
339    }
340}
341
342/// Defines the color for a switch component.
343#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
344pub enum SwitchColor {
345    #[default]
346    Default,
347    Accent,
348    Error,
349    Warning,
350    Success,
351    Custom(Hsla),
352}
353
354impl SwitchColor {
355    fn get_colors(&self, is_on: bool, cx: &App) -> (Hsla, Hsla) {
356        if !is_on {
357            return (
358                cx.theme().colors().element_disabled,
359                cx.theme().colors().border,
360            );
361        }
362
363        match self {
364            SwitchColor::Default => {
365                let colors = cx.theme().colors();
366                let base_color = colors.text;
367                let bg_color = colors.element_background.blend(base_color.opacity(0.08));
368                (bg_color, colors.border_variant)
369            }
370            SwitchColor::Accent => {
371                let status = cx.theme().status();
372                (status.info.opacity(0.4), status.info.opacity(0.2))
373            }
374            SwitchColor::Error => {
375                let status = cx.theme().status();
376                (status.error.opacity(0.4), status.error.opacity(0.2))
377            }
378            SwitchColor::Warning => {
379                let status = cx.theme().status();
380                (status.warning.opacity(0.4), status.warning.opacity(0.2))
381            }
382            SwitchColor::Success => {
383                let status = cx.theme().status();
384                (status.success.opacity(0.4), status.success.opacity(0.2))
385            }
386            SwitchColor::Custom(color) => (*color, color.opacity(0.6)),
387        }
388    }
389}
390
391impl From<SwitchColor> for Color {
392    fn from(color: SwitchColor) -> Self {
393        match color {
394            SwitchColor::Default => Color::Default,
395            SwitchColor::Accent => Color::Accent,
396            SwitchColor::Error => Color::Error,
397            SwitchColor::Warning => Color::Warning,
398            SwitchColor::Success => Color::Success,
399            SwitchColor::Custom(_) => Color::Default,
400        }
401    }
402}
403
404/// # Switch
405///
406/// Switches are used to represent opposite states, such as enabled or disabled.
407#[derive(IntoElement, RegisterComponent)]
408pub struct Switch {
409    id: ElementId,
410    toggle_state: ToggleState,
411    disabled: bool,
412    on_click: Option<Box<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>>,
413    label: Option<SharedString>,
414    key_binding: Option<KeyBinding>,
415    color: SwitchColor,
416}
417
418impl Switch {
419    /// Creates a new [`Switch`].
420    pub fn new(id: impl Into<ElementId>, state: ToggleState) -> Self {
421        Self {
422            id: id.into(),
423            toggle_state: state,
424            disabled: false,
425            on_click: None,
426            label: None,
427            key_binding: None,
428            color: SwitchColor::default(),
429        }
430    }
431
432    /// Sets the color of the switch using the specified [`SwitchColor`].
433    pub fn color(mut self, color: SwitchColor) -> Self {
434        self.color = color;
435        self
436    }
437
438    /// Sets the disabled state of the [`Switch`].
439    pub fn disabled(mut self, disabled: bool) -> Self {
440        self.disabled = disabled;
441        self
442    }
443
444    /// Binds a handler to the [`Switch`] that will be called when clicked.
445    pub fn on_click(
446        mut self,
447        handler: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
448    ) -> Self {
449        self.on_click = Some(Box::new(handler));
450        self
451    }
452
453    /// Sets the label of the [`Switch`].
454    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
455        self.label = Some(label.into());
456        self
457    }
458
459    /// Display the keybinding that triggers the switch action.
460    pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
461        self.key_binding = key_binding.into();
462        self
463    }
464}
465
466impl RenderOnce for Switch {
467    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
468        let is_on = self.toggle_state == ToggleState::Selected;
469        let adjust_ratio = if is_light(cx) { 1.5 } else { 1.0 };
470
471        let base_color = cx.theme().colors().text;
472        let thumb_color = base_color;
473        let (bg_color, border_color) = self.color.get_colors(is_on, cx);
474
475        let bg_hover_color = if is_on {
476            bg_color.blend(base_color.opacity(0.16 * adjust_ratio))
477        } else {
478            bg_color.blend(base_color.opacity(0.05 * adjust_ratio))
479        };
480
481        let thumb_opacity = match (is_on, self.disabled) {
482            (_, true) => 0.2,
483            (true, false) => 1.0,
484            (false, false) => 0.5,
485        };
486
487        let group_id = format!("switch_group_{:?}", self.id);
488
489        let switch = h_flex()
490            .w(DynamicSpacing::Base32.rems(cx))
491            .h(DynamicSpacing::Base20.rems(cx))
492            .group(group_id.clone())
493            .child(
494                h_flex()
495                    .when(is_on, |on| on.justify_end())
496                    .when(!is_on, |off| off.justify_start())
497                    .size_full()
498                    .rounded_full()
499                    .px(DynamicSpacing::Base02.px(cx))
500                    .bg(bg_color)
501                    .when(!self.disabled, |this| {
502                        this.group_hover(group_id.clone(), |el| el.bg(bg_hover_color))
503                    })
504                    .border_1()
505                    .border_color(border_color)
506                    .child(
507                        div()
508                            .size(DynamicSpacing::Base12.rems(cx))
509                            .rounded_full()
510                            .bg(thumb_color)
511                            .opacity(thumb_opacity),
512                    ),
513            );
514
515        h_flex()
516            .id(self.id)
517            .gap(DynamicSpacing::Base06.rems(cx))
518            .cursor_pointer()
519            .child(switch)
520            .when_some(
521                self.on_click.filter(|_| !self.disabled),
522                |this, on_click| {
523                    this.on_click(move |_, window, cx| {
524                        on_click(&self.toggle_state.inverse(), window, cx)
525                    })
526                },
527            )
528            .when_some(self.label, |this, label| {
529                this.child(Label::new(label).size(LabelSize::Small))
530            })
531            .children(self.key_binding)
532    }
533}
534
535/// A [`Switch`] that has a [`Label`].
536#[derive(IntoElement)]
537pub struct SwitchWithLabel {
538    id: ElementId,
539    label: Label,
540    toggle_state: ToggleState,
541    on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
542    disabled: bool,
543    color: SwitchColor,
544}
545
546impl SwitchWithLabel {
547    /// Creates a switch with an attached label.
548    pub fn new(
549        id: impl Into<ElementId>,
550        label: Label,
551        toggle_state: impl Into<ToggleState>,
552        on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
553    ) -> Self {
554        Self {
555            id: id.into(),
556            label,
557            toggle_state: toggle_state.into(),
558            on_click: Arc::new(on_click),
559            disabled: false,
560            color: SwitchColor::default(),
561        }
562    }
563
564    /// Sets the disabled state of the [`SwitchWithLabel`].
565    pub fn disabled(mut self, disabled: bool) -> Self {
566        self.disabled = disabled;
567        self
568    }
569
570    /// Sets the color of the switch using the specified [`SwitchColor`].
571    pub fn color(mut self, color: SwitchColor) -> Self {
572        self.color = color;
573        self
574    }
575}
576
577impl RenderOnce for SwitchWithLabel {
578    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
579        h_flex()
580            .id(SharedString::from(format!("{}-container", self.id)))
581            .gap(DynamicSpacing::Base08.rems(cx))
582            .child(
583                Switch::new(self.id.clone(), self.toggle_state)
584                    .disabled(self.disabled)
585                    .color(self.color)
586                    .on_click({
587                        let on_click = self.on_click.clone();
588                        move |checked, window, cx| {
589                            (on_click)(checked, window, cx);
590                        }
591                    }),
592            )
593            .child(
594                div()
595                    .id(SharedString::from(format!("{}-label", self.id)))
596                    .child(self.label),
597            )
598    }
599}
600
601impl Component for Checkbox {
602    fn scope() -> ComponentScope {
603        ComponentScope::Input
604    }
605
606    fn description() -> Option<&'static str> {
607        Some("A checkbox component that can be used for multiple choice selections")
608    }
609
610    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
611        Some(
612            v_flex()
613                .gap_6()
614                .children(vec![
615                    example_group_with_title(
616                        "States",
617                        vec![
618                            single_example(
619                                "Unselected",
620                                Checkbox::new("checkbox_unselected", ToggleState::Unselected)
621                                    .into_any_element(),
622                            ),
623                            single_example(
624                                "Placeholder",
625                                Checkbox::new("checkbox_indeterminate", ToggleState::Selected)
626                                    .placeholder(true)
627                                    .into_any_element(),
628                            ),
629                            single_example(
630                                "Indeterminate",
631                                Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate)
632                                    .into_any_element(),
633                            ),
634                            single_example(
635                                "Selected",
636                                Checkbox::new("checkbox_selected", ToggleState::Selected)
637                                    .into_any_element(),
638                            ),
639                        ],
640                    ),
641                    example_group_with_title(
642                        "Styles",
643                        vec![
644                            single_example(
645                                "Default",
646                                Checkbox::new("checkbox_default", ToggleState::Selected)
647                                    .into_any_element(),
648                            ),
649                            single_example(
650                                "Filled",
651                                Checkbox::new("checkbox_filled", ToggleState::Selected)
652                                    .fill()
653                                    .into_any_element(),
654                            ),
655                            single_example(
656                                "ElevationBased",
657                                Checkbox::new("checkbox_elevation", ToggleState::Selected)
658                                    .style(ToggleStyle::ElevationBased(
659                                        ElevationIndex::EditorSurface,
660                                    ))
661                                    .into_any_element(),
662                            ),
663                            single_example(
664                                "Custom Color",
665                                Checkbox::new("checkbox_custom", ToggleState::Selected)
666                                    .style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7)))
667                                    .into_any_element(),
668                            ),
669                        ],
670                    ),
671                    example_group_with_title(
672                        "Disabled",
673                        vec![
674                            single_example(
675                                "Unselected",
676                                Checkbox::new(
677                                    "checkbox_disabled_unselected",
678                                    ToggleState::Unselected,
679                                )
680                                .disabled(true)
681                                .into_any_element(),
682                            ),
683                            single_example(
684                                "Selected",
685                                Checkbox::new("checkbox_disabled_selected", ToggleState::Selected)
686                                    .disabled(true)
687                                    .into_any_element(),
688                            ),
689                        ],
690                    ),
691                    example_group_with_title(
692                        "With Label",
693                        vec![single_example(
694                            "Default",
695                            Checkbox::new("checkbox_with_label", ToggleState::Selected)
696                                .label("Always save on quit")
697                                .into_any_element(),
698                        )],
699                    ),
700                ])
701                .into_any_element(),
702        )
703    }
704}
705
706impl Component for Switch {
707    fn scope() -> ComponentScope {
708        ComponentScope::Input
709    }
710
711    fn description() -> Option<&'static str> {
712        Some("A switch component that represents binary states like on/off")
713    }
714
715    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
716        Some(
717            v_flex()
718                .gap_6()
719                .children(vec![
720                    example_group_with_title(
721                        "States",
722                        vec![
723                            single_example(
724                                "Off",
725                                Switch::new("switch_off", ToggleState::Unselected)
726                                    .on_click(|_, _, _cx| {})
727                                    .into_any_element(),
728                            ),
729                            single_example(
730                                "On",
731                                Switch::new("switch_on", ToggleState::Selected)
732                                    .on_click(|_, _, _cx| {})
733                                    .into_any_element(),
734                            ),
735                        ],
736                    ),
737                    example_group_with_title(
738                        "Colors",
739                        vec![
740                            single_example(
741                                "Default",
742                                Switch::new("switch_default_style", ToggleState::Selected)
743                                    .color(SwitchColor::Default)
744                                    .on_click(|_, _, _cx| {})
745                                    .into_any_element(),
746                            ),
747                            single_example(
748                                "Accent",
749                                Switch::new("switch_accent_style", ToggleState::Selected)
750                                    .color(SwitchColor::Accent)
751                                    .on_click(|_, _, _cx| {})
752                                    .into_any_element(),
753                            ),
754                            single_example(
755                                "Error",
756                                Switch::new("switch_error_style", ToggleState::Selected)
757                                    .color(SwitchColor::Error)
758                                    .on_click(|_, _, _cx| {})
759                                    .into_any_element(),
760                            ),
761                            single_example(
762                                "Warning",
763                                Switch::new("switch_warning_style", ToggleState::Selected)
764                                    .color(SwitchColor::Warning)
765                                    .on_click(|_, _, _cx| {})
766                                    .into_any_element(),
767                            ),
768                            single_example(
769                                "Success",
770                                Switch::new("switch_success_style", ToggleState::Selected)
771                                    .color(SwitchColor::Success)
772                                    .on_click(|_, _, _cx| {})
773                                    .into_any_element(),
774                            ),
775                            single_example(
776                                "Custom",
777                                Switch::new("switch_custom_style", ToggleState::Selected)
778                                    .color(SwitchColor::Custom(hsla(300.0 / 360.0, 0.6, 0.6, 1.0)))
779                                    .on_click(|_, _, _cx| {})
780                                    .into_any_element(),
781                            ),
782                        ],
783                    ),
784                    example_group_with_title(
785                        "Disabled",
786                        vec![
787                            single_example(
788                                "Off",
789                                Switch::new("switch_disabled_off", ToggleState::Unselected)
790                                    .disabled(true)
791                                    .into_any_element(),
792                            ),
793                            single_example(
794                                "On",
795                                Switch::new("switch_disabled_on", ToggleState::Selected)
796                                    .disabled(true)
797                                    .into_any_element(),
798                            ),
799                        ],
800                    ),
801                    example_group_with_title(
802                        "With Label",
803                        vec![
804                            single_example(
805                                "Label",
806                                Switch::new("switch_with_label", ToggleState::Selected)
807                                    .label("Always save on quit")
808                                    .into_any_element(),
809                            ),
810                            // TODO: Where did theme_preview_keybinding go?
811                            // single_example(
812                            //     "Keybinding",
813                            //     Switch::new("switch_with_keybinding", ToggleState::Selected)
814                            //         .key_binding(theme_preview_keybinding("cmd-shift-e"))
815                            //         .into_any_element(),
816                            // ),
817                        ],
818                    ),
819                ])
820                .into_any_element(),
821        )
822    }
823}
824
825impl Component for CheckboxWithLabel {
826    fn scope() -> ComponentScope {
827        ComponentScope::Input
828    }
829
830    fn description() -> Option<&'static str> {
831        Some("A checkbox component with an attached label")
832    }
833
834    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
835        Some(
836            v_flex()
837                .gap_6()
838                .children(vec![example_group_with_title(
839                    "States",
840                    vec![
841                        single_example(
842                            "Unselected",
843                            CheckboxWithLabel::new(
844                                "checkbox_with_label_unselected",
845                                Label::new("Always save on quit"),
846                                ToggleState::Unselected,
847                                |_, _, _| {},
848                            )
849                            .into_any_element(),
850                        ),
851                        single_example(
852                            "Indeterminate",
853                            CheckboxWithLabel::new(
854                                "checkbox_with_label_indeterminate",
855                                Label::new("Always save on quit"),
856                                ToggleState::Indeterminate,
857                                |_, _, _| {},
858                            )
859                            .into_any_element(),
860                        ),
861                        single_example(
862                            "Selected",
863                            CheckboxWithLabel::new(
864                                "checkbox_with_label_selected",
865                                Label::new("Always save on quit"),
866                                ToggleState::Selected,
867                                |_, _, _| {},
868                            )
869                            .into_any_element(),
870                        ),
871                    ],
872                )])
873                .into_any_element(),
874        )
875    }
876}