{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroller",
  "title": "Scroller",
  "description": "A horizontal strip that fades where there is more.",
  "registryDependencies": [
    "https://creance.pages.dev/r/creance-base.json"
  ],
  "files": [
    {
      "path": "scroller/Scroller.css",
      "content": ".cr-scroller {\n  position: relative;\n}\n\n.cr-scroller__track {\n  display: flex;\n  gap: var(--cr-space-3);\n  padding: var(--cr-space-1);\n  overflow-x: auto;\n  scroll-snap-type: x proximity;\n  /* Snap to the padding edge, so the strip rests at scrollLeft 0 and shows no start fade. */\n  scroll-padding-inline: var(--cr-space-1);\n  scrollbar-width: none;\n}\n\n.cr-scroller__track::-webkit-scrollbar {\n  display: none;\n}\n\n.cr-scroller__track > * {\n  flex: none;\n  scroll-snap-align: start;\n}\n\n.cr-scroller__track:focus-visible {\n  outline: var(--cr-focus-width) solid var(--cr-color-focus);\n  outline-offset: var(--cr-focus-offset);\n  border-radius: var(--cr-radius-md);\n}\n\n/* Fade only the edges that have more beyond them. */\n.cr-scroller[data-start] .cr-scroller__track {\n  -webkit-mask-image: linear-gradient(to right, transparent, black 80px);\n  mask-image: linear-gradient(to right, transparent, black 80px);\n}\n\n.cr-scroller[data-end] .cr-scroller__track {\n  -webkit-mask-image: linear-gradient(to left, transparent, black 80px);\n  mask-image: linear-gradient(to left, transparent, black 80px);\n}\n\n.cr-scroller[data-start][data-end] .cr-scroller__track {\n  -webkit-mask-image: linear-gradient(to right, transparent, black 80px, black calc(100% - 80px), transparent);\n  mask-image: linear-gradient(to right, transparent, black 80px, black calc(100% - 80px), transparent);\n}\n\n.cr-scroller__arrow {\n  position: absolute;\n  top: 50%;\n  display: none;\n  place-items: center;\n  width: 36px;\n  height: 36px;\n  border: 1px solid var(--cr-color-glass-edge);\n  border-radius: var(--cr-radius-full);\n  background: var(--cr-color-glass-popover);\n  box-shadow: var(--cr-shadow-sheet);\n  color: var(--cr-color-text-default);\n  cursor: pointer;\n  transform: translateY(-50%);\n}\n\n.cr-scroller__arrow[data-side='start'] { left: 8px; }\n.cr-scroller__arrow[data-side='end'] { right: 8px; }\n\n.cr-scroller:is(:hover, :focus-within)[data-start] .cr-scroller__arrow[data-side='start'],\n.cr-scroller:is(:hover, :focus-within)[data-end] .cr-scroller__arrow[data-side='end'] {\n  display: grid;\n}\n",
      "type": "registry:file",
      "target": "@components/creance/scroller/Scroller.css"
    },
    {
      "path": "scroller/Scroller.tsx",
      "content": "'use client';\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { cx } from '@/lib/creance/cx';\nimport { ChevronIcon, ChevronLeftIcon } from '@/lib/creance/icons';\nimport './Scroller.css';\n\nexport interface ScrollerProps {\n  children: ReactNode;\n  /** Names the scrolling region, which is focusable so keyboard users can scroll it. */\n  label: string;\n  className?: string;\n}\n\n/** A horizontal strip that fades at an edge with more beyond it, with arrows on hover and focus. */\nexport function Scroller({ children, label, className }: ScrollerProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const [edges, setEdges] = useState({ start: false, end: false });\n\n  const measure = useCallback(() => {\n    const el = ref.current;\n    if (!el) return;\n    setEdges({ start: el.scrollLeft > 1, end: el.scrollLeft + el.clientWidth < el.scrollWidth - 1 });\n  }, []);\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const observer = new ResizeObserver(measure);\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, [measure]);\n\n  const page = (dir: 1 | -1) => {\n    const el = ref.current;\n    if (!el) return;\n    const smooth = !window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;\n    el.scrollBy({ left: dir * el.clientWidth * 0.8, behavior: smooth ? 'smooth' : 'auto' });\n  };\n\n  return (\n    <div className={cx('cr-scroller', className)} data-start={edges.start || undefined} data-end={edges.end || undefined}>\n      {/* The region is focusable so arrow keys scroll it; its contents keep their own focus order. */}\n      {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}\n      <div ref={ref} role=\"region\" aria-label={label} tabIndex={0} className=\"cr-scroller__track\" onScroll={measure}>\n        {children}\n      </div>\n      {/* Mouse helpers: keyboard users scroll the focused region directly. */}\n      <button type=\"button\" tabIndex={-1} aria-hidden=\"true\" className=\"cr-scroller__arrow\" data-side=\"start\" onClick={() => page(-1)}>\n        <ChevronLeftIcon />\n      </button>\n      <button type=\"button\" tabIndex={-1} aria-hidden=\"true\" className=\"cr-scroller__arrow\" data-side=\"end\" onClick={() => page(1)}>\n        <ChevronIcon />\n      </button>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/creance/scroller/Scroller.tsx"
    }
  ],
  "categories": [
    "molecule",
    "navigation"
  ],
  "type": "registry:ui"
}