{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-area",
  "type": "registry:ui",
  "title": "Scroll Area",
  "description": "Custom scroll area for Tile UI.",
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/core",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/scroll-area/scroll-area.tsx",
      "type": "registry:ui",
      "target": "components/ui/scroll-area/scroll-area.tsx",
      "content": "import React, { createContext, useContext, useEffect, useRef, useState } from 'react';\nimport { getScrollBarSizeKey, scrollAreaStyleKeys } from '../lib/core';\nimport type { ScrollAreaBaseProps, ScrollBarBaseProps } from '../lib/core';\nimport styles from './scroll-area.module.scss';\n\ninterface ScrollAreaContextValue {\n\tviewportRef: React.RefObject<HTMLDivElement | null>;\n}\n\nconst ScrollAreaContext = createContext<ScrollAreaContextValue | null>(null);\n\nfunction useScrollAreaContext(): ScrollAreaContextValue {\n\tconst context = useContext(ScrollAreaContext);\n\tif (!context) {\n\t\tthrow new Error('ScrollArea sub-components must be used within <ScrollArea>.');\n\t}\n\treturn context;\n}\n\nexport interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement>, ScrollAreaBaseProps {}\n\nconst ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(({ className = '', children, ...props }, ref) => {\n\tconst viewportRef = useRef<HTMLDivElement | null>(null);\n\n\treturn (\n\t\t<ScrollAreaContext.Provider value={{ viewportRef }}>\n\t\t\t<div ref={ref} className={`${styles[scrollAreaStyleKeys.root]} ${className}`} {...props}>\n\t\t\t\t<div ref={viewportRef} tabIndex={0} className={styles[scrollAreaStyleKeys.viewport]}>\n\t\t\t\t\t{children}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</ScrollAreaContext.Provider>\n\t);\n});\nScrollArea.displayName = 'ScrollArea';\n\ninterface ScrollBarMetrics {\n\tsize: number;\n\toffset: number;\n\tvisible: boolean;\n}\n\nexport interface ScrollBarProps extends React.HTMLAttributes<HTMLDivElement>, ScrollBarBaseProps {}\n\nconst ScrollBar = React.forwardRef<HTMLDivElement, ScrollBarProps>(({ className = '', orientation = 'vertical', ...props }, ref) => {\n\tconst context = useScrollAreaContext();\n\tconst trackRef = useRef<HTMLDivElement | null>(null);\n\tconst dragRef = useRef<number | null>(null);\n\tconst [metrics, setMetrics] = useState<ScrollBarMetrics>({ size: 0, offset: 0, visible: false });\n\tconst isVertical = orientation === 'vertical';\n\n\tuseEffect(() => {\n\t\tconst viewport = context.viewportRef.current;\n\t\tconst track = trackRef.current;\n\t\tif (!viewport || !track) {\n\t\t\treturn;\n\t\t}\n\n\t\tfunction update() {\n\t\t\tconst track = trackRef.current;\n\t\t\tconst viewport = context.viewportRef.current;\n\t\t\tif (!track || !viewport) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst trackSize = isVertical ? track.clientHeight : track.clientWidth;\n\t\t\tconst scrollable = isVertical ? viewport.scrollHeight - viewport.clientHeight : viewport.scrollWidth - viewport.clientWidth;\n\t\t\tconst visibleSize = isVertical ? viewport.clientHeight : viewport.clientWidth;\n\t\t\tconst total = visibleSize + scrollable;\n\t\t\tconst size = total > 0 ? Math.max((visibleSize / total) * trackSize, 24) : 0;\n\t\t\tconst maxOffset = trackSize - size;\n\t\t\tconst scrollPos = isVertical ? viewport.scrollTop : viewport.scrollLeft;\n\t\t\tconst offset = scrollable > 0 && maxOffset > 0 ? (scrollPos / scrollable) * maxOffset : 0;\n\t\t\tsetMetrics({ size, offset, visible: scrollable > 0 });\n\t\t}\n\n\t\tupdate();\n\t\tviewport.addEventListener('scroll', update, { passive: true });\n\t\twindow.addEventListener('resize', update);\n\t\tconst observer = new ResizeObserver(update);\n\t\tobserver.observe(viewport);\n\t\tobserver.observe(track);\n\n\t\treturn () => {\n\t\t\tviewport.removeEventListener('scroll', update);\n\t\t\twindow.removeEventListener('resize', update);\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [context.viewportRef, isVertical]);\n\n\tfunction scrollToOffset(offset: number) {\n\t\tconst track = trackRef.current;\n\t\tconst viewport = context.viewportRef.current;\n\t\tif (!track || !viewport) {\n\t\t\treturn;\n\t\t}\n\t\tconst trackSize = isVertical ? track.clientHeight : track.clientWidth;\n\t\tconst maxOffset = trackSize - metrics.size;\n\t\tconst scrollable = isVertical ? viewport.scrollHeight - viewport.clientHeight : viewport.scrollWidth - viewport.clientWidth;\n\t\tif (maxOffset <= 0 || scrollable <= 0) {\n\t\t\treturn;\n\t\t}\n\t\tconst ratio = offset / maxOffset;\n\t\tif (isVertical) {\n\t\t\tviewport.scrollTop = ratio * scrollable;\n\t\t} else {\n\t\t\tviewport.scrollLeft = ratio * scrollable;\n\t\t}\n\t}\n\n\tfunction handlePointerDown(event: React.PointerEvent<HTMLDivElement>) {\n\t\tconst track = trackRef.current;\n\t\tif (!track) {\n\t\t\treturn;\n\t\t}\n\t\ttrack.setPointerCapture(event.pointerId);\n\t\tconst rect = track.getBoundingClientRect();\n\t\tconst pos = isVertical ? event.clientY : event.clientX;\n\t\tconst trackStart = isVertical ? rect.top : rect.left;\n\t\tconst trackSize = isVertical ? rect.height : rect.width;\n\t\tconst maxOffset = trackSize - metrics.size;\n\t\tconst offset = Math.min(Math.max(0, pos - trackStart - metrics.size / 2), maxOffset);\n\t\tdragRef.current = offset;\n\t\tscrollToOffset(offset);\n\t}\n\n\tfunction handlePointerMove(event: React.PointerEvent<HTMLDivElement>) {\n\t\tconst dragOffset = dragRef.current;\n\t\tconst track = trackRef.current;\n\t\tif (dragOffset === null || !track || !track.hasPointerCapture(event.pointerId)) {\n\t\t\treturn;\n\t\t}\n\t\tconst rect = track.getBoundingClientRect();\n\t\tconst pos = isVertical ? event.clientY : event.clientX;\n\t\tconst trackStart = isVertical ? rect.top : rect.left;\n\t\tconst trackSize = isVertical ? rect.height : rect.width;\n\t\tconst maxOffset = trackSize - metrics.size;\n\t\tconst offset = Math.min(Math.max(0, pos - trackStart - metrics.size / 2), maxOffset);\n\t\tdragRef.current = offset;\n\t\tscrollToOffset(offset);\n\t}\n\n\tfunction handlePointerUp() {\n\t\tdragRef.current = null;\n\t}\n\n\tfunction setTrackRef(element: HTMLDivElement | null) {\n\t\ttrackRef.current = element;\n\n\t\tif (typeof ref === 'function') {\n\t\t\tref(element);\n\t\t} else if (ref) {\n\t\t\tref.current = element;\n\t\t}\n\t}\n\n\treturn (\n\t\t<div\n\t\t\t{...props}\n\t\t\tref={setTrackRef}\n\t\t\tdata-orientation={orientation}\n\t\t\tclassName={`${styles[scrollAreaStyleKeys.scrollbar]} ${styles[getScrollBarSizeKey(orientation)]} ${className}`}\n\t\t\tstyle={{ opacity: metrics.visible ? 1 : 0 }}\n\t\t\tonPointerDown={handlePointerDown}\n\t\t\tonPointerMove={handlePointerMove}\n\t\t\tonPointerUp={handlePointerUp}>\n\t\t\t<div\n\t\t\t\tclassName={styles[scrollAreaStyleKeys.thumb]}\n\t\t\t\tstyle={\n\t\t\t\t\tisVertical\n\t\t\t\t\t\t? { height: `${metrics.size}px`, transform: `translateY(${metrics.offset}px)` }\n\t\t\t\t\t\t: { width: `${metrics.size}px`, transform: `translateX(${metrics.offset}px)` }\n\t\t\t\t}\n\t\t\t/>\n\t\t</div>\n\t);\n});\nScrollBar.displayName = 'ScrollBar';\n\nexport { ScrollArea, ScrollBar };\nexport default ScrollArea;\n"
    },
    {
      "path": "packages/styles/scss/components/scroll-area.module.scss",
      "type": "registry:file",
      "target": "components/ui/scroll-area/scroll-area.module.scss",
      "content": "@use '../../../styles/variables/colors' as *;\n\n.root {\n\tposition: relative;\n\toverflow: hidden;\n}\n\n.viewport {\n\twidth: 100%;\n\theight: 100%;\n\toverflow: auto;\n\tscrollbar-width: none;\n\t-ms-overflow-style: none;\n\n\t&::-webkit-scrollbar {\n\t\tdisplay: none;\n\t}\n\n\t&:focus-visible {\n\t\toutline: none;\n\t\tbox-shadow: var(--tile-scroll-area-focus-shadow, 0 0 0 3px color-mix(in srgb, $ring 50%, transparent));\n\t}\n}\n\n.scrollbar {\n\tposition: absolute;\n\tz-index: 10;\n\tdisplay: flex;\n\tpadding: 1px;\n\ttouch-action: none;\n\tuser-select: none;\n\ttransition: opacity $transition-fast;\n\n\t&.vertical {\n\t\ttop: 0;\n\t\tright: 0;\n\t\twidth: 0.625rem;\n\t\theight: 100%;\n\t\tborder-left: 1px solid transparent;\n\t}\n\n\t&.horizontal {\n\t\tbottom: 0;\n\t\tleft: 0;\n\t\theight: 0.625rem;\n\t\twidth: 100%;\n\t\tflex-direction: column;\n\t\tborder-top: 1px solid transparent;\n\t}\n}\n\n.thumb {\n\tposition: relative;\n\tborder-radius: $radius-full;\n\tbackground-color: var(--tile-scroll-area-thumb, $border);\n\n\t.vertical & {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.horizontal & {\n\t\theight: 100%;\n\t\twidth: 100%;\n\t}\n}\n"
    },
    {
      "path": "packages/react/src/components/scroll-area/index.ts",
      "type": "registry:ui",
      "target": "components/ui/scroll-area/index.ts",
      "content": "export { ScrollArea, ScrollBar, type ScrollAreaProps, type ScrollBarProps } from './scroll-area';\n"
    }
  ]
}
