import * as React from 'react'; import { Toast } from '@/components/toast/toast'; import type { ToastKind } from '@/components/toast/toast'; import { MatrixGrid } from '~/chrome/ui/matrix'; import { useMessages } from '../i18n'; /* Type-only: the band table below restates the canvas order as a literal, and this keeps it checked against the registry's list. */ import type { FIGMA_TOAST_STATES } from '../registry/overlays'; import { useParam } from '../router'; /** * The Toast set as CBAR's canvas lays it out. * * Read off the set's own `absoluteBoundingBox` values (node `2414:10758`, 5 * variants): every variant sits at x=146 and the only axis is **state**, 90px * apart — `success(382) → error(472) → warning(562) → info(652) → * neutral(742)`. So this is a single column with a row per state, which is why * the grid below passes `cols={[null]}`: one column, no header. * * Each variant is 306px wide with `padding: 12px 16px`, a 6px radius and a 3px * border down the **leading edge only** — `border-left`, not a full outline. * Anatomy is icon / (title + description) / close, gap 10px, the two lines 5px * apart. Like Alert, the set draws no interaction states, so every prop shown * here is a real prop. * * Four things the page says out loud rather than silently copying: * * - The canvas draws the **same** `Solid/Status/Info-circle` glyph on all five * states and only recolours it — verified through the bridge on all five * (`2414:10752`, `2414:10761`, `2416:71`, `2416:80`, `2416:89`, every one an * instance of `2163:653`). The kit was changed to match, so this is no * longer a divergence and this tab forces nothing: `defaultIcons` in * `toast.tsx` is that one glyph and `--ctl-icon` carries the five inks. * - `iconRight?` (the close button) defaults **true** on the canvas, while the * kit's `Toaster.closeButton` defaults false. This tab draws it on for * fidelity; the Gallery tab uses the kit default. * - The canvas frames each toast at a fixed 306px; the kit's column is 384px * and the toast fills it. `figma/css-diff.ts` excludes `width` for exactly * this reason — a Figma variant frames fixed content. * - The kit adds `shadow-lg`. `toast.tsx` already records why. */ /** * CBAR's state order, paired with the kit kind that paints it. * * There is no guesswork here for once: `figma_css` reports the bound variable * names directly, and they line up with `kindPalettes` in `toast.tsx`: * * success --surface-subtle-positive #DCFCE7 / --border-colored-positive #22C55E green * error --surface-subtle-negative #FEE2E2 / --border-colored-negative #F00000 red * warning --surface-subtle-warning #FEF9C3 / --border-colored-warning #F3C67F yellow * info --surface-subtle-secondary #CFF3EC / --border-colored-secondary #A6E8DA secondary * neutral --surface-subtle-gray #F9F9F9 / --border-colored-black #474747 black * * So CBAR's `info` is the turquoise `secondary` — the third set to say so, after * Alert's `info` band and Badge's `default` colour. `loading` is the one kit * kind with no state here; it is on the Gallery tab. */ const STATE_BANDS = [ { state: 'success', kind: 'success' }, { state: 'error', kind: 'error' }, { state: 'warning', kind: 'warning' }, { state: 'info', kind: 'info' }, { state: 'neutral', kind: 'default' }, ] as const satisfies readonly { state: (typeof FIGMA_TOAST_STATES)[number]; kind: ToastKind }[]; /** * `iconLeft?` and `iconRight?` are BOOLEAN properties on the set and both * default to **true**, so every variant on the canvas carries a status glyph on * the left and a close button on the right. The kit expresses the first as the * kind's own icon and the second as `Toaster.closeButton`, so neither is a * per-toast prop — this is a control here instead, starting where Figma starts. */ const ICON_SLOTS = ['both', 'left', 'right', 'none'] as const; type IconSlots = (typeof ICON_SLOTS)[number]; /** * The canvas's own copy, per state. Demo data, so it stays out of the catalogue * and out of translation — the call people copy contains it. * * Four of the five are read straight off the set (`figma_text` on each variant): * `Success` / `Error` / `Warning` / `Info`. The fifth is not — CBAR's `neutral` * variant is also titled `Info`, which is a copy-paste leftover rather than a * decision, so it says `Neutral` here. Same call as the stray outline fill on * the Alert set: replicate the design, not its slips. * * `loading` has no counterpart on the canvas and only appears on the Gallery tab. */ const TITLES: Record = { default: 'Neutral', success: 'Success', info: 'Info', warning: 'Warning', error: 'Error', loading: 'Loading', }; const DESCRIPTION = 'Description'; function Field({ label, children }: { label: string; children: React.ReactNode }) { return ( ); } /** The imperative call that produces the toast a cell is showing. */ const CALLS: Record = { default: 'toast', success: 'toast.success', info: 'toast.info', warning: 'toast.warning', error: 'toast.error', loading: 'toast.loading', }; /** * What the copy button emits, in place of the element on screen. * * This goes through `MatrixGrid`'s `snippet` prop rather than its `code` one: * `code` still runs `snippetOf`, and here there is no JSX worth serialising. * The element on screen is `Toast`, which is **internal to the kit** — * `/toast` publishes `Toaster` and `toast`, nothing else — so serialising * it would hand out an import line that does not resolve. A toast's kind is * chosen by which function you call, so the honest snippet is the call. */ export function toastCall(kind: ToastKind): string { return `${CALLS[kind]}('${TITLES[kind]}', { description: '${DESCRIPTION}' });`; } /** The record both tabs draw, so the two cannot describe different toasts. */ export function toastDemo(kind: ToastKind, idPrefix: string) { return { id: `${idPrefix}-${kind}`, kind, title: TITLES[kind], description: DESCRIPTION }; } export function FigmaLayoutView() { const m = useMessages(); const [state, setState] = useParam('state', 'all'); const [slots, setSlots] = useParam('icons', 'both'); const iconSlots: IconSlots = ICON_SLOTS.includes(slots as IconSlots) ? (slots as IconSlots) : 'both'; const showLeft = iconSlots === 'both' || iconSlots === 'left'; const showRight = iconSlots === 'both' || iconSlots === 'right'; const bands = React.useMemo( () => (state === 'all' ? STATE_BANDS : STATE_BANDS.filter((b) => b.state === state)), [state] ); /* `duration={Infinity}` is what makes a toast sit still: `toast.tsx` skips the dismiss timer entirely at that lifetime, so no cell tries to dismiss a record the store never held. The `
    ` is not decoration — a toast is an `
  1. `, and a stray one fails the axe panel's list-structure rule. */ const cell = (kind: ToastKind) => (
    ); return (

    {m.toasts.figma.lead}

    {m.toasts.figma.bandNote}

    {m.toasts.figma.neutralNote}

    {m.toasts.figma.iconNote}

    {m.toasts.figma.inkNote}

    {m.toasts.figma.closeNote}

    {m.toasts.figma.copyNote}

    b.state)} cols={[null]} cell={(row) => { const band = STATE_BANDS.find((b) => b.state === row); return band ? cell(band.kind) : null; }} snippet={(row) => { const band = STATE_BANDS.find((b) => b.state === row); return band ? toastCall(band.kind) : ''; }} cellTitle={(row) => { const band = STATE_BANDS.find((b) => b.state === row); return `state=${String(row)} (kind=${band?.kind ?? '?'})`; }} copyAll={false} />
    ); }