From d598e586661a377a46a55673f063f74cac791082 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Fri, 6 Jun 2025 13:35:24 +0800 Subject: [PATCH] tag: Add `rounded` to Tag and support `outline` for all variants. (#923) image --- crates/story/src/tag_story.rs | 109 ++++++++++++++++++++------- crates/ui/src/tag.rs | 136 ++++++++++++++++++++++++++++------ 2 files changed, 193 insertions(+), 52 deletions(-) diff --git a/crates/story/src/tag_story.rs b/crates/story/src/tag_story.rs index 6c99c8c3..a9fc5806 100644 --- a/crates/story/src/tag_story.rs +++ b/crates/story/src/tag_story.rs @@ -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( diff --git a/crates/ui/src/tag.rs b/crates/ui/src/tag.rs index 7db115ee..c9dc9ff4 100644 --- a/crates/ui/src/tag.rs +++ b/crates/ui/src/tag.rs @@ -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, } 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) -> Self { Self::new().with_variant(TagVariant::Color(color.into())) } + + /// Set rounded corners. + pub fn rounded(mut self, radius: impl Into) -> 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) -> 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)) } }