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#[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 pub fn outline(mut self) -> Self {
151 self.outline = true;
152 self
153 }
154
155 pub fn primary() -> Self {
157 Self::new().with_variant(TagVariant::Primary)
158 }
159
160 pub fn secondary() -> Self {
162 Self::new().with_variant(TagVariant::Secondary)
163 }
164
165 pub fn danger() -> Self {
167 Self::new().with_variant(TagVariant::Danger)
168 }
169
170 pub fn success() -> Self {
172 Self::new().with_variant(TagVariant::Success)
173 }
174
175 pub fn warning() -> Self {
177 Self::new().with_variant(TagVariant::Warning)
178 }
179
180 pub fn info() -> Self {
182 Self::new().with_variant(TagVariant::Info)
183 }
184
185 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 pub fn color(color: impl Into<ColorName>) -> Self {
196 Self::new().with_variant(TagVariant::Color(color.into()))
197 }
198
199 pub fn rounded(mut self, radius: impl Into<AbsoluteLength>) -> Self {
201 self.rounded = Some(radius.into());
202 self
203 }
204
205 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}