import * as React from 'react'; import { Avatar, AvatarFallback } from '@/components/avatar'; 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 { AVATAR_SHAPES as SHAPES, AVATAR_SIZES as SIZES, AVATAR_VARIANTS as VARIANTS, } from '../registry/avatar'; import { PALETTES, type Palette } from '../registry/axis-values'; import { useParam } from '../router'; import { FigmaLayoutView } from './avatars-figma'; /** * Every avatar the kit can draw, on one page, in two shapes — the same split * `/buttons` and `/alerts` use. * * **Gallery** is the kit's own shape: a grid per treatment, palettes across, * sizes down. Avatar has a third axis the other two pages do not — `shape` — * so a grid here is pinned to one shape and the filter cuts the set in half * rather than adding a dimension nobody can read. * * **Figma layout** is CBAR's shape — one band per palette, a block per * treatment, shape down and size across — read off the avatar set's own canvas * geometry. It lives in `./avatars-figma`. * * Copying is one cell at a time on both tabs, same reasoning as the other two: * a grid here is a whole sweep, and nobody pastes a sweep. */ type Variant = (typeof VARIANTS)[number]; type Size = (typeof SIZES)[number]; type Shape = (typeof SHAPES)[number]; const EVERY_TOKEN: readonly string[] = [...VARIANTS, ...PALETTES, ...SIZES, ...SHAPES]; /** * The Gallery's demo initials, matching `registry/avatar.tsx`. * * Deliberately not an i18n key: `chrome/lib/codegen.ts` serialises this tree into * the snippet people copy, and two-letter initials are demo data rather than * copy — the same rule that keeps `solid` and `2xs` English in every locale. * The Figma tab uses the canvas's own `AX` for the same reason. */ const INITIALS = 'CB'; const TABS = ['gallery', 'figma'] as const; export function AvatarsPage() { const m = useMessages(); return ( m.avatars.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 [shape, setShape] = useParam('shape', 'all'); 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 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]); const shapes = React.useMemo(() => { const chosen = shape === 'all' ? SHAPES : SHAPES.filter((s) => s === shape); return narrow(chosen, needle); }, [shape, needle]); const cell = (variant: Variant, colorPalette: Palette, cellSize: Size, cellShape: Shape) => ( {INITIALS} ); 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.avatars.searchPlaceholder} className="h-9 min-w-52 flex-1 rounded-md border border-input bg-background px-2 text-sm" />
{nothingMatches ? (

{m.avatars.empty(query)}

) : ( variants.flatMap((variant) => shapes.map((cellShape) => ( cell(variant, col as Palette, row as Size, cellShape)} cellTitle={(row, col) => `variant=${variant}, shape=${cellShape}, size=${String(row)}, colorPalette=${String(col)}` } copyAll={false} /> )) ) )}
); }