From 59d524427e1a4bf437b05dc5212ec36b393dabf9 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Wed, 16 Jul 2025 01:15:45 -0300 Subject: [PATCH] ui: Add Chip component (#34521) Possibly the simplest component in our set, but a nice one to have so we can standardize how it looks across the app. Release Notes: - N/A --- crates/agent_ui/src/agent_configuration.rs | 23 ++--- crates/component/src/component_layout.rs | 12 +-- crates/extensions_ui/src/extensions_ui.rs | 17 +--- crates/ui/src/components.rs | 2 + crates/ui/src/components/chip.rs | 106 ++++++++++++++++++++ crates/ui/src/components/keybinding_hint.rs | 2 +- crates/ui/src/components/tooltip.rs | 2 +- 7 files changed, 124 insertions(+), 40 deletions(-) create mode 100644 crates/ui/src/components/chip.rs diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index 699a776330..0697f5dee7 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -27,7 +27,7 @@ use project::{ use proto::Plan; use settings::{Settings, update_settings_file}; use ui::{ - ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu, + Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*, }; use util::ResultExt as _; @@ -227,7 +227,7 @@ impl AgentConfiguration { ) .map(|this| { if is_zed_provider { - this.child( + this.gap_2().child( self.render_zed_plan_info(current_plan, cx), ) } else { @@ -474,26 +474,15 @@ impl AgentConfiguration { .opacity(0.5) .blend(cx.theme().colors().text_accent.opacity(0.2)); - let (plan_name, plan_color, bg_color) = match plan { + let (plan_name, label_color, bg_color) = match plan { Plan::Free => ("Free", Color::Default, free_chip_bg), Plan::ZedProTrial => ("Pro Trial", Color::Accent, pro_chip_bg), Plan::ZedPro => ("Pro", Color::Accent, pro_chip_bg), }; - h_flex() - .ml_1() - .px_1() - .rounded_sm() - .border_1() - .border_color(cx.theme().colors().border) - .bg(bg_color) - .overflow_hidden() - .child( - Label::new(plan_name.to_string()) - .color(plan_color) - .size(LabelSize::XSmall) - .buffer_font(cx), - ) + Chip::new(plan_name.to_string()) + .bg_color(bg_color) + .label_color(label_color) .into_any_element() } else { div().into_any_element() diff --git a/crates/component/src/component_layout.rs b/crates/component/src/component_layout.rs index b749ea20ea..9fe52507d8 100644 --- a/crates/component/src/component_layout.rs +++ b/crates/component/src/component_layout.rs @@ -48,20 +48,20 @@ impl RenderOnce for ComponentExample { ) .child( div() - .flex() - .w_full() - .rounded_xl() .min_h(px(100.)) - .justify_center() + .w_full() .p_8() + .flex() + .items_center() + .justify_center() + .rounded_xl() .border_1() .border_color(cx.theme().colors().border.opacity(0.5)) .bg(pattern_slash( - cx.theme().colors().surface_background.opacity(0.5), + cx.theme().colors().surface_background.opacity(0.25), 12.0, 12.0, )) - .shadow_xs() .child(self.element), ) .into_any_element() diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index b944b1ec50..fe3e94f5c2 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -24,7 +24,7 @@ use settings::Settings; use strum::IntoEnumIterator as _; use theme::ThemeSettings; use ui::{ - CheckboxWithLabel, ContextMenu, PopoverMenu, ScrollableHandle, Scrollbar, ScrollbarState, + CheckboxWithLabel, Chip, ContextMenu, PopoverMenu, ScrollableHandle, Scrollbar, ScrollbarState, ToggleButton, Tooltip, prelude::*, }; use vim_mode_setting::VimModeSetting; @@ -759,20 +759,7 @@ impl ExtensionsPage { _ => {} } - Some( - div() - .px_1() - .border_1() - .rounded_sm() - .border_color(cx.theme().colors().border) - .bg(cx.theme().colors().element_background) - .child( - Label::new(extension_provides_label( - *provides, - )) - .size(LabelSize::XSmall), - ), - ) + Some(Chip::new(extension_provides_label(*provides))) }) .collect::>(), ), diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index 88676e8a2b..9c2961c55f 100644 --- a/crates/ui/src/components.rs +++ b/crates/ui/src/components.rs @@ -2,6 +2,7 @@ mod avatar; mod banner; mod button; mod callout; +mod chip; mod content_group; mod context_menu; mod disclosure; @@ -43,6 +44,7 @@ pub use avatar::*; pub use banner::*; pub use button::*; pub use callout::*; +pub use chip::*; pub use content_group::*; pub use context_menu::*; pub use disclosure::*; diff --git a/crates/ui/src/components/chip.rs b/crates/ui/src/components/chip.rs new file mode 100644 index 0000000000..e1262875fe --- /dev/null +++ b/crates/ui/src/components/chip.rs @@ -0,0 +1,106 @@ +use crate::prelude::*; +use gpui::{AnyElement, Hsla, IntoElement, ParentElement, Styled}; + +/// Chips provide a container for an informative label. +/// +/// # Usage Example +/// +/// ``` +/// use ui::{Chip}; +/// +/// Chip::new("This Chip") +/// ``` +#[derive(IntoElement, RegisterComponent)] +pub struct Chip { + label: SharedString, + label_color: Color, + label_size: LabelSize, + bg_color: Option, +} + +impl Chip { + /// Creates a new `Chip` component with the specified label. + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + label_color: Color::Default, + label_size: LabelSize::XSmall, + bg_color: None, + } + } + + /// Sets the color of the label. + pub fn label_color(mut self, color: Color) -> Self { + self.label_color = color; + self + } + + /// Sets the size of the label. + pub fn label_size(mut self, size: LabelSize) -> Self { + self.label_size = size; + self + } + + /// Sets a custom background color for the callout content. + pub fn bg_color(mut self, color: Hsla) -> Self { + self.bg_color = Some(color); + self + } +} + +impl RenderOnce for Chip { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let bg_color = self + .bg_color + .unwrap_or(cx.theme().colors().element_background); + + h_flex() + .min_w_0() + .flex_initial() + .px_1() + .border_1() + .rounded_sm() + .border_color(cx.theme().colors().border) + .bg(bg_color) + .overflow_hidden() + .child( + Label::new(self.label) + .size(self.label_size) + .color(self.label_color) + .buffer_font(cx), + ) + } +} + +impl Component for Chip { + fn scope() -> ComponentScope { + ComponentScope::DataDisplay + } + + fn preview(_window: &mut Window, cx: &mut App) -> Option { + let chip_examples = vec![ + single_example("Default", Chip::new("Chip Example").into_any_element()), + single_example( + "Customized Label Color", + Chip::new("Chip Example") + .label_color(Color::Accent) + .into_any_element(), + ), + single_example( + "Customized Label Size", + Chip::new("Chip Example") + .label_size(LabelSize::Large) + .label_color(Color::Accent) + .into_any_element(), + ), + single_example( + "Customized Background Color", + Chip::new("Chip Example") + .bg_color(cx.theme().colors().text_accent.opacity(0.1)) + .into_any_element(), + ), + ]; + + Some(example_group(chip_examples).vertical().into_any_element()) + } +} diff --git a/crates/ui/src/components/keybinding_hint.rs b/crates/ui/src/components/keybinding_hint.rs index d6dc094d41..a34ca40ed8 100644 --- a/crates/ui/src/components/keybinding_hint.rs +++ b/crates/ui/src/components/keybinding_hint.rs @@ -206,7 +206,7 @@ impl RenderOnce for KeybindingHint { impl Component for KeybindingHint { fn scope() -> ComponentScope { - ComponentScope::None + ComponentScope::DataDisplay } fn description() -> Option<&'static str> { diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index 18c9decc59..ed0fdd0114 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -274,7 +274,7 @@ impl Render for LinkPreview { impl Component for Tooltip { fn scope() -> ComponentScope { - ComponentScope::None + ComponentScope::DataDisplay } fn description() -> Option<&'static str> {