button: Add dropdown_caret option to show a caret icon to end of button. (#1637)

<img width="658" height="162" alt="image"
src="https://github.com/user-attachments/assets/7c233982-04c3-408c-8ae4-f672f8b135a3"
/>
This commit is contained in:
Jason Lee 2025-11-18 16:41:16 +08:00 committed by GitHub
parent 44dc2c7f38
commit 01082f11d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 89 additions and 128 deletions

View file

@ -1,12 +1,11 @@
use gpui::{ use gpui::{
Action, App, AppContext as _, Axis, ClickEvent, Context, Corner, Entity, Focusable, Action, App, AppContext as _, Axis, ClickEvent, Context, Entity, Focusable, InteractiveElement,
InteractiveElement, IntoElement, ParentElement as _, Render, Styled as _, Window, IntoElement, ParentElement as _, Render, Styled as _, Window, prelude::FluentBuilder, px,
prelude::FluentBuilder, px,
}; };
use gpui_component::{ use gpui_component::{
ActiveTheme, Disableable as _, Icon, IconName, Selectable as _, Sizable as _, Theme, ActiveTheme, Disableable as _, Icon, IconName, Selectable as _, Sizable as _, Theme,
button::{Button, ButtonCustomVariant, ButtonGroup, ButtonVariants as _, DropdownButton}, button::{Button, ButtonCustomVariant, ButtonGroup, ButtonVariants as _},
checkbox::Checkbox, checkbox::Checkbox,
h_flex, v_flex, h_flex, v_flex,
}; };
@ -441,6 +440,66 @@ impl Render for ButtonStory {
.on_click(Self::on_click), .on_click(Self::on_click),
), ),
) )
.child(
section("With Dropdown Caret")
.max_w_lg()
.child(
Button::new("button-outline-1")
.primary()
.dropdown_caret(true)
.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")
.dropdown_caret(true)
.disabled(disabled)
.selected(selected)
.loading(loading)
.when(compact, |this| this.compact())
.on_click(Self::on_click),
)
.child(
Button::new("button-outline-5-ghost")
.ghost()
.dropdown_caret(true)
.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()
.dropdown_caret(true)
.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")
.outline()
.small()
.dropdown_caret(true)
.label("Small Button")
.disabled(disabled)
.selected(selected)
.loading(loading)
.when(compact, |this| this.compact())
.on_click(Self::on_click),
),
)
.child( .child(
section("Small Size") section("Small Size")
.child( .child(
@ -690,120 +749,6 @@ impl Render for ButtonStory {
})), })),
), ),
) )
.child(
section("Dropdown Button")
.child(
DropdownButton::new("dropdown-button1")
.small()
.button(Button::new("btn").label("Click Me"))
.selected(selected)
.dropdown_menu(move |this, _, _| {
this.menu_with_check(
"Disabled",
disabled,
Box::new(ButtonAction::Disabled),
)
.menu_with_check(
"Loading",
loading,
Box::new(ButtonAction::Loading),
)
.menu_with_check(
"Selected",
selected,
Box::new(ButtonAction::Selected),
)
.menu_with_check(
"Compact",
compact,
Box::new(ButtonAction::Compact),
)
}),
)
.child(
DropdownButton::new("dropdown-button2")
.button(Button::new("btn").label("Click Me"))
.selected(selected)
.dropdown_menu_with_anchor(Corner::BottomRight, move |this, _, _| {
this.menu_with_check(
"Disabled",
disabled,
Box::new(ButtonAction::Disabled),
)
.menu_with_check(
"Loading",
loading,
Box::new(ButtonAction::Loading),
)
.menu_with_check(
"Selected",
selected,
Box::new(ButtonAction::Selected),
)
.menu_with_check(
"Compact",
compact,
Box::new(ButtonAction::Compact),
)
}),
)
.child(
DropdownButton::new("dropdown-button3")
.outline()
.button(Button::new("btn").label("Outline Dropdown"))
.selected(selected)
.dropdown_menu(move |this, _, _| {
this.menu_with_check(
"Disabled",
disabled,
Box::new(ButtonAction::Disabled),
)
.menu_with_check(
"Loading",
loading,
Box::new(ButtonAction::Loading),
)
.menu_with_check(
"Selected",
selected,
Box::new(ButtonAction::Selected),
)
.menu_with_check(
"Compact",
compact,
Box::new(ButtonAction::Compact),
)
}),
)
.child(
DropdownButton::new("dropdown-button4")
.ghost()
.button(Button::new("btn").label("Ghost Dropdown"))
.selected(selected)
.dropdown_menu(move |this, _, _| {
this.menu_with_check(
"Disabled",
disabled,
Box::new(ButtonAction::Disabled),
)
.menu_with_check(
"Loading",
loading,
Box::new(ButtonAction::Loading),
)
.menu_with_check(
"Selected",
selected,
Box::new(ButtonAction::Selected),
)
.menu_with_check(
"Compact",
compact,
Box::new(ButtonAction::Compact),
)
}),
),
)
.child( .child(
section("Icon Button") section("Icon Button")
.child( .child(

View file

@ -416,6 +416,7 @@ impl Styled for StorySection {
impl RenderOnce for StorySection { impl RenderOnce for StorySection {
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
GroupBox::new() GroupBox::new()
.id(self.title.clone())
.outline() .outline()
.title( .title(
h_flex() h_flex()

View file

@ -2,7 +2,7 @@ use std::rc::Rc;
use crate::{ use crate::{
h_flex, spinner::Spinner, tooltip::Tooltip, ActiveTheme, Colorize as _, Disableable, h_flex, spinner::Spinner, tooltip::Tooltip, ActiveTheme, Colorize as _, Disableable,
FocusableExt as _, Icon, Selectable, Sizable, Size, StyleSized, StyledExt, FocusableExt as _, Icon, IconName, Selectable, Sizable, Size, StyleSized, StyledExt,
}; };
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, relative, Action, AnyElement, App, ClickEvent, Corners, div, prelude::FluentBuilder as _, px, relative, Action, AnyElement, App, ClickEvent, Corners,
@ -189,6 +189,7 @@ pub struct Button {
outline: bool, outline: bool,
border_corners: Corners<bool>, border_corners: Corners<bool>,
border_edges: Edges<bool>, border_edges: Edges<bool>,
dropdown_caret: bool,
size: Size, size: Size,
compact: bool, compact: bool,
tooltip: Option<( tooltip: Option<(
@ -237,6 +238,7 @@ impl Button {
outline: false, outline: false,
children: Vec::new(), children: Vec::new(),
loading_icon: None, loading_icon: None,
dropdown_caret: false,
tab_index: 0, tab_index: 0,
tab_stop: true, tab_stop: true,
} }
@ -352,6 +354,12 @@ impl Button {
self self
} }
/// Set to show a dropdown caret icon at the end of the button.
pub fn dropdown_caret(mut self, dropdown_caret: bool) -> Self {
self.dropdown_caret = dropdown_caret;
self
}
#[inline] #[inline]
fn clickable(&self) -> bool { fn clickable(&self) -> bool {
!(self.disabled || self.loading) && self.on_click.is_some() !(self.disabled || self.loading) && self.on_click.is_some()
@ -579,6 +587,9 @@ impl RenderOnce for Button {
this.child(div().flex_none().line_height(relative(1.)).child(label)) this.child(div().flex_none().line_height(relative(1.)).child(label))
}) })
.children(self.children) .children(self.children)
.when(self.dropdown_caret, |this| {
this.child(Icon::new(IconName::ChevronDown).with_size(icon_size))
})
}) })
.when(self.loading && !self.disabled, |this| { .when(self.loading && !self.disabled, |this| {
this.bg(normal_style.bg.opacity(0.8)) this.bg(normal_style.bg.opacity(0.8))

View file

@ -6,7 +6,7 @@ use gpui::{
use crate::{ use crate::{
menu::{DropdownMenu, PopupMenu}, menu::{DropdownMenu, PopupMenu},
Disableable, IconName, Selectable, Sizable, Size, StyledExt as _, Disableable, Selectable, Sizable, Size, StyledExt as _,
}; };
use super::{Button, ButtonRounded, ButtonVariant, ButtonVariants}; use super::{Button, ButtonRounded, ButtonVariant, ButtonVariants};
@ -178,7 +178,7 @@ impl RenderOnce for DropdownButton {
.when_some(self.menu, |this, menu| { .when_some(self.menu, |this, menu| {
this.child( this.child(
Button::new("popup") Button::new("popup")
.icon(IconName::ChevronDown) .dropdown_caret(true)
.rounded(self.rounded) .rounded(self.rounded)
.border_edges(Edges { .border_edges(Edges {
left: rounded, left: rounded,

View file

@ -845,13 +845,7 @@ where
.when(!show_clean, |this| { .when(!show_clean, |this| {
let icon = match self.options.icon.clone() { let icon = match self.options.icon.clone() {
Some(icon) => icon, Some(icon) => icon,
None => { None => Icon::new(IconName::ChevronDown),
if self.open {
Icon::new(IconName::ChevronUp)
} else {
Icon::new(IconName::ChevronDown)
}
}
}; };
this.child(icon.xsmall().text_color(match self.options.disabled { this.child(icon.xsmall().text_color(match self.options.disabled {

View file

@ -107,6 +107,16 @@ Button::new("btn")
.label("Like") .label("Like")
``` ```
### With a dropdown caret icon
The `.dropdown_caret` method can allows adding a dropdown caret icon to end of the button.
```rust
Button::new("btn")
.label("Options")
.dropdown_caret(true)
```
### Button States ### Button States
There have `disabled`, `loading`, `selected` state for buttons to indicate different statuses. There have `disabled`, `loading`, `selected` state for buttons to indicate different statuses.