import * as React from 'react'; import { Badge } from '@/components/badge'; import { PlusIcon, SettingsIcon } from '@/icons'; import { FilterField, narrow } from '~/chrome/ui/axis-filter'; import { GalleryPage } from '~/chrome/ui/gallery-page'; import { MatrixGrid } from '~/chrome/ui/matrix'; import { PACKAGE } from '~/chrome/lib/package-name'; import { useMessages } from '../i18n'; import { BADGE_SHORTHANDS as SHORTHANDS, BADGE_SIZES as SIZES, BADGE_VARIANTS as VARIANTS, } from '../registry/badge'; import { PALETTES, type Palette } from '../registry/axis-values'; import { useParam } from '../router'; import { FigmaLayoutView } from './badges-figma'; /** * Every badge the kit can draw, on one page, in two shapes — the same split * `/buttons`, `/alerts` and `/avatars` use. * * **Gallery** is the kit's own shape: a grid per treatment, palettes across, * sizes down. It comes in two sections because Badge's ten variants are two * different things — five that compose with `colorPalette` and five that pin * their own semantic colour and ignore it. Crossing the second five with the * palette axis would draw seven identical columns, so they get their own grid * against `size` instead. * * **Figma layout** is CBAR's shape — one band per colour, variant across and * size down — read off the badge set's own canvas geometry. It lives in * `./badges-figma` and covers all 32 variants the set declares. * * Copying is one cell at a time on both tabs, same reasoning as the other three: * a grid here is a whole sweep, and nobody pastes a sweep. */ type Variant = (typeof VARIANTS)[number]; type Shorthand = (typeof SHORTHANDS)[number]; type Size = (typeof SIZES)[number]; /** * Both tabs draw the canvas's glyphs, and for the same reason: CBAR's set has * `.iconStart?` and `.iconEnd?` as booleans that both default to true. The * lookup and the node ids are in `./badges-figma`. */ const ICON_SLOTS = ['both', 'left', 'right', 'none'] as const; type IconSlots = (typeof ICON_SLOTS)[number]; const EVERY_TOKEN: readonly string[] = [...VARIANTS, ...SHORTHANDS, ...PALETTES, ...SIZES]; /** * The Gallery's demo label. * * Deliberately not an i18n key: `chrome/lib/codegen.ts` serialises this tree into * the snippet people copy, so translating it would change the code people paste * — the same rule that keeps `solid` and `xs` English in every locale. The Figma * tab uses the canvas's own `Label` for the same reason. */ const TEXT = 'Badge'; const TABS = ['gallery', 'figma'] as const; export function BadgesPage() { const m = useMessages(); return ( m.badges.tabs[name]} views={{ gallery: , figma: }} /> ); } function GalleryView() { const m = useMessages(); const [query, setQuery] = useParam('q', ''); const [palette, setPalette] = useParam('palette', 'all'); const [size, setSize] = useParam('size', 'all'); const [slots, setSlots] = useParam('icons', 'both'); const iconSlots: IconSlots = ICON_SLOTS.includes(slots as IconSlots) ? (slots as IconSlots) : 'both'; const showLeft = iconSlots === 'both' || iconSlots === 'left'; const showRight = iconSlots === 'both' || iconSlots === 'right'; const needle = query.trim().toLowerCase(); const nothingMatches = needle.length > 0 && !EVERY_TOKEN.some((token) => token.includes(needle)); const variants = React.useMemo(() => narrow(VARIANTS, needle), [needle]); const shorthands = React.useMemo(() => narrow(SHORTHANDS, needle), [needle]); const palettes = React.useMemo(() => { const chosen = palette === 'all' ? PALETTES : PALETTES.filter((p) => p === palette); return narrow(chosen, needle); }, [palette, needle]); const sizes = React.useMemo(() => { const chosen = size === 'all' ? SIZES : SIZES.filter((s) => s === size); return narrow(chosen, needle); }, [size, needle]); /* One cell builder per section, so a snippet cannot describe a badge other than the one on screen. */ const composable = (variant: Variant, colorPalette: Palette, cellSize: Size) => ( {showLeft ? : null} {TEXT} {showRight ? : null} ); /* No `colorPalette` here on purpose — these five read the semantic roles and would ignore it, so passing one would put a lie in the copied snippet. */ const pinned = (variant: Shorthand, cellSize: Size) => ( {showLeft ? : null} {TEXT} {showRight ? : null} ); return (
{/* Native controls on purpose: a page documenting the kit must not document it with itself, or a broken component hides its own bug. */} setQuery(e.target.value)} placeholder={m.badges.searchPlaceholder} className="h-9 min-w-52 flex-1 rounded-md border border-input bg-background px-2 text-sm" />
{nothingMatches ? (

{m.badges.empty(query)}

) : ( <>

{m.badges.composableTitle}

{m.badges.composableLead}

{variants.map((variant) => ( composable(variant, col as Palette, row as Size)} cellTitle={(row, col) => `variant=${variant}, size=${String(row)}, colorPalette=${String(col)}` } copyAll={false} /> ))}

{m.badges.shorthandTitle}

{m.badges.shorthandLead}

pinned(row as Shorthand, col as Size)} cellTitle={(row, col) => `variant=${String(row)}, size=${String(col)}`} copyAll={false} /> )}
); }