Skip to main content

hypersig_ui/
badge.rs

1//! Badges: short labels such as a status or a count.
2
3use hypersig::html::{El, span};
4use hypersig::style::Style;
5use hypersig::style::value::{
6    AlignItems, BorderStyle, Color, Display, FontWeight, LineHeight, WhiteSpace, px, rem,
7};
8
9use crate::token;
10
11/// The look of a badge.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[non_exhaustive]
14pub enum BadgeVariant {
15    /// Filled with [`token::primary`].
16    Default,
17    /// Filled with [`token::secondary`].
18    Secondary,
19    /// Filled with [`token::destructive`].
20    Destructive,
21    /// A border with no fill.
22    Outline,
23}
24
25/// A `<span>` that shows a short label, such as a status, as a badge.
26///
27/// A badge is text and takes no role, so a screen reader reads it where it
28/// stands. Where the color alone carries the meaning, put the meaning in the
29/// text too, such as "Failed" rather than a red dot.
30///
31/// ```
32/// use hypersig::Signals;
33/// use hypersig_ui::{BadgeVariant, badge};
34/// use serde::{Deserialize, Serialize};
35///
36/// #[derive(Signals, Serialize, Deserialize)]
37/// struct App {}
38///
39/// let status = badge::<App>(BadgeVariant::Secondary).text("Draft");
40/// assert!(status.render().starts_with("<span"));
41/// ```
42#[must_use]
43pub fn badge<S>(variant: BadgeVariant) -> El<S> {
44    let (background, text, border) = match variant {
45        BadgeVariant::Default => (
46            token::primary().var(),
47            token::primary_foreground().var(),
48            Color::TRANSPARENT,
49        ),
50        BadgeVariant::Secondary => (
51            token::secondary().var(),
52            token::secondary_foreground().var(),
53            Color::TRANSPARENT,
54        ),
55        BadgeVariant::Destructive => (
56            token::destructive().var(),
57            token::destructive_foreground().var(),
58            Color::TRANSPARENT,
59        ),
60        BadgeVariant::Outline => (
61            Color::TRANSPARENT,
62            token::foreground().var(),
63            token::border().var(),
64        ),
65    };
66    span().css(
67        Style::new()
68            .display(Display::InlineFlex)
69            .align_items(AlignItems::Center)
70            .gap(rem(0.25))
71            .padding_block(rem(0.125))
72            .padding_inline(rem(0.5))
73            .border_width(px(1))
74            .border_style(BorderStyle::Solid)
75            .border_color(border)
76            .border_radius(px(6))
77            .background_color(background)
78            .color(text)
79            .font_size(rem(0.75))
80            .line_height(LineHeight::Length(rem(1)))
81            .font_weight(FontWeight::Number(500.0))
82            .white_space(WhiteSpace::Nowrap),
83    )
84}