{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-area",
  "type": "registry:component",
  "title": "Scroll Area",
  "description": "A scrollable area component with customizable top and bottom shadows that appear when content overflows.",
  "dependencies": [
    "@radix-ui/react-presence"
  ],
  "registryDependencies": [
    "@joyco/utils"
  ],
  "files": [
    {
      "path": "registry/components/scroll-area.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Presence } from '@radix-ui/react-presence'\nimport { cn } from '@/lib/utils'\n\ninterface ScrollAreaContextValue {\n  scrollRef: React.RefObject<HTMLDivElement | null>\n  hasScroll: boolean\n  orientation: 'vertical' | 'horizontal'\n}\n\nconst ScrollAreaContext = React.createContext<ScrollAreaContextValue | null>(\n  null\n)\n\nfunction useScrollAreaContext() {\n  const ctx = React.useContext(ScrollAreaContext)\n  if (!ctx) {\n    throw new Error('ScrollArea components must be used within ScrollArea Root')\n  }\n  return ctx\n}\n\ninterface ScrollAreaViewportProps extends React.ComponentProps<'div'> {\n  orientation?: 'vertical' | 'horizontal'\n  topShadowGradient?: string\n  bottomShadowGradient?: string\n  leftShadowGradient?: string\n  rightShadowGradient?: string\n}\n\nexport function ScrollAreaViewport({\n  className,\n  children,\n  orientation = 'vertical',\n  topShadowGradient,\n  bottomShadowGradient,\n  leftShadowGradient,\n  rightShadowGradient,\n  ref,\n  ...props\n}: ScrollAreaViewportProps) {\n  const scrollRef = React.useRef<HTMLDivElement>(null)\n  const [hasScrollTop, setHasScrollTop] = React.useState(false)\n  const [hasScrollBottom, setHasScrollBottom] = React.useState(false)\n  const [hasScrollLeft, setHasScrollLeft] = React.useState(false)\n  const [hasScrollRight, setHasScrollRight] = React.useState(false)\n\n  const update = React.useCallback(() => {\n    const el = scrollRef.current\n    if (!el) return\n\n    if (orientation === 'vertical') {\n      // Vertical scroll detection\n      const scrollY = Math.ceil(el.scrollTop)\n      const scrollHeight = el.scrollHeight\n      const clientHeight = el.clientHeight\n\n      const newHasScrollTop = scrollY > 0\n      const newHasScrollBottom = scrollY + clientHeight < scrollHeight\n\n      setHasScrollTop(newHasScrollTop)\n      setHasScrollBottom(newHasScrollBottom)\n    } else {\n      const scrollX = Math.ceil(el.scrollLeft)\n      const scrollWidth = el.scrollWidth\n      const clientWidth = el.clientWidth\n\n      const newHasScrollLeft = scrollX > 0\n      const newHasScrollRight = scrollX + clientWidth < scrollWidth\n\n      setHasScrollLeft(newHasScrollLeft)\n      setHasScrollRight(newHasScrollRight)\n    }\n  }, [orientation])\n\n  React.useEffect(() => {\n    const el = scrollRef.current\n    if (!el) return\n\n    const handleScroll = () => requestAnimationFrame(update)\n    el.addEventListener('scroll', handleScroll, { passive: true })\n\n    const ro = new ResizeObserver(update)\n    ro.observe(el)\n\n    const mo = new MutationObserver(update)\n    mo.observe(el, { childList: true })\n\n    update()\n\n    return () => {\n      el.removeEventListener('scroll', handleScroll)\n      ro.disconnect()\n      mo.disconnect()\n    }\n  }, [update])\n\n  const hasScroll =\n    hasScrollTop || hasScrollBottom || hasScrollLeft || hasScrollRight\n\n  const ctxValue = React.useMemo(\n    () => ({\n      scrollRef,\n      hasScroll,\n      orientation,\n    }),\n    [hasScroll, orientation]\n  )\n\n  const shadowBaseClasses =\n    'pointer-events-none absolute z-20 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 duration-300 ease-out'\n\n  const shadows =\n    orientation === 'vertical'\n      ? [\n          {\n            present: hasScrollTop,\n            position: 'left-0 top-0 right-0 h-8',\n            slide:\n              'data-[state=closed]:slide-out-to-top-2 data-[state=open]:slide-in-from-top-2',\n            gradient:\n              topShadowGradient ||\n              'bg-linear-to-b from-background to-transparent',\n          },\n          {\n            present: hasScrollBottom,\n            position: 'left-0 bottom-0 right-0 h-8',\n            slide:\n              'data-[state=closed]:slide-out-to-bottom-2 data-[state=open]:slide-in-from-bottom-2',\n            gradient:\n              bottomShadowGradient ||\n              'bg-linear-to-t from-background to-transparent',\n          },\n        ]\n      : [\n          {\n            present: hasScrollLeft,\n            position: 'inset-y-0 left-0 bottom-0 w-8',\n            slide:\n              'data-[state=closed]:slide-out-to-left-2 data-[state=open]:slide-in-from-left-2',\n            gradient:\n              leftShadowGradient ||\n              'bg-linear-to-r from-background to-transparent',\n          },\n          {\n            present: hasScrollRight,\n            position: 'inset-y-0 right-0 bottom-0 w-8',\n            slide:\n              'data-[state=closed]:slide-out-to-right-2 data-[state=open]:slide-in-from-right-2',\n            gradient:\n              rightShadowGradient ||\n              'bg-linear-to-l from-background to-transparent',\n          },\n        ]\n\n  const dataAttributes =\n    orientation === 'vertical'\n      ? {\n          'data-scroll-top': hasScrollTop,\n          'data-scroll-bottom': hasScrollBottom,\n        }\n      : {\n          'data-scroll-left': hasScrollLeft,\n          'data-scroll-right': hasScrollRight,\n        }\n\n  return (\n    <ScrollAreaContext.Provider value={ctxValue}>\n      <div\n        ref={ref}\n        className={cn('group/scroll-area relative overflow-hidden', className)}\n        data-has-scroll={hasScroll}\n        data-orientation={orientation}\n        {...dataAttributes}\n        {...props}\n      >\n        {children}\n\n        {shadows.map((shadow, index) => (\n          <Presence key={index} present={shadow.present}>\n            <div\n              data-state={shadow.present ? 'open' : 'closed'}\n              className={cn(\n                shadowBaseClasses,\n                shadow.position,\n                shadow.slide,\n                shadow.gradient\n              )}\n            />\n          </Presence>\n        ))}\n      </div>\n    </ScrollAreaContext.Provider>\n  )\n}\n\ntype ScrollAreaContentProps = React.ComponentProps<'div'>\n\nexport function ScrollAreaContent({\n  className,\n  children,\n  ref,\n  ...props\n}: ScrollAreaContentProps) {\n  const { scrollRef, orientation } = useScrollAreaContext()\n\n  const combinedRef = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      scrollRef.current = node\n      if (typeof ref === 'function') ref(node)\n      else if (ref)\n        (ref as React.RefObject<HTMLDivElement | null>).current = node\n    },\n    [ref, scrollRef]\n  )\n\n  const overflowClasses =\n    orientation === 'horizontal'\n      ? 'overflow-x-auto overflow-y-hidden'\n      : 'overflow-y-auto h-full'\n\n  const paddingClasses = orientation === 'horizontal'\n\n  return (\n    <div\n      ref={combinedRef}\n      className={cn(\n        '[scrollbar-color:hsl(0_0%_50%)] [scrollbar-gutter:stable] [scrollbar-width:thin]',\n        overflowClasses,\n        paddingClasses,\n        className\n      )}\n      {...props}\n    >\n      {children}\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}