1use gpui::FontWeight;
2
3use crate::prelude::*;
4
5/// A small, pill-shaped badge that displays a numeric count.
6///
7/// The count is capped at 99 and displayed as "99+" beyond that.
8#[derive(IntoElement, RegisterComponent)]
9pub struct CountBadge {
10 count: usize,
11}
12
13impl CountBadge {
14 pub fn new(count: usize) -> Self {
15 Self { count }
16 }
17}
18
19impl RenderOnce for CountBadge {
20 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
21 let label = if self.count > 99 {
22 "99+".to_string()
23 } else {
24 self.count.to_string()
25 };
26
27 let bg = cx
28 .theme()
29 .colors()
30 .editor_background
31 .blend(cx.theme().status().error.opacity(0.4));
32
33 h_flex()
34 .absolute()
35 .top_0()
36 .right_0()
37 .p_px()
38 .h_3p5()
39 .min_w_3p5()
40 .rounded_full()
41 .justify_center()
42 .text_center()
43 .border_1()
44 .border_color(cx.theme().colors().border)
45 .bg(bg)
46 .shadow_sm()
47 .child(
48 Label::new(label)
49 .size(LabelSize::Custom(rems_from_px(9.)))
50 .weight(FontWeight::MEDIUM),
51 )
52 }
53}
54
55impl Component for CountBadge {
56 fn scope() -> ComponentScope {
57 ComponentScope::Status
58 }
59
60 fn description() -> Option<&'static str> {
61 Some("A small, pill-shaped badge that displays a numeric count.")
62 }
63
64 fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
65 let container = || {
66 div()
67 .relative()
68 .size_8()
69 .border_1()
70 .border_color(cx.theme().colors().border)
71 .bg(cx.theme().colors().background)
72 };
73
74 Some(
75 v_flex()
76 .gap_6()
77 .child(example_group_with_title(
78 "Count Badge",
79 vec![
80 single_example(
81 "Basic Count",
82 container().child(CountBadge::new(3)).into_any_element(),
83 ),
84 single_example(
85 "Capped Count",
86 container().child(CountBadge::new(150)).into_any_element(),
87 ),
88 ],
89 ))
90 .into_any_element(),
91 )
92 }
93}