{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sheet",
  "title": "Sheet",
  "description": "A drawer/modal that slides from an edge — the mobile inspect backbone (bottom sheet) and side drawer.",
  "registryDependencies": [
    "@engram/cn",
    "@engram/tokens"
  ],
  "files": [
    {
      "path": "src/components/sheet.tsx",
      "content": "\"use client\";\n\nimport { type ReactNode, useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/cn.js\";\n\nexport type SheetSide = \"bottom\" | \"top\" | \"left\" | \"right\";\n\nexport interface SheetProps {\n  open: boolean;\n  onClose: () => void;\n  /** Edge the sheet slides from. Default bottom (the mobile inspect pattern). */\n  side?: SheetSide;\n  children: ReactNode;\n  /** Accessible label for the dialog. */\n  label?: string;\n  className?: string;\n}\n\nconst SIDE: Record<SheetSide, string> = {\n  bottom: \"inset-x-0 bottom-0 max-h-[85vh] border-t\",\n  top: \"inset-x-0 top-0 max-h-[85vh] border-b\",\n  right: \"inset-y-0 right-0 w-[min(420px,92vw)] border-l\",\n  left: \"inset-y-0 left-0 w-[min(420px,92vw)] border-r\",\n};\n\n/**\n * A drawer/modal that slides from an edge — the mobile inspect backbone (bottom\n * sheet) and side drawer. Backdrop frosts the page (translucency, not a drop\n * shadow); Escape and backdrop click close it; scroll is locked while open.\n * Portaled to <body>. Controlled via `open`/`onClose`.\n */\nexport function Sheet({\n  open,\n  onClose,\n  side = \"bottom\",\n  children,\n  label,\n  className,\n}: SheetProps) {\n  const [mounted, setMounted] = useState(false);\n  const panelRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => setMounted(true), []);\n\n  useEffect(() => {\n    if (!open) return;\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") onClose();\n    };\n    document.addEventListener(\"keydown\", onKey);\n    const prevOverflow = document.body.style.overflow;\n    document.body.style.overflow = \"hidden\";\n    panelRef.current?.focus();\n    return () => {\n      document.removeEventListener(\"keydown\", onKey);\n      document.body.style.overflow = prevOverflow;\n    };\n  }, [open, onClose]);\n\n  if (!mounted || !open) return null;\n\n  return createPortal(\n    <div className=\"fixed inset-0 z-50\">\n      <button\n        type=\"button\"\n        aria-label=\"Close\"\n        tabIndex={-1}\n        onClick={onClose}\n        className=\"absolute inset-0 bg-black/55 backdrop-blur-[2px]\"\n      />\n      <div\n        ref={panelRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        aria-label={label}\n        tabIndex={-1}\n        className={cn(\n          \"absolute overflow-auto border-engram-border-strong bg-engram-surface outline-none\",\n          SIDE[side],\n          className,\n        )}\n      >\n        {children}\n      </div>\n    </div>,\n    document.body,\n  );\n}\n",
      "type": "registry:component",
      "target": "components/engram/sheet.tsx"
    }
  ],
  "meta": {
    "level": "component"
  },
  "docs": "Extend without forking: edit the copied source, use `asChild` (Radix Slot) to change the rendered element, pass the typed `annotations` prop for curated data (verdict/tags/per-plug), or use slot / render-prop props for arbitrary content. Requires the @engram/tokens theme (--engram-* CSS variables).",
  "categories": [
    "interaction"
  ],
  "type": "registry:component"
}