tag: Add rounded to Tag and support outline for all variants. (#923)

<img width="1316" alt="image"
src="https://github.com/user-attachments/assets/e06f672d-f311-4dc2-b62a-c1f5cbce6973"
/>
This commit is contained in:
Jason Lee 2025-06-06 13:35:24 +08:00 committed by GitHub
parent e3e00a38b8
commit d598e58666
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 193 additions and 52 deletions

View file

@ -1,8 +1,9 @@
use gpui::{ use gpui::{
App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled, Window, px, App, AppContext, Context, Entity, Focusable, IntoElement, ParentElement, Render, Styled,
Window,
}; };
use gpui_component::{h_flex, tag::Tag, v_flex, yellow_500, yellow_800, ColorName, Sizable}; use gpui_component::{h_flex, indigo_500, tag::Tag, v_flex, ColorName, Sizable};
use crate::section; use crate::section;
@ -45,33 +46,85 @@ impl Render for TagStory {
v_flex() v_flex()
.gap_6() .gap_6()
.child( .child(
section("Tag") section("Tag (default)").child(
.child(
h_flex()
.gap_2()
.child(Tag::primary().small().child("Tag"))
.child(Tag::secondary().small().child("Secondary"))
.child(Tag::outline().small().child("Outline"))
.child(Tag::danger().small().child("danger"))
.child(
Tag::custom(yellow_500(), yellow_800(), yellow_500())
.small()
.child("Custom"),
),
)
.child(
h_flex() h_flex()
.gap_2() .gap_2()
.child(Tag::primary().child("Tag")) .child(Tag::primary().child("Tag"))
.child(Tag::secondary().child("Secondary")) .child(Tag::secondary().child("Secondary"))
.child(Tag::outline().child("Outline")) .child(Tag::danger().child("Danger"))
.child(Tag::danger().child("danger")) .child(Tag::success().child("Success"))
.child(Tag::warning().child("Warning"))
.child(Tag::info().child("Info"))
.child( .child(
Tag::custom(yellow_500(), yellow_800(), yellow_500()) Tag::custom(indigo_500(), indigo_500(), indigo_500()).child("Custom"),
),
),
)
.child(
section("Tag (outline)").child(
h_flex()
.gap_2()
.child(Tag::primary().outline().child("Tag"))
.child(Tag::secondary().outline().child("Secondary"))
.child(Tag::danger().outline().child("Danger"))
.child(Tag::success().outline().child("Success"))
.child(Tag::warning().outline().child("Warning"))
.child(Tag::info().outline().child("Info"))
.child(
Tag::custom(indigo_500(), indigo_500(), indigo_500())
.outline()
.child("Custom"), .child("Custom"),
), ),
), ),
) )
.child(
section("Tag (small)").child(
h_flex()
.gap_2()
.child(Tag::primary().small().child("Tag"))
.child(Tag::secondary().small().child("Secondary"))
.child(Tag::danger().small().child("Danger"))
.child(Tag::success().small().child("Success"))
.child(Tag::warning().small().child("Warning"))
.child(Tag::info().small().child("Info")),
),
)
.child(
section("Tag (rounded full)").child(
h_flex()
.gap_2()
.child(Tag::primary().rounded_full().child("Tag"))
.child(Tag::secondary().rounded_full().child("Secondary"))
.child(Tag::danger().rounded_full().child("Danger"))
.child(Tag::success().rounded_full().child("Success"))
.child(Tag::warning().rounded_full().child("Warning"))
.child(Tag::info().rounded_full().child("Info")),
),
)
.child(
section("Tag (small with rounded full)").child(
h_flex()
.gap_2()
.child(Tag::primary().small().rounded_full().child("Tag"))
.child(Tag::secondary().small().rounded_full().child("Secondary"))
.child(Tag::danger().small().rounded_full().child("Danger"))
.child(Tag::success().small().rounded_full().child("Success"))
.child(Tag::warning().small().rounded_full().child("Warning"))
.child(Tag::info().small().rounded_full().child("Info")),
),
)
.child(
section("Tag (rounded 0px)").child(
h_flex()
.gap_2()
.child(Tag::primary().small().rounded(px(0.)).child("Tag"))
.child(Tag::secondary().small().rounded(px(0.)).child("Secondary"))
.child(Tag::danger().small().rounded(px(0.)).child("Danger"))
.child(Tag::success().small().rounded(px(0.)).child("Success"))
.child(Tag::warning().small().rounded(px(0.)).child("Warning"))
.child(Tag::info().small().rounded(px(0.)).child("Info")),
),
)
.child( .child(
section("Color Tags").child( section("Color Tags").child(
v_flex().gap_4().child( v_flex().gap_4().child(

View file

@ -1,16 +1,19 @@
use crate::{theme::ActiveTheme as _, ColorName, Sizable, Size}; use crate::{theme::ActiveTheme as _, ColorName, Sizable, Size};
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, relative, transparent_black, AnyElement, App, Div, Hsla, div, prelude::FluentBuilder as _, relative, rems, transparent_white, AbsoluteLength,
InteractiveElement as _, IntoElement, ParentElement, RenderOnce, Styled, Window, AnyElement, App, Div, Hsla, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,
Styled, Window,
}; };
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TagVariant { pub enum TagVariant {
#[default]
Outline,
Primary, Primary,
#[default]
Secondary, Secondary,
Danger, Danger,
Success,
Warning,
Info,
Color(ColorName), Color(ColorName),
Custom { Custom {
color: Hsla, color: Hsla,
@ -24,8 +27,10 @@ impl TagVariant {
match self { match self {
Self::Primary => cx.theme().primary, Self::Primary => cx.theme().primary,
Self::Secondary => cx.theme().secondary, Self::Secondary => cx.theme().secondary,
Self::Outline => transparent_black(),
Self::Danger => cx.theme().danger, Self::Danger => cx.theme().danger,
Self::Success => cx.theme().success,
Self::Warning => cx.theme().warning,
Self::Info => cx.theme().info,
Self::Color(color) => { Self::Color(color) => {
if cx.theme().is_dark() { if cx.theme().is_dark() {
color.scale(950).opacity(0.5) color.scale(950).opacity(0.5)
@ -40,9 +45,11 @@ impl TagVariant {
fn border(&self, cx: &App) -> Hsla { fn border(&self, cx: &App) -> Hsla {
match self { match self {
Self::Primary => cx.theme().primary, Self::Primary => cx.theme().primary,
Self::Secondary => cx.theme().secondary, Self::Secondary => cx.theme().border,
Self::Outline => cx.theme().border,
Self::Danger => cx.theme().danger, Self::Danger => cx.theme().danger,
Self::Success => cx.theme().success,
Self::Warning => cx.theme().warning,
Self::Info => cx.theme().info,
Self::Color(color) => { Self::Color(color) => {
if cx.theme().is_dark() { if cx.theme().is_dark() {
color.scale(800).opacity(0.5) color.scale(800).opacity(0.5)
@ -54,12 +61,50 @@ impl TagVariant {
} }
} }
fn fg(&self, cx: &App) -> Hsla { fn fg(&self, outline: bool, cx: &App) -> Hsla {
match self { match self {
Self::Primary => cx.theme().primary_foreground, Self::Primary => {
Self::Secondary => cx.theme().secondary_foreground, if outline {
Self::Outline => cx.theme().foreground, cx.theme().primary
Self::Danger => cx.theme().danger_foreground, } else {
cx.theme().primary_foreground
}
}
Self::Secondary => {
if outline {
cx.theme().muted_foreground
} else {
cx.theme().secondary_foreground
}
}
Self::Danger => {
if outline {
cx.theme().danger
} else {
cx.theme().danger_foreground
}
}
Self::Success => {
if outline {
cx.theme().success
} else {
cx.theme().success_foreground
}
}
Self::Warning => {
if outline {
cx.theme().warning
} else {
cx.theme().warning_foreground
}
}
Self::Info => {
if outline {
cx.theme().info
} else {
cx.theme().info_foreground
}
}
Self::Color(color) => { Self::Color(color) => {
if cx.theme().is_dark() { if cx.theme().is_dark() {
color.scale(300) color.scale(300)
@ -79,14 +124,18 @@ impl TagVariant {
pub struct Tag { pub struct Tag {
base: Div, base: Div,
variant: TagVariant, variant: TagVariant,
outline: bool,
size: Size, size: Size,
rounded: Option<AbsoluteLength>,
} }
impl Tag { impl Tag {
fn new() -> Self { fn new() -> Self {
Self { Self {
base: div().flex().items_center().border_1(), base: div().flex().items_center().border_1(),
variant: TagVariant::default(), variant: TagVariant::default(),
outline: false,
size: Size::default(), size: Size::default(),
rounded: None,
} }
} }
@ -95,6 +144,12 @@ impl Tag {
self self
} }
/// Use outline style
pub fn outline(mut self) -> Self {
self.outline = true;
self
}
/// Create a new tag with default variant ([`TagVariant::Primary`]). /// Create a new tag with default variant ([`TagVariant::Primary`]).
pub fn primary() -> Self { pub fn primary() -> Self {
Self::new().with_variant(TagVariant::Primary) Self::new().with_variant(TagVariant::Primary)
@ -105,18 +160,26 @@ impl Tag {
Self::new().with_variant(TagVariant::Secondary) Self::new().with_variant(TagVariant::Secondary)
} }
/// Create a new tag with default variant ([`TagVariant::Outline`]).
///
/// See also [`Tag::new`].
pub fn outline() -> Self {
Self::new().with_variant(TagVariant::Outline)
}
/// Create a new tag with default variant ([`TagVariant::Danger`]). /// Create a new tag with default variant ([`TagVariant::Danger`]).
pub fn danger() -> Self { pub fn danger() -> Self {
Self::new().with_variant(TagVariant::Danger) Self::new().with_variant(TagVariant::Danger)
} }
/// Create a new tag with default variant ([`TagVariant::Success`]).
pub fn success() -> Self {
Self::new().with_variant(TagVariant::Success)
}
/// Create a new tag with default variant ([`TagVariant::Warning`]).
pub fn warning() -> Self {
Self::new().with_variant(TagVariant::Warning)
}
/// Create a new tag with default variant ([`TagVariant::Info`]).
pub fn info() -> Self {
Self::new().with_variant(TagVariant::Info)
}
/// Create a new tag with default variant ([`TagVariant::Custom`]). /// Create a new tag with default variant ([`TagVariant::Custom`]).
pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self { pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self {
Self::new().with_variant(TagVariant::Custom { Self::new().with_variant(TagVariant::Custom {
@ -130,7 +193,20 @@ impl Tag {
pub fn color(color: impl Into<ColorName>) -> Self { pub fn color(color: impl Into<ColorName>) -> Self {
Self::new().with_variant(TagVariant::Color(color.into())) Self::new().with_variant(TagVariant::Color(color.into()))
} }
/// Set rounded corners.
pub fn rounded(mut self, radius: impl Into<AbsoluteLength>) -> Self {
self.rounded = Some(radius.into());
self
}
/// Set rounded full
pub fn rounded_full(mut self) -> Self {
self.rounded = Some(rems(1.).into());
self
}
} }
impl Sizable for Tag { impl Sizable for Tag {
fn with_size(mut self, size: impl Into<Size>) -> Self { fn with_size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into(); self.size = size.into();
@ -144,20 +220,32 @@ impl ParentElement for Tag {
} }
impl RenderOnce for Tag { impl RenderOnce for Tag {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let bg = self.variant.bg(cx); let bg = if self.outline {
let fg = self.variant.fg(cx); transparent_white()
} else {
self.variant.bg(cx)
};
let fg = self.variant.fg(self.outline, cx);
let border = self.variant.border(cx); let border = self.variant.border(cx);
let rounded = self.rounded.unwrap_or(
match self.size {
Size::XSmall | Size::Small => cx.theme().radius / 2.,
_ => cx.theme().radius,
}
.into(),
);
self.base self.base
.line_height(relative(1.3)) .line_height(relative(1.))
.text_xs() .text_xs()
.map(|this| match self.size { .map(|this| match self.size {
Size::XSmall | Size::Small => this.px_1p5().py_0().rounded(cx.theme().radius / 2.), Size::XSmall | Size::Small => this.px_1p5().py_0p5(),
_ => this.px_2p5().py_0p5().rounded(cx.theme().radius), _ => this.px_2p5().py_1(),
}) })
.bg(bg) .bg(bg)
.text_color(fg) .text_color(fg)
.border_color(border) .border_color(border)
.rounded(rounded)
.hover(|this| this.opacity(0.9)) .hover(|this| this.opacity(0.9))
} }
} }