{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "drawer",
  "title": "Drawer",
  "description": "Edge-anchored panel with real drag-to-dismiss on top and bottom.",
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "springs"
  ],
  "files": [
    {
      "path": "src/components/ui/drawer.tsx",
      "content": "\"use client\";\n\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useLayoutEffect,\n  useRef,\n  useState,\n  type ReactNode,\n} from \"react\";\nimport { Dialog as DrawerPrimitive } from \"@base-ui/react/dialog\";\nimport { motion, useDragControls, type PanInfo } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { spring } from \"@/lib/springs\";\n\ntype Side = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst VELOCITY_THRESHOLD = 400;\nconst CLOSE_THRESHOLD_RATIO = 0.25;\nconst FALLBACK_SIZE = 400;\n\ninterface DrawerContextValue {\n  actionsRef: React.RefObject<DrawerPrimitive.Root.Actions | null>;\n}\n\nconst DrawerContext = createContext<DrawerContextValue | null>(null);\n\nfunction Drawer({ children, ...props }: DrawerPrimitive.Root.Props) {\n  const actionsRef = useRef<DrawerPrimitive.Root.Actions>(null);\n  return (\n    <DrawerContext.Provider value={{ actionsRef }}>\n      <DrawerPrimitive.Root data-slot=\"drawer\" actionsRef={actionsRef} {...props}>\n        {children}\n      </DrawerPrimitive.Root>\n    </DrawerContext.Provider>\n  );\n}\n\nfunction DrawerTrigger({ ...props }: DrawerPrimitive.Trigger.Props) {\n  return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />;\n}\n\nfunction DrawerClose({ ...props }: DrawerPrimitive.Close.Props) {\n  return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />;\n}\n\nfunction DrawerPortal({ ...props }: DrawerPrimitive.Portal.Props) {\n  return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />;\n}\n\nfunction DrawerOverlay({ className, ...props }: DrawerPrimitive.Backdrop.Props) {\n  return (\n    <DrawerPrimitive.Backdrop\n      data-slot=\"drawer-overlay\"\n      render={(backdropProps, state) => {\n        const exiting = state.transitionStatus === \"ending\";\n        return (\n          <motion.div\n            {...(backdropProps as Record<string, unknown>)}\n            className={cn(\"fixed inset-0 z-50 bg-black/30 backdrop-blur-none\", className)}\n            initial={{ opacity: 0 }}\n            animate={{ opacity: exiting ? 0 : 1 }}\n            transition={exiting ? spring.slow.exit : spring.slow.enter}\n          />\n        );\n      }}\n      {...props}\n    />\n  );\n}\n\nfunction DrawerContent({\n  className,\n  children,\n  side = \"right\",\n  showHandle = side === \"top\" || side === \"bottom\",\n  ...props\n}: DrawerPrimitive.Popup.Props & {\n  side?: Side;\n  showHandle?: boolean;\n}) {\n  const ctx = useContext(DrawerContext);\n  const dragControls = useDragControls();\n  const panelRef = useRef<HTMLDivElement>(null);\n  const [size, setSize] = useState<number | null>(null);\n\n  const axis: \"x\" | \"y\" = side === \"left\" || side === \"right\" ? \"x\" : \"y\";\n  const sign = side === \"bottom\" || side === \"right\" ? 1 : -1;\n  const draggable = axis === \"y\";\n  const dimension = size ?? FALLBACK_SIZE;\n  const closedOffset = sign * dimension;\n\n  useLayoutEffect(() => {\n    const el = panelRef.current;\n    if (!el) return;\n    const measure = () =>\n      setSize(axis === \"y\" ? el.getBoundingClientRect().height : el.getBoundingClientRect().width);\n    measure();\n    window.addEventListener(\"resize\", measure);\n    return () => window.removeEventListener(\"resize\", measure);\n  }, [axis]);\n\n  const handleDragEnd = useCallback(\n    (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => {\n      const offset = axis === \"y\" ? info.offset.y : info.offset.x;\n      const velocity = axis === \"y\" ? info.velocity.y : info.velocity.x;\n      const closingOffset = sign * offset;\n      const closingVelocity = sign * velocity;\n      if (\n        closingVelocity > VELOCITY_THRESHOLD ||\n        closingOffset > dimension * CLOSE_THRESHOLD_RATIO\n      ) {\n        ctx?.actionsRef.current?.close();\n      }\n    },\n    [axis, sign, dimension, ctx],\n  );\n\n  return (\n    <DrawerPortal>\n      <DrawerOverlay />\n      <DrawerPrimitive.Popup\n        ref={panelRef}\n        data-slot=\"drawer-content\"\n        data-side={side}\n        render={(popupProps, state) => {\n          const exiting = state.transitionStatus === \"ending\";\n          return (\n            <motion.div\n              {...(popupProps as Record<string, unknown>)}\n              {...(props as Record<string, unknown>)}\n              className={cn(\n                \"fixed z-50 flex flex-col overflow-hidden bg-background shadow-panel outline-none\",\n                side === \"left\" && \"inset-y-0 left-0 w-64 max-w-[85vw] rounded-r-xl\",\n                side === \"right\" && \"inset-y-0 right-0 w-64 max-w-[85vw] rounded-l-xl\",\n                side === \"top\" && \"inset-x-0 top-0 h-auto max-h-[85vh] rounded-b-xl\",\n                side === \"bottom\" && \"inset-x-0 bottom-0 h-auto max-h-[85vh] rounded-t-xl\",\n                className,\n              )}\n              drag={draggable ? axis : false}\n              dragControls={dragControls}\n              dragListener={false}\n              dragMomentum={false}\n              dragConstraints={\n                axis === \"y\"\n                  ? {\n                      top: side === \"top\" ? closedOffset : 0,\n                      bottom: side === \"bottom\" ? closedOffset : 0,\n                    }\n                  : {\n                      left: side === \"left\" ? closedOffset : 0,\n                      right: side === \"right\" ? closedOffset : 0,\n                    }\n              }\n              dragElastic={\n                side === \"bottom\"\n                  ? { top: 0.15, bottom: 1 }\n                  : side === \"top\"\n                    ? { top: 1, bottom: 0.15 }\n                    : side === \"right\"\n                      ? { left: 0.15, right: 1 }\n                      : { left: 1, right: 0.15 }\n              }\n              onDragEnd={draggable ? handleDragEnd : undefined}\n              initial={{ [axis]: closedOffset }}\n              animate={{ [axis]: exiting ? closedOffset : 0 }}\n              transition={exiting ? spring.slow.exit : spring.slow.enter}\n            >\n              {showHandle && (\n                <div\n                  data-slot=\"drawer-handle\"\n                  onPointerDown={(event) => dragControls.start(event)}\n                  className={cn(\n                    \"mx-auto h-1 w-9 shrink-0 touch-none rounded-full bg-border\",\n                    side === \"top\" ? \"order-last mt-1 mb-2\" : \"mt-2 mb-1\",\n                  )}\n                />\n              )}\n              {axis === \"y\" ? (\n                <div className=\"mx-auto flex w-full max-w-lg flex-1 flex-col overflow-hidden\">\n                  {children}\n                </div>\n              ) : (\n                children\n              )}\n            </motion.div>\n          );\n        }}\n      />\n    </DrawerPortal>\n  );\n}\n\nfunction DrawerHeader({ className, ...props }: { className?: string; children?: ReactNode }) {\n  return (\n    <div\n      data-slot=\"drawer-header\"\n      className={cn(\"flex h-14 shrink-0 items-center border-b border-border px-6\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DrawerTitle({ className, ...props }: DrawerPrimitive.Title.Props) {\n  return (\n    <DrawerPrimitive.Title\n      data-slot=\"drawer-title\"\n      className={cn(\"text-control font-medium text-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nfunction DrawerDescription({ className, ...props }: DrawerPrimitive.Description.Props) {\n  return (\n    <DrawerPrimitive.Description\n      data-slot=\"drawer-description\"\n      className={cn(\"text-caption text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nexport {\n  Drawer,\n  DrawerTrigger,\n  DrawerClose,\n  DrawerPortal,\n  DrawerOverlay,\n  DrawerContent,\n  DrawerHeader,\n  DrawerTitle,\n  DrawerDescription,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}