{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-animated-height",
  "title": "useAnimatedHeight",
  "description": "A custom React hook for animated height.",
  "files": [
    {
      "path": "registry/default/hooks/use-animated-height.ts",
      "content": "import { useCallback, useEffect, useRef } from \"react\";\r\n\r\nconst MIN_FADE_DURATION = 0.15;\r\nconst MAX_FADE_DURATION = 0.27;\r\n\r\n/**\r\n * Tracks the inner element's height and writes it onto the outer element, plus\r\n * a size-adaptive `--fade-duration`.\r\n *\r\n * `onResize` runs after each write with the measured height and the outer\r\n * element. Consumers driving their own height animation use it to retarget when\r\n * content resizes mid-flight. It's held in a ref, so passing an inline function\r\n * won't tear down and rebuild the observer on every render. Pass a stable\r\n * callback if it reads state: the ref syncs in a passive effect (after paint)\r\n * while the observer fires before it, so an inline closure can be a render late.\r\n */\r\nexport function useAnimatedHeight(\r\n  onResize?: (height: number, outer: HTMLElement) => void,\r\n) {\r\n  const outerRef = useRef<HTMLDivElement>(null);\r\n  const observerRef = useRef<ResizeObserver | null>(null);\r\n  const previousHeight = useRef(0);\r\n\r\n  // Held in a ref rather than `useEffectEvent`: the observer is attached from a\r\n  // callback ref, and an effect event closed over inside that `useCallback`\r\n  // trips the React Compiler's memoization check (it wants the event in the\r\n  // deps, which effect events must never go in). Refs are recognized as stable,\r\n  // so this keeps the observer from rebuilding on every render.\r\n  const onResizeRef = useRef(onResize);\r\n  useEffect(() => {\r\n    onResizeRef.current = onResize;\r\n  }, [onResize]);\r\n\r\n  const innerRef = useCallback((node: HTMLDivElement | null) => {\r\n    if (observerRef.current) {\r\n      observerRef.current.disconnect();\r\n      observerRef.current = null;\r\n    }\r\n\r\n    if (!node) return;\r\n\r\n    const observer = new ResizeObserver((entries) => {\r\n      const outer = outerRef.current;\r\n      if (!outer) return;\r\n      // Use the layout border-box height, not `getBoundingClientRect`. The\r\n      // bounding rect includes ancestor transforms (e.g. a parent popover's\r\n      // open-time `scale-95` → `scale-100`), which previously caused this\r\n      // hook to write the *scaled* height onto the outer element on first\r\n      // observation — under-sizing the container until a later layout\r\n      // change happened to trigger another ResizeObserver tick.\r\n      const entry = entries[0];\r\n      const height =\r\n        entry.borderBoxSize?.[0]?.blockSize ??\r\n        (entry.target as HTMLElement).offsetHeight;\r\n      if (height > 0) {\r\n        const diff = Math.abs(height - previousHeight.current);\r\n        previousHeight.current = height;\r\n        const fadeDuration = Math.min(\r\n          Math.max(diff / 500, MIN_FADE_DURATION),\r\n          MAX_FADE_DURATION,\r\n        );\r\n\r\n        outer.style.height = `${height}px`;\r\n        outer.style.setProperty(\"--fade-duration\", `${fadeDuration}s`);\r\n        onResizeRef.current?.(height, outer);\r\n      }\r\n    });\r\n\r\n    observer.observe(node);\r\n    observerRef.current = observer;\r\n  }, []);\r\n\r\n  useEffect(() => {\r\n    return () => {\r\n      observerRef.current?.disconnect();\r\n    };\r\n  }, []);\r\n\r\n  return { outerRef, innerRef };\r\n}\r\n",
      "type": "registry:hook",
      "target": "hooks/cubby-ui/use-animated-height.ts"
    }
  ],
  "type": "registry:hook"
}