import * as React from 'react'; import { Button } from '@/components/button'; import { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, } from '@/components/command'; import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/dialog'; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger, } from '@/components/drawer'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from '@/components/dropdown-menu'; import { Input } from '@/components/input'; import { Label } from '@/components/label'; import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, } from '@/components/popover'; import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, } from '@/components/sheet'; import { toast, Toaster } from '@/components/toast'; /* Deep import on purpose: `Toast` is the presentational half and is deliberately absent from the package's own barrel, so the published surface stays `Toaster` + `toast`. The showcase compiles against `src/`, which is the only reason it can reach it — see the note at the bottom of `toast.tsx`. */ import { Toast } from '@/components/toast/toast'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/tooltip'; import { CalendarIcon, SearchIcon } from '@/icons'; import type { Entry } from './types'; /* Overlays get compositions rather than matrices: a variant grid of closed dialogs shows seven identical triggers. Each page opens the real thing. */ const SIDES = ['top', 'right', 'bottom', 'left'] as const; export const dialog: Entry = { slug: 'dialog', name: 'Dialog', group: 'Overlays', description: 'A modal that takes focus until it is answered. Built on Radix Dialog — focus trap, scroll lock and Escape handling are the primitive’s, not ours.', imports: { subpath: 'dialog', names: ['Dialog', 'DialogTrigger', 'DialogContent'] }, compositions: [ { name: 'With a form', render: () => ( Edit profile Changes apply the moment you save.
), }, { name: 'Destructive confirmation', render: () => ( Delete this workspace? Every project, deployment and log inside it goes with it. This cannot be undone. ), }, ], }; export const sheet: Entry = { slug: 'sheet', name: 'Sheet', group: 'Overlays', description: 'A panel that slides in from an edge. Same primitive as Dialog, different geometry — reach for it when the content is a side task rather than a question.', imports: { subpath: 'sheet', names: ['Sheet', 'SheetTrigger', 'SheetContent'] }, compositions: [ { name: 'Every side', render: () => (
{SIDES.map((side) => ( Filters Narrow the list without leaving the page. ))}
), }, ], }; export const drawer: Entry = { slug: 'drawer', name: 'Drawer', group: 'Overlays', description: 'A draggable panel with a grab handle. Built on radix-ui’s Dialog rather than vaul — the drag, the snap and the outward direction are the kit’s own code.', imports: { subpath: 'drawer', names: ['Drawer', 'DrawerTrigger', 'DrawerContent'] }, compositions: [ { name: 'Every direction', render: () => (
{SIDES.map((direction) => ( Quick settings Drag the handle to dismiss. ))}
), }, ], }; export const popover: Entry = { slug: 'popover', name: 'Popover', group: 'Overlays', description: 'Non-modal content anchored to a trigger. Unlike Dialog it does not trap focus, so the page behind it stays usable.', imports: { subpath: 'popover', names: ['Popover', 'PopoverTrigger', 'PopoverContent'] }, compositions: [ { name: 'With a header', render: () => ( Dimensions Applies to the selected frame.
), }, ], }; export const tooltip: Entry = { slug: 'tooltip', name: 'Tooltip', group: 'Overlays', description: 'A short label on hover or focus. In CBAR a tooltip inverts against the page rather than sitting on a surface, which is why it has its own `--tooltip` role instead of reusing `--popover`.', imports: { subpath: 'tooltip', names: ['Tooltip', 'TooltipTrigger', 'TooltipContent', 'TooltipProvider'], }, figma: { set: 'Tooltip' }, compositions: [ { name: 'Every side', description: 'One TooltipProvider around the group shares the open/close delay.', render: () => (
{SIDES.map((side) => ( Opens on {side} ))}
), }, ], }; export const dropdownMenu: Entry = { slug: 'dropdown-menu', name: 'DropdownMenu', group: 'Overlays', description: 'An action menu anchored to a trigger, with checkbox items, radio groups, submenus and shortcut hints.', imports: { subpath: 'dropdown-menu', names: ['DropdownMenu', 'DropdownMenuTrigger', 'DropdownMenuContent', 'DropdownMenuItem'], }, compositions: [ { name: 'Actions and toggles', render: () => ( My account Profile ⇧⌘P Settings ⌘, Show sidebar Compact rows Sign out ), }, ], }; /** * The ⌘K pattern: you own the shortcut, `CommandDialog` owns everything else. * * Named, with an explicit `displayName`, for `chrome/lib/codegen.ts` — see the note * on `ControlledSortDemo` in `registry/data.tsx`. */ function CommandPaletteDemo() { const [open, setOpen] = React.useState(false); React.useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'k' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); setOpen((value) => !value); } }; document.addEventListener('keydown', onKeyDown); return () => document.removeEventListener('keydown', onKeyDown); }, []); return ( <> Nothing matches. setOpen(false)}> Go to dashboard setOpen(false)}> Go to settings ); } CommandPaletteDemo.displayName = 'CommandPaletteDemo'; export const command: Entry = { slug: 'command', name: 'Command', group: 'Overlays', description: 'A filterable command palette. Written locally — cmdk is one of the fifteen dependencies the kit dropped — so it is the kit’s own matcher and keyboard model.', imports: { subpath: 'command', names: ['Command', 'CommandInput', 'CommandList', 'CommandItem'] }, compositions: [ { name: 'Inline palette', render: () => ( Nothing matches. Calendar Search projects Profile ⌘P Billing ⌘B ), }, { name: 'Matching on keywords', description: '`value` is what an item is matched on; `keywords` adds the synonyms its label does not contain. Type “dark” or “logout” — neither word is on screen.', render: () => ( Nothing matches. Toggle theme Sign out Billing (unavailable) ), }, { name: 'As a ⌘K palette', description: 'CommandDialog is the same list inside a Dialog. Binding the shortcut is deliberately left to you — a kit that registers a global key handler on import is a kit that fights the app around it.', render: () => , }, ], }; /** * Toast's kinds, in the kit's own order. Exported so `pages/toasts.tsx` and * `pages/toasts-figma.tsx` read one list — the rule `registry/alert.tsx` and * `registry/badge.tsx` state. * * Unlike every other axis in this registry these are not a prop: a toast's kind * is chosen by which function you call (`toast.success(…)`). The list is still * an axis for the parity report and for the grids on `/toasts`. */ export const TOAST_KINDS = [ 'default', 'success', 'info', 'warning', 'error', 'loading', ] as const; /** * CBAR's five states, in canvas order (top to bottom, 90px apart). * * `neutral` is what the kit calls `default`; `loading` has no counterpart on the * canvas and is the one kit-only kind. */ export const FIGMA_TOAST_STATES = ['success', 'error', 'warning', 'info', 'neutral'] as const; export const toastEntry: Entry = { slug: 'toast', name: 'Toast', group: 'Overlays', description: 'Transient notifications, driven by an imperative `toast()` rather than by rendered state. The queue is a standalone subscribable store, which is what lets `toast.promise` update a toast in place. It depends on no theme library — `theme` defaults to `inherit`.', imports: { subpath: 'toast', names: ['Toaster', 'toast'] }, figma: { set: 'Toast', /* CBAR's axis is called `state` but holds statuses, so it is declared a value axis — see `statusAxes` in `./types.ts`. Without that line the parity report drops Toast's only axis as a CSS concern. */ statusAxes: ['state'], axisMap: { state: 'kind' }, /* Paired by the tokens the set names outright (`--surface-subtle-secondary` for `info`, `--border-colored-black` for `neutral`), which is already what `kindPalettes` in `toast.tsx` maps them to. Only the name differs. */ valueMap: { state: { default: 'neutral' } }, }, /* No `render` on purpose, so no playground and no matrices here: the only component that can draw a toast statically is `Toast`, which is internal to the kit. `chrome/lib/codegen.ts` would serialise it into `import { Toast } from '/toast'` — a line that does not resolve. The grids live on `/toasts`, where a `snippet` override emits the imperative API instead. `chrome/ui/playground.tsx` returns null without a `render`, so this axis costs nothing but gives the parity page something to diff. */ axes: { kind: TOAST_KINDS }, compositions: [ { /* Every other component page opens with the component on screen; this one used to open with five buttons and nothing to look at, because a toast only exists once something calls for it. This draws the five CBAR states standing still — the same `Toast` the live queue renders, pinned with `duration={Infinity}` so no timer runs and nothing is dismissed. `snippet` supplies the copy text: the element here is internal, and the honest thing to paste is the call. */ name: 'Every state', description: "The five states CBAR draws, standing still. One glyph on all of them — Solid/Status/Info-circle — told apart by colour alone, which is the design file's own scheme.", snippet: TOAST_KINDS.filter((kind) => kind !== 'loading') .map((kind) => kind === 'default' ? `toast('Neutral', { description: 'Description' });` : `toast.${kind}('${kind[0].toUpperCase()}${kind.slice(1)}', { description: 'Description' });` ) .join('\n'), render: () => (
    {( [ ['success', 'Success'], ['error', 'Error'], ['warning', 'Warning'], ['info', 'Info'], ['default', 'Neutral'], ] as const ).map(([kind, title]) => ( ))}
), }, { name: 'Every kind', description: 'Mount one near the root of the app; everything else is a function call.', render: () => (
), }, { name: 'Promise', description: 'Pinned while pending, then replaced in place — one toast, not three.', render: () => (
), }, ], spec: { caption: "Measured off CBAR's Toast component set (`2414:10758`) through the Figma bridge — `figma_css` for the box, `figma_text` for the type, the instance frames for the glyph. Every row matches what the kit draws. Two deliberate differences are not in the table: the canvas frames each toast at a fixed 306px where the kit's column is 384px and the toast fills it, and the kit adds `shadow-lg` because a notification floating over live content has to read as elevated where a canvas swatch does not.", head: ['Property', 'Figma', 'Kit'], rows: [ ['Padding', '12px 16px', 'py-3 px-4'], ['Radius', '6px (radius/sm)', 'rounded-sm'], ['Leading edge', '3px left border', 'border-l-3 border-l-(--ctl-border)'], ['Surface', 'subtle, per status', 'bg-(--ctl-subtle)'], ['Icon → text', '10px', 'gap-2.5'], ['Title', '14px / 600', 'text-sm font-semibold'], ['Description', '14px / 28px', 'text-sm leading-7'], ['Title → description', '5px', 'space-y-[5px]'], ['Icon', '20px', 'size-5'], ], }, };