{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel-aria",
  "title": "Carousel ARIA",
  "description": "The tabbed carousel from the WAI-ARIA Authoring Practices: optional rotation button, previous and next, and a roving tablist that selects one tabpanel slide.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@tealmac/carousel-kit"
  ],
  "files": [
    {
      "path": "registry/tealmac/carousel-aria.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight, Pause, Play } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  isRtlElement,\n  usePrefersReducedMotion,\n  type CarouselItem,\n} from \"@/registry/tealmac/carousel-kit\"\n\nexport interface CarouselAriaProps {\n  items: CarouselItem[]\n  label: string\n  /**\n   * Rotate on its own every `interval` ms. Leave it unset (the default) for a\n   * carousel that only moves when someone asks. Needs the rotation button, which is always shown when set.\n   */\n  interval?: number\n  className?: string\n  /** Controlled selected slide. Leave unset to let the carousel keep its own. */\n  index?: number\n  onIndexChange?: (index: number) => void\n  /** Show each tab's label as text instead of a dot. Use it for days, versions or short names. */\n  showLabels?: boolean\n}\n\n/**\n * The tabbed carousel from the WAI-ARIA Authoring Practices: a rotation\n * button, previous and next buttons, and a row of tabs that each select one\n * slide. Rotation stops for pointer and focus, and is off for reduced motion.\n */\nexport function CarouselAria({\n  items,\n  label,\n  interval,\n  className,\n  index: controlledIndex,\n  onIndexChange,\n  showLabels = false,\n}: CarouselAriaProps) {\n  const id = React.useId()\n  const reducedMotion = usePrefersReducedMotion()\n  const [innerIndex, setInnerIndex] = React.useState(0)\n  const index = controlledIndex ?? innerIndex\n  const setIndex = (value: number | ((current: number) => number)) => {\n    const next = typeof value === \"function\" ? value(index) : value\n    setInnerIndex(next)\n    onIndexChange?.(next)\n  }\n  const [userPaused, setUserPaused] = React.useState(false)\n  const [engaged, setEngaged] = React.useState(false)\n  const tabs = React.useRef<(HTMLButtonElement | null)[]>([])\n  const count = items.length\n  const rotating =\n    interval !== undefined && !userPaused && !engaged && !reducedMotion\n\n  const advance = React.useRef(() => {})\n  advance.current = () => setIndex((index + 1) % count)\n  React.useEffect(() => {\n    if (!rotating || interval === undefined) return\n    const timer = window.setTimeout(() => advance.current(), interval)\n    return () => window.clearTimeout(timer)\n  }, [rotating, interval, index])\n\n  const go = (to: number, focusTab = false) => {\n    const next = (to + count) % count\n    setIndex(next)\n    if (focusTab) tabs.current[next]?.focus()\n  }\n\n  const onTabKeyDown = (event: React.KeyboardEvent, i: number) => {\n    const rtl = isRtlElement(event.currentTarget)\n    const forward = rtl ? \"ArrowLeft\" : \"ArrowRight\"\n    const back = rtl ? \"ArrowRight\" : \"ArrowLeft\"\n    if (event.key === forward) go(i + 1, true)\n    else if (event.key === back) go(i - 1, true)\n    else if (event.key === \"Home\") go(0, true)\n    else if (event.key === \"End\") go(count - 1, true)\n    else return\n    event.preventDefault()\n  }\n\n  const controlClass =\n    \"inline-flex size-9 items-center justify-center rounded-full border bg-background hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none\"\n\n  return (\n    <div\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label={label}\n      data-slot=\"carousel-aria\"\n      onPointerEnter={() => setEngaged(true)}\n      onPointerLeave={() => setEngaged(false)}\n      onFocus={() => setEngaged(true)}\n      onBlur={(event) => {\n        if (!event.currentTarget.contains(event.relatedTarget as Node | null))\n          setEngaged(false)\n      }}\n      className={cn(\"flex flex-col gap-3\", className)}\n    >\n      <div className=\"flex items-center gap-2\">\n        {interval !== undefined && (\n          <button\n            type=\"button\"\n            className={controlClass}\n            aria-label={\n              userPaused\n                ? \"Start automatic slide show\"\n                : \"Stop automatic slide show\"\n            }\n            onClick={() => setUserPaused((paused) => !paused)}\n          >\n            {userPaused || reducedMotion ? (\n              <Play aria-hidden className=\"size-4\" />\n            ) : (\n              <Pause aria-hidden className=\"size-4\" />\n            )}\n          </button>\n        )}\n        <button\n          type=\"button\"\n          className={controlClass}\n          aria-label=\"Previous slide\"\n          onClick={() => go(index - 1)}\n        >\n          <ChevronLeft aria-hidden className=\"size-4 rtl:-scale-x-100\" />\n        </button>\n        <button\n          type=\"button\"\n          className={controlClass}\n          aria-label=\"Next slide\"\n          onClick={() => go(index + 1)}\n        >\n          <ChevronRight aria-hidden className=\"size-4 rtl:-scale-x-100\" />\n        </button>\n        <div\n          role=\"tablist\"\n          aria-label=\"Slides\"\n          className=\"ms-auto flex items-center gap-1\"\n        >\n          {items.map((item, i) => (\n            <button\n              key={item.id}\n              ref={(node) => {\n                tabs.current[i] = node\n              }}\n              type=\"button\"\n              role=\"tab\"\n              id={`${id}-tab-${i}`}\n              aria-selected={i === index}\n              aria-controls={`${id}-panel-${i}`}\n              aria-label={showLabels ? undefined : item.label}\n              tabIndex={i === index ? 0 : -1}\n              onClick={() => go(i)}\n              onKeyDown={(event) => onTabKeyDown(event, i)}\n              className={cn(\n                \"group focus-visible:ring-ring/50 inline-flex items-center justify-center focus-visible:ring-[3px] focus-visible:outline-none\",\n                showLabels\n                  ? \"hover:bg-accent aria-selected:bg-primary aria-selected:text-primary-foreground h-8 rounded-full px-3 text-sm font-medium\"\n                  : \"size-6 rounded-full\"\n              )}\n            >\n              {showLabels ? (\n                item.label\n              ) : (\n                <span className=\"border-foreground/60 group-aria-selected:bg-foreground block size-2.5 rounded-full border\" />\n              )}\n            </button>\n          ))}\n        </div>\n      </div>\n      <div aria-live={rotating ? \"off\" : \"polite\"}>\n        {items.map((item, i) => (\n          <div\n            key={item.id}\n            role=\"tabpanel\"\n            aria-roledescription=\"slide\"\n            aria-labelledby={`${id}-tab-${i}`}\n            id={`${id}-panel-${i}`}\n            hidden={i !== index}\n          >\n            {item.content}\n          </div>\n        ))}\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}