{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel",
  "title": "Carousel",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/carousel/carousel.tsx",
      "content": "'use client'\n\n/**\n * Carousel\n *\n * A flexible carousel / slider for cycling through content, built on\n * embla-carousel-react (the official shadcn carousel pattern). All parts are\n * consolidated into this single file and wired through a React context that\n * holds the embla api.\n *\n * Tailwind class strings are kept 1:1 with the Vue registry's carousel so the\n * two frameworks render identically.\n *\n * @example - Basic carousel with images\n * <Carousel>\n *   <CarouselContent>\n *     {images.map((img) => (\n *       <CarouselItem key={img.id}>\n *         <img src={img.src} alt={img.alt} className=\"w-full h-full object-cover\" />\n *       </CarouselItem>\n *     ))}\n *   </CarouselContent>\n * </Carousel>\n *\n * @example - Carousel with navigation controls\n * <Carousel opts={{ loop: true }}>\n *   <CarouselContent>\n *     {slides.map((slide) => (\n *       <CarouselItem key={slide.id}>\n *         <SlideContent content={slide} />\n *       </CarouselItem>\n *     ))}\n *   </CarouselContent>\n *   <CarouselPrevious />\n *   <CarouselNext />\n * </Carousel>\n *\n * @example - Vertical carousel\n * <Carousel orientation=\"vertical\">\n *   <CarouselContent>\n *     {items.map((item) => (\n *       <CarouselItem key={item}>{item}</CarouselItem>\n *     ))}\n *   </CarouselContent>\n * </Carousel>\n */\nimport * as React from 'react'\nimport useEmblaCarousel, {\n  type UseEmblaCarouselType,\n} from 'embla-carousel-react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\n\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/components/ui/button'\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\nexport interface CarouselProps {\n  opts?: CarouselOptions\n  plugins?: CarouselPlugin\n  orientation?: 'horizontal' | 'vertical'\n  setApi?: (api: CarouselApi) => void\n}\n\ninterface CarouselContextProps extends CarouselProps {\n  carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n  api: CarouselApi\n  scrollPrev: () => void\n  scrollNext: () => void\n  canScrollPrev: boolean\n  canScrollNext: boolean\n  orientation: 'horizontal' | 'vertical'\n}\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n  const context = React.useContext(CarouselContext)\n\n  if (!context) {\n    throw new Error('useCarousel must be used within a <Carousel />')\n  }\n\n  return context\n}\n\nconst Carousel = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n  (\n    {\n      orientation = 'horizontal',\n      opts,\n      setApi,\n      plugins,\n      className,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const [carouselRef, api] = useEmblaCarousel(\n      {\n        ...opts,\n        axis: orientation === 'horizontal' ? 'x' : 'y',\n      },\n      plugins,\n    )\n    const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n    const [canScrollNext, setCanScrollNext] = React.useState(false)\n\n    const onSelect = React.useCallback((api: CarouselApi) => {\n      if (!api) return\n      setCanScrollPrev(api.canScrollPrev())\n      setCanScrollNext(api.canScrollNext())\n    }, [])\n\n    const scrollPrev = React.useCallback(() => {\n      api?.scrollPrev()\n    }, [api])\n\n    const scrollNext = React.useCallback(() => {\n      api?.scrollNext()\n    }, [api])\n\n    const handleKeyDown = React.useCallback(\n      (event: React.KeyboardEvent<HTMLDivElement>) => {\n        if (event.key === 'ArrowLeft') {\n          event.preventDefault()\n          scrollPrev()\n        } else if (event.key === 'ArrowRight') {\n          event.preventDefault()\n          scrollNext()\n        }\n      },\n      [scrollPrev, scrollNext],\n    )\n\n    React.useEffect(() => {\n      if (!api || !setApi) return\n      setApi(api)\n    }, [api, setApi])\n\n    React.useEffect(() => {\n      if (!api) return\n      onSelect(api)\n      api.on('reInit', onSelect)\n      api.on('select', onSelect)\n\n      return () => {\n        api?.off('select', onSelect)\n      }\n    }, [api, onSelect])\n\n    return (\n      <CarouselContext.Provider\n        value={{\n          carouselRef,\n          api,\n          opts,\n          orientation:\n            orientation || (opts?.axis === 'y' ? 'vertical' : 'horizontal'),\n          scrollPrev,\n          scrollNext,\n          canScrollPrev,\n          canScrollNext,\n        }}\n      >\n        <div\n          ref={ref}\n          data-uipkge=\"\"\n          data-slot=\"carousel\"\n          onKeyDownCapture={handleKeyDown}\n          className={cn(\n            'relative overflow-hidden',\n            orientation === 'horizontal' ? 'w-full' : 'h-full flex-col',\n            className,\n          )}\n          role=\"region\"\n          aria-roledescription=\"carousel\"\n          aria-label=\"Carousel\"\n          {...props}\n        >\n          {children}\n        </div>\n      </CarouselContext.Provider>\n    )\n  },\n)\nCarousel.displayName = 'Carousel'\n\nconst CarouselContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const { carouselRef, orientation } = useCarousel()\n\n  return (\n    <div\n      ref={carouselRef}\n      data-uipkge=\"\"\n      data-slot=\"carousel-content\"\n      className={cn(\n        orientation === 'horizontal'\n          ? 'flex overflow-x-auto scroll-smooth snap-x snap-mandatory'\n          : 'flex flex-col overflow-y-auto snap-y snap-mandatory',\n        'relative h-full w-full',\n        '[-ms-overflow-style:none] [scrollbar-width:none]',\n        '[&::-webkit-scrollbar]:hidden',\n      )}\n      aria-orientation={orientation}\n      role=\"group\"\n    >\n      <div\n        ref={ref}\n        className={cn(\n          'flex',\n          // w-full clamps the track to the viewport width. Without it the track's\n          // min-width:auto sizes to the slides' intrinsic content and overflows the\n          // viewport, so basis-full slides resolve against the wider track and\n          // render too big — inflating aspect-ratio slide heights vs Vue (whose\n          // CarouselContent is a single w-full div). Keeps the two frameworks 1:1.\n          orientation === 'horizontal' ? 'w-full' : 'flex-col',\n          className,\n        )}\n        {...props}\n      />\n    </div>\n  )\n})\nCarouselContent.displayName = 'CarouselContent'\n\nconst CarouselItem = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const { orientation } = useCarousel()\n\n  return (\n    <div\n      ref={ref}\n      data-uipkge=\"\"\n      data-slot=\"carousel-item\"\n      className={cn(\n        'flex shrink-0 grow-0 basis-full flex-col',\n        orientation === 'horizontal' ? 'w-full' : 'h-full',\n        'snap-start',\n        'relative',\n        className,\n      )}\n      role=\"group\"\n      aria-roledescription=\"slide\"\n      {...props}\n    />\n  )\n})\nCarouselItem.displayName = 'CarouselItem'\n\nconst CarouselPrevious = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<typeof Button> & { label?: string }\n>(\n  (\n    { className, variant = 'outline', size = 'icon', label = 'Previous slide', children, ...props },\n    ref,\n  ) => {\n    const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n    const isHorizontal = orientation !== 'vertical'\n\n    return (\n      <Button\n        ref={ref}\n        type=\"button\"\n        variant={variant}\n        size={size}\n        className={cn(\n          'absolute z-10 size-8 shrink-0 rounded-full',\n          'bg-background/80 border shadow-md backdrop-blur-sm',\n          'hover:bg-accent hover:text-accent-foreground',\n          'disabled:pointer-events-none disabled:opacity-50',\n          isHorizontal\n            ? 'top-1/2 -left-3 -translate-y-1/2'\n            : '-top-3 left-1/2 -translate-x-1/2 rotate-90',\n          className,\n        )}\n        disabled={!canScrollPrev}\n        aria-label={label}\n        onClick={scrollPrev}\n        {...props}\n      >\n        {children ?? <ChevronLeft className=\"size-4\" aria-hidden=\"true\" />}\n      </Button>\n    )\n  },\n)\nCarouselPrevious.displayName = 'CarouselPrevious'\n\nconst CarouselNext = React.forwardRef<\n  HTMLButtonElement,\n  React.ComponentProps<typeof Button> & { label?: string }\n>(\n  (\n    { className, variant = 'outline', size = 'icon', label = 'Next slide', children, ...props },\n    ref,\n  ) => {\n    const { orientation, scrollNext, canScrollNext } = useCarousel()\n    const isHorizontal = orientation !== 'vertical'\n\n    return (\n      <Button\n        ref={ref}\n        type=\"button\"\n        variant={variant}\n        size={size}\n        className={cn(\n          'absolute z-10 size-8 shrink-0 rounded-full',\n          'bg-background/80 border shadow-md backdrop-blur-sm',\n          'hover:bg-accent hover:text-accent-foreground',\n          'disabled:pointer-events-none disabled:opacity-50',\n          isHorizontal\n            ? 'top-1/2 -right-3 -translate-y-1/2'\n            : '-bottom-3 left-1/2 -translate-x-1/2 rotate-90',\n          className,\n        )}\n        disabled={!canScrollNext}\n        aria-label={label}\n        onClick={scrollNext}\n        {...props}\n      >\n        {children ?? <ChevronRight className=\"size-4\" aria-hidden=\"true\" />}\n      </Button>\n    )\n  },\n)\nCarouselNext.displayName = 'CarouselNext'\n\nconst CarouselHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    data-uipkge=\"\"\n    data-slot=\"carousel-header\"\n    className={cn('flex items-center justify-between px-1 pb-2', className)}\n    {...props}\n  />\n))\nCarouselHeader.displayName = 'CarouselHeader'\n\nconst CarouselFooter = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    data-uipkge=\"\"\n    data-slot=\"carousel-footer\"\n    className={cn('flex items-center justify-between px-1 pt-2', className)}\n    {...props}\n  />\n))\nCarouselFooter.displayName = 'CarouselFooter'\n\nconst CarouselIndicators = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const { api } = useCarousel()\n  const [snaps, setSnaps] = React.useState<number[]>([])\n  const [selectedIndex, setSelectedIndex] = React.useState(0)\n\n  React.useEffect(() => {\n    if (!api) return\n    const onUpdate = () => {\n      setSnaps(api.scrollSnapList())\n      setSelectedIndex(api.selectedScrollSnap())\n    }\n    onUpdate()\n    api.on('reInit', onUpdate)\n    api.on('select', onUpdate)\n    return () => {\n      api?.off('reInit', onUpdate)\n      api?.off('select', onUpdate)\n    }\n  }, [api])\n\n  if (!api) return null\n\n  return (\n    <div\n      ref={ref}\n      data-uipkge=\"\"\n      data-slot=\"carousel-indicators\"\n      className={cn('flex items-center justify-center gap-1.5 py-2', className)}\n      role=\"tablist\"\n      aria-label=\"Carousel navigation\"\n      {...props}\n    >\n      {snaps.map((_, index) => (\n        <button\n          key={index}\n          type=\"button\"\n          role=\"tab\"\n          aria-label={`Go to slide ${index + 1}`}\n          aria-selected={index === selectedIndex}\n          className={cn(\n            'h-2 w-2 rounded-full transition-all duration-200',\n            index === selectedIndex\n              ? 'bg-primary w-6'\n              : 'bg-muted-foreground/30 hover:bg-muted-foreground/50',\n          )}\n          onClick={() => api.scrollTo(index)}\n        />\n      ))}\n    </div>\n  )\n})\nCarouselIndicators.displayName = 'CarouselIndicators'\n\nexport {\n  type CarouselApi,\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselPrevious,\n  CarouselNext,\n  CarouselHeader,\n  CarouselFooter,\n  CarouselIndicators,\n  useCarousel,\n}\n",
      "type": "registry:ui",
      "target": "~/components/ui/carousel/carousel.tsx"
    },
    {
      "path": "packages/registry-react/components/carousel/index.ts",
      "content": "export {\n  type CarouselApi,\n  type CarouselProps,\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselPrevious,\n  CarouselNext,\n  CarouselHeader,\n  CarouselFooter,\n  CarouselIndicators,\n  useCarousel,\n} from './carousel'\n",
      "type": "registry:ui",
      "target": "~/components/ui/carousel/index.ts"
    }
  ],
  "dependencies": [
    "embla-carousel-react",
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/button.json"
  ],
  "description": "Horizontal or vertical scroller with previous/next controls. Built on embla-carousel-react (the official shadcn carousel pattern) and wired through a React context. Drop in images, cards, or any custom slide content.",
  "categories": [
    "data-display"
  ]
}