{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel",
  "type": "registry:ui",
  "title": "Carousel",
  "description": "A carousel component with card stack visualization for cycling through content.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": ["@scrollxui/button"],
  "dependencies": [
    "lucide-react",
    "@radix-ui/react-slot",
    "class-variance-authority",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/carousel.tsx",
      "content": "\"use client\";\n\nimport React, { createContext, useContext, useState, useEffect } from \"react\";\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\n\ntype CarouselContextProps = {\n  activeIndex: number;\n  totalCards: number;\n  goToPrevious: () => void;\n  goToNext: () => void;\n  hasPrevious: boolean;\n  hasNext: boolean;\n  setTotalCards: (total: number) => void;\n};\n\nconst CarouselContext = createContext<CarouselContextProps | null>(null);\n\nfunction useCarousel() {\n  const context = useContext(CarouselContext);\n  if (!context) {\n    throw new Error(\"useCarousel must be used within a <Carousel />\");\n  }\n  return context;\n}\n\nfunction Carousel({\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\">) {\n  const [activeIndex, setActiveIndex] = useState(0);\n  const [totalCards, setTotalCards] = useState(0);\n\n  const goToPrevious = React.useCallback(() => {\n    setActiveIndex((prev) => Math.max(0, prev - 1));\n  }, []);\n\n  const goToNext = React.useCallback(() => {\n    setActiveIndex((prev) => Math.min(totalCards - 1, prev + 1));\n  }, [totalCards]);\n\n  const hasPrevious = activeIndex > 0;\n  const hasNext = activeIndex < totalCards - 1;\n\n  const handleKeyDown = React.useCallback(\n    (event: React.KeyboardEvent<HTMLDivElement>) => {\n      if (event.key === \"ArrowUp\") {\n        event.preventDefault();\n        goToPrevious();\n      } else if (event.key === \"ArrowDown\") {\n        event.preventDefault();\n        goToNext();\n      }\n    },\n    [goToPrevious, goToNext]\n  );\n\n  return (\n    <CarouselContext.Provider\n      value={{\n        activeIndex,\n        totalCards,\n        goToPrevious,\n        goToNext,\n        hasPrevious,\n        hasNext,\n        setTotalCards,\n      }}\n    >\n      <div\n        onKeyDownCapture={handleKeyDown}\n        className={cn(\"relative flex flex-col\", className)}\n        role=\"region\"\n        aria-roledescription=\"carousel\"\n        data-slot=\"carousel\"\n        {...props}\n      >\n        {children}\n        <div className=\"flex justify-between mt-4\">\n          <CarouselPrevious />\n          <CarouselNext />\n        </div>\n      </div>\n    </CarouselContext.Provider>\n  );\n}\n\ntype CarouselContentProps = {\n  maxVisible?: number;\n  stackOffset?: number;\n};\n\nfunction CarouselContent({\n  maxVisible = 3,\n  stackOffset = 20,\n  className,\n  children,\n  ...props\n}: React.ComponentProps<\"div\"> & CarouselContentProps) {\n  const { activeIndex, setTotalCards } = useCarousel();\n  const childrenArray = React.Children.toArray(children);\n  const totalCards = childrenArray.length;\n  const [contentHeight, setContentHeight] = React.useState<number | null>(null);\n  const firstCardRef = React.useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    setTotalCards(totalCards);\n  }, [totalCards, setTotalCards]);\n\n  useEffect(() => {\n    const updateHeight = () => {\n      if (firstCardRef.current) {\n        const height = firstCardRef.current.offsetHeight;\n        setContentHeight(height);\n      }\n    };\n\n    updateHeight();\n    window.addEventListener(\"resize\", updateHeight);\n    return () => window.removeEventListener(\"resize\", updateHeight);\n  }, [activeIndex, children]);\n\n  const visibleCards = [];\n  for (let i = 0; i < maxVisible; i++) {\n    const cardIndex = activeIndex + i;\n    if (cardIndex < totalCards) {\n      visibleCards.push({\n        content: childrenArray[cardIndex],\n        index: cardIndex,\n      });\n    }\n  }\n\n  const additionalHeight = (maxVisible - 1) * stackOffset;\n  const totalHeight = contentHeight ? contentHeight + additionalHeight : 0;\n\n  return (\n    <div\n      data-slot=\"carousel-content\"\n      className={cn(\"relative w-full\", className)}\n      style={totalHeight ? { height: `${totalHeight}px` } : undefined}\n      {...props}\n    >\n      {visibleCards.map((card, stackIndex) => (\n        <div\n          key={card.index}\n          ref={stackIndex === 0 ? firstCardRef : null}\n          role=\"group\"\n          aria-roledescription=\"slide\"\n          data-slot=\"carousel-item\"\n          className=\"absolute w-full transition-all duration-300 ease-in-out\"\n          style={{\n            zIndex: maxVisible - stackIndex,\n            top: `${stackIndex * stackOffset}px`,\n            opacity: 1 - stackIndex * 0.15,\n            transform: `scale(${1 - stackIndex * 0.05})`,\n          }}\n        >\n          {card.content}\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction CarouselItem({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div className={className} {...props} />;\n}\n\nfunction CarouselPrevious({\n  className,\n  variant = \"outline\",\n  size = \"icon\",\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { goToPrevious, hasPrevious } = useCarousel();\n\n  return (\n    <Button\n      data-slot=\"carousel-previous\"\n      variant={variant}\n      size={size}\n      className={cn(\"size-8 rounded-full\", className)}\n      disabled={!hasPrevious}\n      onClick={goToPrevious}\n      {...props}\n    >\n      <ChevronUp className=\"size-4\" />\n      <span className=\"sr-only\">Previous card</span>\n    </Button>\n  );\n}\n\nfunction CarouselNext({\n  className,\n  variant = \"outline\",\n  size = \"icon\",\n  ...props\n}: React.ComponentProps<typeof Button>) {\n  const { goToNext, hasNext } = useCarousel();\n\n  return (\n    <Button\n      data-slot=\"carousel-next\"\n      variant={variant}\n      size={size}\n      className={cn(\"size-8 rounded-full\", className)}\n      disabled={!hasNext}\n      onClick={goToNext}\n      {...props}\n    >\n      <ChevronDown className=\"size-4\" />\n      <span className=\"sr-only\">Next card</span>\n    </Button>\n  );\n}\n\nexport {\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n  CarouselPrevious,\n  CarouselNext,\n};\n"
    },
    {
      "type": "registry:ui",
      "path": "components/ui/button.tsx",
      "content": "import * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '@/lib/utils';\n\nconst buttonVariants = cva(\n  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      variant: {\n        default:\n          'bg-primary text-primary-foreground shadow-sm hover:bg-primary/90',\n        destructive:\n          'bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90',\n        outline:\n          'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',\n        secondary:\n          'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',\n        ghost: 'hover:bg-accent hover:text-accent-foreground',\n        link: 'text-primary underline-offset-4 hover:underline',\n        success: 'bg-green-600 text-white hover:bg-green-700',\n        warning: 'bg-yellow-500 text-black hover:bg-yellow-600',\n        info: 'bg-blue-500 text-white hover:bg-blue-600',\n        dark: 'bg-gray-800 text-white hover:bg-gray-700',\n        light: 'bg-gray-100 text-gray-800 hover:bg-gray-200',\n        gradient:\n          'bg-linear-to-r from-indigo-500 via-purple-500 to-pink-500 text-white hover:opacity-90',\n        glass:\n          'bg-white/10 backdrop-blur-md text-white border border-white/20 hover:bg-white/20',\n      },\n      size: {\n        default: 'h-9 px-4 py-2',\n        sm: 'h-8 rounded-md px-3 text-xs',\n        lg: 'h-10 rounded-md px-8',\n        icon: 'h-9 w-9',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n    },\n  },\n);\n\nexport interface ButtonProps\n  extends\n    React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : 'button';\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    );\n  },\n);\nButton.displayName = 'Button';\n\nexport { Button, buttonVariants };\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
