{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"collapsible","type":"registry:ui","title":"Collapsible","description":"A section that opens and closes by animating its height.","dependencies":["@radix-ui/react-collapsible@^1.1.20","motion@^14.0.0"],"files":[{"path":"components/ui/collapsible.tsx","type":"registry:ui","content":"\"use client\";\n\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport * as React from \"react\";\nimport { chewyEase } from \"@/lib/motion\";\nimport { useControllableState } from \"@/hooks/use-controllable-state\";\nimport { cn } from \"@/lib/utils\";\n\nconst CollapsibleOpenContext = React.createContext(false);\n\nfunction Collapsible({\n  open: openProp,\n  defaultOpen,\n  onOpenChange,\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {\n  const [open, setOpen] = useControllableState(openProp, defaultOpen ?? false, onOpenChange);\n\n  return (\n    <CollapsibleOpenContext.Provider value={open}>\n      <CollapsiblePrimitive.Root data-slot=\"collapsible\" open={open} onOpenChange={setOpen} {...props} />\n    </CollapsibleOpenContext.Provider>\n  );\n}\n\nfunction CollapsibleTrigger({ ...props }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {\n  return <CollapsiblePrimitive.CollapsibleTrigger data-slot=\"collapsible-trigger\" {...props} />;\n}\n\nfunction CollapsibleContent({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {\n  const open = React.useContext(CollapsibleOpenContext);\n\n  return (\n    <CollapsiblePrimitive.CollapsibleContent data-slot=\"collapsible-content\" forceMount {...props}>\n      <AnimatePresence initial={false}>\n        {open && (\n          <motion.div\n            initial={{ height: 0, opacity: 0 }}\n            animate={{ height: \"auto\", opacity: 1 }}\n            exit={{ height: 0, opacity: 0 }}\n            transition={{ duration: 0.3, ease: chewyEase }}\n            className={cn(\"overflow-hidden\", className)}\n          >\n            {children}\n          </motion.div>\n        )}\n      </AnimatePresence>\n    </CollapsiblePrimitive.CollapsibleContent>\n  );\n}\n\nexport { Collapsible, CollapsibleContent, CollapsibleTrigger };\n"},{"path":"lib/motion.ts","type":"registry:lib","content":"\"use client\";\n\nimport * as React from \"react\";\nimport { useControllableState } from \"@/hooks/use-controllable-state\";\nimport type { MotionProps, Transition } from \"motion/react\";\n\nexport const chewyEase = [0.32, 0.72, 0, 1] as const;\n\nexport const growTransition = { duration: 0.6, ease: chewyEase };\n\nconst springIn: Transition = { type: \"spring\", bounce: 0.2, duration: 0.3 };\nconst quickOut: Transition = { duration: 0.12, ease: \"easeIn\" };\n\nexport const popMotion = {\n  initial: { opacity: 0, scale: 0.96 },\n  animate: { opacity: 1, scale: 1, transition: springIn },\n  exit: { opacity: 0, scale: 0.96, transition: quickOut },\n};\n\nexport const tooltipMotion = {\n  initial: { opacity: 0, scale: 0.94 },\n  animate: { opacity: 1, scale: 1, transition: { ...springIn, duration: 0.2 } },\n  exit: { opacity: 0, scale: 0.94, transition: { duration: 0.08 } },\n};\n\nexport const dialogMotion = {\n  initial: { opacity: 0, scale: 0.95, y: 8 },\n  animate: { opacity: 1, scale: 1, y: 0, transition: { type: \"spring\", bounce: 0.25, duration: 0.4 } as Transition },\n  exit: { opacity: 0, scale: 0.97, y: 4, transition: { duration: 0.15, ease: \"easeIn\" } as Transition },\n};\n\nexport const overlayMotion = {\n  initial: { opacity: 0 },\n  animate: { opacity: 1, transition: { duration: 0.2 } },\n  exit: { opacity: 0, transition: { duration: 0.15 } },\n};\n\nexport type PanelSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nexport function panelMotion(side: PanelSide): Pick<MotionProps, \"initial\" | \"animate\" | \"exit\"> {\n  const offscreen = side === \"left\" || side === \"top\" ? \"calc(-100% - 1rem)\" : \"calc(100% + 1rem)\";\n  const horizontal = side === \"left\" || side === \"right\";\n  const hidden = horizontal ? { x: offscreen } : { y: offscreen };\n  const shown = horizontal ? { x: 0 } : { y: 0 };\n\n  return {\n    initial: hidden,\n    animate: { ...shown, transition: { duration: 0.45, ease: chewyEase } },\n    exit: { ...hidden, transition: { duration: 0.3, ease: chewyEase } },\n  };\n}\n\nexport function useAnimatedOpen(\n  openProp: boolean | undefined,\n  defaultOpen: boolean | undefined,\n  onOpenChange: ((open: boolean) => void) | undefined,\n) {\n  const [open, setOpen] = useControllableState(openProp, defaultOpen ?? false, onOpenChange);\n  const [exiting, setExiting] = React.useState(false);\n  const [wasOpen, setWasOpen] = React.useState(open);\n\n  if (open !== wasOpen) {\n    setWasOpen(open);\n    setExiting(!open);\n  }\n\n  const onExitComplete = React.useCallback(() => setExiting(false), []);\n\n  return { open, setOpen, radixOpen: open || exiting, onExitComplete };\n}\n\ntype AnimatedOpenState = { open: boolean; onExitComplete: () => void };\n\ntype OpenProps = { open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void };\n\nexport function createAnimatedRoot<P extends OpenProps>(Primitive: React.ComponentType<P>, slot: string) {\n  const Context = React.createContext<AnimatedOpenState>({ open: false, onExitComplete: () => {} });\n\n  function AnimatedRoot({ open: openProp, defaultOpen, onOpenChange, ...props }: P) {\n    const { open, setOpen, radixOpen, onExitComplete } = useAnimatedOpen(openProp, defaultOpen, onOpenChange);\n    const value = React.useMemo(() => ({ open, onExitComplete }), [open, onExitComplete]);\n    const primitiveProps = { \"data-slot\": slot, ...props, open: radixOpen, onOpenChange: setOpen } as unknown as P;\n\n    return React.createElement(Context.Provider, { value }, React.createElement(Primitive, primitiveProps));\n  }\n\n  return [AnimatedRoot, () => React.useContext(Context)] as const;\n}\n"},{"path":"hooks/use-controllable-state.ts","type":"registry:hook","content":"import * as React from \"react\";\n\nexport function useControllableState<T>(prop: T | undefined, defaultProp: T, onChange?: (value: T) => void) {\n  const [uncontrolled, setUncontrolled] = React.useState(defaultProp);\n  const isControlled = prop !== undefined;\n  const value = isControlled ? prop : uncontrolled;\n\n  const setValue = React.useCallback(\n    (next: T) => {\n      if (!isControlled) setUncontrolled(next);\n      onChange?.(next);\n    },\n    [isControlled, onChange],\n  );\n\n  return [value, setValue] as const;\n}\n"}]}