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 Primary,
23 Success,
24 Warning,
25 Danger,
26}
27
28impl BadgeColor {
29 fn shade(self, step: u16) -> Hsla {
30 match self {
31 BadgeColor::Neutral => palette::neutral(step),
32 BadgeColor::Primary => palette::primary(step),
33 BadgeColor::Success => palette::success(step),
34 BadgeColor::Warning => palette::warning(step),
35 BadgeColor::Danger => palette::danger(step),
36 }
37 }
38}
39
40#[derive(IntoElement, RegisterComponent)]
42pub struct Badge {
43 label: SharedString,
44 variant: BadgeVariant,
45 color: BadgeColor,
46 dot: bool,
47}
48
49impl Badge {
50 pub fn new(label: impl Into<SharedString>) -> Self {
51 Self {
52 label: label.into(),
53 variant: BadgeVariant::default(),
54 color: BadgeColor::default(),
55 dot: false,
56 }
57 }
58
59 pub fn variant(mut self, variant: BadgeVariant) -> Self {
60 self.variant = variant;
61 self
62 }
63
64 pub fn color(mut self, color: BadgeColor) -> Self {
65 self.color = color;
66 self
67 }
68
69 pub fn dot(mut self, dot: bool) -> Self {
70 self.dot = dot;
71 self
72 }
73}
74
75impl RenderOnce for Badge {
76 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
77 let mut base = h_flex()
78 .items_center()
79 .gap_1()
80 .px_2()
81 .py_0p5()
82 .rounded_full();
83
84 base = match self.variant {
85 BadgeVariant::Soft => base
86 .bg(self.color.shade(100))
87 .text_color(self.color.shade(800)),
88 BadgeVariant::Solid => base.bg(self.color.shade(600)).text_color(white()),
89 BadgeVariant::Outline => base
90 .border_1()
91 .border_color(self.color.shade(300))
92 .text_color(self.color.shade(700)),
93 };
94
95 base.when(self.dot, |this| {
96 this.child(div().size_1p5().rounded_full().bg(self.color.shade(500)))
97 })
98 .child(
99 Label::new(self.label)
100 .size(LabelSize::XSmall)
101 .weight(FontWeight::MEDIUM)
102 .color(Color::Custom(match self.variant {
103 BadgeVariant::Solid => white(),
104 BadgeVariant::Soft => self.color.shade(800),
105 BadgeVariant::Outline => self.color.shade(700),
106 })),
107 )
108 }
109}
110
111impl Component for Badge {
112 fn scope() -> ComponentScope {
113 ComponentScope::Status
114 }
115
116 fn description() -> Option<&'static str> {
117 Some("A small pill label conveying status or category.")
118 }
119
120 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
121 let colors = [
122 ("Neutral", BadgeColor::Neutral),
123 ("Primary", BadgeColor::Primary),
124 ("Success", BadgeColor::Success),
125 ("Warning", BadgeColor::Warning),
126 ("Danger", BadgeColor::Danger),
127 ];
128 let row = |variant: BadgeVariant, dot: bool| {
129 let mut r = h_flex().gap_2();
130 for (name, color) in colors {
131 r = r.child(Badge::new(name).variant(variant).color(color).dot(dot));
132 }
133 r
134 };
135 let status_row = || {
136 h_flex()
137 .gap_2()
138 .child(
139 Badge::new("Active")
140 .variant(BadgeVariant::Soft)
141 .color(BadgeColor::Success)
142 .dot(true),
143 )
144 .child(
145 Badge::new("Pending")
146 .variant(BadgeVariant::Soft)
147 .color(BadgeColor::Warning)
148 .dot(true),
149 )
150 .child(
151 Badge::new("Failed")
152 .variant(BadgeVariant::Solid)
153 .color(BadgeColor::Danger),
154 )
155 .child(
156 Badge::new("Draft")
157 .variant(BadgeVariant::Outline)
158 .color(BadgeColor::Neutral),
159 )
160 };
161
162 Some(
163 v_flex()
164 .gap_3()
165 .child(row(BadgeVariant::Soft, true))
166 .child(row(BadgeVariant::Solid, true))
167 .child(row(BadgeVariant::Outline, true))
168 .child(row(BadgeVariant::Soft, false))
169 .child(row(BadgeVariant::Solid, false))
170 .child(row(BadgeVariant::Outline, false))
171 .child(status_row())
172 .into_any_element(),
173 )
174 }
175}