1use gpui::{FontWeight, Hsla, white};
2
3use crate::prelude::*;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum BadgeVariant {
8 #[default]
10 Soft,
11 Solid,
13 Outline,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum BadgeColor {
20 #[default]
21 Neutral,
22 Secondary,
23 Primary,
24 Success,
25 Warning,
26 Danger,
27}
28
29impl BadgeColor {
30 fn shade(self, step: u16) -> Hsla {
31 match self {
32 BadgeColor::Neutral | BadgeColor::Secondary => palette::neutral(step),
33 BadgeColor::Primary => palette::primary(step),
34 BadgeColor::Success => palette::success(step),
35 BadgeColor::Warning => palette::warning(step),
36 BadgeColor::Danger => palette::danger(step),
37 }
38 }
39}
40
41#[derive(IntoElement, RegisterComponent)]
43pub struct Badge {
44 label: SharedString,
45 variant: BadgeVariant,
46 color: BadgeColor,
47 dot: bool,
48}
49
50impl Badge {
51 pub fn new(label: impl Into<SharedString>) -> Self {
52 Self {
53 label: label.into(),
54 variant: BadgeVariant::default(),
55 color: BadgeColor::default(),
56 dot: false,
57 }
58 }
59
60 pub fn variant(mut self, variant: BadgeVariant) -> Self {
61 self.variant = variant;
62 self
63 }
64
65 pub fn color(mut self, color: BadgeColor) -> Self {
66 self.color = color;
67 self
68 }
69
70 pub fn dot(mut self, dot: bool) -> Self {
71 self.dot = dot;
72 self
73 }
74}
75
76impl RenderOnce for Badge {
77 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
78 let mut base = h_flex()
79 .items_center()
80 .gap_1()
81 .px_2()
82 .py_0p5()
83 .rounded_full();
84
85 let is_secondary = self.color == BadgeColor::Secondary;
91 let (fill, fill_text, dot_color, outline_border, outline_text) = if is_secondary {
92 let bg = semantic::secondary_bg(cx);
93 let fg = semantic::secondary_fg(cx);
94 (bg, fg, fg, semantic::border(cx), fg)
95 } else {
96 match self.variant {
97 BadgeVariant::Solid => (
98 self.color.shade(600),
99 white(),
100 self.color.shade(500),
101 self.color.shade(300),
102 self.color.shade(700),
103 ),
104 BadgeVariant::Soft | BadgeVariant::Outline => (
105 self.color.shade(100),
106 self.color.shade(800),
107 self.color.shade(500),
108 self.color.shade(300),
109 self.color.shade(700),
110 ),
111 }
112 };
113
114 base = match self.variant {
115 BadgeVariant::Soft | BadgeVariant::Solid => base.bg(fill).text_color(fill_text),
116 BadgeVariant::Outline => base
117 .border_1()
118 .border_color(outline_border)
119 .text_color(outline_text),
120 };
121
122 let text_color = match self.variant {
123 BadgeVariant::Outline => outline_text,
124 _ => fill_text,
125 };
126
127 base.when(self.dot, |this| {
128 this.child(div().size_1p5().rounded_full().bg(dot_color))
129 })
130 .child(
131 Label::new(self.label)
132 .size(LabelSize::XSmall)
133 .weight(FontWeight::MEDIUM)
134 .color(Color::Custom(text_color)),
135 )
136 }
137}
138
139impl Component for Badge {
140 fn scope() -> ComponentScope {
141 ComponentScope::Status
142 }
143
144 fn description() -> Option<&'static str> {
145 Some("A small pill label conveying status or category.")
146 }
147
148 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
149 let colors = [
150 ("Neutral", BadgeColor::Neutral),
151 ("Secondary", BadgeColor::Secondary),
152 ("Primary", BadgeColor::Primary),
153 ("Success", BadgeColor::Success),
154 ("Warning", BadgeColor::Warning),
155 ("Danger", BadgeColor::Danger),
156 ];
157 let row = |variant: BadgeVariant, dot: bool| {
158 let mut r = h_flex().gap_2();
159 for (name, color) in colors {
160 r = r.child(Badge::new(name).variant(variant).color(color).dot(dot));
161 }
162 r
163 };
164 let status_row = || {
165 h_flex()
166 .gap_2()
167 .child(
168 Badge::new("Active")
169 .variant(BadgeVariant::Soft)
170 .color(BadgeColor::Success)
171 .dot(true),
172 )
173 .child(
174 Badge::new("Pending")
175 .variant(BadgeVariant::Soft)
176 .color(BadgeColor::Warning)
177 .dot(true),
178 )
179 .child(
180 Badge::new("Failed")
181 .variant(BadgeVariant::Solid)
182 .color(BadgeColor::Danger),
183 )
184 .child(
185 Badge::new("Draft")
186 .variant(BadgeVariant::Outline)
187 .color(BadgeColor::Neutral),
188 )
189 };
190
191 Some(
192 v_flex()
193 .gap_3()
194 .child(row(BadgeVariant::Soft, true))
195 .child(row(BadgeVariant::Solid, true))
196 .child(row(BadgeVariant::Outline, true))
197 .child(row(BadgeVariant::Soft, false))
198 .child(row(BadgeVariant::Solid, false))
199 .child(row(BadgeVariant::Outline, false))
200 .child(status_row())
201 .into_any_element(),
202 )
203 }
204}