diff --git a/crates/story/src/group_box_story.rs b/crates/story/src/group_box_story.rs index 0a6a7cf5..21d8db2a 100644 --- a/crates/story/src/group_box_story.rs +++ b/crates/story/src/group_box_story.rs @@ -7,7 +7,9 @@ use gpui_component::{ button::{Button, ButtonVariants}, checkbox::Checkbox, group_box::GroupBox, + h_flex, radio::{Radio, RadioGroup}, + switch::Switch, text::TextView, v_flex, ActiveTheme as _, StyledExt, }; @@ -24,7 +26,8 @@ impl super::Story for GroupBoxStory { } fn description() -> &'static str { - "A styled container element that with an optional title to groups related content together." + "A styled container element that with an optional title \ + to groups related content together." } fn new_view(window: &mut Window, cx: &mut App) -> Entity { @@ -58,21 +61,57 @@ impl Render for GroupBoxStory { .justify_center() .gap_4() .child( - section("A simple GroupBox").w_128().child( + section("Default Style").w_128().child( GroupBox::new() - .title(Checkbox::new("all").label("Subscribe All")) + .child("Subscriptions") + .child(Checkbox::new("all").label("All")) .child(Checkbox::new("news-letter").label("News Letter")) .child(Checkbox::new("account-activity").label("Account Activity")) - .child(Button::new("ok").primary().label("Send Mail")), + .child(Button::new("ok").primary().label("Update Subscriptions")), ), ) .child( - section("Outline style").w_128().child( - GroupBox::new().outline().title("Appearance").child( - RadioGroup::vertical("theme") - .child(Radio::new("light").label("Light")) - .child(Radio::new("dark").label("Dark")) - .child(Radio::new("system").label("System")), + section("Fill Style").w_128().child( + GroupBox::new() + .id("activity") + .fill() + .title("Contributions & activity") + .child( + h_flex() + .justify_between() + .child("Make profile private and hide activity") + .child(Switch::new("toggle-0").checked(true)), + ) + .child( + h_flex() + .justify_between() + .child("Include private contributions on my profile") + .child(Switch::new("toggle-1").checked(false)), + ) + .child(Button::new("btn-1").primary().label("Save")), + ), + ) + .child( + section("Outline Style").w_128().child( + GroupBox::new() + .id("appearance") + .outline() + .title("Appearance") + .child( + RadioGroup::vertical("theme") + .child(Radio::new("light").label("Light")) + .child(Radio::new("dark").label("Dark")) + .child(Radio::new("system").label("System")), + ), + ), + ) + .child( + section("Without Title").w_128().child( + GroupBox::new().outline().child( + h_flex() + .justify_between() + .child("Make profile private and hide activity") + .child(Switch::new("toggle-1").checked(true)), ), ), ) @@ -84,16 +123,23 @@ impl Render for GroupBoxStory { .rounded_xl() .p_5() .title("This is a custom style") - .title_style(StyleRefinement::default().font_semibold().line_height(relative(1.0)).px_3()) + .title_style( + StyleRefinement::default() + .font_semibold() + .line_height(relative(1.0)) + .px_3(), + ) .content_style( - StyleRefinement::default().rounded_xl() + StyleRefinement::default() + .rounded_xl() .py_3() .px_4() - .border_2() + .border_2(), ) .child(TextView::markdown( "custom-style", - "You can use `title_style` to customize the style of the title. \n \ + "You can use `title_style` to customize the style \ + of the title. \n \ And any style in `GroupBox` will apply to the content container.", )), ), diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index f1c4e2c6..93615113 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -408,7 +408,6 @@ impl RenderOnce for StorySection { .gap_4() .child(self.title), ) - .title_style(StyleRefinement::default().px_2()) .content_style( StyleRefinement::default() .rounded_lg() diff --git a/crates/ui/src/group_box.rs b/crates/ui/src/group_box.rs index d4986101..b293b4e4 100644 --- a/crates/ui/src/group_box.rs +++ b/crates/ui/src/group_box.rs @@ -1,35 +1,71 @@ use gpui::{ - div, prelude::FluentBuilder, relative, AnyElement, App, IntoElement, ParentElement, RenderOnce, - StyleRefinement, Styled, Window, + div, prelude::FluentBuilder, relative, AnyElement, App, ElementId, InteractiveElement as _, + IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window, }; use smallvec::SmallVec; use crate::{v_flex, ActiveTheme, StyledExt as _}; +#[derive(Debug, Clone, Default, Copy, PartialEq, Eq, Hash)] +pub enum GroupBoxVariant { + #[default] + Normal, + Fill, + Outline, +} + /// GroupBox is a styled container element that with /// an optional title to groups related content together. #[derive(IntoElement)] pub struct GroupBox { + id: Option, + variant: GroupBoxVariant, style: StyleRefinement, title_style: StyleRefinement, title: Option, content_style: StyleRefinement, - outline: bool, children: SmallVec<[AnyElement; 1]>, } impl GroupBox { pub fn new() -> Self { Self { + id: None, + variant: GroupBoxVariant::default(), style: StyleRefinement::default(), title_style: StyleRefinement::default(), content_style: StyleRefinement::default(), title: None, - outline: false, children: SmallVec::new(), } } + /// Set the variant of the group box. + pub fn variant(mut self, variant: GroupBoxVariant) -> Self { + self.variant = variant; + self + } + + /// Set to use Fill variant. + pub fn fill(mut self) -> Self { + self.variant = GroupBoxVariant::Fill; + self + } + + /// Set use outline style of the group box. + /// + /// If true, the group box will have a border around it, and no background color. + pub fn outline(mut self) -> Self { + self.variant = GroupBoxVariant::Outline; + self + } + + /// Set the id of the group box, default is None. + pub fn id(mut self, id: impl Into) -> Self { + self.id = Some(id.into()); + self + } + /// Set the title of the group box, default is None. pub fn title(mut self, title: impl IntoElement) -> Self { self.title = Some(title.into_any_element()); @@ -47,14 +83,6 @@ impl GroupBox { self.content_style = style; self } - - /// Set use outline style of the group box, default is false. - /// - /// If true, the group box will have a border around it, and no background color. - pub fn outline(mut self) -> Self { - self.outline = true; - self - } } impl ParentElement for GroupBox { @@ -71,15 +99,22 @@ impl Styled for GroupBox { impl RenderOnce for GroupBox { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let (bg, border, has_paddings) = match self.variant { + GroupBoxVariant::Normal => (None, None, false), + GroupBoxVariant::Fill => (Some(cx.theme().group_box), None, true), + GroupBoxVariant::Outline => (None, Some(cx.theme().border), true), + }; + v_flex() + .id(self.id.unwrap_or("group-box".into())) .size_full() - .gap_2() + .when(has_paddings, |this| this.gap_3()) + .when(!has_paddings, |this| this.gap_4()) .refine_style(&self.style) .when_some(self.title, |this, title| { this.child( div() - .px_4() - .text_color(cx.theme().group_box_foreground) + .text_color(cx.theme().muted_foreground) .line_height(relative(1.)) .refine_style(&self.title_style) .child(title), @@ -87,13 +122,11 @@ impl RenderOnce for GroupBox { }) .child( v_flex() - .when(!self.outline, |this| this.bg(cx.theme().group_box)) - .when(self.outline, |this| { - this.border_color(cx.theme().border).border_1() - }) + .when_some(bg, |this, bg| this.bg(bg)) + .when_some(border, |this, border| this.border_color(border).border_1()) .text_color(cx.theme().group_box_foreground) - .p_4() - .gap_3() + .when(has_paddings, |this| this.p_4()) + .gap_4() .rounded(cx.theme().radius) .refine_style(&self.content_style) .children(self.children),