import type { CategoricalIndex, ControlSize } from "@charcuterie/tokens"; import { type ReactNode } from "react"; /** * Where one portrait's colour comes from. Two arms, and the fleet * has both — the same split * [`cardAccentEdge.ts`](../Card/cardAccentEdge.ts) already draws, * for the same reason. * * - **`categorical`** — one of the ten audited hues. A colour the * library owns, re-themed by `data-variant`, gated in both * schemes. Right whenever the colour is a *choice from what we * offered*. * - **`color`** — any CSS colour, from data. Right when the colour * **arrived from the world** and re-theming it would be lying * about a physical object. points-market is exactly this: each * kid's colour matches the NFC card they tap, by the workspace's * 2026-07-20 `kid-identity-colors-match-nfc-cards` record. Ten * palette hues cannot represent that, and swapping them in would * have made the app disagree with the cards on the fridge. * * Neither is a default. A portrait that names no colour takes its * position's entry in `CATEGORICAL_SEQUENCE` — the palette walked so * neighbours contrast, rather than the hue-ordered ring, which would * put red beside orange. */ type PortraitTileColour = { categorical?: CategoricalIndex; color?: never; } | { categorical?: never; color: string; }; export type PortraitTileItem = PortraitTileColour & { /** * The quiet line under the number, naming its unit — "points", * "episodes left", "due today". * * Inside the tile, so it is part of the accessible name. "1,240" * on its own tells a screen-reader user nothing. */ hint?: ReactNode; /** Makes the portrait a real ``. Same rules as `ActionTiles`. */ href?: string; /** * A picture of the subject. Falls back to {@link initials} when * absent, and **also** when the file 404s — a broken portrait grid * is the one failure this shape cannot absorb. */ imageSrc?: string; /** * Drawn in the hue's solid fill when there is no picture. One or * two characters; longer strings are the caller's problem to * shorten, because "which letters" is a naming question this * component cannot answer (a mononym, a family that shares an * initial). */ initials?: string; isDisabled?: boolean; /** Opens in a new tab, and says so to a screen reader. `href` only. */ isExternal?: boolean; /** The name. */ label: ReactNode; /** * The one number this portrait is chosen by — a balance, a count, * a streak. Optional: a picker that is only ever "who are you" * passes none and the tile closes up around the name. */ stat?: ReactNode; /** Identity. The React key, and what `onChoose` is handed. */ value: string; }; /** * How a portrait arranges itself. * * - `auto` — a **row** while the set is narrow, a **column** once * it has room. The switch is a container query on the set, not a * media query: a picker in a 320px sidebar is a list of rows on a * 2560px monitor, and the same picker filling a phone is a column * per screenful. * - `row` / `column` — fixed, for a layout that has already decided. */ export type PortraitTilesLayout = "auto" | "column" | "row"; export type PortraitTilesProps = { className?: string; items: readonly PortraitTileItem[]; /** The set's accessible name. Required. */ label: string; layout?: PortraitTilesLayout; /** The narrowest a portrait track may be, in CSS px. */ minTileInlineSize?: number; /** Fill available rows and center an incomplete final row without widening its tiles. */ isFillingRow?: boolean; /** What `isExternal` announces. Not shown. */ newTabLabel?: string; /** Fired by a portrait that carries no `href`. */ onChoose?: (value: string) => void; size?: ControlSize; }; /** * A set of PEOPLE — or of anything else picked by its face — drawn * as round pictures with a name and one big number. * * points-market's "Who's shopping?" is the shape this is taken from, * and it is deliberately **not** an `ActionTiles` variant. The two * answer different questions: * * - `ActionTiles` asks *what do you want to do* — a name, a line of * help, an icon that illustrates the verb. * - This asks *who is this* — a face, and one number that decides * it. There is no description, because a person is not explained. * * Collapsing them would mean one component whose icon is sometimes a * 20px glyph beside the title and sometimes a 144px circle above it, * with `hint` meaning "what this does" in one mode and "points" in * the other. That is two components wearing one name, which is the * mistake the * [2026-08-25 record](../../../../docs/decisions/2026-08-25-a-choice-tile-is-a-radiogroup-shape-not-a-third-component.md) * warns about from the other direction. * * What they DO share is the box — `TILE_BOX_CLASS` and the same * `auto-fill` grid — so a page carrying both does not show two * different cards. * * ### It scales, and the scaling is the point * * The owner's one requirement when this moved into the library: * *"that one is meant to scale. I don't wanna lose that * functionality."* Two axes carry it. * * 1. **The container.** `layout="auto"` is a row while the set is * narrow and a column once it has room, off a `cq-sm` query on * the set itself. Nothing reads the viewport, so the same picker * works in a sidebar and on a wall. * 2. **The density.** Every length is a token, so `data-density="kiosk"` * grows the type ramp and the control heights under it. points-market * runs `kiosk` today; that is where its big numbers come from, * and it keeps them. * * ### The colour is the person's, not the position's * * A positional hue is the default because it costs a call site * nothing. It is also the wrong default for a household: add a * fifth member and the first four change colour, which is exactly * the identity the picker was using to be fast. Any set whose * subjects persist should name its colour per subject. * * There are two ways to name one, and the difference is where the * colour came from: * * - **`categorical`** — an index into the ten audited hues. A * choice from what the library offered, so the library keeps * owning it: re-themed by `data-variant`, contrast-gated in both * schemes. The same call Docket makes for a project. * - **`color`** — any CSS colour, straight from data. For a colour * the system does **not** own and cannot re-theme without lying: * points-market's kids are coloured to match the NFC cards they * tap, so the picker and the cards on the fridge have to agree, * and ten palette hues cannot promise that. * * The second arm is the narrow one. Reach for it only when the * colour is a fact about the subject rather than a decision about * the design — the same line `Swatch` draws, for the same reason. */ export declare const PortraitTiles: ({ className, items, label, layout, minTileInlineSize, isFillingRow, newTabLabel, onChoose, size, }: PortraitTilesProps) => ReactNode; export {}; //# sourceMappingURL=PortraitTiles.d.ts.map