{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"drawer","type":"registry:component","title":"Drawer","description":"Side panel that slides in from the left or right with a spring, backdrop blur, body scroll lock and esc-to-close.","author":"Saurabh <saurabh10102@gmail.com>","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/drawer.tsx","type":"registry:component","target":"@components/motion/drawer.tsx","content":"\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useEffect, type ReactNode } from \"react\";\nimport { EASE_OUT, SPRING_PANEL } from \"@/lib/ease\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface DrawerProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  side?: \"left\" | \"right\";\n  children: ReactNode;\n  /** Class for the panel surface. */\n  className?: string;\n  /** Class for the backdrop. */\n  backdropClassName?: string;\n  ariaLabel?: string;\n  /** Close when the backdrop is clicked. Default true. */\n  dismissable?: boolean;\n}\n\nexport function Drawer({\n  open,\n  onOpenChange,\n  side = \"right\",\n  children,\n  className,\n  backdropClassName,\n  ariaLabel,\n  dismissable = true,\n}: DrawerProps) {\n  const reduce = useReducedMotion();\n\n  useEffect(() => {\n    if (!open) return;\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") onOpenChange(false);\n    };\n    window.addEventListener(\"keydown\", onKey);\n    const prevOverflow = document.body.style.overflow;\n    document.body.style.overflow = \"hidden\";\n    return () => {\n      window.removeEventListener(\"keydown\", onKey);\n      document.body.style.overflow = prevOverflow;\n    };\n  }, [open, onOpenChange]);\n\n  const offscreen = side === \"right\" ? \"100%\" : \"-100%\";\n\n  return (\n    <AnimatePresence>\n      {open ? (\n        <div className=\"fixed inset-0 z-50\">\n          <motion.button\n            type=\"button\"\n            aria-label=\"Close\"\n            tabIndex={dismissable ? 0 : -1}\n            onClick={() => dismissable && onOpenChange(false)}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={{ duration: 0.25, ease: EASE_OUT }}\n            className={cn(\n              \"absolute inset-0 h-full w-full cursor-default bg-black/40 backdrop-blur-sm\",\n              backdropClassName,\n            )}\n          />\n          <motion.aside\n            role=\"dialog\"\n            aria-modal=\"true\"\n            aria-label={ariaLabel}\n            initial={reduce ? { opacity: 0 } : { x: offscreen }}\n            animate={reduce ? { opacity: 1 } : { x: 0 }}\n            exit={reduce ? { opacity: 0 } : { x: offscreen }}\n            transition={reduce ? { duration: 0.2, ease: EASE_OUT } : SPRING_PANEL}\n            className={cn(\n              \"absolute inset-y-0 flex w-80 max-w-[85vw] flex-col bg-background shadow-2xl\",\n              side === \"right\"\n                ? \"right-0 border-l border-border\"\n                : \"left-0 border-r border-border\",\n              className,\n            )}\n          >\n            {children}\n          </motion.aside>\n        </div>\n      ) : null}\n    </AnimatePresence>\n  );\n}\n"},{"path":"lib/ease.ts","type":"registry:lib","target":"@lib/ease.ts","content":"// Shared motion tokens. Easing curves mirror the CSS custom properties in\n// globals.css; springs are the canonical physics used across components.\n// Strong custom variants — defaults like `ease-in`/`ease-out` feel weak.\n\nexport const EASE_OUT = [0.16, 1, 0.3, 1] as const;\nexport const EASE_IN_OUT = [0.77, 0, 0.175, 1] as const;\nexport const EASE_DRAWER = [0.32, 0.72, 0, 1] as const;\n\n/** CSS string form of EASE_OUT for inline style transitions. */\nexport const EASE_OUT_CSS = \"cubic-bezier(0.16, 1, 0.3, 1)\";\n\n/** Press feedback on buttons and other tappable surfaces. */\nexport const SPRING_PRESS = {\n  type: \"spring\",\n  stiffness: 500,\n  damping: 30,\n  mass: 0.6,\n} as const;\n\n/** Content swaps — label/icon slots trading places inside a control. */\nexport const SPRING_SWAP = {\n  type: \"spring\",\n  stiffness: 460,\n  damping: 30,\n  mass: 0.55,\n} as const;\n\n/** Overlay panel entrances — modals and sheets summoned by pointer. */\nexport const SPRING_PANEL = {\n  type: \"spring\",\n  stiffness: 420,\n  damping: 40,\n  mass: 0.5,\n} as const;\n\n/** Shared-layout glides — pills, indicators and panels morphing between positions. */\nexport const SPRING_LAYOUT = {\n  type: \"spring\",\n  stiffness: 360,\n  damping: 32,\n  mass: 0.6,\n} as const;\n\n/** Cursor-follow physics for decorative mouse tracking (magnetic, tilt, dock). */\nexport const SPRING_MOUSE = {\n  stiffness: 200,\n  damping: 15,\n  mass: 0.3,\n} as const;\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}