1#![allow(clippy::unusual_byte_groupings)]
3
4use crate::motion::StyledSlot;
5use crate::theme::{ActiveTheme, Theme};
6use gpui::{
7 div, prelude::*, px, App, FontWeight, IntoElement, RenderOnce, SharedString, StyleRefinement,
8 Styled, Window,
9};
10
11use crate::button::ButtonVariant;
12use crate::chrome::{box_shadow, button_chrome, ButtonChrome};
13
14const BADGE_HEIGHT: f32 = 22.0;
15const BADGE_RADIUS: f32 = 6.0;
16const BADGE_PAD_X: f32 = 8.0;
17
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub enum BadgeVariant {
21 #[default]
22 Default,
23 Muted,
24 Destructive,
25}
26
27impl BadgeVariant {
28 fn button_variant(self) -> ButtonVariant {
29 match self {
30 Self::Default => ButtonVariant::Primary,
31 Self::Muted => ButtonVariant::Ghost,
32 Self::Destructive => ButtonVariant::Destructive,
33 }
34 }
35}
36
37fn badge_chrome(theme: Theme, variant: BadgeVariant) -> ButtonChrome {
38 let mut chrome = button_chrome(theme, variant.button_variant());
39 if variant == BadgeVariant::Muted {
40 chrome.fg = theme.muted_fg();
41 }
42 chrome
43}
44
45#[derive(IntoElement)]
47pub struct Badge {
48 label: SharedString,
49 variant: BadgeVariant,
50 style: StyleRefinement,
51}
52
53impl Badge {
54 pub fn new(label: impl Into<SharedString>) -> Self {
55 Self {
56 label: label.into(),
57 variant: BadgeVariant::Default,
58 style: StyleRefinement::default(),
59 }
60 }
61
62 pub fn variant(mut self, variant: BadgeVariant) -> Self {
63 self.variant = variant;
64 self
65 }
66}
67
68impl Styled for Badge {
69 fn style(&mut self) -> &mut StyleRefinement {
70 &mut self.style
71 }
72}
73
74impl RenderOnce for Badge {
75 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
76 let theme = cx.theme();
77 let chrome = badge_chrome(theme, self.variant);
78 let mut shadows = vec![box_shadow(0., 1., chrome.inset, 0., 0.)];
79 if chrome.shadow_blur > 0.0 {
80 shadows.push(box_shadow(
81 0.,
82 chrome.shadow_y,
83 chrome.shadow,
84 chrome.shadow_blur,
85 0.,
86 ));
87 }
88
89 div()
90 .flex()
91 .items_center()
92 .justify_center()
93 .h(px(BADGE_HEIGHT))
94 .flex_shrink_0()
95 .px(px(BADGE_PAD_X))
96 .rounded(px(BADGE_RADIUS))
97 .border_1()
98 .border_color(chrome.border)
99 .bg(chrome.bg)
100 .shadow(shadows)
101 .cursor_default()
102 .refine_style(&self.style)
103 .child(
104 div()
105 .font_family(theme.font_family)
106 .font_weight(FontWeight::MEDIUM)
107 .text_size(px(12.))
108 .line_height(px(16.))
109 .text_color(chrome.fg)
110 .child(self.label),
111 )
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::theme::{paint, rgb};
119
120 #[test]
121 fn metrics_match_paper() {
122 assert_eq!(BADGE_HEIGHT, 22.0);
123 assert_eq!(BADGE_RADIUS, 6.0);
124 assert_eq!(BADGE_PAD_X, 8.0);
125 }
126
127 #[test]
128 fn light_variants_match_paper() {
129 let theme = Theme::light();
130 let default = badge_chrome(theme, BadgeVariant::Default);
131 assert_eq!(default.bg, paint(0x18181B_B8));
132 assert_eq!(default.border, paint(0xFFFFFF_29));
133 assert_eq!(default.fg, rgb(0xFAFAFA));
134
135 let muted = badge_chrome(theme, BadgeVariant::Muted);
136 assert_eq!(muted.bg, paint(0xFFFFFF_29));
137 assert_eq!(muted.border, paint(0xFFFFFF_47));
138 assert_eq!(muted.fg, rgb(0xA1A1AA));
139
140 let destructive = badge_chrome(theme, BadgeVariant::Destructive);
141 assert_eq!(destructive.bg, paint(0xDC2626_C7));
142 assert_eq!(destructive.border, paint(0xFFFFFF_47));
143 assert_eq!(destructive.fg, rgb(0xFFFFFF));
144 }
145
146 #[test]
147 fn dark_variants_match_paper() {
148 let theme = Theme::dark();
149 let default = badge_chrome(theme, BadgeVariant::Default);
150 assert_eq!(default.bg, paint(0xFFFFFF_29));
151 assert_eq!(default.border, paint(0xFFFFFF_38));
152 assert_eq!(default.fg, rgb(0xFAFAFA));
153
154 let muted = badge_chrome(theme, BadgeVariant::Muted);
155 assert_eq!(muted.bg, paint(0xFFFFFF_08));
156 assert_eq!(muted.border, paint(0xFFFFFF_0F));
157 assert_eq!(muted.fg, rgb(0x71717A));
158
159 let destructive = badge_chrome(theme, BadgeVariant::Destructive);
160 assert_eq!(destructive.bg, paint(0xDC2626_9E));
161 assert_eq!(destructive.border, paint(0xFFFFFF_29));
162 assert_eq!(destructive.fg, rgb(0xFAFAFA));
163 }
164}