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: () => (
),
},
{
name: 'Destructive confirmation',
render: () => (
),
},
],
};
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) => (
FiltersNarrow 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 settingsDrag 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: () => (
DimensionsApplies 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 sidebarCompact rowsSign 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'],
],
},
};