{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-scroll-fade",
  "type": "registry:hook",
  "title": "useScrollFade",
  "description": "Vertical edge-fade masks for a height-capped scrollable panel; returns the CSS class to apply.",
  "categories": [
    "hooks"
  ],
  "files": [
    {
      "path": "hooks/use-scroll-fade.ts",
      "type": "registry:hook",
      "target": "hooks/use-scroll-fade.ts",
      "content": "import { useEffect, useState, type RefObject } from 'react'\n\nexport type ScrollFadeClass = '' | 'scroll-fade-top' | 'scroll-fade-bottom' | 'scroll-fade-both'\n\n/**\n * Vertical counterpart of `useTabBarFade`: tracks whether the given scroll\n * container has overflow content above, below, both, or neither, and returns\n * the CSS mask class to apply (`scroll-fade-top` / `scroll-fade-bottom` /\n * `scroll-fade-both` from the DS utilities). Apply the class to the scroll\n * container itself -- the overflowing edge fades out, and once scrolled with\n * content on both sides, both edges fade.\n *\n * Listens to scroll on the container, plus resize (window + ResizeObserver on\n * the container and its children) and child add/remove, so the class updates\n * when the container size or its content changes.\n *\n * `changeKey` is an optional value that, when it changes, forces the effect to\n * re-run. Use this when the scroll container is conditionally rendered or only\n * becomes scrollable on a state change (e.g. an expandable grid): pass that\n * state so the hook re-attaches once the ref points at the scrollable element.\n */\nexport function useScrollFade(ref: RefObject<HTMLElement | null>, changeKey?: unknown): ScrollFadeClass {\n  const [fade, setFade] = useState<ScrollFadeClass>('')\n\n  useEffect(() => {\n    const el = ref.current\n    if (!el) return\n\n    const update = () => {\n      const { scrollTop, scrollHeight, clientHeight } = el\n      // 1px tolerance handles sub-pixel rendering at the extreme edges.\n      const canUp = scrollTop > 1\n      const canDown = scrollTop < scrollHeight - clientHeight - 1\n      const cls: ScrollFadeClass =\n        canUp && canDown ? 'scroll-fade-both'\n          : canUp ? 'scroll-fade-top'\n          : canDown ? 'scroll-fade-bottom'\n          : ''\n      setFade(prev => prev === cls ? prev : cls)\n    }\n\n    update()\n    el.addEventListener('scroll', update, { passive: true })\n    const ro = new ResizeObserver(update)\n    ro.observe(el)\n    // Also observe each child -- a scrollable container's own size doesn't\n    // change when overflow children are added, but ResizeObserver on the\n    // children catches their sizing (e.g., async-loaded rows).\n    Array.from(el.children).forEach(child => ro.observe(child))\n    // MutationObserver catches add/remove of rows (e.g., when the data\n    // snapshot arrives and renders the list).\n    const mo = new MutationObserver(() => {\n      Array.from(el.children).forEach(child => ro.observe(child))\n      update()\n    })\n    mo.observe(el, { childList: true })\n    window.addEventListener('resize', update)\n\n    return () => {\n      el.removeEventListener('scroll', update)\n      ro.disconnect()\n      mo.disconnect()\n      window.removeEventListener('resize', update)\n    }\n  }, [ref, changeKey])\n\n  return fade\n}\n"
    }
  ],
  "docs": "The vertical counterpart to useTabBarFade, for reorder lists and icon grids. Apply the returned class to the scroll container. ReorderList integrates it already. Applies at every viewport size.",
  "meta": {
    "group": "hooks",
    "related": [
      "use-tab-bar-fade",
      "reorder-list"
    ],
    "exports": [
      "useScrollFade"
    ],
    "siteSlug": "use-scroll-fade"
  }
}
