ui: Remove the `ToggleButton` component (#43115)

Danilo Leal created

This PR removes the old `ToggleButton` component, replacing it with the
newer `ToggleButtonGroup` component in the couple of places that used to
use it. Ended up also adding a few more methods to the newer toggle
button group so the UI for the extensions page and the debugger main
picker didn't get visually impacted much. Then, as I was already in the
extensions page, decided to bake in some reasonably small UI
improvements to it as well.

Release Notes:

- N/A

Change summary

crates/debugger_ui/src/new_process_modal.rs       | 131 +++---
crates/extensions_ui/src/extensions_ui.rs         | 151 ++++---
crates/storybook/src/story_selector.rs            |   2 
crates/ui/src/components/button/toggle_button.rs  | 327 +---------------
crates/ui/src/components/stories.rs               |   2 
crates/ui/src/components/stories/toggle_button.rs |  93 ----
6 files changed, 174 insertions(+), 532 deletions(-)

Detailed changes

crates/debugger_ui/src/new_process_modal.rs 🔗

@@ -25,12 +25,9 @@ use settings::Settings;
 use task::{DebugScenario, RevealTarget, VariableName, ZedDebugConfig};
 use theme::ThemeSettings;
 use ui::{
-    ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context,
-    ContextMenu, Disableable, DropdownMenu, FluentBuilder, Icon, IconName, IconSize,
-    IconWithIndicator, Indicator, InteractiveElement, IntoElement, KeyBinding, Label,
-    LabelCommon as _, LabelSize, ListItem, ListItemSpacing, ParentElement, RenderOnce,
-    SharedString, Styled, StyledExt, ToggleButton, ToggleState, Toggleable, Tooltip, Window, div,
-    h_flex, relative, rems, v_flex,
+    CheckboxWithLabel, ContextMenu, DropdownMenu, FluentBuilder, IconWithIndicator, Indicator,
+    KeyBinding, ListItem, ListItemSpacing, ToggleButtonGroup, ToggleButtonSimple, ToggleState,
+    Tooltip, prelude::*,
 };
 use util::{ResultExt, rel_path::RelPath, shell::ShellKind};
 use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr, pane};
@@ -620,72 +617,64 @@ impl Render for NewProcessModal {
                     .border_b_1()
                     .border_color(cx.theme().colors().border_variant)
                     .child(
-                        ToggleButton::new(
-                            "debugger-session-ui-tasks-button",
-                            NewProcessMode::Task.to_string(),
-                        )
-                        .size(ButtonSize::Default)
-                        .toggle_state(matches!(self.mode, NewProcessMode::Task))
-                        .style(ui::ButtonStyle::Subtle)
-                        .on_click(cx.listener(|this, _, window, cx| {
-                            this.mode = NewProcessMode::Task;
-                            this.mode_focus_handle(cx).focus(window);
-                            cx.notify();
-                        }))
-                        .tooltip(Tooltip::text("Run predefined task"))
-                        .first(),
-                    )
-                    .child(
-                        ToggleButton::new(
-                            "debugger-session-ui-launch-button",
-                            NewProcessMode::Debug.to_string(),
-                        )
-                        .size(ButtonSize::Default)
-                        .style(ui::ButtonStyle::Subtle)
-                        .toggle_state(matches!(self.mode, NewProcessMode::Debug))
-                        .on_click(cx.listener(|this, _, window, cx| {
-                            this.mode = NewProcessMode::Debug;
-                            this.mode_focus_handle(cx).focus(window);
-                            cx.notify();
-                        }))
-                        .tooltip(Tooltip::text("Start a predefined debug scenario"))
-                        .middle(),
-                    )
-                    .child(
-                        ToggleButton::new(
-                            "debugger-session-ui-attach-button",
-                            NewProcessMode::Attach.to_string(),
-                        )
-                        .size(ButtonSize::Default)
-                        .toggle_state(matches!(self.mode, NewProcessMode::Attach))
-                        .style(ui::ButtonStyle::Subtle)
-                        .on_click(cx.listener(|this, _, window, cx| {
-                            this.mode = NewProcessMode::Attach;
-
-                            if let Some(debugger) = this.debugger.as_ref() {
-                                Self::update_attach_picker(&this.attach_mode, debugger, window, cx);
-                            }
-                            this.mode_focus_handle(cx).focus(window);
-                            cx.notify();
-                        }))
-                        .tooltip(Tooltip::text("Attach the debugger to a running process"))
-                        .middle(),
-                    )
-                    .child(
-                        ToggleButton::new(
-                            "debugger-session-ui-custom-button",
-                            NewProcessMode::Launch.to_string(),
+                        ToggleButtonGroup::single_row(
+                            "debugger-mode-buttons",
+                            [
+                                ToggleButtonSimple::new(
+                                    NewProcessMode::Task.to_string(),
+                                    cx.listener(|this, _, window, cx| {
+                                        this.mode = NewProcessMode::Task;
+                                        this.mode_focus_handle(cx).focus(window);
+                                        cx.notify();
+                                    }),
+                                )
+                                .tooltip(Tooltip::text("Run predefined task")),
+                                ToggleButtonSimple::new(
+                                    NewProcessMode::Debug.to_string(),
+                                    cx.listener(|this, _, window, cx| {
+                                        this.mode = NewProcessMode::Debug;
+                                        this.mode_focus_handle(cx).focus(window);
+                                        cx.notify();
+                                    }),
+                                )
+                                .tooltip(Tooltip::text("Start a predefined debug scenario")),
+                                ToggleButtonSimple::new(
+                                    NewProcessMode::Attach.to_string(),
+                                    cx.listener(|this, _, window, cx| {
+                                        this.mode = NewProcessMode::Attach;
+
+                                        if let Some(debugger) = this.debugger.as_ref() {
+                                            Self::update_attach_picker(
+                                                &this.attach_mode,
+                                                debugger,
+                                                window,
+                                                cx,
+                                            );
+                                        }
+                                        this.mode_focus_handle(cx).focus(window);
+                                        cx.notify();
+                                    }),
+                                )
+                                .tooltip(Tooltip::text("Attach the debugger to a running process")),
+                                ToggleButtonSimple::new(
+                                    NewProcessMode::Launch.to_string(),
+                                    cx.listener(|this, _, window, cx| {
+                                        this.mode = NewProcessMode::Launch;
+                                        this.mode_focus_handle(cx).focus(window);
+                                        cx.notify();
+                                    }),
+                                )
+                                .tooltip(Tooltip::text("Launch a new process with a debugger")),
+                            ],
                         )
-                        .size(ButtonSize::Default)
-                        .toggle_state(matches!(self.mode, NewProcessMode::Launch))
-                        .style(ui::ButtonStyle::Subtle)
-                        .on_click(cx.listener(|this, _, window, cx| {
-                            this.mode = NewProcessMode::Launch;
-                            this.mode_focus_handle(cx).focus(window);
-                            cx.notify();
-                        }))
-                        .tooltip(Tooltip::text("Launch a new process with a debugger"))
-                        .last(),
+                        .label_size(LabelSize::Default)
+                        .auto_width()
+                        .selected_index(match self.mode {
+                            NewProcessMode::Task => 0,
+                            NewProcessMode::Debug => 1,
+                            NewProcessMode::Attach => 2,
+                            NewProcessMode::Launch => 3,
+                        }),
                     ),
             )
             .child(v_flex().child(self.render_mode(window, cx)))

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -24,8 +24,9 @@ use settings::{Settings, SettingsContent};
 use strum::IntoEnumIterator as _;
 use theme::ThemeSettings;
 use ui::{
-    Banner, Chip, ContextMenu, Divider, PopoverMenu, ScrollableHandle, Switch, ToggleButton,
-    Tooltip, WithScrollbar, prelude::*,
+    Banner, Chip, ContextMenu, Divider, PopoverMenu, ScrollableHandle, Switch, ToggleButtonGroup,
+    ToggleButtonGroupSize, ToggleButtonGroupStyle, ToggleButtonSimple, Tooltip, WithScrollbar,
+    prelude::*,
 };
 use vim_mode_setting::VimModeSetting;
 use workspace::{
@@ -805,37 +806,47 @@ impl ExtensionsPage {
             )
             .child(
                 h_flex()
-                    .gap_1()
                     .justify_between()
                     .child(
-                        Icon::new(IconName::Person)
-                            .size(IconSize::XSmall)
-                            .color(Color::Muted),
-                    )
-                    .child(
-                        Label::new(extension.manifest.authors.join(", "))
-                            .size(LabelSize::Small)
-                            .color(Color::Muted)
-                            .truncate(),
+                        h_flex()
+                            .gap_1()
+                            .child(
+                                Icon::new(IconName::Person)
+                                    .size(IconSize::XSmall)
+                                    .color(Color::Muted),
+                            )
+                            .child(
+                                Label::new(extension.manifest.authors.join(", "))
+                                    .size(LabelSize::Small)
+                                    .color(Color::Muted)
+                                    .truncate(),
+                            ),
                     )
                     .child(
                         h_flex()
-                            .ml_auto()
                             .gap_1()
-                            .child(
+                            .child({
+                                let repo_url_for_tooltip = repository_url.clone();
+
                                 IconButton::new(
                                     SharedString::from(format!("repository-{}", extension.id)),
                                     IconName::Github,
                                 )
                                 .icon_size(IconSize::Small)
-                                .on_click(cx.listener({
-                                    let repository_url = repository_url.clone();
+                                .tooltip(move |_, cx| {
+                                    Tooltip::with_meta(
+                                        "Visit Extension Repository",
+                                        None,
+                                        repo_url_for_tooltip.clone(),
+                                        cx,
+                                    )
+                                })
+                                .on_click(cx.listener(
                                     move |_, _, _, cx| {
                                         cx.open_url(&repository_url);
-                                    }
-                                }))
-                                .tooltip(Tooltip::text(repository_url)),
-                            )
+                                    },
+                                ))
+                            })
                             .child(
                                 PopoverMenu::new(SharedString::from(format!(
                                     "more-{}",
@@ -1136,15 +1147,14 @@ impl ExtensionsPage {
         h_flex()
             .key_context(key_context)
             .h_8()
-            .flex_1()
             .min_w(rems_from_px(384.))
+            .flex_1()
             .pl_1p5()
             .pr_2()
-            .py_1()
             .gap_2()
             .border_1()
             .border_color(editor_border)
-            .rounded_lg()
+            .rounded_md()
             .child(Icon::new(IconName::MagnifyingGlass).color(Color::Muted))
             .child(self.render_text_input(&self.query_editor, cx))
     }
@@ -1544,13 +1554,13 @@ impl Render for ExtensionsPage {
                     .child(
                         h_flex()
                             .w_full()
-                            .gap_2()
+                            .gap_1p5()
                             .justify_between()
                             .child(Headline::new("Extensions").size(HeadlineSize::XLarge))
                             .child(
                                 Button::new("install-dev-extension", "Install Dev Extension")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
+                                    .style(ButtonStyle::Outlined)
+                                    .size(ButtonSize::Medium)
                                     .on_click(|_event, window, cx| {
                                         window.dispatch_action(Box::new(InstallDevExtension), cx)
                                     }),
@@ -1559,58 +1569,51 @@ impl Render for ExtensionsPage {
                     .child(
                         h_flex()
                             .w_full()
-                            .gap_4()
                             .flex_wrap()
+                            .gap_2()
                             .child(self.render_search(cx))
                             .child(
-                                h_flex()
-                                    .child(
-                                        ToggleButton::new("filter-all", "All")
-                                            .style(ButtonStyle::Filled)
-                                            .size(ButtonSize::Large)
-                                            .toggle_state(self.filter == ExtensionFilter::All)
-                                            .on_click(cx.listener(|this, _event, _, cx| {
-                                                this.filter = ExtensionFilter::All;
-                                                this.filter_extension_entries(cx);
-                                                this.scroll_to_top(cx);
-                                            }))
-                                            .tooltip(move |_, cx| {
-                                                Tooltip::simple("Show all extensions", cx)
-                                            })
-                                            .first(),
-                                    )
-                                    .child(
-                                        ToggleButton::new("filter-installed", "Installed")
-                                            .style(ButtonStyle::Filled)
-                                            .size(ButtonSize::Large)
-                                            .toggle_state(self.filter == ExtensionFilter::Installed)
-                                            .on_click(cx.listener(|this, _event, _, cx| {
-                                                this.filter = ExtensionFilter::Installed;
-                                                this.filter_extension_entries(cx);
-                                                this.scroll_to_top(cx);
-                                            }))
-                                            .tooltip(move |_, cx| {
-                                                Tooltip::simple("Show installed extensions", cx)
-                                            })
-                                            .middle(),
+                                div().child(
+                                    ToggleButtonGroup::single_row(
+                                        "filter-buttons",
+                                        [
+                                            ToggleButtonSimple::new(
+                                                "All",
+                                                cx.listener(|this, _event, _, cx| {
+                                                    this.filter = ExtensionFilter::All;
+                                                    this.filter_extension_entries(cx);
+                                                    this.scroll_to_top(cx);
+                                                }),
+                                            ),
+                                            ToggleButtonSimple::new(
+                                                "Installed",
+                                                cx.listener(|this, _event, _, cx| {
+                                                    this.filter = ExtensionFilter::Installed;
+                                                    this.filter_extension_entries(cx);
+                                                    this.scroll_to_top(cx);
+                                                }),
+                                            ),
+                                            ToggleButtonSimple::new(
+                                                "Not Installed",
+                                                cx.listener(|this, _event, _, cx| {
+                                                    this.filter = ExtensionFilter::NotInstalled;
+                                                    this.filter_extension_entries(cx);
+                                                    this.scroll_to_top(cx);
+                                                }),
+                                            ),
+                                        ],
                                     )
-                                    .child(
-                                        ToggleButton::new("filter-not-installed", "Not Installed")
-                                            .style(ButtonStyle::Filled)
-                                            .size(ButtonSize::Large)
-                                            .toggle_state(
-                                                self.filter == ExtensionFilter::NotInstalled,
-                                            )
-                                            .on_click(cx.listener(|this, _event, _, cx| {
-                                                this.filter = ExtensionFilter::NotInstalled;
-                                                this.filter_extension_entries(cx);
-                                                this.scroll_to_top(cx);
-                                            }))
-                                            .tooltip(move |_, cx| {
-                                                Tooltip::simple("Show not installed extensions", cx)
-                                            })
-                                            .last(),
-                                    ),
+                                    .style(ToggleButtonGroupStyle::Outlined)
+                                    .size(ToggleButtonGroupSize::Custom(rems_from_px(30.))) // Perfectly matches the input
+                                    .label_size(LabelSize::Default)
+                                    .auto_width()
+                                    .selected_index(match self.filter {
+                                        ExtensionFilter::All => 0,
+                                        ExtensionFilter::Installed => 1,
+                                        ExtensionFilter::NotInstalled => 2,
+                                    })
+                                    .into_any_element(),
+                                ),
                             ),
                     ),
             )

crates/storybook/src/story_selector.rs 🔗

@@ -28,7 +28,6 @@ pub enum ComponentStory {
     Tab,
     TabBar,
     Text,
-    ToggleButton,
     ViewportUnits,
     WithRemSize,
     IndentGuides,
@@ -58,7 +57,6 @@ impl ComponentStory {
             Self::Tab => cx.new(|_| ui::TabStory).into(),
             Self::TabBar => cx.new(|_| ui::TabBarStory).into(),
             Self::Text => TextStory::model(cx).into(),
-            Self::ToggleButton => cx.new(|_| ui::ToggleButtonStory).into(),
             Self::ViewportUnits => cx.new(|_| crate::stories::ViewportUnitsStory).into(),
             Self::WithRemSize => cx.new(|_| crate::stories::WithRemSizeStory).into(),
             Self::IndentGuides => crate::stories::IndentGuidesStory::model(window, cx).into(),

crates/ui/src/components/button/toggle_button.rs 🔗

@@ -2,7 +2,7 @@ use std::rc::Rc;
 
 use gpui::{AnyView, ClickEvent, relative};
 
-use crate::{ButtonLike, ButtonLikeRounding, ElevationIndex, TintColor, Tooltip, prelude::*};
+use crate::{ButtonLike, ButtonLikeRounding, TintColor, Tooltip, prelude::*};
 
 /// The position of a [`ToggleButton`] within a group of buttons.
 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -43,290 +43,6 @@ impl ToggleButtonPosition {
     }
 }
 
-#[derive(IntoElement, RegisterComponent)]
-pub struct ToggleButton {
-    base: ButtonLike,
-    position_in_group: Option<ToggleButtonPosition>,
-    label: SharedString,
-    label_color: Option<Color>,
-}
-
-impl ToggleButton {
-    pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
-        Self {
-            base: ButtonLike::new(id),
-            position_in_group: None,
-            label: label.into(),
-            label_color: None,
-        }
-    }
-
-    pub fn color(mut self, label_color: impl Into<Option<Color>>) -> Self {
-        self.label_color = label_color.into();
-        self
-    }
-
-    pub fn position_in_group(mut self, position: ToggleButtonPosition) -> Self {
-        self.position_in_group = Some(position);
-        self
-    }
-
-    pub fn first(self) -> Self {
-        self.position_in_group(ToggleButtonPosition::HORIZONTAL_FIRST)
-    }
-
-    pub fn middle(self) -> Self {
-        self.position_in_group(ToggleButtonPosition::HORIZONTAL_MIDDLE)
-    }
-
-    pub fn last(self) -> Self {
-        self.position_in_group(ToggleButtonPosition::HORIZONTAL_LAST)
-    }
-}
-
-impl Toggleable for ToggleButton {
-    fn toggle_state(mut self, selected: bool) -> Self {
-        self.base = self.base.toggle_state(selected);
-        self
-    }
-}
-
-impl SelectableButton for ToggleButton {
-    fn selected_style(mut self, style: ButtonStyle) -> Self {
-        self.base.selected_style = Some(style);
-        self
-    }
-}
-
-impl FixedWidth for ToggleButton {
-    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
-        self.base.width = Some(width.into());
-        self
-    }
-
-    fn full_width(mut self) -> Self {
-        self.base.width = Some(relative(1.));
-        self
-    }
-}
-
-impl Disableable for ToggleButton {
-    fn disabled(mut self, disabled: bool) -> Self {
-        self.base = self.base.disabled(disabled);
-        self
-    }
-}
-
-impl Clickable for ToggleButton {
-    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
-        self.base = self.base.on_click(handler);
-        self
-    }
-
-    fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self {
-        self.base = self.base.cursor_style(cursor_style);
-        self
-    }
-}
-
-impl ButtonCommon for ToggleButton {
-    fn id(&self) -> &ElementId {
-        self.base.id()
-    }
-
-    fn style(mut self, style: ButtonStyle) -> Self {
-        self.base = self.base.style(style);
-        self
-    }
-
-    fn size(mut self, size: ButtonSize) -> Self {
-        self.base = self.base.size(size);
-        self
-    }
-
-    fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
-        self.base = self.base.tooltip(tooltip);
-        self
-    }
-
-    fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
-        self.base = self.base.tab_index(tab_index);
-        self
-    }
-
-    fn layer(mut self, elevation: ElevationIndex) -> Self {
-        self.base = self.base.layer(elevation);
-        self
-    }
-
-    fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
-        self.base = self.base.track_focus(focus_handle);
-        self
-    }
-}
-
-impl RenderOnce for ToggleButton {
-    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
-        let is_disabled = self.base.disabled;
-        let is_selected = self.base.selected;
-
-        let label_color = if is_disabled {
-            Color::Disabled
-        } else if is_selected {
-            Color::Selected
-        } else {
-            self.label_color.unwrap_or_default()
-        };
-
-        self.base
-            .when_some(self.position_in_group, |this, position| {
-                this.rounding(position.to_rounding())
-            })
-            .child(
-                Label::new(self.label)
-                    .color(label_color)
-                    .line_height_style(LineHeightStyle::UiLabel),
-            )
-    }
-}
-
-impl Component for ToggleButton {
-    fn scope() -> ComponentScope {
-        ComponentScope::Input
-    }
-
-    fn sort_name() -> &'static str {
-        "ButtonC"
-    }
-
-    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
-        Some(
-            v_flex()
-                .gap_6()
-                .children(vec![
-                    example_group_with_title(
-                        "Button Styles",
-                        vec![
-                            single_example(
-                                "Off",
-                                ToggleButton::new("off", "Off")
-                                    .layer(ElevationIndex::Background)
-                                    .style(ButtonStyle::Filled)
-                                    .into_any_element(),
-                            ),
-                            single_example(
-                                "On",
-                                ToggleButton::new("on", "On")
-                                    .layer(ElevationIndex::Background)
-                                    .toggle_state(true)
-                                    .style(ButtonStyle::Filled)
-                                    .into_any_element(),
-                            ),
-                            single_example(
-                                "Off – Disabled",
-                                ToggleButton::new("disabled_off", "Disabled Off")
-                                    .layer(ElevationIndex::Background)
-                                    .disabled(true)
-                                    .style(ButtonStyle::Filled)
-                                    .into_any_element(),
-                            ),
-                            single_example(
-                                "On – Disabled",
-                                ToggleButton::new("disabled_on", "Disabled On")
-                                    .layer(ElevationIndex::Background)
-                                    .disabled(true)
-                                    .toggle_state(true)
-                                    .style(ButtonStyle::Filled)
-                                    .into_any_element(),
-                            ),
-                        ],
-                    ),
-                    example_group_with_title(
-                        "Button Group",
-                        vec![
-                            single_example(
-                                "Three Buttons",
-                                h_flex()
-                                    .child(
-                                        ToggleButton::new("three_btn_first", "First")
-                                            .layer(ElevationIndex::Background)
-                                            .style(ButtonStyle::Filled)
-                                            .first()
-                                            .into_any_element(),
-                                    )
-                                    .child(
-                                        ToggleButton::new("three_btn_middle", "Middle")
-                                            .layer(ElevationIndex::Background)
-                                            .style(ButtonStyle::Filled)
-                                            .middle()
-                                            .toggle_state(true)
-                                            .into_any_element(),
-                                    )
-                                    .child(
-                                        ToggleButton::new("three_btn_last", "Last")
-                                            .layer(ElevationIndex::Background)
-                                            .style(ButtonStyle::Filled)
-                                            .last()
-                                            .into_any_element(),
-                                    )
-                                    .into_any_element(),
-                            ),
-                            single_example(
-                                "Two Buttons",
-                                h_flex()
-                                    .child(
-                                        ToggleButton::new("two_btn_first", "First")
-                                            .layer(ElevationIndex::Background)
-                                            .style(ButtonStyle::Filled)
-                                            .first()
-                                            .into_any_element(),
-                                    )
-                                    .child(
-                                        ToggleButton::new("two_btn_last", "Last")
-                                            .layer(ElevationIndex::Background)
-                                            .style(ButtonStyle::Filled)
-                                            .last()
-                                            .into_any_element(),
-                                    )
-                                    .into_any_element(),
-                            ),
-                        ],
-                    ),
-                    example_group_with_title(
-                        "Alternate Sizes",
-                        vec![
-                            single_example(
-                                "None",
-                                ToggleButton::new("none", "None")
-                                    .layer(ElevationIndex::Background)
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::None)
-                                    .into_any_element(),
-                            ),
-                            single_example(
-                                "Compact",
-                                ToggleButton::new("compact", "Compact")
-                                    .layer(ElevationIndex::Background)
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Compact)
-                                    .into_any_element(),
-                            ),
-                            single_example(
-                                "Large",
-                                ToggleButton::new("large", "Large")
-                                    .layer(ElevationIndex::Background)
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .into_any_element(),
-                            ),
-                        ],
-                    ),
-                ])
-                .into_any_element(),
-        )
-    }
-}
-
 pub struct ButtonConfiguration {
     label: SharedString,
     icon: Option<IconName>,
@@ -447,6 +163,8 @@ pub enum ToggleButtonGroupStyle {
 pub enum ToggleButtonGroupSize {
     Default,
     Medium,
+    Large,
+    Custom(Rems),
 }
 
 #[derive(IntoElement)]
@@ -458,7 +176,9 @@ where
     rows: [[T; COLS]; ROWS],
     style: ToggleButtonGroupStyle,
     size: ToggleButtonGroupSize,
+    label_size: LabelSize,
     group_width: Option<DefiniteLength>,
+    auto_width: bool,
     selected_index: usize,
     tab_index: Option<isize>,
 }
@@ -470,7 +190,9 @@ impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS> {
             rows: [buttons],
             style: ToggleButtonGroupStyle::Transparent,
             size: ToggleButtonGroupSize::Default,
+            label_size: LabelSize::Small,
             group_width: None,
+            auto_width: false,
             selected_index: 0,
             tab_index: None,
         }
@@ -488,7 +210,9 @@ impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS, 2> {
             rows: [first_row, second_row],
             style: ToggleButtonGroupStyle::Transparent,
             size: ToggleButtonGroupSize::Default,
+            label_size: LabelSize::Small,
             group_width: None,
+            auto_width: false,
             selected_index: 0,
             tab_index: None,
         }
@@ -511,6 +235,18 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> ToggleButtonGroup<T
         self
     }
 
+    /// Makes the button group size itself to fit the content of the buttons,
+    /// rather than filling the full width of its parent.
+    pub fn auto_width(mut self) -> Self {
+        self.auto_width = true;
+        self
+    }
+
+    pub fn label_size(mut self, label_size: LabelSize) -> Self {
+        self.label_size = label_size;
+        self
+    }
+
     /// Sets the tab index for the toggle button group.
     /// The tab index is set to the initial value provided, then the
     /// value is incremented by the number of buttons in the group.
@@ -543,6 +279,11 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
     for ToggleButtonGroup<T, COLS, ROWS>
 {
     fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+        let custom_height = match self.size {
+            ToggleButtonGroupSize::Custom(height) => Some(height),
+            _ => None,
+        };
+
         let entries =
             self.rows.into_iter().enumerate().map(|(row_index, row)| {
                 let group_name = self.group_name.clone();
@@ -558,7 +299,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
                     let entry_index = row_index * COLS + col_index;
 
                     ButtonLike::new((group_name.clone(), entry_index))
-                        .full_width()
+                        .when(!self.auto_width, |this| this.full_width())
                         .rounding(Some(
                             ToggleButtonPosition {
                                 leftmost: col_index == 0,
@@ -581,13 +322,17 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
                         .when(self.size == ToggleButtonGroupSize::Medium, |button| {
                             button.size(ButtonSize::Medium)
                         })
+                        .when(self.size == ToggleButtonGroupSize::Large, |button| {
+                            button.size(ButtonSize::Large)
+                        })
+                        .when_some(custom_height, |button, height| button.height(height.into()))
                         .child(
                             h_flex()
                                 .w_full()
+                                .px_2()
                                 .gap_1p5()
-                                .px_3()
-                                .py_1()
                                 .justify_center()
+                                .flex_none()
                                 .when_some(icon, |this, icon| {
                                     this.py_2()
                                         .child(Icon::new(icon).size(IconSize::XSmall).map(|this| {
@@ -598,7 +343,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
                                             }
                                         }))
                                 })
-                                .child(Label::new(label).size(LabelSize::Small).when(
+                                .child(Label::new(label).size(self.label_size).when(
                                     entry_index == self.selected_index || selected,
                                     |this| this.color(Color::Accent),
                                 )),
@@ -620,6 +365,8 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
             .map(|this| {
                 if let Some(width) = self.group_width {
                     this.w(width)
+                } else if self.auto_width {
+                    this
                 } else {
                     this.w_full()
                 }
@@ -646,7 +393,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
                             .when(is_outlined_or_filled && !last_item, |this| {
                                 this.border_r_1().border_color(border_color)
                             })
-                            .w(Self::button_width())
+                            .when(!self.auto_width, |this| this.w(Self::button_width()))
                             .overflow_hidden()
                             .child(item)
                     }))

crates/ui/src/components/stories.rs 🔗

@@ -6,7 +6,6 @@ mod list_header;
 mod list_item;
 mod tab;
 mod tab_bar;
-mod toggle_button;
 
 pub use context_menu::*;
 pub use icon_button::*;
@@ -16,4 +15,3 @@ pub use list_header::*;
 pub use list_item::*;
 pub use tab::*;
 pub use tab_bar::*;
-pub use toggle_button::*;

crates/ui/src/components/stories/toggle_button.rs 🔗

@@ -1,93 +0,0 @@
-use gpui::Render;
-use story::{Story, StoryItem, StorySection};
-
-use crate::{ToggleButton, prelude::*};
-
-pub struct ToggleButtonStory;
-
-impl Render for ToggleButtonStory {
-    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        Story::container(cx)
-            .child(Story::title_for::<ToggleButton>(cx))
-            .child(
-                StorySection::new().child(
-                    StoryItem::new(
-                        "Default",
-                        ToggleButton::new("default_toggle_button", "Hello"),
-                    )
-                    .description("Displays a toggle button.")
-                    .usage(""),
-                ),
-            )
-            .child(
-                StorySection::new().child(
-                    StoryItem::new(
-                        "Toggle button group",
-                        h_flex()
-                            .child(
-                                ToggleButton::new(1, "Apple")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .first(),
-                            )
-                            .child(
-                                ToggleButton::new(2, "Banana")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .middle(),
-                            )
-                            .child(
-                                ToggleButton::new(3, "Cherry")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .middle(),
-                            )
-                            .child(
-                                ToggleButton::new(4, "Dragonfruit")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .last(),
-                            ),
-                    )
-                    .description("Displays a group of toggle buttons.")
-                    .usage(""),
-                ),
-            )
-            .child(
-                StorySection::new().child(
-                    StoryItem::new(
-                        "Toggle button group with selection",
-                        h_flex()
-                            .child(
-                                ToggleButton::new(1, "Apple")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .first(),
-                            )
-                            .child(
-                                ToggleButton::new(2, "Banana")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .toggle_state(true)
-                                    .middle(),
-                            )
-                            .child(
-                                ToggleButton::new(3, "Cherry")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .middle(),
-                            )
-                            .child(
-                                ToggleButton::new(4, "Dragonfruit")
-                                    .style(ButtonStyle::Filled)
-                                    .size(ButtonSize::Large)
-                                    .last(),
-                            ),
-                    )
-                    .description("Displays a group of toggle buttons.")
-                    .usage(""),
-                ),
-            )
-            .into_element()
-    }
-}