@jpgilldev / steid

2.8 KBRaw
1use topcoat::{
2 Result,
3 view::{Attributes, View, class, component, view},
4};
5
6/// The visual style of a [`badge`].
7///
8/// [`Default`] is `BadgeVariant::Primary`, used when no variant is given.
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10#[allow(dead_code)]
11pub enum BadgeVariant {
12 /// The primary-filled badge for highlighted statuses.
13 #[default]
14 Primary,
15 /// A muted, tinted fill for neutral statuses.
16 Secondary,
17 /// A hairline-bordered badge on the page background.
18 Outline,
19 /// A destructive-filled badge for errors and warnings.
20 Destructive,
21}
22
23impl BadgeVariant {
24 /// The Tailwind classes for this variant.
25 ///
26 /// Each variant sets its own border color rather than inheriting a
27 /// transparent one from [`BASE`]: with two border-color classes on the
28 /// same element, stylesheet order (not class order) would decide the
29 /// winner.
30 fn classes(self) -> &'static str {
31 match self {
32 Self::Primary => "border-transparent bg-primary text-primary-foreground",
33 Self::Secondary => "border-transparent bg-foreground/5 text-foreground",
34 Self::Outline => "border-border text-foreground",
35 Self::Destructive => "border-transparent bg-destructive text-destructive-foreground",
36 }
37 }
38}
39
40/// The classes shared by every badge, regardless of variant.
41///
42/// Every badge carries a border (colored per variant) so that the `Outline`
43/// variant, which only recolors it, does not change the badge's dimensions.
44const BASE: &str = "inline-flex w-fit shrink-0 items-center justify-center gap-1 rounded-md \
45 border px-2 py-0.5 text-xs font-medium whitespace-nowrap";
46
47/// Builds the full class string for a badge of the given `variant`.
48///
49/// Use it to give badge styling to another element, such as a link:
50///
51/// ```ignore
52/// view! {
53/// <a href="/releases/v2" class=(badge_variants(BadgeVariant::Outline))>"v2.0"</a>
54/// }
55/// ```
56#[must_use]
57pub fn badge_variants(variant: BadgeVariant) -> String {
58 format!("{BASE} {}", variant.classes())
59}
60
61/// A badge component: a small inline pill for statuses, counts, and tags.
62///
63/// The `variant` parameter selects the styling, defaulting to `Primary`. The
64/// `attrs` (such as `class` or `title`) are forwarded to the underlying
65/// `<span>`; a `class` among them is appended to the computed classes. Child
66/// nodes become the badge's content.
67///
68/// ```ignore
69/// view! {
70/// badge(variant: BadgeVariant::Destructive, "Failed")
71/// }
72/// ```
73///
74/// To style another element like a badge, use [`badge_variants`] directly.
75#[component]
76pub async fn badge(
77 #[default] variant: BadgeVariant,
78 #[default] mut attrs: Attributes,
79 #[default] child: View,
80) -> Result {
81 view! {
82 <span class=(class!(BASE, variant.classes(), attrs.remove("class"))) (attrs)>
83 (child)
84 </span>
85 }
86}