{"$schema":"https://ui.shadcn.com/schema/registry-item.json","author":"Eduardo Calvo <educlopez93@gmail.com>","css":{},"dependencies":["motion"],"description":"An animated Drawer component for SmoothUI that slides from any side.","devDependencies":[],"files":[{"content":"\"use client\";\n\nimport {\n  DrawerClose,\n  DrawerContent,\n  DrawerDescription,\n  DrawerFooter,\n  DrawerHeader,\n  Drawer as DrawerPrimitive,\n  DrawerTitle,\n  DrawerTrigger,\n} from \"@/components/ui/drawer\";\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\n/* ------------------------------------------------------------------ */\n/*  Animation constants                                                */\n/* ------------------------------------------------------------------ */\n\nconst BACKDROP_DURATION = 0.2;\nconst STAGGER_BASE_DELAY = 0.12;\nconst STAGGER_CHILD_DELAY = 0.05;\n\n/* ------------------------------------------------------------------ */\n/*  Types                                                              */\n/* ------------------------------------------------------------------ */\n\nexport type DrawerSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nexport interface DrawerProps {\n  /** Drawer content */\n  children?: React.ReactNode;\n  /** Additional CSS class names */\n  className?: string;\n  /** Description displayed below the title */\n  description?: string;\n  /** Footer content */\n  footer?: React.ReactNode;\n  /** Callback when the open state changes */\n  onOpenChange?: (open: boolean) => void;\n  /** Whether the drawer is open */\n  open?: boolean;\n  /** The side from which the drawer opens */\n  side?: DrawerSide;\n  /** Title displayed in the drawer header */\n  title?: string;\n  /** Trigger element that opens the drawer */\n  trigger?: React.ReactNode;\n}\n\n/* ------------------------------------------------------------------ */\n/*  Stagger child — animates content items in sequence after open      */\n/* ------------------------------------------------------------------ */\n\nconst StaggerChild = ({\n  children,\n  index,\n  shouldReduceMotion,\n}: {\n  children: React.ReactNode;\n  index: number;\n  shouldReduceMotion: boolean | null;\n}) => (\n  <motion.div\n    animate={\n      shouldReduceMotion\n        ? { opacity: 1 }\n        : { opacity: 1, transform: \"translateY(0px)\" }\n    }\n    exit={\n      shouldReduceMotion\n        ? { opacity: 0, transition: { duration: 0 } }\n        : { opacity: 0, transition: { duration: 0.1 } }\n    }\n    initial={\n      shouldReduceMotion\n        ? { opacity: 1 }\n        : { opacity: 0, transform: \"translateY(6px)\" }\n    }\n    transition={\n      shouldReduceMotion\n        ? { duration: 0 }\n        : {\n            bounce: 0,\n            delay: STAGGER_BASE_DELAY + index * STAGGER_CHILD_DELAY,\n            duration: 0.25,\n            type: \"spring\" as const,\n          }\n    }\n  >\n    {children}\n  </motion.div>\n);\n\n/* ------------------------------------------------------------------ */\n/*  Animated handle bar with glow pulse                                */\n/* ------------------------------------------------------------------ */\n\nconst AnimatedHandle = ({\n  shouldReduceMotion,\n}: {\n  shouldReduceMotion: boolean | null;\n}) => (\n  <motion.div\n    animate={\n      shouldReduceMotion\n        ? {}\n        : {\n            opacity: [0.5, 1, 0.5],\n          }\n    }\n    className=\"mx-auto mt-4 h-2 w-[100px] shrink-0 rounded-full bg-muted\"\n    transition={\n      shouldReduceMotion\n        ? { duration: 0 }\n        : {\n            duration: 2,\n            ease: [0.645, 0.045, 0.355, 1],\n            repeat: Number.POSITIVE_INFINITY,\n          }\n    }\n  />\n);\n\n/* ------------------------------------------------------------------ */\n/*  Drawer                                                             */\n/* ------------------------------------------------------------------ */\n\nexport default function Drawer({\n  open,\n  onOpenChange,\n  side = \"bottom\",\n  title,\n  description,\n  className,\n  children,\n  trigger,\n  footer,\n}: DrawerProps) {\n  const shouldReduceMotion = useReducedMotion();\n  const [isAnimatingOut, setIsAnimatingOut] = useState(false);\n  const prevOpenRef = useRef(false);\n\n  useEffect(() => {\n    // When going from open -> closed, we need animation-out time\n    if (prevOpenRef.current && !open) {\n      setIsAnimatingOut(true);\n    }\n    prevOpenRef.current = !!open;\n  }, [open]);\n\n  const handleExitComplete = useCallback(() => {\n    setIsAnimatingOut(false);\n  }, []);\n\n  // Keep vaul's drawer open during exit animation so the portal stays mounted\n  const vaulOpen = open || isAnimatingOut;\n\n  return (\n    <DrawerPrimitive\n      direction={side}\n      onOpenChange={(next) => {\n        if (!next && isAnimatingOut) {\n          return; // Ignore vaul's close during our exit animation\n        }\n        onOpenChange?.(next);\n      }}\n      open={vaulOpen}\n    >\n      {trigger ? <DrawerTrigger asChild>{trigger}</DrawerTrigger> : null}\n\n      {/*\n        We render DrawerContent so vaul's drag gestures still work, but we\n        layer motion.div overlays on top for the visual polish (backdrop blur,\n        spring slide-in, content stagger, handle pulse).\n      */}\n      <DrawerContent\n        className={cn(\n          // Hide vaul's default CSS transition — we animate with motion\n          \"!transition-none !duration-0\",\n          // Hide the default handle for bottom drawers — we render our own animated one\n          side === \"bottom\" &&\n            \"[&>[class*='h-2'][class*='rounded-full']]:hidden\",\n          className\n        )}\n      >\n        {/* Animated handle for bottom drawers */}\n        {side === \"bottom\" && (\n          <AnimatedHandle shouldReduceMotion={shouldReduceMotion} />\n        )}\n\n        <AnimatePresence onExitComplete={handleExitComplete}>\n          {open ? (\n            <motion.div\n              animate={shouldReduceMotion ? { opacity: 1 } : { opacity: 1 }}\n              exit={\n                shouldReduceMotion\n                  ? { opacity: 0, transition: { duration: 0 } }\n                  : { opacity: 0, transition: { duration: 0.15 } }\n              }\n              initial={shouldReduceMotion ? { opacity: 1 } : { opacity: 0 }}\n              transition={{\n                duration: shouldReduceMotion ? 0 : BACKDROP_DURATION,\n              }}\n            >\n              {/* Staggered header */}\n              {title || description ? (\n                <StaggerChild index={0} shouldReduceMotion={shouldReduceMotion}>\n                  <DrawerHeader>\n                    {title ? <DrawerTitle>{title}</DrawerTitle> : null}\n                    {description ? (\n                      <DrawerDescription>{description}</DrawerDescription>\n                    ) : null}\n                  </DrawerHeader>\n                </StaggerChild>\n              ) : null}\n\n              {/* Staggered body */}\n              {children ? (\n                <StaggerChild\n                  index={title || description ? 1 : 0}\n                  shouldReduceMotion={shouldReduceMotion}\n                >\n                  <div className=\"px-4\">{children}</div>\n                </StaggerChild>\n              ) : null}\n\n              {/* Staggered footer */}\n              {footer ? (\n                <StaggerChild\n                  index={(title || description ? 1 : 0) + (children ? 1 : 0)}\n                  shouldReduceMotion={shouldReduceMotion}\n                >\n                  <DrawerFooter>{footer}</DrawerFooter>\n                </StaggerChild>\n              ) : null}\n            </motion.div>\n          ) : null}\n        </AnimatePresence>\n      </DrawerContent>\n    </DrawerPrimitive>\n  );\n}\n\n/* ------------------------------------------------------------------ */\n/*  Re-exports                                                         */\n/* ------------------------------------------------------------------ */\n\nexport {\n  DrawerClose,\n  DrawerContent,\n  DrawerDescription,\n  DrawerFooter,\n  DrawerHeader,\n  DrawerTitle,\n  DrawerTrigger,\n};\n","path":"index.tsx","target":"components/smoothui/drawer/index.tsx","type":"registry:ui"}],"name":"drawer","registryDependencies":["drawer"],"title":"Drawer","type":"registry:ui"}