{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-size",
  "title": "Animated Size",
  "description": "A container that animates its height or width to fit content that changes.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/ndeko/ui/animated-size.tsx",
      "content": "'use client';\n\nimport { motion, useReducedMotion, type Transition } from 'motion/react';\nimport { useLayoutEffect, useState } from 'react';\n\nimport { cn } from '@/registry/ndeko/lib/utils';\n\ntype SizeAxis = 'height' | 'width' | 'both';\n\ntype MeasuredSize = {\n  width: number;\n  height: number;\n};\n\nconst SIZE_SPRING: Transition = { type: 'spring', bounce: 0, duration: 0.3 };\n\nconst INSTANT: Transition = { duration: 0 };\n\nfunction useMeasuredSize(target: HTMLElement | null) {\n  const [size, setSize] = useState<MeasuredSize | null>(null);\n\n  useLayoutEffect(() => {\n    if (!target) {\n      return;\n    }\n    const observer = new ResizeObserver(([entry]) => {\n      const box = entry.borderBoxSize[0];\n      setSize({ width: box.inlineSize, height: box.blockSize });\n    });\n    observer.observe(target);\n    return () => observer.disconnect();\n  }, [target]);\n\n  return size;\n}\n\nfunction targetOf(axis: SizeAxis, size: MeasuredSize | null) {\n  if (!size) {\n    return { height: 'auto', width: 'auto' };\n  }\n  if (axis === 'height') {\n    return { height: size.height };\n  }\n  if (axis === 'width') {\n    return { width: size.width };\n  }\n  return { height: size.height, width: size.width };\n}\n\nfunction transitionOf(shouldReduceMotion: boolean | null) {\n  if (shouldReduceMotion) {\n    return INSTANT;\n  }\n  return SIZE_SPRING;\n}\n\ntype AnimatedSizeProps = React.HTMLAttributes<HTMLElement> & {\n  axis?: SizeAxis;\n  /** `span` keeps the markup valid inside a button or a label. */\n  as?: 'div' | 'span';\n  /** Classes of the measured content, for example its flex layout. */\n  contentClassName?: string;\n};\n\n/** Animates its own size to the size of its content, so a container that changes content resizes smoothly. */\nfunction AnimatedSize({\n  axis = 'height',\n  as = 'div',\n  className,\n  contentClassName,\n  children,\n  ...props\n}: AnimatedSizeProps) {\n  const [content, setContent] = useState<HTMLElement | null>(null);\n  const size = useMeasuredSize(content);\n  const shouldReduceMotion = useReducedMotion();\n  const isWidthAnimated = axis !== 'height';\n  const outerClassName = cn('-m-1 overflow-hidden', className);\n  const innerClassName = cn(\n    'p-1',\n    isWidthAnimated && 'w-max',\n    contentClassName\n  );\n\n  if (as === 'span') {\n    return (\n      <motion.span\n        data-slot='animated-size'\n        initial={false}\n        animate={targetOf(axis, size)}\n        transition={transitionOf(shouldReduceMotion)}\n        className={cn('inline-block align-middle', outerClassName)}\n      >\n        <span\n          ref={setContent}\n          className={cn('block', innerClassName)}\n          {...props}\n        >\n          {children}\n        </span>\n      </motion.span>\n    );\n  }\n\n  return (\n    <motion.div\n      data-slot='animated-size'\n      initial={false}\n      animate={targetOf(axis, size)}\n      transition={transitionOf(shouldReduceMotion)}\n      className={outerClassName}\n    >\n      <div ref={setContent} className={innerClassName} {...props}>\n        {children}\n      </div>\n    </motion.div>\n  );\n}\n\nexport { AnimatedSize, type SizeAxis };\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "primitives"
  ],
  "type": "registry:ui"
}