From 0a28800049e2a7e9093e870c5a4e4ad83aa80430 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 8 Nov 2024 22:53:15 -0500 Subject: [PATCH] Add preview for Checkbox with Label (#20448) Add previews for Checkbox with Label. Merge checkbox components. Release Notes: - N/A --- crates/storybook/src/story_selector.rs | 2 - crates/ui/src/components/checkbox.rs | 89 +++++++++++++- crates/ui/src/components/checkbox/checkbox.rs | 114 ------------------ .../checkbox/checkbox_with_label.rs | 51 -------- crates/ui/src/components/stories.rs | 2 - crates/ui/src/components/stories/checkbox.rs | 47 -------- crates/ui/src/traits/component_preview.rs | 4 +- crates/workspace/src/theme_preview.rs | 3 +- 8 files changed, 90 insertions(+), 222 deletions(-) delete mode 100644 crates/ui/src/components/checkbox/checkbox.rs delete mode 100644 crates/ui/src/components/checkbox/checkbox_with_label.rs delete mode 100644 crates/ui/src/components/stories/checkbox.rs diff --git a/crates/storybook/src/story_selector.rs b/crates/storybook/src/story_selector.rs index 3a1c2f5630..fd68fa879c 100644 --- a/crates/storybook/src/story_selector.rs +++ b/crates/storybook/src/story_selector.rs @@ -16,7 +16,6 @@ pub enum ComponentStory { AutoHeightEditor, Avatar, Button, - Checkbox, CollabNotification, ContextMenu, Cursor, @@ -52,7 +51,6 @@ impl ComponentStory { Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(), Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(), Self::Button => cx.new_view(|_| ui::ButtonStory).into(), - Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(), Self::CollabNotification => cx .new_view(|_| collab_ui::notifications::CollabNotificationStory) .into(), diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs index 4245c0dba2..c8113c9373 100644 --- a/crates/ui/src/components/checkbox.rs +++ b/crates/ui/src/components/checkbox.rs @@ -1,7 +1,4 @@ #![allow(missing_docs)] -mod checkbox_with_label; - -pub use checkbox_with_label::*; use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; @@ -163,3 +160,89 @@ impl ComponentPreview for Checkbox { ] } } + +use std::sync::Arc; + +/// A [`Checkbox`] that has a [`Label`]. +#[derive(IntoElement)] +pub struct CheckboxWithLabel { + id: ElementId, + label: Label, + checked: Selection, + on_click: Arc, +} + +impl CheckboxWithLabel { + pub fn new( + id: impl Into, + label: Label, + checked: Selection, + on_click: impl Fn(&Selection, &mut WindowContext) + 'static, + ) -> Self { + Self { + id: id.into(), + label, + checked, + on_click: Arc::new(on_click), + } + } +} + +impl RenderOnce for CheckboxWithLabel { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + h_flex() + .gap(Spacing::Large.rems(cx)) + .child(Checkbox::new(self.id.clone(), self.checked).on_click({ + let on_click = self.on_click.clone(); + move |checked, cx| { + (on_click)(checked, cx); + } + })) + .child( + div() + .id(SharedString::from(format!("{}-label", self.id))) + .on_click(move |_event, cx| { + (self.on_click)(&self.checked.inverse(), cx); + }) + .child(self.label), + ) + } +} + +impl ComponentPreview for CheckboxWithLabel { + fn description() -> impl Into> { + "A checkbox with an associated label, allowing users to select an option while providing a descriptive text." + } + + fn examples() -> Vec> { + vec![example_group(vec![ + single_example( + "Unselected", + CheckboxWithLabel::new( + "checkbox_with_label_unselected", + Label::new("Always save on quit"), + Selection::Unselected, + |_, _| {}, + ), + ), + single_example( + "Indeterminate", + CheckboxWithLabel::new( + "checkbox_with_label_indeterminate", + Label::new("Always save on quit"), + Selection::Indeterminate, + |_, _| {}, + ), + ), + single_example( + "Selected", + CheckboxWithLabel::new( + "checkbox_with_label_selected", + Label::new("Always save on quit"), + Selection::Selected, + |_, _| {}, + ), + ), + ])] + } +} diff --git a/crates/ui/src/components/checkbox/checkbox.rs b/crates/ui/src/components/checkbox/checkbox.rs deleted file mode 100644 index 472bfb23f1..0000000000 --- a/crates/ui/src/components/checkbox/checkbox.rs +++ /dev/null @@ -1,114 +0,0 @@ -#![allow(missing_docs)] - -use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext}; - -use crate::prelude::*; -use crate::{Color, Icon, IconName, Selection}; - -/// # Checkbox -/// -/// Checkboxes are used for multiple choices, not for mutually exclusive choices. -/// Each checkbox works independently from other checkboxes in the list, -/// therefore checking an additional box does not affect any other selections. -#[derive(IntoElement)] -pub struct Checkbox { - id: ElementId, - checked: Selection, - disabled: bool, - on_click: Option>, -} - -impl Checkbox { - pub fn new(id: impl Into, checked: Selection) -> Self { - Self { - id: id.into(), - checked, - disabled: false, - on_click: None, - } - } - - pub fn disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } - - pub fn on_click(mut self, handler: impl Fn(&Selection, &mut WindowContext) + 'static) -> Self { - self.on_click = Some(Box::new(handler)); - self - } -} - -impl RenderOnce for Checkbox { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - let group_id = format!("checkbox_group_{:?}", self.id); - - let icon = match self.checked { - Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color( - if self.disabled { - Color::Disabled - } else { - Color::Selected - }, - )), - Selection::Indeterminate => Some( - Icon::new(IconName::Dash) - .size(IconSize::Small) - .color(if self.disabled { - Color::Disabled - } else { - Color::Selected - }), - ), - Selection::Unselected => None, - }; - - let selected = - self.checked == Selection::Selected || self.checked == Selection::Indeterminate; - - let (bg_color, border_color) = match (self.disabled, selected) { - (true, _) => ( - cx.theme().colors().ghost_element_disabled, - cx.theme().colors().border_disabled, - ), - (false, true) => ( - cx.theme().colors().element_selected, - cx.theme().colors().border, - ), - (false, false) => ( - cx.theme().colors().element_background, - cx.theme().colors().border, - ), - }; - - h_flex() - .id(self.id) - .justify_center() - .items_center() - .size(crate::styles::custom_spacing(cx, 20.)) - .group(group_id.clone()) - .child( - div() - .flex() - .flex_none() - .justify_center() - .items_center() - .m(Spacing::Small.px(cx)) - .size(crate::styles::custom_spacing(cx, 16.)) - .rounded_sm() - .bg(bg_color) - .border_1() - .border_color(border_color) - .when(!self.disabled, |this| { - this.group_hover(group_id.clone(), |el| { - el.bg(cx.theme().colors().element_hover) - }) - }) - .children(icon), - ) - .when_some( - self.on_click.filter(|_| !self.disabled), - |this, on_click| this.on_click(move |_, cx| on_click(&self.checked.inverse(), cx)), - ) - } -} diff --git a/crates/ui/src/components/checkbox/checkbox_with_label.rs b/crates/ui/src/components/checkbox/checkbox_with_label.rs deleted file mode 100644 index 2b92e47938..0000000000 --- a/crates/ui/src/components/checkbox/checkbox_with_label.rs +++ /dev/null @@ -1,51 +0,0 @@ -#![allow(missing_docs)] - -use std::sync::Arc; - -use crate::{prelude::*, Checkbox}; - -/// A [`Checkbox`] that has a [`Label`]. -#[derive(IntoElement)] -pub struct CheckboxWithLabel { - id: ElementId, - label: Label, - checked: Selection, - on_click: Arc, -} - -impl CheckboxWithLabel { - pub fn new( - id: impl Into, - label: Label, - checked: Selection, - on_click: impl Fn(&Selection, &mut WindowContext) + 'static, - ) -> Self { - Self { - id: id.into(), - label, - checked, - on_click: Arc::new(on_click), - } - } -} - -impl RenderOnce for CheckboxWithLabel { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - h_flex() - .gap(Spacing::Large.rems(cx)) - .child(Checkbox::new(self.id.clone(), self.checked).on_click({ - let on_click = self.on_click.clone(); - move |checked, cx| { - (on_click)(checked, cx); - } - })) - .child( - div() - .id(SharedString::from(format!("{}-label", self.id))) - .on_click(move |_event, cx| { - (self.on_click)(&self.checked.inverse(), cx); - }) - .child(self.label), - ) - } -} diff --git a/crates/ui/src/components/stories.rs b/crates/ui/src/components/stories.rs index 88325f2201..b55aa064f9 100644 --- a/crates/ui/src/components/stories.rs +++ b/crates/ui/src/components/stories.rs @@ -3,7 +3,6 @@ #![allow(missing_docs)] mod avatar; mod button; -mod checkbox; mod context_menu; mod disclosure; mod icon; @@ -20,7 +19,6 @@ mod tool_strip; pub use avatar::*; pub use button::*; -pub use checkbox::*; pub use context_menu::*; pub use disclosure::*; pub use icon::*; diff --git a/crates/ui/src/components/stories/checkbox.rs b/crates/ui/src/components/stories/checkbox.rs deleted file mode 100644 index e1b698aee4..0000000000 --- a/crates/ui/src/components/stories/checkbox.rs +++ /dev/null @@ -1,47 +0,0 @@ -use gpui::{Render, ViewContext}; -use story::Story; - -use crate::prelude::*; -use crate::{h_flex, Checkbox}; - -pub struct CheckboxStory; - -impl Render for CheckboxStory { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - Story::container() - .child(Story::title_for::()) - .child(Story::label("Default")) - .child( - h_flex() - .p_2() - .gap_2() - .rounded_md() - .border_1() - .border_color(cx.theme().colors().border) - .child(Checkbox::new("checkbox-enabled", Selection::Unselected)) - .child(Checkbox::new( - "checkbox-intermediate", - Selection::Indeterminate, - )) - .child(Checkbox::new("checkbox-selected", Selection::Selected)), - ) - .child(Story::label("Disabled")) - .child( - h_flex() - .p_2() - .gap_2() - .rounded_md() - .border_1() - .border_color(cx.theme().colors().border) - .child(Checkbox::new("checkbox-disabled", Selection::Unselected).disabled(true)) - .child( - Checkbox::new("checkbox-disabled-intermediate", Selection::Indeterminate) - .disabled(true), - ) - .child( - Checkbox::new("checkbox-disabled-selected", Selection::Selected) - .disabled(true), - ), - ) - } -} diff --git a/crates/ui/src/traits/component_preview.rs b/crates/ui/src/traits/component_preview.rs index 12408ff49e..1fece0804a 100644 --- a/crates/ui/src/traits/component_preview.rs +++ b/crates/ui/src/traits/component_preview.rs @@ -9,9 +9,9 @@ pub enum ExampleLabelSide { Left, /// Right side Right, + #[default] /// Top side Top, - #[default] /// Bottom side Bottom, } @@ -81,7 +81,7 @@ pub trait ComponentPreview: IntoElement { v_flex() .gap_2() .when_some(group.title, |this, title| { - this.child(Headline::new(title).size(HeadlineSize::Small)) + this.child(Label::new(title).size(LabelSize::Small)) }) .child( h_flex() diff --git a/crates/workspace/src/theme_preview.rs b/crates/workspace/src/theme_preview.rs index f7f3c9d34e..4788842d4f 100644 --- a/crates/workspace/src/theme_preview.rs +++ b/crates/workspace/src/theme_preview.rs @@ -5,7 +5,7 @@ use theme::all_theme_colors; use ui::{ element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus, Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, - Checkbox, ElevationIndex, Facepile, Indicator, Table, TintColor, Tooltip, + Checkbox, CheckboxWithLabel, ElevationIndex, Facepile, Indicator, Table, TintColor, Tooltip, }; use crate::{Item, Workspace}; @@ -510,6 +510,7 @@ impl ThemePreview { .size_full() .gap_2() .child(Checkbox::render_component_previews(cx)) + .child(CheckboxWithLabel::render_component_previews(cx)) .child(Facepile::render_component_previews(cx)) .child(Button::render_component_previews(cx)) .child(Indicator::render_component_previews(cx))