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)]
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#[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 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 pub fn primary() -> Self {
148 Self::new().with_variant(TagVariant::Primary)
149 }
150
151 pub fn secondary() -> Self {
153 Self::new().with_variant(TagVariant::Secondary)
154 }
155
156 pub fn danger() -> Self {
158 Self::new().with_variant(TagVariant::Danger)
159 }
160
161 pub fn success() -> Self {
163 Self::new().with_variant(TagVariant::Success)
164 }
165
166 pub fn warning() -> Self {
168 Self::new().with_variant(TagVariant::Warning)
169 }
170
171 pub fn info() -> Self {
173 Self::new().with_variant(TagVariant::Info)
174 }
175
176 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 pub fn color(color: impl Into<ColorName>) -> Self {
187 Self::new().with_variant(TagVariant::Color(color.into()))
188 }
189
190 pub fn with_variant(mut self, variant: TagVariant) -> Self {
192 self.variant = variant;
193 self
194 }
195
196 pub fn outline(mut self) -> Self {
198 self.outline = true;
199 self
200 }
201
202 pub fn rounded(mut self, radius: impl Into<AbsoluteLength>) -> Self {
204 self.rounded = Some(radius.into());
205 self
206 }
207
208 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}