{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"scroll-area","type":"registry:ui","title":"Scroll area","description":"A labelled, keyboard-focusable scroll region with a quiet scrollbar.","dependencies":["clsx","tailwind-merge"],"registryDependencies":["https://caliperui.site/r/styles.json"],"files":[{"path":"ui/lib/cn.ts","type":"registry:ui","target":"components/ui/lib/cn.ts","content":"import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\nconst twMerge = extendTailwindMerge({\n  extend: { theme: { radius: [\"control\", \"surface\", \"overlay\"], shadow: [\"overlay\"] } },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"},{"path":"ui/scroll-area.tsx","type":"registry:ui","target":"components/ui/scroll-area.tsx","content":"\"use client\";\n\nimport { useEffect, useRef, type ReactNode } from \"react\";\nimport { cn } from \"./lib/cn\";\nimport \"./scroll-area.css\";\n\nexport function ScrollArea({\n  label,\n  children,\n  className,\n}: {\n  label: string;\n  children: ReactNode;\n  className?: string;\n}) {\n  const root = useRef<HTMLDivElement>(null);\n  const viewport = useRef<HTMLDivElement>(null);\n  const thumb = useRef<HTMLSpanElement>(null);\n\n  useEffect(() => {\n    const shell = root.current;\n    const element = viewport.current;\n    const marker = thumb.current;\n    if (!shell || !element || !marker) return;\n    let idle = 0;\n    const place = () => {\n      const overflow = element.scrollHeight > element.clientHeight + 1;\n      const visible = element.clientHeight / Math.max(element.scrollHeight, 1);\n      const height = Math.max(24, visible * element.clientHeight);\n      const travel = Math.max(0, element.clientHeight - height);\n      const top = overflow ? (element.scrollTop / (element.scrollHeight - element.clientHeight)) * travel : 0;\n      marker.style.height = `${height}px`;\n      marker.style.transform = `translateY(${top}px)`;\n      shell.toggleAttribute(\"data-overflow\", overflow);\n      shell.toggleAttribute(\"data-overflow-top\", overflow && element.scrollTop > 4);\n      shell.toggleAttribute(\"data-overflow-bottom\", overflow && element.scrollHeight - element.clientHeight - element.scrollTop > 4);\n      if (!overflow) return;\n      shell.dataset.scrolling = \"true\";\n      window.clearTimeout(idle);\n      idle = window.setTimeout(() => {\n        delete shell.dataset.scrolling;\n      }, 700);\n    };\n    place();\n    element.addEventListener(\"scroll\", place, { passive: true });\n    const observer = new ResizeObserver(place);\n    observer.observe(element);\n    if (element.firstElementChild) observer.observe(element.firstElementChild);\n    return () => {\n      element.removeEventListener(\"scroll\", place);\n      observer.disconnect();\n      window.clearTimeout(idle);\n    };\n  }, []);\n\n  return (\n    <div ref={root} data-slot=\"scroll-area\" className={cn(\"relative h-44 max-w-sm\", className)}>\n      <div ref={viewport} tabIndex={0} aria-label={label} data-slot=\"scroll-viewport\" className=\"h-full overflow-auto pr-3 outline-none focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\">\n        {children}\n      </div>\n      <span aria-hidden=\"true\" data-slot=\"scroll-fade\" data-edge=\"top\" />\n      <span aria-hidden=\"true\" data-slot=\"scroll-fade\" data-edge=\"bottom\" />\n      <span ref={thumb} aria-hidden=\"true\" data-slot=\"scroll-thumb\" />\n    </div>\n  );\n}\n"},{"path":"ui/scroll-area.css","type":"registry:file","target":"components/ui/scroll-area.css","content":"/* scroll-area.tsx's styles; it imports this file. Edit freely. */\n@layer theme, base, components, utilities;\n\n@layer components {\n  [data-slot=\"scroll-viewport\"] {\n    scrollbar-width: none;\n  }\n  [data-slot=\"scroll-viewport\"]::-webkit-scrollbar {\n    display: none;\n  }\n  [data-slot=\"scroll-fade\"] {\n    position: absolute;\n    right: 0.75rem;\n    left: 0;\n    height: 1.25rem;\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"scroll-fade\"][data-edge=\"top\"] {\n    top: 0;\n    background: linear-gradient(var(--background), transparent);\n  }\n  [data-slot=\"scroll-fade\"][data-edge=\"bottom\"] {\n    bottom: 0;\n    background: linear-gradient(transparent, var(--background));\n  }\n  [data-slot=\"scroll-area\"][data-overflow-top] [data-slot=\"scroll-fade\"][data-edge=\"top\"],\n  [data-slot=\"scroll-area\"][data-overflow-bottom] [data-slot=\"scroll-fade\"][data-edge=\"bottom\"] {\n    opacity: 1;\n  }\n  [data-slot=\"scroll-thumb\"] {\n    position: absolute;\n    top: 0;\n    right: 2px;\n    width: 4px;\n    border-radius: 999px;\n    background: var(--muted-foreground);\n    opacity: 0;\n    pointer-events: none;\n    transition: opacity var(--duration-fast) var(--ease-out);\n  }\n  [data-slot=\"scroll-area\"][data-overflow]:hover [data-slot=\"scroll-thumb\"],\n  [data-slot=\"scroll-area\"][data-scrolling=\"true\"] [data-slot=\"scroll-thumb\"] {\n    opacity: 0.75;\n  }\n\n  @media (prefers-reduced-motion: reduce) {\n    [data-slot=\"scroll-thumb\"] {\n      transition-duration: 400ms;\n    }\n  }\n}\n"}],"categories":["overlays"],"meta":{"tier":"free","license":"MIT","family":"disclosure","docs":"/docs/components/scroll-area"}}