{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "expandable",
  "type": "registry:ui",
  "description": "Expandable component system with smooth animations and customizable presets",
  "dependencies": [
    "motion",
    "react-use-measure"
  ],
  "files": [
    {
      "path": "registry/default/ui/expandable.tsx",
      "content": "\"use client\"\n\nimport React, {\n  createContext,\n  ReactNode,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\"\nimport {\n  AnimatePresence,\n  HTMLMotionProps,\n  motion,\n  TargetAndTransition,\n  useMotionValue,\n  useSpring,\n} from \"motion/react\"\nimport useMeasure from \"react-use-measure\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst springConfig = { stiffness: 200, damping: 20, bounce: 0.2 }\n\ninterface ExpandableContextType {\n  isExpanded: boolean // Indicates whether the component is expanded\n  toggleExpand: () => void // Function to toggle the expanded state\n  expandDirection: \"vertical\" | \"horizontal\" | \"both\" // Direction of expansion\n  expandBehavior: \"replace\" | \"push\" // How the expansion affects surrounding content\n  transitionDuration: number // Duration of the expansion/collapse animation\n  easeType:\n    | \"easeInOut\"\n    | \"easeIn\"\n    | \"easeOut\"\n    | \"linear\"\n    | [number, number, number, number] // Easing function for the animation\n  initialDelay: number // Delay before the animation starts\n  onExpandEnd?: () => void // Callback function when expansion ends\n  onCollapseEnd?: () => void // Callback function when collapse ends\n}\n\n// Create a context with default values\nconst ExpandableContext = createContext<ExpandableContextType>({\n  isExpanded: false,\n  toggleExpand: () => {},\n  expandDirection: \"vertical\", // 'vertical' | 'horizontal' | 'both' // Direction of expansion\n  expandBehavior: \"replace\", // How the expansion affects surrounding content\n  transitionDuration: 0.3, // Duration of the expansion/collapse animation\n  easeType: \"easeInOut\" as const, // Easing function for the animation\n  initialDelay: 0,\n})\n\n// Custom hook to use the ExpandableContext\nconst useExpandable = () => useContext(ExpandableContext)\n\ntype ExpandablePropsBase = Omit<HTMLMotionProps<\"div\">, \"children\">\n\ninterface ExpandableProps extends ExpandablePropsBase {\n  children: ReactNode | ((props: { isExpanded: boolean }) => ReactNode)\n  expanded?: boolean\n  onToggle?: () => void\n  transitionDuration?: number\n  easeType?:\n    | \"easeInOut\"\n    | \"easeIn\"\n    | \"easeOut\"\n    | \"linear\"\n    | [number, number, number, number]\n  expandDirection?: \"vertical\" | \"horizontal\" | \"both\"\n  expandBehavior?: \"replace\" | \"push\"\n  initialDelay?: number\n  onExpandStart?: () => void\n  onExpandEnd?: () => void\n  onCollapseStart?: () => void\n  onCollapseEnd?: () => void\n}\n// ROOT Expand component\nconst Expandable = React.forwardRef<HTMLDivElement, ExpandableProps>(\n  (\n    {\n      children,\n      expanded,\n      onToggle,\n      transitionDuration = 0.3,\n      easeType = \"easeInOut\" as const,\n      expandDirection = \"vertical\",\n      expandBehavior = \"replace\",\n      initialDelay = 0,\n      onExpandStart,\n      onExpandEnd,\n      onCollapseStart,\n      onCollapseEnd,\n      ...props\n    },\n    ref\n  ) => {\n    // Internal state for expansion when the component is uncontrolled\n    const [isExpandedInternal, setIsExpandedInternal] = useState(false)\n\n    // Use the provided expanded prop if available, otherwise use internal state\n    const isExpanded = expanded !== undefined ? expanded : isExpandedInternal\n\n    // Use the provided onToggle function if available, otherwise use internal toggle function\n    const toggleExpand =\n      onToggle || (() => setIsExpandedInternal((prev) => !prev))\n\n    // Effect to call onExpandStart or onCollapseStart when isExpanded changes\n    useEffect(() => {\n      if (isExpanded) {\n        onExpandStart?.()\n      } else {\n        onCollapseStart?.()\n      }\n    }, [isExpanded, onExpandStart, onCollapseStart])\n\n    // Create the context value to be provided to child components\n    const contextValue: ExpandableContextType = {\n      isExpanded,\n      toggleExpand,\n      expandDirection,\n      expandBehavior,\n      transitionDuration,\n      easeType,\n      initialDelay,\n      onExpandEnd,\n      onCollapseEnd,\n    }\n\n    return (\n      <ExpandableContext.Provider value={contextValue}>\n        <motion.div\n          ref={ref}\n          initial={false}\n          transition={{\n            duration: transitionDuration,\n            ease: easeType,\n            delay: initialDelay,\n          }}\n          {...props}\n        >\n          {/* Render children as a function if provided, otherwise render as is */}\n          {typeof children === \"function\" ? children({ isExpanded }) : children}\n        </motion.div>\n      </ExpandableContext.Provider>\n    )\n  }\n)\n\n// Simplify animation types\ntype AnimationPreset = {\n  initial: { [key: string]: any }\n  animate: { [key: string]: any }\n  exit: { [key: string]: any }\n}\n\n// Update ANIMATION_PRESETS type\nconst ANIMATION_PRESETS: Record<string, AnimationPreset> = {\n  fade: {\n    initial: { opacity: 0 },\n    animate: { opacity: 1 },\n    exit: { opacity: 0 },\n  },\n  \"slide-up\": {\n    initial: { opacity: 0, y: 20 },\n    animate: { opacity: 1, y: 0 },\n    exit: { opacity: 0, y: 20 },\n  },\n  \"slide-down\": {\n    initial: { opacity: 0, y: -20 },\n    animate: { opacity: 1, y: 0 },\n    exit: { opacity: 0, y: -20 },\n  },\n  \"slide-left\": {\n    initial: { opacity: 0, x: 20 },\n    animate: { opacity: 1, x: 0 },\n    exit: { opacity: 0, x: 20 },\n  },\n  \"slide-right\": {\n    initial: { opacity: 0, x: -20 },\n    animate: { opacity: 1, x: 0 },\n    exit: { opacity: 0, x: -20 },\n  },\n  scale: {\n    initial: { opacity: 0, scale: 0.8 },\n    animate: { opacity: 1, scale: 1 },\n    exit: { opacity: 0, scale: 0.8 },\n  },\n  rotate: {\n    initial: { opacity: 0, rotate: -10 },\n    animate: { opacity: 1, rotate: 0 },\n    exit: { opacity: 0, rotate: -10 },\n  },\n  \"blur-sm\": {\n    initial: { opacity: 0, filter: \"blur(4px)\" },\n    animate: { opacity: 1, filter: \"blur(0px)\" },\n    exit: { opacity: 0, filter: \"blur(4px)\" },\n  },\n  \"blur-md\": {\n    initial: { opacity: 0, filter: \"blur(8px)\" },\n    animate: { opacity: 1, filter: \"blur(0px)\" },\n    exit: { opacity: 0, filter: \"blur(8px)\" },\n  },\n  \"blur-lg\": {\n    initial: { opacity: 0, filter: \"blur(16px)\" },\n    animate: { opacity: 1, filter: \"blur(0px)\" },\n    exit: { opacity: 0, filter: \"blur(16px)\" },\n  },\n}\n\n// Update type definitions\ntype AnimationConfig = {\n  initial: { [key: string]: number | string }\n  animate: { [key: string]: number | string }\n  exit: { [key: string]: number | string }\n}\n\n// Props for defining custom animations\ninterface AnimationProps {\n  initial?: TargetAndTransition\n  animate?: TargetAndTransition\n  exit?: TargetAndTransition\n  transition?: any\n}\n\n// Inside ExpandableContent component\nconst getAnimationProps = (\n  preset: keyof typeof ANIMATION_PRESETS | undefined,\n  animateIn?: AnimationProps,\n  animateOut?: AnimationProps\n) => {\n  const defaultAnimation = {\n    initial: {},\n    animate: {},\n    exit: {},\n  }\n\n  const presetAnimation = preset ? ANIMATION_PRESETS[preset] : defaultAnimation\n\n  return {\n    initial: presetAnimation.initial,\n    animate: presetAnimation.animate,\n    exit: animateOut?.exit || presetAnimation.exit,\n  }\n}\n\n// Wrap this around items in the card that you want to be hidden then animated in on expansion\nconst ExpandableContent = React.forwardRef<\n  HTMLDivElement,\n  Omit<HTMLMotionProps<\"div\">, \"ref\"> & {\n    preset?: keyof typeof ANIMATION_PRESETS\n    animateIn?: AnimationProps\n    animateOut?: AnimationProps\n    stagger?: boolean\n    staggerChildren?: number\n    keepMounted?: boolean\n  }\n>(\n  (\n    {\n      children,\n      preset,\n      animateIn,\n      animateOut,\n      stagger = false,\n      staggerChildren = 0.1,\n      keepMounted = false,\n      ...props\n    },\n    ref\n  ) => {\n    const { isExpanded, transitionDuration, easeType } = useExpandable()\n    // useMeasure is used to measure the height of the content\n    const [measureRef, { height: measuredHeight }] = useMeasure()\n    // useMotionValue creates a value that can be animated smoothly\n    const animatedHeight = useMotionValue(0)\n    // useSpring applies a spring animation to the height value\n    const smoothHeight = useSpring(animatedHeight, springConfig)\n\n    useEffect(() => {\n      // Animate the height based on whether the content is expanded or collapsed\n      if (isExpanded) {\n        animatedHeight.set(measuredHeight)\n      } else {\n        animatedHeight.set(0)\n      }\n    }, [isExpanded, measuredHeight, animatedHeight])\n\n    const animationProps = getAnimationProps(preset, animateIn, animateOut)\n\n    return (\n      // This motion.div animates the height of the content\n      <motion.div\n        ref={ref}\n        style={{\n          height: smoothHeight,\n          overflow: \"hidden\",\n        }}\n        transition={{ duration: transitionDuration, ease: easeType }}\n        {...props}\n      >\n        {/* AnimatePresence handles the entering and exiting of components */}\n        <AnimatePresence initial={false}>\n          {(isExpanded || keepMounted) && (\n            // This motion.div handles the animation of the content itself\n            <motion.div\n              ref={measureRef}\n              initial={animationProps.initial}\n              animate={animationProps.animate}\n              exit={animationProps.exit}\n              transition={{ duration: transitionDuration, ease: easeType }}\n            >\n              {stagger ? (\n                // If stagger is true, we apply a staggered animation to the children\n                <motion.div\n                  variants={{\n                    hidden: {},\n                    visible: {\n                      transition: {\n                        staggerChildren: staggerChildren,\n                      },\n                    },\n                  }}\n                  initial=\"hidden\"\n                  animate=\"visible\"\n                >\n                  {React.Children.map(\n                    children as React.ReactNode,\n                    (child, index) => (\n                      <motion.div\n                        key={`${child?.toLocaleString}-${index}`}\n                        variants={{\n                          hidden: { opacity: 0, y: 20 },\n                          visible: { opacity: 1, y: 0 },\n                        }}\n                      >\n                        {child}\n                      </motion.div>\n                    )\n                  )}\n                </motion.div>\n              ) : (\n                children\n              )}\n            </motion.div>\n          )}\n        </AnimatePresence>\n      </motion.div>\n    )\n  }\n)\n\ninterface ExpandableCardProps {\n  children: ReactNode\n  className?: string\n  collapsedSize?: { width?: number; height?: number } // Size when collapsed\n  expandedSize?: { width?: number; height?: number } // Size when expanded\n  hoverToExpand?: boolean // Whether to expand on hover\n  expandDelay?: number // Delay before expanding\n  collapseDelay?: number // Delay before collapsing\n}\n\nconst ExpandableCard = React.forwardRef<HTMLDivElement, ExpandableCardProps>(\n  (\n    {\n      children,\n      className = \"\",\n      collapsedSize = { width: 320, height: 211 },\n      expandedSize = { width: 480, height: undefined },\n      hoverToExpand = false,\n      expandDelay = 0,\n      collapseDelay = 0,\n      ...props\n    },\n    ref\n  ) => {\n    // Get the expansion state and toggle function from the ExpandableContext\n    const { isExpanded, toggleExpand, expandDirection } = useExpandable()\n\n    // Use useMeasure hook to get the dimensions of the content\n    const [measureRef, { width, height }] = useMeasure()\n\n    // Create motion values for width and height\n    const animatedWidth = useMotionValue(collapsedSize.width || 0)\n    const animatedHeight = useMotionValue(collapsedSize.height || 0)\n\n    // Apply spring animation to the motion values\n    const smoothWidth = useSpring(animatedWidth, springConfig)\n    const smoothHeight = useSpring(animatedHeight, springConfig)\n\n    // Effect to update the animated dimensions when expansion state changes\n    useEffect(() => {\n      if (isExpanded) {\n        animatedWidth.set(expandedSize.width || width)\n        animatedHeight.set(expandedSize.height || height)\n      } else {\n        animatedWidth.set(collapsedSize.width || width)\n        animatedHeight.set(collapsedSize.height || height)\n      }\n    }, [\n      isExpanded,\n      collapsedSize,\n      expandedSize,\n      width,\n      height,\n      animatedWidth,\n      animatedHeight,\n    ])\n\n    // Handler for hover start event\n    const handleHover = () => {\n      if (hoverToExpand && !isExpanded) {\n        setTimeout(toggleExpand, expandDelay)\n      }\n    }\n\n    // Handler for hover end event\n    const handleHoverEnd = () => {\n      if (hoverToExpand && isExpanded) {\n        setTimeout(toggleExpand, collapseDelay)\n      }\n    }\n\n    return (\n      <motion.div\n        ref={ref}\n        className={cn(\"cursor-pointer\", className)}\n        style={{\n          // Set width and height based on expansion direction\n          width:\n            expandDirection === \"vertical\" ? collapsedSize.width : smoothWidth,\n          height:\n            expandDirection === \"horizontal\"\n              ? collapsedSize.height\n              : smoothHeight,\n        }}\n        transition={springConfig}\n        onHoverStart={handleHover}\n        onHoverEnd={handleHoverEnd}\n        {...props}\n      >\n        <div\n          className={cn(\n            \"grid grid-cols-1 rounded-lg sm:rounded-xl md:rounded-[2rem]\",\n            \"shadow-[inset_0_0_1px_1px_hsl(var(--border)/0.3)] dark:shadow-[inset_0_0_1px_1px_hsl(var(--border)/0.5)]\",\n            \"sm:shadow-[inset_0_0_2px_1px_hsl(var(--border)/0.3)] dark:sm:shadow-[inset_0_0_2px_1px_hsl(var(--border)/0.5)]\",\n            \"ring-border/50 ring-1\",\n            \"max-w-[calc(100%-1rem)] sm:max-w-[calc(100%-2rem)] md:max-w-[calc(100%-4rem)]\",\n            \"mx-auto w-full\",\n            \"transition-all duration-300 ease-in-out\"\n          )}\n        >\n          {/* Nested divs purely for styling and layout (the shadow ring around the card) */}\n          <div className=\"grid grid-cols-1 rounded-lg p-1 shadow-md sm:rounded-xl sm:p-1.5 md:rounded-[2rem] md:p-2\">\n            <div className=\"dark:bg-muted ring-border/50 rounded-md bg-white p-2 shadow-xl ring-1 sm:rounded-lg sm:p-3 md:rounded-3xl md:p-4\">\n              <div className=\"h-full w-full overflow-hidden\">\n                {/* Ref for measuring content dimensions (so we can let framer know to animate into the dimensions) */}\n                <div ref={measureRef} className=\"flex h-full flex-col\">\n                  {children}\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </motion.div>\n    )\n  }\n)\n\nExpandableCard.displayName = \"ExpandableCard\"\n\n// I'm telling you we just have to expand 🤌💵\nconst ExpandableTrigger = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ children, className, ...props }, ref) => {\n  const { toggleExpand } = useExpandable()\n\n  const handleKeyDown = (e: React.KeyboardEvent) => {\n    if (e.key === \"Enter\" || e.key === \" \") {\n      e.preventDefault()\n      toggleExpand()\n    }\n  }\n\n  return (\n    <div\n      ref={ref}\n      onClick={toggleExpand}\n      onKeyDown={handleKeyDown}\n      role=\"button\"\n      tabIndex={0}\n      aria-label=\"Toggle expand\"\n      className={cn(\"cursor-pointer\", className)}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n})\n\nExpandableTrigger.displayName = \"ExpandableTrigger\"\n\nconst ExpandableCardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, children, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n    {...props}\n  >\n    <motion.div layout className=\"flex items-start justify-between\">\n      {children}\n    </motion.div>\n  </div>\n))\n\nExpandableCardHeader.displayName = \"ExpandableCardHeader\"\n\nconst ExpandableCardContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, children, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex-grow overflow-hidden p-6 px-4 pt-0\", className)}\n    {...props}\n  >\n    <motion.div layout>{children}</motion.div>\n  </div>\n))\nExpandableCardContent.displayName = \"ExpandableCardContent\"\n\nconst ExpandableCardFooter = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-center p-4 pt-0\", className)}\n    {...props}\n  />\n))\nExpandableCardFooter.displayName = \"ExpandableCardFooter\"\n\nexport {\n  Expandable,\n  useExpandable,\n  ExpandableCard,\n  ExpandableContent,\n  ExpandableContext,\n  ExpandableTrigger,\n  ExpandableCardHeader,\n  ExpandableCardContent,\n  ExpandableCardFooter,\n}\n",
      "type": "registry:ui"
    }
  ]
}