1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import type { HTMLAttributes, ReactNode } from 'react'
import { variants } from '@/lib/variants'
/**
* Badge
*
* A compact, non-interactive label. Tones map to the status token trios, and
* because a badge is often the only signal in a dense table, the `dot` option
* exists to add a second, colour-independent cue alongside the text.
*/
const badgeVariants = variants('inline-flex items-center gap-1.5 whitespace-nowrap font-medium', {
variants: {
tone: {
neutral: 'bg-surface-sunken text-ink-muted border-line',
accent: 'bg-accent-soft text-accent-soft-ink border-accent-line',
success: 'bg-success-soft text-success border-success-line',
warning: 'bg-warning-soft text-warning border-warning-line',
danger: 'bg-danger-soft text-danger border-danger-line',
info: 'bg-info-soft text-info border-info-line',
inverse: 'bg-surface-inverse text-ink-inverse border-transparent',
},
appearance: {
soft: 'border',
outline: 'border bg-transparent',
solid: 'border border-transparent',
},
size: {
sm: 'h-4.5 rounded-sm px-1.5 text-2xs',
md: 'h-5.5 rounded-sm px-2 text-xs',
},
},
defaultVariants: { tone: 'neutral', appearance: 'soft', size: 'md' },
compound: [
{ appearance: 'solid', tone: 'accent', class: 'bg-accent text-accent-ink' },
{ appearance: 'solid', tone: 'success', class: 'bg-success text-white' },
{ appearance: 'solid', tone: 'warning', class: 'bg-warning text-white' },
{ appearance: 'solid', tone: 'danger', class: 'bg-danger text-white' },
{ appearance: 'solid', tone: 'info', class: 'bg-info text-white' },
{ appearance: 'solid', tone: 'neutral', class: 'bg-ink text-ink-inverse' },
{ appearance: 'outline', tone: 'neutral', class: 'text-ink-muted' },
],
})
export type BadgeTone = 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info' | 'inverse'
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
tone?: BadgeTone
appearance?: 'soft' | 'outline' | 'solid'
size?: 'sm' | 'md'
/** Adds a leading dot so the badge does not rely on hue alone. */
dot?: boolean
icon?: ReactNode
}
export function Badge({
tone = 'neutral',
appearance = 'soft',
size = 'md',
dot = false,
icon,
className,
children,
...props
}: BadgeProps) {
return (
<span className={badgeVariants({ tone, appearance, size, className })} {...props}>
{dot ? (
<span className="size-1.5 shrink-0 rounded-full bg-current" aria-hidden="true" />
) : null}
{icon}
{children}
</span>
)
}