{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "widget-stack",
  "title": "Widget Stack",
  "description": "A stack of small or medium widgets with vertical snap scrolling and page dots.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/spark-ui/widget-stack.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\nexport type WidgetStackSize = \"small\" | \"medium\";\n\nexport interface WidgetStackProps {\n  children: React.ReactNode;\n  size?: WidgetStackSize;\n  defaultIndex?: number;\n  onIndexChange?: (index: number) => void;\n  className?: string;\n  ariaLabel?: string;\n}\n\nexport function WidgetStack({\n  children,\n  size = \"small\",\n  defaultIndex = 0,\n  onIndexChange,\n  className,\n  ariaLabel = \"Widget stack\",\n}: WidgetStackProps) {\n  const pages = React.Children.toArray(children);\n  const initialIndex = Math.min(Math.max(defaultIndex, 0), pages.length - 1);\n  const [activeIndex, setActiveIndex] = React.useState(initialIndex);\n  const [scrollProgress, setScrollProgress] = React.useState(initialIndex);\n  const scrollRef = React.useRef<HTMLDivElement>(null);\n\n  const scrollToIndex = React.useCallback(\n    (index: number, behavior: ScrollBehavior = \"smooth\") => {\n      const container = scrollRef.current;\n      if (!container) return;\n\n      const nextIndex = Math.min(Math.max(index, 0), pages.length - 1);\n      container.scrollTo({\n        top: nextIndex * container.clientHeight,\n        behavior:\n          window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n            ? \"auto\"\n            : behavior,\n      });\n    },\n    [pages.length],\n  );\n\n  React.useEffect(() => {\n    scrollToIndex(initialIndex, \"auto\");\n  }, [initialIndex, scrollToIndex]);\n\n  if (pages.length === 0) return null;\n\n  return (\n    <div\n      data-size={size}\n      className={cn(\n        \"relative shrink-0\",\n        size === \"small\"\n          ? \"size-40\"\n          : \"h-40 w-[21rem] max-w-[calc(100vw-3rem)]\",\n        className,\n      )}\n    >\n      <div\n        ref={scrollRef}\n        role=\"region\"\n        aria-label={ariaLabel}\n        tabIndex={0}\n        onScroll={(event) => {\n          const container = event.currentTarget;\n          const progress = Math.min(\n            container.scrollTop / container.clientHeight,\n            pages.length - 1,\n          );\n          const index = Math.round(progress);\n          setScrollProgress(progress);\n          if (index !== activeIndex) {\n            setActiveIndex(index);\n            onIndexChange?.(index);\n          }\n        }}\n        onKeyDown={(event) => {\n          if (event.target !== event.currentTarget) return;\n\n          const nextIndex =\n            event.key === \"ArrowDown\" || event.key === \"PageDown\"\n              ? activeIndex + 1\n              : event.key === \"ArrowUp\" || event.key === \"PageUp\"\n                ? activeIndex - 1\n                : event.key === \"Home\"\n                  ? 0\n                  : event.key === \"End\"\n                    ? pages.length - 1\n                    : null;\n\n          if (nextIndex !== null) {\n            event.preventDefault();\n            scrollToIndex(nextIndex);\n          }\n        }}\n        className=\"flex h-full w-full snap-y snap-mandatory flex-col overflow-y-auto overscroll-contain rounded-[1.35rem] bg-card shadow-[0_12px_35px_rgba(0,0,0,0.2)] outline-none ring-1 ring-black/5 scroll-smooth focus-visible:ring-2 focus-visible:ring-foreground/40 motion-reduce:scroll-auto dark:ring-white/10 [&::-webkit-scrollbar]:hidden [scrollbar-width:none]\"\n      >\n        {pages.map((page, index) => (\n          <section\n            key={index}\n            aria-label={`Widget ${index + 1} of ${pages.length}`}\n            className=\"h-full min-h-full w-full shrink-0 snap-start snap-always overflow-hidden\"\n          >\n            {page}\n          </section>\n        ))}\n      </div>\n\n      {pages.length > 1 && (\n        <div\n          className=\"absolute -right-5 top-1/2 z-10 flex -translate-y-1/2 flex-col\"\n          aria-label=\"Choose widget\"\n        >\n          <span\n            className=\"pointer-events-none absolute left-[7px] top-1 h-3 w-1.5 rounded-full bg-foreground/80 transition-transform duration-150 ease-out motion-reduce:transition-none\"\n            style={{ transform: `translateY(${scrollProgress * 20}px)` }}\n            aria-hidden\n          />\n          {pages.map((_, index) => (\n            <button\n              key={index}\n              type=\"button\"\n              aria-label={`Show widget ${index + 1}`}\n              aria-current={activeIndex === index ? \"true\" : undefined}\n              onClick={() => scrollToIndex(index)}\n              className=\"grid size-5 place-items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n            >\n              <span\n                className={cn(\n                  \"block size-1.5 rounded-full bg-foreground/25 transition-opacity motion-reduce:transition-none\",\n                  activeIndex === index && \"opacity-0\",\n                )}\n              />\n            </button>\n          ))}\n        </div>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}