radix_leptos_primitives/components/
badge.rs

1use leptos::prelude::*;
2use crate::utils::{merge_classes, generate_id};
3
4/// Badge variant for different status types
5#[derive(Clone, Copy, PartialEq, Eq, Hash)]
6pub enum BadgeVariant {
7    Default,
8    Primary,
9    Secondary,
10    Success,
11    Error,
12    Warning,
13    Info,
14    Outline,
15}
16
17/// Badge size variant
18#[derive(Clone, Copy, PartialEq, Eq, Hash)]
19pub enum BadgeSize {
20    Small,
21    Medium,
22    Large,
23}
24
25
26/// Root Badge component
27#[component]
28pub fn Badge(
29    /// Badge variant
30    #[prop(optional, default = BadgeVariant::Default)]
31    variant: BadgeVariant,
32    /// Badge size
33    #[prop(optional, default = BadgeSize::Medium)]
34    size: BadgeSize,
35    /// Whether the badge is interactive (clickable)
36    #[prop(optional, default = false)]
37    interactive: bool,
38    /// Whether the badge is disabled
39    #[prop(optional, default = false)]
40    disabled: bool,
41    /// CSS classes
42    #[prop(optional)]
43    class: Option<String>,
44    /// Click handler
45    #[prop(optional)]
46    on_click: Option<Callback<web_sys::MouseEvent>>,
47    /// Child content
48    children: Children,
49) -> impl IntoView {
50    let variant_class = move || match variant {
51        BadgeVariant::Default => "radix-badge--variant-default",
52        BadgeVariant::Primary => "radix-badge--variant-primary",
53        BadgeVariant::Secondary => "radix-badge--variant-secondary",
54        BadgeVariant::Success => "radix-badge--variant-success",
55        BadgeVariant::Error => "radix-badge--variant-error",
56        BadgeVariant::Warning => "radix-badge--variant-warning",
57        BadgeVariant::Info => "radix-badge--variant-info",
58        BadgeVariant::Outline => "radix-badge--variant-outline",
59    };
60
61    let size_class = move || match size {
62        BadgeSize::Small => "radix-badge--size-small",
63        BadgeSize::Medium => "radix-badge--size-medium",
64        BadgeSize::Large => "radix-badge--size-large",
65    };
66
67    let handle_click = move |e: web_sys::MouseEvent| {
68        if !disabled && interactive {
69            if let Some(callback) = on_click {
70                callback.run(e);
71            }
72        }
73    };
74
75    let class_value = class.unwrap_or_default();
76    let children_view = children();
77
78    let mut base_classes = vec!["radix-badge", &variant_class(), &size_class(), &class_value];
79
80    if interactive && !disabled {
81        base_classes.push("radix-badge--interactive");
82    }
83
84    let final_classes = base_classes;
85
86    view! {
87        <span
88            class=merge_classes(final_classes)
89            role="status"
90            on:click=handle_click
91        >
92            {children_view}
93        </span>
94    }
95}
96
97/// Badge with count/number
98#[component]
99pub fn BadgeCount(
100    /// The count to display
101    count: u32,
102    /// Maximum count to display (shows as "99+" if exceeded)
103    #[prop(optional, default = 99)]
104    max_count: u32,
105    /// Badge variant
106    #[prop(optional, default = BadgeVariant::Error)]
107    variant: BadgeVariant,
108    /// Badge size
109    #[prop(optional, default = BadgeSize::Small)]
110    size: BadgeSize,
111    /// Whether to show the badge when count is 0
112    #[prop(optional, default = false)]
113    show_zero: bool,
114    /// CSS classes
115    #[prop(optional)]
116    class: Option<String>,
117) -> impl IntoView {
118    let display_count = move || {
119        if count > max_count {
120            format!("{}+", max_count)
121        } else {
122            count.to_string()
123        }
124    };
125
126    let should_show = move || show_zero || count > 0;
127
128    let class_value = class.unwrap_or_default();
129
130    view! {
131        {move || {
132            if should_show() {
133                view! {
134                    <Badge
135                        variant=variant
136                        size=size
137                        class=class_value.clone()
138                    >
139                        <span class="radix-badge-count-text">
140                            {display_count()}
141                        </span>
142                    </Badge>
143                }.into_any()
144            } else {
145                let _: () = view! { <></> };
146                ().into_any()
147            }
148        }}
149    }
150}
151
152/// Badge with dot indicator
153#[component]
154pub fn BadgeDot(
155    /// Badge variant
156    #[prop(optional, default = BadgeVariant::Success)]
157    variant: BadgeVariant,
158    /// Badge size
159    #[prop(optional, default = BadgeSize::Small)]
160    size: BadgeSize,
161    /// Whether the dot is pulsing
162    #[prop(optional, default = false)]
163    pulsing: bool,
164    /// CSS classes
165    #[prop(optional)]
166    class: Option<String>,
167) -> impl IntoView {
168    let class_value = class.unwrap_or_default();
169    let variant_class = move || match variant {
170        BadgeVariant::Default => "radix-badge-dot--variant-default",
171        BadgeVariant::Primary => "radix-badge-dot--variant-primary",
172        BadgeVariant::Secondary => "radix-badge-dot--variant-secondary",
173        BadgeVariant::Success => "radix-badge-dot--variant-success",
174        BadgeVariant::Error => "radix-badge-dot--variant-error",
175        BadgeVariant::Warning => "radix-badge-dot--variant-warning",
176        BadgeVariant::Info => "radix-badge-dot--variant-info",
177        BadgeVariant::Outline => "radix-badge-dot--variant-outline",
178    };
179
180    let size_class = move || match size {
181        BadgeSize::Small => "radix-badge-dot--size-small",
182        BadgeSize::Medium => "radix-badge-dot--size-medium",
183        BadgeSize::Large => "radix-badge-dot--size-large",
184    };
185
186    let pulsing_class = move || {
187        if pulsing {
188            "radix-badge-dot--pulsing"
189        } else {
190            ""
191        }
192    };
193
194    view! {
195        <span
196            class=merge_classes(vec![
197                "radix-badge-dot",
198                variant_class(),
199                size_class(),
200                pulsing_class(),
201                &class_value,
202            ])
203            role="status"
204        >
205            <span class="radix-badge-dot-inner"></span>
206        </span>
207    }
208}