import type { ReactNode } from 'react'; /** * The showcase's data model. * * A registry entry describes a component the way CBAR's Figma file describes a * component set: a name, a set of *axes* (Size, Variant, colorPalette, …) and * the grids you want those axes laid out in. `` then renders an axis * pair as rows × columns — the same shape the component set has on the canvas, * which is the whole reason this exists next to Storybook. */ /** One value a prop can take. Kept wide so booleans can be an axis too. */ export type AxisValue = string | number | boolean; /** * The kit's palette axis. * * Re-exported from `./axis-values` — which derives it from `@/lib` — rather * than restated here. Components whose props are a discriminated union * (Accordion) or that require a value (RadioGroupItem) cannot take a spread * `PropBag`, so their entries pass props one by one and need this name. */ export type { Palette } from './axis-values'; /** `{ variant: ['solid', …], size: ['xs', …] }` — a prop and everything it accepts. */ export type Axes = Record; /** Props handed to `Entry.render`. */ export type PropBag = Record; /** One grid: two axes crossed, every other axis pinned. */ export interface MatrixSpec { /** Heading above the grid. Defaults to `" × "`. */ title?: string; /** Prose under the heading — say why this pairing is worth looking at. */ description?: string; /** Axis walked down the rows. */ rows: string; /** Axis walked across the columns. Omit for a single column. */ cols?: string; /** Props held fixed for every cell, on top of `Entry.defaults`. */ pin?: PropBag; } /** * What a consumer would import to render the entry. * * Stored as a subpath and a name list rather than as the finished line, because * the package name is not the registry's to know: `chrome/lib/package-name.ts` reads * it out of `package.json`, so a rebranded kit prints its own imports on all 40 * pages instead of the scaffold's `cbar-uikit-plate`. `chrome/lib/import-line.ts` * renders it, in the same shape `chrome/lib/codegen.ts` prints a snippet's imports. * * The order of `names` is the reading order — outer part first * (`Accordion, AccordionItem, AccordionTrigger, AccordionContent`), not * alphabetical — so it is written out rather than sorted. */ export interface ImportSpec { /** Published subpath: `button`, `date-picker`, `dropdown-menu`. */ subpath: string; names: readonly string[]; } /** A hand-built example — anything a props matrix cannot express. */ export interface Composition { name: string; description?: string; render: () => ReactNode; /** * The copy text for this example, verbatim, instead of serialising what it * renders. * * Almost every composition wants the default: the tree on screen *is* the * code, which is the rule the whole showcase runs on. Toast is the exception * that made this necessary — a toast is produced by a function call, and the * only component that can draw one statically is internal to the kit, so * `snippetOf` would emit an import line that does not resolve. */ snippet?: string; } /** * One row of the props table. * * Rows are **generated** from the TypeScript types by `scripts/gen-props.mjs` * into `props.generated.json`; `Entry.props` then overrides a generated row by * name or adds one the generator could not see. `propsFor()` in `./props.ts` * does the merge and explains where each half comes from. */ export interface PropDoc { name: string; type: string; default?: string; /** Absent on a hand-written row means "not required", which is the common case. */ required?: boolean; description: string; } /** * Ties an entry to a component set in CBAR's Figma file so the parity page can * diff the two. * * Set names are matched case-insensitively but are otherwise verbatim — the * file is inconsistent (`Button`, `avatar`, `tabsList`, `Accordion Item`), and * normalising them here would hide which name to look for on the canvas. */ export interface FigmaLink { /** Component set name exactly as `figma_sets` reports it. */ set: string; /** * Node id, when it is worth having for a `figma_node` dump. * * Rarely needed: `figma-spec.json` carries the set's id, so the Figma panel * looks it up by name rather than being told. */ node?: string; /** * Figma axis name → the entry's prop name, for the ones that differ. * Badge's axis is `color`, Switch's are `Size`/`State`, and so on. */ axisMap?: Record; /** * Figma axis → (kit value → Figma value), for the values that differ. * * CBAR's yellow ramp is `third` where the kit says `tertiary`, and a boolean * prop often maps onto a named state (`disabled: true` → `state=disabled`). * The Figma panel walks this to find the variant node matching the * playground; `compare()` walks it too, so a renamed value reads as a match * rather than a gap on both sides. */ valueMap?: Record>; /** * Figma axis → a fixed value, for an axis the kit has no prop for. * * Says which cell of the set the component corresponds to — `state: 'default'` * on everything that draws its states in CSS. */ pin?: Record; /** * Figma axes named like a CSS state that are really a value axis. * * `compare()` treats an axis called `state` as an interaction concern and * excludes it, which is right for the fourteen sets whose `state` holds * `default`/`hover`/`focused`/`disabled`. Toast is the exception: its `state` * holds `success`/`error`/`warning`/`info`/`neutral` — statuses, the same * thing Alert calls `status` and Badge calls `color` — so without this the * component's only axis was silently dropped from the report. * * Naming the axis here is an explicit opt-out rather than a cleverer * heuristic, because `axisMap` cannot carry the signal: `registry/button.tsx` * already maps `state` onto its `disabled` prop while genuinely being a CSS * state, so letting `axisMap` win would misreport Button. */ statusAxes?: readonly string[]; } /** Measurements straight from the Figma component set. */ export interface SpecTable { caption?: string; head: readonly string[]; rows: readonly (readonly (string | number)[])[]; } export interface Entry { /** URL segment — `#/button`. */ slug: string; /** Display name, matching the exported component. */ name: string; /** Sidebar grouping. */ group: string; /** One or two sentences: what it is and when to reach for it. */ description: string; /** What a consumer imports, shown as a copyable line. */ imports: ImportSpec; /** Where it comes from in CBAR's file, when there is a counterpart. */ figma?: FigmaLink; /** Props that take a closed set of values. Drives both the playground and the matrices. */ axes?: Axes; /** Starting props for the playground and the baseline for every matrix cell. */ defaults?: PropBag; /** Renders one instance. Required for `axes` and `matrices` to mean anything. */ render?: (props: PropBag) => ReactNode; matrices?: readonly MatrixSpec[]; compositions?: readonly Composition[]; props?: readonly PropDoc[]; spec?: SpecTable; }