{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel-styled",
  "title": "Carousel essentials",
  "description": "Styled carousel: the essentials set. 5 decorative carousel variations (SlideCarousel, FadeCarousel, CardsCarousel, DotsCarousel, AutoCarousel) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2",
    "lucide-react@^1.23.0"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/carousel-styled.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { ChevronLeft, ChevronRight } from \"lucide-react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Carousel family: 5 self-contained (NO embla, NO radix) sliding components. Each\n   holds the active slide index internally; prev/next buttons and/or dots change\n   it, and the slides transition with framer. Deterministic (index-based, no\n   Date.now or Math.random). The root carries role=\"region\"\n   aria-roledescription=\"carousel\". Color comes ONLY from semantic tokens; the\n   active dot is primary, via alpha color-mix (never var(--x)/0.4). Gated on\n   framer's useReducedMotion (under reduced motion the slide change is instant). */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst trackHeight: Record<StyledSize, string> = {\n  sm: \"h-40\",\n  md: \"h-56\",\n  lg: \"h-72\",\n  xl: \"h-96\",\n}\n\nconst focusRing =\n  \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50\"\n\nconst arrowBtn =\n  \"absolute top-1/2 z-10 inline-flex size-9 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-[color-mix(in_oklab,var(--color-background)_72%,transparent)] text-foreground shadow-sm transition-colors supports-[backdrop-filter]:backdrop-blur hover:bg-background [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\n/* Token-surfaced, numbered placeholder panels - for prop-less rendering. */\nconst panelTones = [\"bg-surface-2\", \"bg-surface-3\", \"bg-muted\", \"bg-accent\"]\n\nfunction defaultSlides(count = 4): React.ReactNode[] {\n  return Array.from({ length: count }, (_, i) => (\n    <div\n      key={i}\n      className={cn(\n        \"flex h-full w-full items-center justify-center\",\n        panelTones[i % panelTones.length]\n      )}\n    >\n      <span className=\"text-4xl font-semibold tabular-nums text-foreground\">{i + 1}</span>\n    </div>\n  ))\n}\n\nfunction resolveSlides(slides?: React.ReactNode[]): React.ReactNode[] {\n  return slides && slides.length > 0 ? slides : defaultSlides()\n}\n\n/* Ortak index state: sonsuz sarma (wrap) ile prev/next ve dogrudan jump. */\nfunction useCarousel(count: number) {\n  const [index, setIndex] = React.useState(0)\n  const total = Math.max(count, 1)\n  const wrap = React.useCallback((i: number) => ((i % total) + total) % total, [total])\n  const go = React.useCallback((i: number) => setIndex(wrap(i)), [wrap])\n  const next = React.useCallback(() => setIndex((p) => wrap(p + 1)), [wrap])\n  const prev = React.useCallback(() => setIndex((p) => wrap(p - 1)), [wrap])\n  return { index: Math.min(index, total - 1), setIndex: go, next, prev }\n}\n\n/* Shared dot group: the active one widens and turns primary. */\nfunction CarouselDots({\n  count,\n  index,\n  onSelect,\n  className,\n}: {\n  count: number\n  index: number\n  onSelect: (i: number) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  return (\n    <div\n      role=\"tablist\"\n      aria-label=\"Choose slide to display\"\n      className={cn(\"flex items-center justify-center\", className)}\n    >\n      {Array.from({ length: count }, (_, i) => {\n        const active = i === index\n        return (\n          <button\n            key={i}\n            type=\"button\"\n            role=\"tab\"\n            aria-selected={active}\n            aria-label={`Go to slide ${i + 1}`}\n            onClick={() => onSelect(i)}\n            className={cn(\n              \"relative inline-flex h-6 min-w-6 shrink-0 items-center justify-center rounded-full\",\n              focusRing\n            )}\n          >\n            <motion.span\n              aria-hidden=\"true\"\n              layout={!reduce}\n              transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 500, damping: 34 }}\n              className={cn(\n                \"block h-2 rounded-full transition-colors\",\n                active\n                  ? \"w-6 bg-primary\"\n                  : \"w-2 bg-[color-mix(in_oklab,var(--color-foreground)_25%,transparent)] hover:bg-[color-mix(in_oklab,var(--color-foreground)_45%,transparent)]\"\n              )}\n            />\n          </button>\n        )\n      })}\n    </div>\n  )\n}\n\ninterface CarouselProps {\n  className?: string\n  size?: StyledSize\n  slides?: React.ReactNode[]\n}\n\n/* SlideCarousel: slide'lar yatay kayar (translateX), oklar + noktalar. */\nexport function SlideCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, setIndex, next, prev } = useCarousel(items.length)\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <div className={cn(\"relative w-full overflow-hidden rounded-xl border border-border\", trackHeight[size])}>\n        <motion.div\n          className=\"flex h-full w-full\"\n          animate={{ x: `-${index * 100}%` }}\n          transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 320, damping: 34 }}\n        >\n          {items.map((slide, i) => (\n            <div\n              key={i}\n              role=\"group\"\n              aria-roledescription=\"slide\"\n              aria-label={`${i + 1} of ${items.length}`}\n              aria-hidden={i !== index}\n              className=\"h-full w-full shrink-0 basis-full overflow-hidden\"\n            >\n              {slide}\n            </div>\n          ))}\n        </motion.div>\n        <button type=\"button\" aria-label=\"Previous slide\" onClick={prev} className={cn(arrowBtn, focusRing, \"left-3\")}>\n          <ChevronLeft />\n        </button>\n        <button type=\"button\" aria-label=\"Next slide\" onClick={next} className={cn(arrowBtn, focusRing, \"right-3\")}>\n          <ChevronRight />\n        </button>\n      </div>\n      <CarouselDots count={items.length} index={index} onSelect={setIndex} className=\"mt-3\" />\n    </div>\n  )\n}\n\n/* FadeCarousel: the slides cross-fade, with dots. */\nexport function FadeCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, setIndex } = useCarousel(items.length)\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <div className={cn(\"relative w-full overflow-hidden rounded-xl border border-border\", trackHeight[size])}>\n        <AnimatePresence initial={false} mode=\"sync\">\n          <motion.div\n            key={index}\n            role=\"group\"\n            aria-roledescription=\"slide\"\n            aria-label={`${index + 1} of ${items.length}`}\n            className=\"absolute inset-0 h-full w-full\"\n            initial={reduce ? false : { opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={reduce ? { opacity: 0 } : { opacity: 0 }}\n            transition={reduce ? { duration: 0 } : { duration: 0.4, ease: \"easeInOut\" }}\n          >\n            {items[index]}\n          </motion.div>\n        </AnimatePresence>\n      </div>\n      <CarouselDots count={items.length} index={index} onSelect={setIndex} className=\"mt-3\" />\n    </div>\n  )\n}\n\n/* CardsCarousel: a sliver of the previous/next card shows at the sides and the\n   middle card comes forward (larger + fully opaque). Arrows + dots. */\nexport function CardsCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, setIndex, next, prev } = useCarousel(items.length)\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n    >\n      <div className={cn(\"relative w-full overflow-hidden rounded-xl\", trackHeight[size])}>\n        <motion.div\n          className=\"flex h-full w-full items-center\"\n          animate={{ x: `calc(${-index * 70}% + 15%)` }}\n          transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 300, damping: 32 }}\n        >\n          {items.map((slide, i) => {\n            const active = i === index\n            return (\n              <motion.div\n                key={i}\n                role=\"group\"\n                aria-roledescription=\"slide\"\n                aria-label={`${i + 1} of ${items.length}`}\n                aria-hidden={!active}\n                className=\"h-full shrink-0 basis-[70%] px-2\"\n                animate={{ scale: active ? 1 : 0.86, opacity: active ? 1 : 0.5 }}\n                transition={reduce ? { duration: 0 } : { type: \"spring\", stiffness: 300, damping: 32 }}\n              >\n                <button\n                  type=\"button\"\n                  tabIndex={active ? -1 : 0}\n                  aria-label={`Go to slide ${i + 1}`}\n                  onClick={() => setIndex(i)}\n                  className={cn(\"block h-full w-full overflow-hidden rounded-xl border border-border\", focusRing)}\n                >\n                  {slide}\n                </button>\n              </motion.div>\n            )\n          })}\n        </motion.div>\n        <button type=\"button\" aria-label=\"Previous slide\" onClick={prev} className={cn(arrowBtn, focusRing, \"left-3\")}>\n          <ChevronLeft />\n        </button>\n        <button type=\"button\" aria-label=\"Next slide\" onClick={next} className={cn(arrowBtn, focusRing, \"right-3\")}>\n          <ChevronRight />\n        </button>\n      </div>\n      <CarouselDots count={items.length} index={index} onSelect={setIndex} className=\"mt-3\" />\n    </div>\n  )\n}\n\n/* DotsCarousel: minimal, dots only (no arrows); tapping a dot jumps to that slide.\n   The dots overlap the bottom edge of the image. */\nexport function DotsCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, setIndex } = useCarousel(items.length)\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full overflow-hidden rounded-xl border border-border\", trackHeight[size], className)}\n    >\n      <AnimatePresence initial={false} mode=\"sync\">\n        <motion.div\n          key={index}\n          role=\"group\"\n          aria-roledescription=\"slide\"\n          aria-label={`${index + 1} of ${items.length}`}\n          className=\"absolute inset-0 h-full w-full\"\n          initial={reduce ? false : { opacity: 0 }}\n          animate={{ opacity: 1 }}\n          exit={{ opacity: 0 }}\n          transition={reduce ? { duration: 0 } : { duration: 0.35, ease: \"easeInOut\" }}\n        >\n          {items[index]}\n        </motion.div>\n      </AnimatePresence>\n      <div className=\"absolute inset-x-0 bottom-3 z-10\">\n        <CarouselDots count={items.length} index={index} onSelect={setIndex} />\n      </div>\n    </div>\n  )\n}\n\n/* AutoCarousel: advances automatically on an interval with a token-coloured progress bar; it pauses on hover. setInterval plus cleanup (not Date.now, which is allowed). */\nexport function AutoCarousel({ className, size = \"md\", slides }: CarouselProps) {\n  const reduce = useReducedMotion()\n  const items = resolveSlides(slides)\n  const { index, setIndex, next } = useCarousel(items.length)\n  const [paused, setPaused] = React.useState(false)\n  const intervalMs = 4000\n\n  React.useEffect(() => {\n    if (paused || items.length <= 1) return\n    const id = window.setInterval(() => next(), intervalMs)\n    return () => window.clearInterval(id)\n  }, [paused, next, items.length])\n\n  return (\n    <div\n      data-slot=\"styled-carousel\"\n      role=\"region\"\n      aria-roledescription=\"carousel\"\n      aria-label=\"Gallery\"\n      className={cn(\"relative w-full\", className)}\n      onMouseEnter={() => setPaused(true)}\n      onMouseLeave={() => setPaused(false)}\n      onFocusCapture={() => setPaused(true)}\n      onBlurCapture={() => setPaused(false)}\n    >\n      <div className={cn(\"relative w-full overflow-hidden rounded-xl border border-border\", trackHeight[size])}>\n        <AnimatePresence initial={false} mode=\"sync\">\n          <motion.div\n            key={index}\n            role=\"group\"\n            aria-roledescription=\"slide\"\n            aria-label={`${index + 1} of ${items.length}`}\n            className=\"absolute inset-0 h-full w-full\"\n            initial={reduce ? false : { opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            transition={reduce ? { duration: 0 } : { duration: 0.4, ease: \"easeInOut\" }}\n          >\n            {items[index]}\n          </motion.div>\n        </AnimatePresence>\n        <div className=\"absolute inset-x-0 bottom-0 z-10 h-1 bg-[color-mix(in_oklab,var(--color-foreground)_14%,transparent)]\">\n          {reduce ? (\n            <div className=\"h-full bg-primary\" style={{ width: \"100%\" }} />\n          ) : paused ? (\n            <div className=\"h-full bg-primary\" style={{ width: \"0%\" }} />\n          ) : (\n            <motion.div\n              key={index}\n              className=\"h-full bg-primary\"\n              initial={{ width: \"0%\" }}\n              animate={{ width: \"100%\" }}\n              transition={{ duration: intervalMs / 1000, ease: \"linear\" }}\n            />\n          )}\n        </div>\n      </div>\n      <CarouselDots count={items.length} index={index} onSelect={setIndex} className=\"mt-3\" />\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/carousel-styled.tsx"
    }
  ],
  "categories": [
    "styled",
    "carousel"
  ],
  "type": "registry:component"
}