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