gpui_component/
tag.rs

1use crate::{theme::ActiveTheme as _, ColorName, Sizable, Size, StyledExt};
2use gpui::{
3    div, prelude::FluentBuilder as _, relative, rems, transparent_white, AbsoluteLength,
4    AnyElement, App, Hsla, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,
5    StyleRefinement, Styled, Window,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum TagVariant {
10    Primary,
11    #[default]
12    Secondary,
13    Danger,
14    Success,
15    Warning,
16    Info,
17    Color(ColorName),
18    Custom {
19        color: Hsla,
20        foreground: Hsla,
21        border: Hsla,
22    },
23}
24
25impl TagVariant {
26    fn bg(&self, cx: &App) -> Hsla {
27        match self {
28            Self::Primary => cx.theme().primary,
29            Self::Secondary => cx.theme().secondary,
30            Self::Danger => cx.theme().danger,
31            Self::Success => cx.theme().success,
32            Self::Warning => cx.theme().warning,
33            Self::Info => cx.theme().info,
34            Self::Color(color) => {
35                if cx.theme().is_dark() {
36                    color.scale(950).opacity(0.5)
37                } else {
38                    color.scale(50)
39                }
40            }
41            Self::Custom { color, .. } => *color,
42        }
43    }
44
45    fn border(&self, cx: &App) -> Hsla {
46        match self {
47            Self::Primary => cx.theme().primary,
48            Self::Secondary => cx.theme().border,
49            Self::Danger => cx.theme().danger,
50            Self::Success => cx.theme().success,
51            Self::Warning => cx.theme().warning,
52            Self::Info => cx.theme().info,
53            Self::Color(color) => {
54                if cx.theme().is_dark() {
55                    color.scale(800).opacity(0.5)
56                } else {
57                    color.scale(200)
58                }
59            }
60            Self::Custom { border, .. } => *border,
61        }
62    }
63
64    fn fg(&self, outline: bool, cx: &App) -> Hsla {
65        match self {
66            Self::Primary => {
67                if outline {
68                    cx.theme().primary
69                } else {
70                    cx.theme().primary_foreground
71                }
72            }
73            Self::Secondary => {
74                if outline {
75                    cx.theme().muted_foreground
76                } else {
77                    cx.theme().secondary_foreground
78                }
79            }
80            Self::Danger => {
81                if outline {
82                    cx.theme().danger
83                } else {
84                    cx.theme().danger_foreground
85                }
86            }
87            Self::Success => {
88                if outline {
89                    cx.theme().success
90                } else {
91                    cx.theme().success_foreground
92                }
93            }
94            Self::Warning => {
95                if outline {
96                    cx.theme().warning
97                } else {
98                    cx.theme().warning_foreground
99                }
100            }
101            Self::Info => {
102                if outline {
103                    cx.theme().info
104                } else {
105                    cx.theme().info_foreground
106                }
107            }
108            Self::Color(color) => {
109                if cx.theme().is_dark() {
110                    color.scale(300)
111                } else {
112                    color.scale(600)
113                }
114            }
115            Self::Custom { foreground, .. } => *foreground,
116        }
117    }
118}
119
120/// Tag is a small status indicator.
121///
122/// Only support: Medium, Small
123#[derive(IntoElement)]
124pub struct Tag {
125    style: StyleRefinement,
126    variant: TagVariant,
127    outline: bool,
128    size: Size,
129    rounded: Option<AbsoluteLength>,
130    children: Vec<AnyElement>,
131}
132impl Tag {
133    fn new() -> Self {
134        Self {
135            style: StyleRefinement::default(),
136            variant: TagVariant::default(),
137            outline: false,
138            size: Size::default(),
139            rounded: None,
140            children: Vec::new(),
141        }
142    }
143
144    pub fn with_variant(mut self, variant: TagVariant) -> Self {
145        self.variant = variant;
146        self
147    }
148
149    /// Use outline style
150    pub fn outline(mut self) -> Self {
151        self.outline = true;
152        self
153    }
154
155    /// Create a new tag with default variant ([`TagVariant::Primary`]).
156    pub fn primary() -> Self {
157        Self::new().with_variant(TagVariant::Primary)
158    }
159
160    /// Create a new tag with default variant ([`TagVariant::Secondary`]).
161    pub fn secondary() -> Self {
162        Self::new().with_variant(TagVariant::Secondary)
163    }
164
165    /// Create a new tag with default variant ([`TagVariant::Danger`]).
166    pub fn danger() -> Self {
167        Self::new().with_variant(TagVariant::Danger)
168    }
169
170    /// Create a new tag with default variant ([`TagVariant::Success`]).
171    pub fn success() -> Self {
172        Self::new().with_variant(TagVariant::Success)
173    }
174
175    /// Create a new tag with default variant ([`TagVariant::Warning`]).
176    pub fn warning() -> Self {
177        Self::new().with_variant(TagVariant::Warning)
178    }
179
180    /// Create a new tag with default variant ([`TagVariant::Info`]).
181    pub fn info() -> Self {
182        Self::new().with_variant(TagVariant::Info)
183    }
184
185    /// Create a new tag with default variant ([`TagVariant::Custom`]).
186    pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self {
187        Self::new().with_variant(TagVariant::Custom {
188            color,
189            foreground,
190            border,
191        })
192    }
193
194    /// Create a new tag with default variant ([`TagVariant::Color`]).
195    pub fn color(color: impl Into<ColorName>) -> Self {
196        Self::new().with_variant(TagVariant::Color(color.into()))
197    }
198
199    /// Set rounded corners.
200    pub fn rounded(mut self, radius: impl Into<AbsoluteLength>) -> Self {
201        self.rounded = Some(radius.into());
202        self
203    }
204
205    /// Set rounded full
206    pub fn rounded_full(mut self) -> Self {
207        self.rounded = Some(rems(1.).into());
208        self
209    }
210}
211
212impl Sizable for Tag {
213    fn with_size(mut self, size: impl Into<Size>) -> Self {
214        self.size = size.into();
215        self
216    }
217}
218
219impl ParentElement for Tag {
220    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
221        self.children.extend(elements);
222    }
223}
224
225impl Styled for Tag {
226    fn style(&mut self) -> &mut StyleRefinement {
227        &mut self.style
228    }
229}
230
231impl RenderOnce for Tag {
232    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
233        let bg = if self.outline {
234            transparent_white()
235        } else {
236            self.variant.bg(cx)
237        };
238        let fg = self.variant.fg(self.outline, cx);
239        let border = self.variant.border(cx);
240        let rounded = self.rounded.unwrap_or(
241            match self.size {
242                Size::XSmall | Size::Small => cx.theme().radius / 2.,
243                _ => cx.theme().radius,
244            }
245            .into(),
246        );
247
248        div()
249            .flex()
250            .items_center()
251            .border_1()
252            .line_height(relative(1.))
253            .text_xs()
254            .map(|this| match self.size {
255                Size::XSmall | Size::Small => this.px_1p5().py_0p5(),
256                _ => this.px_2p5().py_1(),
257            })
258            .bg(bg)
259            .text_color(fg)
260            .border_color(border)
261            .rounded(rounded)
262            .hover(|this| this.opacity(0.9))
263            .refine_style(&self.style)
264            .children(self.children)
265    }
266}