diff --git a/crates/story/src/accordion_story.rs b/crates/story/src/accordion_story.rs index 31cf445b..c2a875b5 100644 --- a/crates/story/src/accordion_story.rs +++ b/crates/story/src/accordion_story.rs @@ -4,7 +4,7 @@ use gpui::{ }; use gpui_component::{ accordion::Accordion, - button::{Button, ButtonGroup, ButtonVariants as _}, + button::{Button, ButtonGroup}, checkbox::Checkbox, h_flex, switch::Switch, diff --git a/crates/story/src/button_story.rs b/crates/story/src/button_story.rs index bc489b62..da81cc5c 100644 --- a/crates/story/src/button_story.rs +++ b/crates/story/src/button_story.rs @@ -4,10 +4,9 @@ use gpui::{ }; use gpui_component::{ - black, button::{Button, ButtonCustomVariant, ButtonGroup, ButtonVariants as _, DropdownButton}, checkbox::Checkbox, - green_500, green_600, green_800, green_900, green_950, h_flex, v_flex, white, ActiveTheme, + green_600, green_700, green_800, green_950, h_flex, v_flex, white, ActiveTheme, Disableable as _, Icon, IconName, Selectable as _, Sizable as _, Theme, }; @@ -73,6 +72,33 @@ impl Render for ButtonStory { let compact = self.compact; let toggle_multiple = self.toggle_multiple; + let custom_variant = ButtonCustomVariant::new(cx) + .color(if cx.theme().mode.is_dark() { + green_800() + } else { + green_600() + }) + .foreground(if cx.theme().mode.is_dark() { + white() + } else { + white() + }) + .border(if cx.theme().mode.is_dark() { + green_800() + } else { + green_700() + }) + .hover(if cx.theme().mode.is_dark() { + green_800() + } else { + green_600() + }) + .active(if cx.theme().mode.is_dark() { + green_950() + } else { + green_700() + }); + v_flex() .on_action(cx.listener(|this, _: &Disabled, _, _| this.disabled = !this.disabled)) .on_action(cx.listener(|this, _: &Loading, _, _| this.loading = !this.loading)) @@ -165,16 +191,6 @@ impl Render for ButtonStory { .when(compact, |this| this.compact()) .on_click(Self::on_click), ) - .child( - Button::new("button-5") - .outline() - .label("Outline Button") - .disabled(disabled) - .selected(selected) - .loading(loading) - .when(compact, |this| this.compact()) - .on_click(Self::on_click), - ) .child( Button::new("button-5-ghost") .ghost() @@ -207,34 +223,7 @@ impl Render for ButtonStory { ) .child( Button::new("button-6-custom") - .custom( - ButtonCustomVariant::new(cx) - .color(if cx.theme().mode.is_dark() { - green_900() - } else { - green_500() - }) - .foreground(if cx.theme().mode.is_dark() { - white() - } else { - black() - }) - .border(if cx.theme().mode.is_dark() { - green_800() - } else { - green_600() - }) - .hover(if cx.theme().mode.is_dark() { - green_800() - } else { - green_500() - }) - .active(if cx.theme().mode.is_dark() { - green_950() - } else { - green_600() - }), - ) + .custom(custom_variant) .label("Custom Button") .disabled(disabled) .selected(selected) @@ -328,6 +317,87 @@ impl Render for ButtonStory { ), ), ) + .child( + h_flex().gap_6().child( + section("Outline Button", cx) + .child( + Button::new("button-outline-1") + .primary() + .outline() + .label("Primary Button") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-outline-2") + .outline() + .label("Secondary Button") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-outline-4") + .danger() + .outline() + .label("Danger Button") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-outline-6-custom") + .outline() + .custom(custom_variant) + .label("Custom Button") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-outline-5-ghost") + .ghost() + .outline() + .label("Ghost Button") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-outline-5-link") + .link() + .outline() + .label("Link Button") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ) + .child( + Button::new("button-outline-5-text") + .text() + .outline() + .label("Text Button") + .disabled(disabled) + .selected(selected) + .loading(loading) + .when(compact, |this| this.compact()) + .on_click(Self::on_click), + ), + ), + ) .child( h_flex() .gap_6() diff --git a/crates/story/src/scrollable_story.rs b/crates/story/src/scrollable_story.rs index 83287101..0c81cb66 100644 --- a/crates/story/src/scrollable_story.rs +++ b/crates/story/src/scrollable_story.rs @@ -6,7 +6,7 @@ use gpui::{ ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Styled, Window, }; use gpui_component::{ - button::{Button, ButtonGroup, ButtonVariants as _}, + button::{Button, ButtonGroup}, divider::Divider, gray_100, gray_800, h_flex, label::Label, diff --git a/crates/ui/src/button/button.rs b/crates/ui/src/button/button.rs index 35eb0382..bf4bdda9 100644 --- a/crates/ui/src/button/button.rs +++ b/crates/ui/src/button/button.rs @@ -46,11 +46,6 @@ pub trait ButtonVariants: Sized { self.with_variant(ButtonVariant::Danger) } - /// With the outline style for the Button. - fn outline(self) -> Self { - self.with_variant(ButtonVariant::Outline) - } - /// With the ghost style for the Button. fn ghost(self) -> Self { self.with_variant(ButtonVariant::Ghost) @@ -121,7 +116,6 @@ pub enum ButtonVariant { Primary, Secondary, Danger, - Outline, Ghost, Link, Text, @@ -160,6 +154,7 @@ pub struct Button { pub(crate) selected: bool, variant: ButtonVariant, rounded: ButtonRounded, + outline: bool, border_corners: Corners, border_edges: Edges, size: Size, @@ -196,11 +191,18 @@ impl Button { stop_propagation: true, loading: false, compact: false, + outline: false, children: Vec::new(), loading_icon: None, } } + /// Set the outline style of the Button. + pub fn outline(mut self) -> Self { + self.outline = true; + self + } + /// Set the border radius of the Button. pub fn rounded(mut self, rounded: impl Into) -> Self { self.rounded = rounded.into(); @@ -321,7 +323,7 @@ impl InteractiveElement for Button { impl RenderOnce for Button { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let style: ButtonVariant = self.variant; - let normal_style = style.normal(cx); + let normal_style = style.normal(self.outline, cx); let icon_size = match self.size { Size::Size(v) => Size::Size(v * 0.75), _ => self.size, @@ -382,7 +384,7 @@ impl RenderOnce for Button { .when(self.border_edges.bottom, |this| this.border_b_1()) .text_color(normal_style.fg) .when(self.selected, |this| { - let selected_style = style.selected(cx); + let selected_style = style.selected(self.outline, cx); this.bg(selected_style.bg) .border_color(selected_style.border) .text_color(selected_style.fg) @@ -392,13 +394,13 @@ impl RenderOnce for Button { .bg(normal_style.bg) .when(normal_style.underline, |this| this.text_decoration_1()) .hover(|this| { - let hover_style = style.hovered(cx); + let hover_style = style.hovered(self.outline, cx); this.bg(hover_style.bg) .border_color(hover_style.border) .text_color(crate::red_400()) }) .active(|this| { - let active_style = style.active(cx); + let active_style = style.active(self.outline, cx); this.bg(active_style.bg) .border_color(active_style.border) .text_color(active_style.fg) @@ -420,7 +422,7 @@ impl RenderOnce for Button { }, ) .when(self.disabled, |this| { - let disabled_style = style.disabled(cx); + let disabled_style = style.disabled(self.outline, cx); this.cursor_not_allowed() .bg(disabled_style.bg) .text_color(disabled_style.fg) @@ -471,38 +473,56 @@ struct ButtonVariantStyle { } impl ButtonVariant { - fn bg_color(&self, cx: &mut App) -> Hsla { + fn bg_color(&self, outline: bool, cx: &mut App) -> Hsla { + if outline { + return cx.theme().background; + } + match self { ButtonVariant::Primary => cx.theme().primary, ButtonVariant::Secondary => cx.theme().secondary, ButtonVariant::Danger => cx.theme().danger, - ButtonVariant::Outline - | ButtonVariant::Ghost - | ButtonVariant::Link - | ButtonVariant::Text => cx.theme().transparent, + ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => { + cx.theme().transparent + } ButtonVariant::Custom(colors) => colors.color, } } - fn text_color(&self, cx: &mut App) -> Hsla { + fn text_color(&self, outline: bool, cx: &mut App) -> Hsla { match self { - ButtonVariant::Primary => cx.theme().primary_foreground, - ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => { - cx.theme().secondary_foreground + ButtonVariant::Primary => { + if outline { + cx.theme().primary + } else { + cx.theme().primary_foreground + } + } + ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_foreground, + ButtonVariant::Danger => { + if outline { + cx.theme().danger + } else { + cx.theme().danger_foreground + } } - ButtonVariant::Danger => cx.theme().danger_foreground, ButtonVariant::Link => cx.theme().link, ButtonVariant::Text => cx.theme().foreground, - ButtonVariant::Custom(colors) => colors.foreground, + ButtonVariant::Custom(colors) => { + if outline { + colors.color + } else { + colors.foreground + } + } } } - fn border_color(&self, cx: &mut App) -> Hsla { + fn border_color(&self, _outline: bool, cx: &mut App) -> Hsla { match self { ButtonVariant::Primary => cx.theme().primary, ButtonVariant::Secondary => cx.theme().border, ButtonVariant::Danger => cx.theme().danger, - ButtonVariant::Outline => cx.theme().border, ButtonVariant::Ghost | ButtonVariant::Link | ButtonVariant::Text => { cx.theme().transparent } @@ -517,7 +537,7 @@ impl ButtonVariant { } } - fn shadow(&self, _: &App) -> bool { + fn shadow(&self, _outline: bool, _: &App) -> bool { match self { ButtonVariant::Primary | ButtonVariant::Secondary | ButtonVariant::Danger => true, ButtonVariant::Custom(c) => c.shadow, @@ -525,12 +545,12 @@ impl ButtonVariant { } } - fn normal(&self, cx: &mut App) -> ButtonVariantStyle { - let bg = self.bg_color(cx); - let border = self.border_color(cx); - let fg = self.text_color(cx); + fn normal(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle { + let bg = self.bg_color(outline, cx); + let border = self.border_color(outline, cx); + let fg = self.text_color(outline, cx); let underline = self.underline(cx); - let shadow = self.shadow(cx); + let shadow = self.shadow(outline, cx); ButtonVariantStyle { bg, @@ -541,11 +561,23 @@ impl ButtonVariant { } } - fn hovered(&self, cx: &mut App) -> ButtonVariantStyle { + fn hovered(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle { let bg = match self { - ButtonVariant::Primary => cx.theme().primary_hover, - ButtonVariant::Secondary | ButtonVariant::Outline => cx.theme().secondary_hover, - ButtonVariant::Danger => cx.theme().danger_hover, + ButtonVariant::Primary => { + if outline { + cx.theme().secondary_hover + } else { + cx.theme().primary_hover + } + } + ButtonVariant::Secondary => cx.theme().secondary_hover, + ButtonVariant::Danger => { + if outline { + cx.theme().secondary_hover + } else { + cx.theme().danger_hover + } + } ButtonVariant::Ghost => { if cx.theme().mode.is_dark() { cx.theme().secondary.lighten(0.1).opacity(0.8) @@ -555,15 +587,23 @@ impl ButtonVariant { } ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent, - ButtonVariant::Custom(colors) => colors.hover, + ButtonVariant::Custom(colors) => { + if outline { + cx.theme().secondary_hover + } else { + colors.hover + } + } }; - let border = self.border_color(cx); + + let border = self.border_color(outline, cx); let fg = match self { ButtonVariant::Link => cx.theme().link_hover, - _ => self.text_color(cx), + _ => self.text_color(outline, cx), }; + let underline = self.underline(cx); - let shadow = self.shadow(cx); + let shadow = self.shadow(outline, cx); ButtonVariantStyle { bg, @@ -574,10 +614,16 @@ impl ButtonVariant { } } - fn active(&self, cx: &mut App) -> ButtonVariantStyle { + fn active(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle { let bg = match self { - ButtonVariant::Primary => cx.theme().primary_active, - ButtonVariant::Secondary | ButtonVariant::Outline => cx.theme().secondary_active, + ButtonVariant::Primary => { + if outline { + cx.theme().primary_active.opacity(0.1) + } else { + cx.theme().primary_active + } + } + ButtonVariant::Secondary => cx.theme().secondary_active, ButtonVariant::Ghost => { if cx.theme().mode.is_dark() { cx.theme().secondary.lighten(0.2).opacity(0.8) @@ -585,19 +631,31 @@ impl ButtonVariant { cx.theme().secondary.darken(0.2).opacity(0.8) } } - ButtonVariant::Danger => cx.theme().danger_active, + ButtonVariant::Danger => { + if outline { + cx.theme().danger_active.opacity(0.1) + } else { + cx.theme().danger_active + } + } ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent, - ButtonVariant::Custom(colors) => colors.active, + ButtonVariant::Custom(colors) => { + if outline { + colors.active.opacity(0.1) + } else { + colors.active + } + } }; - let border = self.border_color(cx); + let border = self.border_color(outline, cx); let fg = match self { ButtonVariant::Link => cx.theme().link_active, ButtonVariant::Text => cx.theme().foreground.opacity(0.7), - _ => self.text_color(cx), + _ => self.text_color(outline, cx), }; let underline = self.underline(cx); - let shadow = self.shadow(cx); + let shadow = self.shadow(outline, cx); ButtonVariantStyle { bg, @@ -608,26 +666,24 @@ impl ButtonVariant { } } - fn selected(&self, cx: &mut App) -> ButtonVariantStyle { + fn selected(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle { let bg = match self { ButtonVariant::Primary => cx.theme().primary_active, - ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => { - cx.theme().secondary_active - } + ButtonVariant::Secondary | ButtonVariant::Ghost => cx.theme().secondary_active, ButtonVariant::Danger => cx.theme().danger_active, ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Custom(colors) => colors.active, }; - let border = self.border_color(cx); + let border = self.border_color(outline, cx); let fg = match self { ButtonVariant::Link => cx.theme().link_active, ButtonVariant::Text => cx.theme().foreground.opacity(0.7), - _ => self.text_color(cx), + _ => self.text_color(outline, cx), }; let underline = self.underline(cx); - let shadow = self.shadow(cx); + let shadow = self.shadow(outline, cx); ButtonVariantStyle { bg, @@ -638,12 +694,11 @@ impl ButtonVariant { } } - fn disabled(&self, cx: &mut App) -> ButtonVariantStyle { + fn disabled(&self, outline: bool, cx: &mut App) -> ButtonVariantStyle { let bg = match self { - ButtonVariant::Link - | ButtonVariant::Ghost - | ButtonVariant::Outline - | ButtonVariant::Text => cx.theme().transparent, + ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => { + cx.theme().transparent + } ButtonVariant::Primary => cx.theme().primary.opacity(0.15), ButtonVariant::Danger => cx.theme().danger.opacity(0.15), ButtonVariant::Secondary => cx.theme().secondary.opacity(1.5), @@ -656,9 +711,10 @@ impl ButtonVariant { _ => cx.theme().secondary_foreground.opacity(0.5).grayscale(), }; - let border = match self { - ButtonVariant::Outline => cx.theme().border.opacity(0.5), - _ => bg, + let (bg, border) = if outline { + (cx.theme().transparent, cx.theme().border.opacity(0.5)) + } else { + (bg, bg) }; let underline = self.underline(cx); diff --git a/crates/ui/src/button/button_group.rs b/crates/ui/src/button/button_group.rs index 14e59e1b..ccf0cd4b 100644 --- a/crates/ui/src/button/button_group.rs +++ b/crates/ui/src/button/button_group.rs @@ -19,7 +19,8 @@ pub struct ButtonGroup { pub(super) disabled: bool, // The button props - pub(super) compact: Option, + pub(super) compact: bool, + pub(super) outline: bool, pub(super) variant: Option, pub(super) size: Option, @@ -42,7 +43,8 @@ impl ButtonGroup { id: id.into(), variant: None, size: None, - compact: None, + compact: false, + outline: false, multiple: false, disabled: false, on_click: None, @@ -69,7 +71,13 @@ impl ButtonGroup { /// With the compact mode for the ButtonGroup. pub fn compact(mut self) -> Self { - self.compact = Some(true); + self.compact = true; + self + } + + /// With the outline mode for the ButtonGroup. + pub fn outline(mut self) -> Self { + self.outline = true; self } @@ -193,7 +201,8 @@ impl RenderOnce for ButtonGroup { .stop_propagation(false) .when_some(self.size, |this, size| this.with_size(size)) .when_some(self.variant, |this, variant| this.with_variant(variant)) - .when_some(self.compact, |this, _| this.compact()) + .when(self.compact, |this| this.compact()) + .when(self.outline, |this| this.outline()) .on_click(move |_, _, _| { state.set(Some(child_index)); });