import * as React from 'react'; import { Button } from '@/components/button'; import { Toast, toast } from '@/components/toast/toast'; import type { ToastKind } from '@/components/toast/toast'; import { FilterField, narrow } from '~/chrome/ui/axis-filter'; import { CopyBlock } from '~/chrome/ui/copy'; import { GalleryPage } from '~/chrome/ui/gallery-page'; import { MatrixGrid } from '~/chrome/ui/matrix'; import { PACKAGE } from '~/chrome/lib/package-name'; import { TOAST_POSITIONS, useToastPosition } from '~/chrome/hooks/use-toast-position'; import { useMessages } from '../i18n'; import { TOAST_KINDS } from '../registry/overlays'; import { useParam } from '../router'; import { FigmaLayoutView, toastCall, toastDemo } from './toasts-figma'; /** * Every toast the kit can draw, on one page, in two shapes — the same split * `/buttons`, `/alerts`, `/avatars` and `/badges` use. * * Toast is the odd one out among them, and the page is shaped around why: its * kind is not a prop. You do not render a toast, you call `toast.success(…)` * and a store hands it to the one `` mounted near the app root. So * the Gallery comes in two sections that answer two different questions — * *what does each kind look like* (a static grid) and *how do you actually use * it* (live, imperative, exactly as a consumer would write it). * * **Figma layout** is CBAR's shape — a single column, one row per state, in * canvas order. It lives in `./toasts-figma` and covers all five variants the * set declares. * * The static grids draw the kit's internal `Toast` with `duration={Infinity}`, * because nothing in the published surface can put five toasts side by side. * That is why both grids pass `snippet` rather than letting the copy button * serialise what it sees: the copied text is the call, never ``. */ type Kind = (typeof TOAST_KINDS)[number]; const EVERY_TOKEN: readonly string[] = [...TOAST_KINDS]; const TABS = ['gallery', 'figma'] as const; export function ToastsPage() { const m = useMessages(); return ( m.toasts.tabs[name]} views={{ gallery: , figma: }} /> ); } function GalleryView() { const m = useMessages(); const [query, setQuery] = useParam('q', ''); const [closeParam, setClose] = useParam('close', ''); const closeButton = closeParam === '1'; const needle = query.trim().toLowerCase(); const nothingMatches = needle.length > 0 && !EVERY_TOKEN.some((token) => token.includes(needle)); const kinds = React.useMemo(() => narrow(TOAST_KINDS, needle), [needle]); /* Same static path as the Figma tab: `Infinity` skips the dismiss timer outright, and the `
    ` gives the `
  1. ` the list parent axe expects. */ const cell = (kind: Kind) => (
    ); return (
    {/* Native controls on purpose: a page documenting the kit must not document it with itself, or a broken component hides its own bug. */} setQuery(e.target.value)} placeholder={m.toasts.searchPlaceholder} className="h-9 min-w-52 flex-1 rounded-md border border-input bg-background px-2 text-sm" /> setClose(e.target.checked ? '1' : '')} className="size-4 rounded border-input" />
    {nothingMatches ? (

    {m.toasts.empty(query)}

    ) : ( <>

    {m.toasts.kindsTitle}

    {m.toasts.kindsLead}

    cell(row as Kind)} snippet={(row) => toastCall(row as ToastKind)} cellTitle={(row) => `kind=${String(row)}`} copyAll={false} />

    {m.toasts.liveTitle}

    {m.toasts.liveLead}

    {m.toasts.position.title}

    {m.toasts.position.lead}

    )}
    ); } /** * The imperative half — the API a consumer actually writes. * * Nothing here pins a toast (`duration: Infinity`) and nothing fires on mount. * The store is a module singleton, so a pinned toast would follow the visitor * onto every other page in the showcase; letting them expire on their own is * both the honest demo and the safe one. No `` either — `main.tsx` * mounts the only one the showcase needs. */ function LiveView() { const m = useMessages(); return (
    ); } /** Where the bar sits inside its cell — a miniature of the corner it stands for. */ const CELL_ALIGN: Record<(typeof TOAST_POSITIONS)[number], string> = { 'top-left': 'items-start justify-start', 'top-center': 'items-start justify-center', 'top-right': 'items-start justify-end', 'bottom-left': 'items-end justify-start', 'bottom-center': 'items-end justify-center', 'bottom-right': 'items-end justify-end', }; /** * The corner, which is the one part of a toast that is not chosen at the call * site. * * `position` belongs to the `Toaster`, so this section writes it into the hash * and the showcase's own mounted `Toaster` follows — see `chrome/ui/toaster.tsx` * for why a second one on this page would have duplicated every toast instead * of moving it. Picking a corner therefore *is* the demo: the button below * raises a real toast, and it appears where the diagram says it will. * * The picker is a native radio group inside a fieldset. Each cell's accessible * name is the prop value itself (`top-left`), untranslated like every other * axis value in the showcase, because it is the string a consumer types. */ function PositionView() { const m = useMessages(); const [position, setPosition] = useToastPosition(); const snippet = [ `import { Toaster } from '${PACKAGE}/toast';`, '', '/* One per app, mounted near the root. Every toast() renders into it. */', ``, ].join('\n'); return (
    {m.toasts.position.legend} {/* A stand-in for the viewport: two rows of three, so each cell is the corner it selects and the bar inside it lands where the toast will. */}
    {TOAST_POSITIONS.map((value) => { const active = value === position; return ( ); })}

    {m.toasts.position.note}

    ); }