{
  "name": "overlay",
  "type": "registry:ui",
  "title": "Overlay",
  "description": "The one mount above z-float: modal (460/640px) or bare. Focus trap, Esc, scroll lock, stage shadow, scale-in. Overlays never stack two deep.",
  "dependencies": [
    "@candlelight-ds/tokens",
    "@radix-ui/react-dialog",
    "lucide-react",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "components/ui/Overlay.tsx",
      "type": "registry:ui",
      "content": "'use client'\n\n/**\n * Overlay — the one mount point for floating content above z-float.\n * Radix Dialog mechanics (focus trap, Escape, scroll lock), candlelight\n * scrim, 18px title (display is scale — there is no second face).\n *\n * Variants: `sm`/`lg` modals (forms, confirmations; 460/640px) · `bare`\n * (full-custom panels like help / pre-flight).\n *\n * Use when: the user must address something before continuing on this\n * surface. Modal is the last resort, not the first thought.\n * Don't use when: editing an object adjacent to a list — that's Sheet.\n * Rule: overlays never stack two deep. A modal needing a picker uses an\n * inline combobox, not another modal.\n */\n\nimport * as Dialog from '@radix-ui/react-dialog'\nimport { X } from 'lucide-react'\n// The law numbers live in ONE home (spec §8) — import, never re-declare.\nimport {\n  MODAL_BARE_MAX_W,\n  MODAL_LG_MAX_W,\n  MODAL_SM_MAX_W,\n} from '@candlelight-ds/tokens/metrics'\nimport { cn } from '@/lib/utils'\n\ninterface OverlayProps {\n  open: boolean\n  onOpenChange: (open: boolean) => void\n  title: string\n  /** Visually hide the title (still announced to screen readers). */\n  hideTitle?: boolean\n  size?: 'sm' | 'lg' | 'bare'\n  /** Block backdrop-click dismissal (typed-confirm flows). */\n  locked?: boolean\n  children: React.ReactNode\n  /** Footer slot — actions row, separated by a hairline. */\n  footer?: React.ReactNode\n  /** Copy (§9) — the close verb reaches your copy module. */\n  closeLabel?: string\n  className?: string\n}\n\nexport function Overlay({\n  open,\n  onOpenChange,\n  title,\n  hideTitle = false,\n  size = 'sm',\n  locked = false,\n  children,\n  footer,\n  closeLabel = 'Close',\n  className,\n}: OverlayProps) {\n  return (\n    <Dialog.Root open={open} onOpenChange={onOpenChange}>\n      <Dialog.Portal>\n        <Dialog.Overlay className=\"fixed inset-0 z-overlay bg-stage/80 backdrop-blur-[2px]\" />\n        <Dialog.Content\n          onPointerDownOutside={locked ? (event) => event.preventDefault() : undefined}\n          onInteractOutside={locked ? (event) => event.preventDefault() : undefined}\n          className={cn(\n            'fixed left-1/2 top-1/2 z-overlay w-full -translate-x-1/2 -translate-y-1/2',\n            'rounded-md border border-hairline bg-surface-2 shadow-stage animate-scale-in',\n            'focus:outline-none',\n            className\n          )}\n          style={{\n            maxWidth:\n              size === 'lg'\n                ? MODAL_LG_MAX_W\n                : size === 'bare'\n                  ? MODAL_BARE_MAX_W\n                  : MODAL_SM_MAX_W,\n          }}\n        >\n          {size === 'bare' ? (\n            <>\n              <Dialog.Title className=\"sr-only\">{title}</Dialog.Title>\n              {children}\n            </>\n          ) : (\n            <>\n              <div\n                className={cn(\n                  'flex items-center justify-between hairline-b px-6 py-4',\n                  hideTitle && 'sr-only'\n                )}\n              >\n                <Dialog.Title className=\"text-overlay-title text-ink\">{title}</Dialog.Title>\n                <Dialog.Close\n                  aria-label={closeLabel}\n                  className=\"flex h-7 w-7 items-center justify-center rounded-sm text-ink-faint transition-colors hover:bg-fill-hover hover:text-ink\"\n                >\n                  <X className=\"h-4 w-4\" />\n                </Dialog.Close>\n              </div>\n              <div className=\"max-h-[60vh] overflow-y-auto scrollbar-custom px-6 py-5\">\n                {children}\n              </div>\n              {footer ? (\n                <div className=\"flex justify-end gap-2 hairline-t px-6 py-4\">\n                  {footer}\n                </div>\n              ) : null}\n            </>\n          )}\n        </Dialog.Content>\n      </Dialog.Portal>\n    </Dialog.Root>\n  )\n}\n",
      "target": "components/ui/Overlay.tsx"
    }
  ],
  "meta": {
    "candlelight": {
      "category": "primitive",
      "gRules": [
        "G1"
      ],
      "useWhen": "The user must address something before continuing on this surface — a blocking decision.",
      "dontUseWhen": "Editing an object adjacent to a list (Sheet) or a whole workflow (a route). Modal is the last resort, not the first thought.",
      "states": [
        "closed",
        "open sm (460px)",
        "open lg (640px)",
        "bare",
        "locked (typed-confirm)"
      ],
      "keyboard": [
        {
          "keys": "Esc",
          "does": "closes (unless locked)"
        },
        {
          "keys": "Tab",
          "does": "cycles inside the trap"
        }
      ],
      "acceptanceTests": [
        "Overlays never stack two deep — a modal needing a picker uses an inline combobox.",
        "Scrim is stage/80 with 2px blur; the panel wears shadow-stage."
      ]
    }
  },
  "registryDependencies": [
    "utils"
  ]
}