{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "drawer",
  "title": "Drawer",
  "author": "AlgorizeTech",
  "description": "A panel that slides in from any screen edge, with overlay and GSAP easing. Use it for mobile navigation, filters and detail panels.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/Drawer.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport { X } from \"lucide-react\";\nimport React, {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\n\ntype DrawerSide = \"top\" | \"bottom\" | \"left\" | \"right\";\n\ninterface DrawerContextType {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  side: DrawerSide;\n}\n\nconst DrawerContext = createContext<DrawerContextType | null>(null);\n\n// ==========================================\n// MOTION\n// Opening fades the overlay in (0.3s power2.out) and slides the panel in from\n// its edge (0.4s power3.out); closing reverses both in 0.3s (power2.in /\n// power3.in). A transition takes its timing from the state it is heading to,\n// so each state carries its own. @starting-style is where a freshly mounted\n// layer starts from.\n// ==========================================\nconst POWER3_OUT =\n  \"linear(0, .1268, .2412, .3439, .4358, .5177, .5904, .6545, .7108, .7599, .8025, .8391, .8704, .8969, .9191, .9375, .9526, .9647, .9744, .9819, .9877, .9919, .9949, .997, .9984, .9992, .9997, .9999, 1, 1, 1)\";\nconst POWER3_IN =\n  \"linear(0, 0, 0, .0001, .0003, .0008, .0016, .003, .0051, .0081, .0123, .0181, .0256, .0353, .0474, .0625, .0809, .1031, .1296, .1609, .1975, .2401, .2892, .3455, .4096, .4823, .5642, .6561, .7588, .8732, 1)\";\n\nconst DRAWER_CSS = `\n.drawer-overlay {\n  opacity: 0;\n  transition: opacity 0.3s cubic-bezier(0.3333, 0, 0.6667, 0);\n}\n.drawer-overlay[data-state=\"open\"] {\n  opacity: 1;\n  transition: opacity 0.3s cubic-bezier(0.3333, 1, 0.6667, 1);\n}\n.drawer-content { transition: transform 0.3s ${POWER3_IN}; }\n.drawer-content[data-side=\"top\"] { transform: translateY(-100%); }\n.drawer-content[data-side=\"bottom\"] { transform: translateY(100%); }\n.drawer-content[data-side=\"left\"] { transform: translateX(-100%); }\n.drawer-content[data-side=\"right\"] { transform: translateX(100%); }\n.drawer-content[data-state=\"open\"] {\n  transform: none;\n  transition: transform 0.4s ${POWER3_OUT};\n}\n@starting-style {\n  .drawer-overlay[data-state=\"open\"] { opacity: 0; }\n  .drawer-content[data-state=\"open\"][data-side=\"top\"] { transform: translateY(-100%); }\n  .drawer-content[data-state=\"open\"][data-side=\"bottom\"] { transform: translateY(100%); }\n  .drawer-content[data-state=\"open\"][data-side=\"left\"] { transform: translateX(-100%); }\n  .drawer-content[data-state=\"open\"][data-side=\"right\"] { transform: translateX(100%); }\n}\n`;\n\nexport const Drawer = ({\n  open,\n  onOpenChange,\n  side = \"right\",\n  children,\n}: {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  side?: DrawerSide;\n  children: React.ReactNode;\n}) => {\n  const [renderLayer, setRenderLayer] = useState(open);\n  const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n  const layerRef = useRef<HTMLDivElement>(null);\n\n  // Resolves the document this drawer actually lives in, which is not always\n  // the global one — previews portal components into an iframe. Resolving in a\n  // callback ref rather than reading a ref during render keeps render pure, and\n  // a null target doubles as the \"not mounted yet\" signal that guards SSR.\n  const setAnchor = useCallback((node: HTMLSpanElement | null) => {\n    setPortalTarget(node ? node.ownerDocument.body : null);\n  }, []);\n\n  // Opening must show the layer immediately. Adjusting state during render on a\n  // prop change is React's documented alternative to an effect here, and avoids\n  // the extra render an effect would cost on every open.\n  const [prevOpen, setPrevOpen] = useState(open);\n  if (prevOpen !== open) {\n    setPrevOpen(open);\n    if (open) setRenderLayer(true);\n  }\n\n  useEffect(() => {\n    // Resolve the document the drawer actually lives in (supports iframe rendering).\n    const doc = layerRef.current?.ownerDocument ?? document;\n    if (open) {\n      doc.body.style.overflow = \"hidden\";\n      return () => {\n        doc.body.style.overflow = \"\";\n      };\n    }\n    doc.body.style.overflow = \"\";\n    // Match the closing transition (300ms) before unmounting from DOM\n    const timer = setTimeout(() => setRenderLayer(false), 300);\n    return () => clearTimeout(timer);\n  }, [open, portalTarget]);\n\n  useEffect(() => {\n    const doc = layerRef.current?.ownerDocument ?? document;\n    const handleEsc = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") onOpenChange(false);\n    };\n    if (open) doc.addEventListener(\"keydown\", handleEsc);\n    return () => doc.removeEventListener(\"keydown\", handleEsc);\n  }, [open, onOpenChange, portalTarget]);\n\n  if (!portalTarget || !renderLayer) return <span ref={setAnchor} hidden />;\n\n  return (\n    <>\n      <span ref={setAnchor} hidden />\n      {createPortal(\n        <DrawerContext.Provider value={{ open, onOpenChange, side }}>\n          <div ref={layerRef} className=\"fixed inset-0 z-50 pointer-events-none\">\n            <style>{DRAWER_CSS}</style>\n            {children}\n          </div>\n        </DrawerContext.Provider>,\n        portalTarget,\n      )}\n    </>\n  );\n};\n\nexport const DrawerOverlay = ({\n  className,\n  style,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => {\n  const { open, onOpenChange } = useContext(DrawerContext)!;\n\n  return (\n    <div\n      onClick={() => onOpenChange(false)}\n      data-state={open ? \"open\" : \"closed\"}\n      className={cn(\n        \"drawer-overlay absolute inset-0 bg-black/60 backdrop-blur-sm pointer-events-auto\",\n        className,\n      )}\n      style={style}\n      {...props}\n    />\n  );\n};\n\nexport const DrawerContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, children, style, ...props }, ref) => {\n  const { open, side, onOpenChange } = useContext(DrawerContext)!;\n\n  // Strict layout boundaries that prevent vertical/horizontal drift. Where the\n  // panel hides off-screen for each side lives in DRAWER_CSS.\n  const sideConfig = {\n    top: {\n      class: \"top-0 left-0 right-0 w-full max-h-[85vh] sm:max-h-[80vh] rounded-b-2xl\",\n    },\n    bottom: {\n      class: \"bottom-0 left-0 right-0 w-full max-h-[85vh] sm:max-h-[80vh] rounded-t-2xl\",\n    },\n    left: {\n      class:\n        \"top-0 left-0 bottom-0 h-full w-[400px] max-w-[85vw] rounded-r-2xl\",\n    },\n    right: {\n      class:\n        \"top-0 right-0 bottom-0 h-full w-[400px] max-w-[85vw] rounded-l-2xl\",\n    },\n  };\n\n  return (\n    <div\n      ref={ref}\n      data-state={open ? \"open\" : \"closed\"}\n      data-side={side}\n      className={cn(\n        \"drawer-content absolute bg-background shadow-2xl pointer-events-auto flex flex-col overflow-hidden border border-border\",\n        sideConfig[side].class,\n        className,\n      )}\n      style={style}\n      {...props}\n    >\n      <button\n        onClick={() => onOpenChange(false)}\n        className=\"absolute top-4 right-4 z-50 p-2 bg-muted hover:bg-muted text-muted-foreground rounded-full transition-colors\"\n      >\n        <X className=\"w-4 h-4\" />\n      </button>\n      {children}\n    </div>\n  );\n});\nDrawerContent.displayName = \"DrawerContent\";\n\nexport const DrawerHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"p-6 border-b border-border shrink-0\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const DrawerTitle = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLHeadingElement>) => (\n  <h2\n    className={cn(\n      \"text-lg font-semibold text-foreground\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const DrawerDescription = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLParagraphElement>) => (\n  <p\n    className={cn(\"text-sm text-muted-foreground mt-1.5\", className)}\n    {...props}\n  />\n);\n\nexport const DrawerFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"p-6 border-t border-border bg-muted shrink-0 flex items-center justify-end gap-3\",\n      className,\n    )}\n    {...props}\n  />\n);\n\nexport const Button = React.forwardRef<\n  HTMLButtonElement,\n  React.ButtonHTMLAttributes<HTMLButtonElement> & {\n    variant?: \"default\" | \"outline\";\n  }\n>(({ className, variant = \"default\", ...props }, ref) => (\n  <button\n    ref={ref}\n    className={cn(\n      \"inline-flex items-center justify-center rounded-lg text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2\",\n      variant === \"outline\"\n        ? \"border border-border bg-transparent hover:bg-muted text-foreground\"\n        : \"bg-primary text-primary-foreground hover:bg-primary\",\n      className,\n    )}\n    {...props}\n  />\n));\nButton.displayName = \"Button\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/Drawer.tsx"
    }
  ],
  "docs": "Named exports: Drawer, DrawerOverlay, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter. `side` chooses the entry edge. Controlled via `open` and `onOpenChange`. Note it also exports a local `Button` used by its demo — import your own for real use.",
  "categories": [
    "overlays"
  ],
  "type": "registry:component"
}
