toggle.rs

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