import type { Entry, PropBag } from '../registry/types'; import { norm, type FigmaSet } from './spec'; import { variantKey } from './use-figma'; /** * Turning the playground's props into a Figma variant. * * The two sides do not name things identically and were never going to: CBAR's * axis is `color` where the kit's prop is `colorPalette`, its yellow ramp is * `third` where the kit says `tertiary`, and its `state` axis covers what the * kit draws in CSS. `FigmaLink` carries the three declarations that close the * gap — `axisMap` renames an axis, `valueMap` renames a value, `pin` supplies * an axis the kit has no prop for at all. * * Anything still unresolved falls back to the axis's own `defaultValue` and is * reported as guessed, so the panel can say which parts of the match it chose * rather than derived. */ export interface Resolution { /** Figma axis name → the value picked for it, in the set's own order. */ axes: Record; /** Lookup key for `SetIndex.byKey`. */ key: string; /** `variant=solid, size=md` — how Figma itself names the variant. */ label: string; /** Axes filled from the Figma default because nothing here decided them. */ guessed: string[]; /** * Axes the playground *did* decide, onto a value CBAR does not draw. * * Left out of `axes` rather than defaulted: Button has an `sm` the set jumps * straight past, and silently showing `xl` beside it would be a comparison * against the wrong cell. */ unmatched: { axis: string; value: string }[]; } export function resolveVariant(entry: Entry, set: FigmaSet, props: PropBag): Resolution { const link = entry.figma; const axes: Record = {}; const guessed: string[] = []; const unmatched: { axis: string; value: string }[] = []; for (const prop of set.props) { if (prop.type !== 'VARIANT') continue; const values = prop.values ?? []; const fallback = typeof prop.default === 'string' ? prop.default : values[0]; const accept = (value: string | undefined) => value === undefined ? undefined : values.find((v) => norm(v) === norm(value)); /* A pinned axis is a decision, not a guess: the entry states outright which cell of the set it is demoing. */ const pinned = accept(link?.pin?.[prop.name]); if (pinned) { axes[prop.name] = pinned; continue; } const kitProp = link?.axisMap?.[prop.name] ?? prop.name; const raw = props[kitProp]; const alias = link?.valueMap?.[prop.name]; const mapped = raw === undefined || raw === null ? undefined : alias?.[String(raw)] ?? String(raw); const picked = accept(mapped); if (picked) { axes[prop.name] = picked; continue; } if (mapped !== undefined) { unmatched.push({ axis: prop.name, value: mapped }); continue; } if (fallback !== undefined) { axes[prop.name] = fallback; guessed.push(prop.name); } } return { axes, key: variantKey(axes), label: Object.entries(axes) .map(([k, v]) => `${k}=${v}`) .join(', '), guessed, unmatched, }; }