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::{
Action, App, AppContext as _, Axis, ClickEvent, Context, Corner, Entity, Focusable,
InteractiveElement, IntoElement, ParentElement as _, Render, Styled as _, Window,
prelude::FluentBuilder, px,
Action, App, AppContext as _, Axis, ClickEvent, Context, Entity, Focusable, InteractiveElement,
IntoElement, ParentElement as _, Render, Styled as _, Window, prelude::FluentBuilder, px,
};
use gpui_component::{
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,
h_flex, v_flex,
};
@ -441,6 +440,66 @@ impl Render for ButtonStory {
.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(
section("Small Size")
.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(
section("Icon Button")
.child(

View file

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

View file

@ -2,7 +2,7 @@ use std::rc::Rc;
use crate::{
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::{
div, prelude::FluentBuilder as _, px, relative, Action, AnyElement, App, ClickEvent, Corners,
@ -189,6 +189,7 @@ pub struct Button {
outline: bool,
border_corners: Corners<bool>,
border_edges: Edges<bool>,
dropdown_caret: bool,
size: Size,
compact: bool,
tooltip: Option<(
@ -237,6 +238,7 @@ impl Button {
outline: false,
children: Vec::new(),
loading_icon: None,
dropdown_caret: false,
tab_index: 0,
tab_stop: true,
}
@ -352,6 +354,12 @@ impl Button {
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]
fn clickable(&self) -> bool {
!(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))
})
.children(self.children)
.when(self.dropdown_caret, |this| {
this.child(Icon::new(IconName::ChevronDown).with_size(icon_size))
})
})
.when(self.loading && !self.disabled, |this| {
this.bg(normal_style.bg.opacity(0.8))

View file

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

View file

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

View file

@ -107,6 +107,16 @@ Button::new("btn")
.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
There have `disabled`, `loading`, `selected` state for buttons to indicate different statuses.