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::{
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;
@ -45,32 +46,84 @@ impl Render for TagStory {
v_flex()
.gap_6()
.child(
section("Tag")
.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()
.gap_2()
.child(Tag::primary().child("Tag"))
.child(Tag::secondary().child("Secondary"))
.child(Tag::outline().child("Outline"))
.child(Tag::danger().child("danger"))
.child(
Tag::custom(yellow_500(), yellow_800(), yellow_500())
.child("Custom"),
),
),
section("Tag (default)").child(
h_flex()
.gap_2()
.child(Tag::primary().child("Tag"))
.child(Tag::secondary().child("Secondary"))
.child(Tag::danger().child("Danger"))
.child(Tag::success().child("Success"))
.child(Tag::warning().child("Warning"))
.child(Tag::info().child("Info"))
.child(
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(
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(
section("Color Tags").child(

View file

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