{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-area",
  "type": "registry:ui",
  "files": [
    {
      "path": "ui/scroll-area.tsx",
      "content": "import type { Accessor, Component, ComponentProps, JSX } from \"solid-js\"\nimport {\n  createContext,\n  createSignal,\n  mergeProps,\n  onCleanup,\n  onMount,\n  splitProps,\n  useContext\n} from \"solid-js\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype ScrollAreaContextProps = {\n  viewportRef: Accessor<HTMLDivElement | undefined>\n  contentRef: Accessor<HTMLDivElement | undefined>\n}\n\nconst ScrollAreaContext = createContext<ScrollAreaContextProps | null>(null)\n\nfunction useScrollArea() {\n  const context = useContext(ScrollAreaContext)\n  if (!context) {\n    throw new Error(\"useScrollArea must be used within a <ScrollArea />\")\n  }\n  return context\n}\n\ntype ScrollAreaProps = ComponentProps<\"div\"> & {\n  class?: string | undefined\n  children?: JSX.Element\n}\n\nconst ScrollArea: Component<ScrollAreaProps> = (rawProps) => {\n  const props = mergeProps({}, rawProps)\n  const [local, others] = splitProps(props, [\"class\", \"children\"])\n\n  let viewportRef: HTMLDivElement | undefined\n\n  return (\n    <ScrollAreaContext.Provider\n      value={{\n        viewportRef: () => viewportRef,\n        contentRef: () => viewportRef\n      }}\n    >\n      <div class={cn(\"relative overflow-hidden\", local.class)} data-slot=\"scroll-area\" {...others}>\n        <div\n          class=\"size-full overflow-scroll rounded-[inherit] [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n          data-slot=\"scroll-area-viewport\"\n          ref={viewportRef!}\n          style={{\n            \"overflow-x\": \"hidden\",\n            \"overflow-y\": \"scroll\"\n          }}\n        >\n          {local.children}\n        </div>\n      </div>\n    </ScrollAreaContext.Provider>\n  )\n}\n\ntype ScrollBarProps = ComponentProps<\"div\"> & {\n  class?: string | undefined\n  orientation?: \"vertical\" | \"horizontal\"\n}\n\nconst ScrollBar: Component<ScrollBarProps> = (rawProps) => {\n  const props = mergeProps({ orientation: \"vertical\" as const }, rawProps)\n  const [local, others] = splitProps(props, [\"class\", \"orientation\"])\n\n  const context = useScrollArea()\n  const [thumbSize, setThumbSize] = createSignal(0)\n  const [thumbPosition, setThumbPosition] = createSignal(0)\n  const [isDragging, setIsDragging] = createSignal(false)\n  const [dragStartY, setDragStartY] = createSignal(0)\n  const [visible, setVisible] = createSignal(false)\n\n  let scrollbarRef: HTMLDivElement | undefined\n  let thumbRef: HTMLDivElement | undefined\n\n  const isVertical = () => local.orientation === \"vertical\"\n\n  const updateScrollbar = () => {\n    const viewport = context.viewportRef()\n    if (!viewport) return\n\n    if (isVertical()) {\n      const ratio = viewport.clientHeight / viewport.scrollHeight\n      const size = Math.max(ratio * 100, 10) // Minimum 10% thumb size\n      setThumbSize(size)\n      setVisible(ratio < 1)\n\n      const maxScrollTop = viewport.scrollHeight - viewport.clientHeight\n      const scrollRatio = maxScrollTop > 0 ? viewport.scrollTop / maxScrollTop : 0\n      const maxThumbPosition = 100 - size\n      setThumbPosition(Math.min(scrollRatio * maxThumbPosition, maxThumbPosition))\n    } else {\n      const ratio = viewport.clientWidth / viewport.scrollWidth\n      const size = Math.max(ratio * 100, 10)\n      setThumbSize(size)\n      setVisible(ratio < 1)\n\n      const maxScrollLeft = viewport.scrollWidth - viewport.clientWidth\n      const scrollRatio = maxScrollLeft > 0 ? viewport.scrollLeft / maxScrollLeft : 0\n      const maxThumbPosition = 100 - size\n      setThumbPosition(Math.min(scrollRatio * maxThumbPosition, maxThumbPosition))\n    }\n  }\n\n  const handleScroll = () => {\n    if (!isDragging()) {\n      updateScrollbar()\n    }\n  }\n\n  const handleThumbMouseDown = (e: MouseEvent) => {\n    e.preventDefault()\n    e.stopPropagation()\n    const viewport = context.viewportRef()\n    if (!viewport || !scrollbarRef || !thumbRef) return\n\n    setIsDragging(true)\n\n    if (isVertical()) {\n      const thumbRect = thumbRef.getBoundingClientRect()\n      // Store offset from top of thumb to mouse position\n      const offsetInThumb = e.clientY - thumbRect.top\n      setDragStartY(offsetInThumb)\n    } else {\n      const thumbRect = thumbRef.getBoundingClientRect()\n      const offsetInThumb = e.clientX - thumbRect.left\n      setDragStartY(offsetInThumb)\n    }\n  }\n\n  const handleMouseMove = (e: MouseEvent) => {\n    if (!isDragging()) return\n    e.preventDefault()\n\n    const viewport = context.viewportRef()\n    if (!viewport || !scrollbarRef) return\n\n    if (isVertical()) {\n      const scrollbarRect = scrollbarRef.getBoundingClientRect()\n      const scrollbarHeight = scrollbarRect.height\n      const maxScrollTop = viewport.scrollHeight - viewport.clientHeight\n\n      if (maxScrollTop <= 0) return\n\n      // Calculate where mouse is relative to scrollbar track (minus the offset in thumb)\n      const mousePositionInTrack = e.clientY - scrollbarRect.top - dragStartY()\n\n      // Convert to percentage (0-100)\n      const ratio = viewport.clientHeight / viewport.scrollHeight\n      const thumbSize = Math.max(ratio * 100, 10)\n      const maxThumbPosition = 100 - thumbSize\n      const trackAvailableHeight = scrollbarHeight * (maxThumbPosition / 100)\n\n      // Calculate thumb position as percentage\n      const thumbPositionPercent = Math.max(\n        0,\n        Math.min((mousePositionInTrack / scrollbarHeight) * 100, maxThumbPosition)\n      )\n\n      // Map thumb position to scroll position\n      const scrollRatio = trackAvailableHeight > 0 ? thumbPositionPercent / maxThumbPosition : 0\n      const newScrollTop = scrollRatio * maxScrollTop\n\n      viewport.scrollTop = newScrollTop\n      setThumbPosition(thumbPositionPercent)\n    } else {\n      const scrollbarRect = scrollbarRef.getBoundingClientRect()\n      const scrollbarWidth = scrollbarRect.width\n      const maxScrollLeft = viewport.scrollWidth - viewport.clientWidth\n\n      if (maxScrollLeft <= 0) return\n\n      const mousePositionInTrack = e.clientX - scrollbarRect.left - dragStartY()\n\n      const ratio = viewport.clientWidth / viewport.scrollWidth\n      const thumbSize = Math.max(ratio * 100, 10)\n      const maxThumbPosition = 100 - thumbSize\n      const trackAvailableWidth = scrollbarWidth * (maxThumbPosition / 100)\n\n      const thumbPositionPercent = Math.max(\n        0,\n        Math.min((mousePositionInTrack / scrollbarWidth) * 100, maxThumbPosition)\n      )\n\n      const scrollRatio = trackAvailableWidth > 0 ? thumbPositionPercent / maxThumbPosition : 0\n      const newScrollLeft = scrollRatio * maxScrollLeft\n\n      viewport.scrollLeft = newScrollLeft\n      setThumbPosition(thumbPositionPercent)\n    }\n  }\n\n  const handleMouseUp = () => {\n    setIsDragging(false)\n  }\n\n  const handleTrackClick = (e: MouseEvent) => {\n    if (!scrollbarRef || !thumbRef) return\n    const viewport = context.viewportRef()\n    if (!viewport) return\n\n    const rect = scrollbarRef.getBoundingClientRect()\n    const thumbRect = thumbRef.getBoundingClientRect()\n\n    if (isVertical()) {\n      const clickY = e.clientY - rect.top\n      const thumbY = thumbRect.top - rect.top\n      const thumbHeight = thumbRect.height\n\n      if (clickY < thumbY) {\n        // Click above thumb - page up\n        viewport.scrollTop -= viewport.clientHeight\n      } else if (clickY > thumbY + thumbHeight) {\n        // Click below thumb - page down\n        viewport.scrollTop += viewport.clientHeight\n      }\n    } else {\n      const clickX = e.clientX - rect.left\n      const thumbX = thumbRect.left - rect.left\n      const thumbWidth = thumbRect.width\n\n      if (clickX < thumbX) {\n        viewport.scrollLeft -= viewport.clientWidth\n      } else if (clickX > thumbX + thumbWidth) {\n        viewport.scrollLeft += viewport.clientWidth\n      }\n    }\n  }\n\n  onMount(() => {\n    const viewport = context.viewportRef()\n    if (!viewport) return\n\n    // Initial calculation\n    updateScrollbar()\n\n    // Listen to scroll events\n    viewport.addEventListener(\"scroll\", handleScroll)\n\n    // ResizeObserver for dynamic content\n    const resizeObserver = new ResizeObserver(() => {\n      updateScrollbar()\n    })\n\n    const content = context.contentRef()\n    if (content) {\n      resizeObserver.observe(content)\n    }\n    resizeObserver.observe(viewport)\n\n    // Mouse events for dragging\n    document.addEventListener(\"mousemove\", handleMouseMove)\n    document.addEventListener(\"mouseup\", handleMouseUp)\n\n    onCleanup(() => {\n      viewport.removeEventListener(\"scroll\", handleScroll)\n      resizeObserver.disconnect()\n      document.removeEventListener(\"mousemove\", handleMouseMove)\n      document.removeEventListener(\"mouseup\", handleMouseUp)\n    })\n  })\n\n  return (\n    <div\n      class={cn(\n        \"cn-scroll-area-scrollbar absolute touch-none select-none p-px transition-colors\",\n        isVertical() && \"top-0 right-0 h-full w-2.5\",\n        !isVertical() && \"bottom-0 left-0 h-2.5 w-full\",\n        !visible() && \"pointer-events-none opacity-0\",\n        local.class\n      )}\n      data-horizontal={!isVertical() ? \"\" : undefined}\n      data-slot=\"scroll-area-scrollbar\"\n      data-vertical={isVertical() ? \"\" : undefined}\n      onClick={handleTrackClick}\n      ref={scrollbarRef}\n      {...others}\n    >\n      <div\n        class=\"cn-scroll-area-thumb absolute cursor-grab rounded-full bg-border active:cursor-grabbing\"\n        data-slot=\"scroll-area-thumb\"\n        onMouseDown={handleThumbMouseDown}\n        ref={thumbRef}\n        style={{\n          ...(isVertical()\n            ? {\n                top: `${thumbPosition()}%`,\n                height: `${thumbSize()}%`,\n                left: \"0\",\n                right: \"0\"\n              }\n            : {\n                left: `${thumbPosition()}%`,\n                width: `${thumbSize()}%`,\n                top: \"0\",\n                bottom: \"0\"\n              })\n        }}\n      />\n    </div>\n  )\n}\n\nexport { ScrollArea, ScrollBar }\n",
      "type": "registry:ui"
    }
  ]
}