import * as React from 'react'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/avatar'; import type { AvatarProps } from '@/components/avatar'; import avatarSample from '../assets/avatar-sample.svg'; import { MatrixGrid } from '~/chrome/ui/matrix'; import { useMessages } from '../i18n'; import { AVATAR_VARIANTS } from '../registry/avatar'; import type { Complete, Palette } from '../registry/axis-values'; import { useParam } from '../router'; /** * The avatar set as CBAR's canvas lays it out. * * Read off the set's own `absoluteBoundingBox` values (node `2446:757`, 150 * variants), which is the reverse of the Button set's arrangement: * * - **x is `variant`**, in three clusters 329px apart — `solid(1988) → * subtle(2317) → outline(2646)`, which is already `AVATAR_VARIANTS`' order. * Inside a cluster x steps by **size**, largest first: `xl → lg → md → sm → * xs → 2xs`, at 77/61/53/49/45px, each one the box width plus a 13px gutter. * - **y is `colorPalette`** — `primary(376) → secondary(584) → third(802) → * neutral(1336)`. Inside a band y steps by **`shape`**, 94px apart, square * first. * * So a band is drawn here as three grids side by side, one per treatment, each * `shape` down and `size` across. The grids scroll sideways rather than * reflowing, because the ladder shrinking left to right is the thing the canvas * is showing. * * `avatar-type=avatar` is the fifth axis and it is not a full cross-product: * the file draws it six times only, at `solid / square / third`, once per rung * (y=1006, inside the third band). It is rendered at the end of that band for * the same reason. The kit expresses it as a child rather than as a prop, which * is why `registry/avatar.tsx` pins it to `initials` for the parity report. * * Like Alert and unlike Button, nothing here is faked: an avatar is not * interactive, so every prop shown is a real prop and `MatrixGrid` needs no * `code` override to strip a forced class. */ /** * CBAR's band order, paired with the kit palette that actually paints it. * * The pairing is read off the fills, not off the names — and on this set the * two disagree. Per band, `solid` fill / `subtle` fill / `outline` border: * * primary #4BC7B5 #CFF3EC #CFF3EC secondary-500 / -100 / -100 * secondary #004976 #D6EAF4 #D6EAF4 brand-500 / -100 / -100 * third #0082BA tertiary-500 * neutral #333333 neutral-700 * * `primary` and `secondary` are crossed, exactly as they are on the Button set: * CBAR's `colorPalette=primary` binds `surface/colored/secondary` and vice * versa, while the variable table says `brand/primary/500 = #004976`. The kit * is the correct side and is not changed for it — finding 1 on * `/parity?tab=findings` has the evidence and the two ways the design file * could fix it. `third` and `neutral` are plain renames and match. * * `red`, `green` and `yellow` are the kit palettes with no band here; they are * still on the Gallery tab, which sweeps all seven. */ const PALETTE_BANDS = [ { band: 'primary', palette: 'secondary' }, { band: 'secondary', palette: 'primary' }, { band: 'third', palette: 'tertiary' }, { band: 'neutral', palette: 'black' }, ] as const satisfies readonly { band: string; palette: Palette }[]; /** The two bands whose Figma name is not the palette that paints them. */ const SWAPPED: ReadonlySet = new Set(['primary', 'secondary']); /** * The size ladder in canvas order — largest first, the reverse of the kit's own * `AVATAR_SIZES`. Restated rather than reversed at runtime so the coverage * check below reads off a literal, the same reason `buttons-figma.tsx` keeps * its own list. */ const FIGMA_SIZES = ['xl', 'lg', 'md', 'sm', 'xs', '2xs'] as const; /** Square first: the canvas draws it on the band's own y, the round one at +94. */ const FIGMA_SHAPES = ['square', 'circle'] as const; type Variant = (typeof AVATAR_VARIANTS)[number]; type Size = (typeof FIGMA_SIZES)[number]; type Shape = (typeof FIGMA_SHAPES)[number]; /* A rung or a shape added to the kit becomes a compile error here rather than a column that quietly stops being drawn. */ export type _SizesCovered = Complete>; export type _ShapesCovered = Complete>; /** * The canvas's own initials, not the registry entry's `CB`. * * Deliberately not an i18n key: `chrome/lib/codegen.ts` serialises this exact tree * into the snippet people copy, and two-letter initials are demo data rather * than copy — the same rule that keeps `solid` and `xl` English in every locale. */ const INITIALS = 'AX'; function Field({ label, children }: { label: string; children: React.ReactNode }) { return ( ); } export function FigmaLayoutView() { const m = useMessages(); const [palette, setPalette] = useParam('palette', 'all'); const bands = React.useMemo( () => (palette === 'all' ? PALETTE_BANDS : PALETTE_BANDS.filter((b) => b.band === palette)), [palette] ); const cell = (variant: Variant, colorPalette: Palette, size: Size, shape: Shape) => ( {INITIALS} ); /* The sixth axis, drawn only at solid/square/third. The fallback stays under the image so the cell still says something before the file resolves. */ const imageCell = (size: Size) => ( {INITIALS} ); return (

{m.avatars.figma.lead}

{m.avatars.figma.bandNote}

{m.avatars.figma.sizeNote}

{bands.map((band) => (

colorPalette="{band.band}" ({band.palette})

{SWAPPED.has(band.band) ? (

{m.avatars.figma.swapNote}

) : null}
{/* One block per treatment, laid out across the way the canvas lays them out — and scrolling rather than wrapping, so the ladder stays readable as a ladder. */}
{AVATAR_VARIANTS.map((variant) => (
cell(variant, band.palette, col as Size, row as Shape)} cellTitle={(row, col) => `variant=${variant}, shape=${String(row)}, size=${String(col)}, colorPalette=${band.palette}` } copyAll={false} />
))}
{band.band === 'third' ? (

{m.avatars.figma.imageNote}

imageCell(col as Size)} cellTitle={(_row, col) => `avatar-type=avatar, size=${String(col)}, variant=solid, shape=square, colorPalette=${band.palette}` } copyAll={false} />
) : null}
))}
); }