{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"carousel","type":"registry:ui","title":"Carousel","description":"Carousel from react-fancy","package":"react-fancy","dependencies":[],"registryDependencies":[],"files":[{"path":"components\/fancy\/carousel\/Carousel.context.ts","content":"import { createContext, useContext } from \"react\";\r\nimport type { CarouselContextValue } from \".\/Carousel.types\";\r\n\r\nexport const CarouselContext = createContext<CarouselContextValue | null>(null);\r\nCarouselContext.displayName = \"CarouselContext\";\r\n\r\nexport function useCarousel(): CarouselContextValue {\r\n  const ctx = useContext(CarouselContext);\r\n  if (!ctx) {\r\n    throw new Error(\"Carousel compound components must be used within <Carousel>\");\r\n  }\r\n  return ctx;\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/Carousel.context.ts"},{"path":"components\/fancy\/carousel\/Carousel.tsx","content":"import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useControllableState } from \"..\/..\/hooks\/use-controllable-state\";\r\nimport { CarouselContext } from \".\/Carousel.context\";\r\nimport { CarouselPanels } from \".\/CarouselPanels\";\r\nimport { CarouselSlide } from \".\/CarouselSlide\";\r\nimport { CarouselControls } from \".\/CarouselControls\";\r\nimport { CarouselSteps } from \".\/CarouselSteps\";\r\nimport type { CarouselProps, CarouselContextValue } from \".\/Carousel.types\";\r\n\r\nconst CarouselRoot = forwardRef<HTMLDivElement, CarouselProps>(\r\n  (\r\n    {\r\n      children,\r\n      defaultIndex = 0,\r\n      activeIndex: controlledIndex,\r\n      onIndexChange,\r\n      className,\r\n      autoPlay = false,\r\n      interval = 5000,\r\n      loop = false,\r\n      variant = \"directional\",\r\n      headless = false,\r\n      onFinish,\r\n    },\r\n    ref,\r\n  ) => {\r\n    const [activeIndex, setActiveIndex] = useControllableState<number>(\r\n      controlledIndex,\r\n      defaultIndex,\r\n      onIndexChange,\r\n    );\r\n    const [totalSlides, setTotalSlides] = useState(0);\r\n    const [slideNames, setSlideNames] = useState<string[]>([]);\r\n    const [isPaused, setIsPaused] = useState(false);\r\n    const onFinishRef = useRef(onFinish);\r\n    onFinishRef.current = onFinish;\r\n\r\n    const registerSlides = useCallback((count: number) => {\r\n      setTotalSlides(count);\r\n    }, []);\r\n\r\n    const registerSlideNames = useCallback((names: string[]) => {\r\n      setSlideNames(names);\r\n    }, []);\r\n\r\n    const next = useCallback(() => {\r\n      setActiveIndex((i: number) => {\r\n        if (i >= totalSlides - 1) {\r\n          return loop ? 0 : i;\r\n        }\r\n        return i + 1;\r\n      });\r\n    }, [totalSlides, loop, setActiveIndex]);\r\n\r\n    const prev = useCallback(() => {\r\n      setActiveIndex((i: number) => {\r\n        if (i <= 0) {\r\n          return loop ? totalSlides - 1 : i;\r\n        }\r\n        return i - 1;\r\n      });\r\n    }, [totalSlides, loop, setActiveIndex]);\r\n\r\n    const goTo = useCallback(\r\n      (index: number) => setActiveIndex(index),\r\n      [setActiveIndex],\r\n    );\r\n\r\n    \/\/ AutoPlay\r\n    useEffect(() => {\r\n      if (!autoPlay || isPaused || totalSlides <= 1) {\r\n        return;\r\n      }\r\n\r\n      const id = setInterval(() => {\r\n        setActiveIndex((i: number) => {\r\n          if (i >= totalSlides - 1) {\r\n            return loop ? 0 : i;\r\n          }\r\n          return i + 1;\r\n        });\r\n      }, interval);\r\n\r\n      return () => clearInterval(id);\r\n    }, [autoPlay, interval, isPaused, totalSlides, loop, setActiveIndex]);\r\n\r\n    const ctx = useMemo<CarouselContextValue>(\r\n      () => ({\r\n        activeIndex,\r\n        totalSlides,\r\n        next,\r\n        prev,\r\n        goTo,\r\n        registerSlides,\r\n        variant,\r\n        loop,\r\n        slideNames,\r\n        registerSlideNames,\r\n        onFinish: onFinishRef.current,\r\n        headless,\r\n      }),\r\n      [\r\n        activeIndex,\r\n        totalSlides,\r\n        next,\r\n        prev,\r\n        goTo,\r\n        registerSlides,\r\n        variant,\r\n        loop,\r\n        slideNames,\r\n        registerSlideNames,\r\n        headless,\r\n      ],\r\n    );\r\n\r\n    const handleMouseEnter = useCallback(() => {\r\n      if (autoPlay) {\r\n        setIsPaused(true);\r\n      }\r\n    }, [autoPlay]);\r\n\r\n    const handleMouseLeave = useCallback(() => {\r\n      if (autoPlay) {\r\n        setIsPaused(false);\r\n      }\r\n    }, [autoPlay]);\r\n\r\n    return (\r\n      <CarouselContext.Provider value={ctx}>\r\n        <div\r\n          data-react-fancy-carousel=\"\"\r\n          ref={ref}\r\n          className={cn(\"relative\", className)}\r\n          onMouseEnter={handleMouseEnter}\r\n          onMouseLeave={handleMouseLeave}\r\n        >\r\n          {children}\r\n        <\/div>\r\n      <\/CarouselContext.Provider>\r\n    );\r\n  },\r\n);\r\n\r\nCarouselRoot.displayName = \"Carousel\";\r\n\r\nexport const Carousel = Object.assign(CarouselRoot, {\r\n  Panels: CarouselPanels,\r\n  Slide: CarouselSlide,\r\n  Controls: CarouselControls,\r\n  Steps: CarouselSteps,\r\n});\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/Carousel.tsx"},{"path":"components\/fancy\/carousel\/Carousel.types.ts","content":"import type { ReactNode } from \"react\";\r\n\r\nexport type CarouselVariant = \"directional\" | \"wizard\";\r\n\r\nexport interface CarouselContextValue {\r\n  activeIndex: number;\r\n  totalSlides: number;\r\n  next: () => void;\r\n  prev: () => void;\r\n  goTo: (index: number) => void;\r\n  registerSlides: (count: number) => void;\r\n  variant: CarouselVariant;\r\n  loop: boolean;\r\n  slideNames: string[];\r\n  registerSlideNames: (names: string[]) => void;\r\n  onFinish?: () => void;\r\n  headless: boolean;\r\n}\r\n\r\nexport interface CarouselProps {\r\n  children: ReactNode;\r\n  defaultIndex?: number;\r\n  activeIndex?: number;\r\n  onIndexChange?: (index: number) => void;\r\n  className?: string;\r\n  autoPlay?: boolean;\r\n  interval?: number;\r\n  loop?: boolean;\r\n  variant?: CarouselVariant;\r\n  headless?: boolean;\r\n  onFinish?: () => void;\r\n}\r\n\r\nexport interface CarouselSlideProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n  name?: string;\r\n}\r\n\r\nexport interface CarouselControlsProps {\r\n  className?: string;\r\n  prevLabel?: ReactNode;\r\n  nextLabel?: ReactNode;\r\n  finishLabel?: ReactNode;\r\n}\r\n\r\nexport interface CarouselStepsProps {\r\n  className?: string;\r\n}\r\n\r\nexport interface CarouselPanelsProps {\r\n  children: ReactNode;\r\n  className?: string;\r\n  transition?: \"none\" | \"fade\";\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/Carousel.types.ts"},{"path":"components\/fancy\/carousel\/CarouselControls.tsx","content":"import { forwardRef } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useCarousel } from \".\/Carousel.context\";\r\nimport type { CarouselControlsProps } from \".\/Carousel.types\";\r\n\r\nexport const CarouselControls = forwardRef<HTMLDivElement, CarouselControlsProps>(\r\n  (\r\n    {\r\n      className,\r\n      prevLabel,\r\n      nextLabel,\r\n      finishLabel,\r\n    },\r\n    ref,\r\n  ) => {\r\n    const { prev, next, activeIndex, totalSlides, variant, loop, onFinish, headless } = useCarousel();\r\n\r\n    if (headless) {\r\n      return null;\r\n    }\r\n\r\n    const isFirst = activeIndex === 0;\r\n    const isLast = activeIndex === totalSlides - 1;\r\n\r\n    if (variant === \"wizard\") {\r\n      return (\r\n        <div ref={ref} data-react-fancy-carousel-controls=\"\" className={cn(\"flex items-center gap-2\", className)}>\r\n          <button\r\n            type=\"button\"\r\n            onClick={prev}\r\n            disabled={isFirst}\r\n            className=\"rounded-lg border px-3 py-1 text-sm disabled:opacity-50 dark:border-zinc-600\"\r\n          >\r\n            {prevLabel ?? \"Back\"}\r\n          <\/button>\r\n          {isLast ? (\r\n            <button\r\n              type=\"button\"\r\n              onClick={onFinish}\r\n              className=\"rounded-lg border border-blue-500 bg-blue-500 px-3 py-1 text-sm text-white hover:bg-blue-600\"\r\n            >\r\n              {finishLabel ?? \"Finish\"}\r\n            <\/button>\r\n          ) : (\r\n            <button\r\n              type=\"button\"\r\n              onClick={next}\r\n              className=\"rounded-lg border px-3 py-1 text-sm disabled:opacity-50 dark:border-zinc-600\"\r\n            >\r\n              {nextLabel ?? \"Next\"}\r\n            <\/button>\r\n          )}\r\n        <\/div>\r\n      );\r\n    }\r\n\r\n    \/\/ Directional variant (default)\r\n    const prevDisabled = !loop && isFirst;\r\n    const nextDisabled = !loop && isLast;\r\n\r\n    return (\r\n      <div ref={ref} data-react-fancy-carousel-controls=\"\" className={cn(\"flex items-center gap-2\", className)}>\r\n        <button\r\n          type=\"button\"\r\n          onClick={prev}\r\n          disabled={prevDisabled}\r\n          className=\"rounded-lg border px-3 py-1 text-sm disabled:opacity-50 dark:border-zinc-600\"\r\n        >\r\n          {prevLabel ?? \"\\u2190\"}\r\n        <\/button>\r\n        <button\r\n          type=\"button\"\r\n          onClick={next}\r\n          disabled={nextDisabled}\r\n          className=\"rounded-lg border px-3 py-1 text-sm disabled:opacity-50 dark:border-zinc-600\"\r\n        >\r\n          {nextLabel ?? \"\\u2192\"}\r\n        <\/button>\r\n      <\/div>\r\n    );\r\n  },\r\n);\r\n\r\nCarouselControls.displayName = \"CarouselControls\";\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/CarouselControls.tsx"},{"path":"components\/fancy\/carousel\/CarouselPanels.tsx","content":"import { Children, forwardRef, isValidElement, useEffect, useRef, useState } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useCarousel } from \".\/Carousel.context\";\r\nimport type { CarouselPanelsProps, CarouselSlideProps } from \".\/Carousel.types\";\r\n\r\nexport const CarouselPanels = forwardRef<HTMLDivElement, CarouselPanelsProps>(\r\n  ({ children, className, transition = \"none\" }, ref) => {\r\n    const { activeIndex, registerSlides, registerSlideNames } = useCarousel();\r\n    const slides = Children.toArray(children);\r\n    const prevIndexRef = useRef(activeIndex);\r\n    const [transitionState, setTransitionState] = useState<\"enter\" | \"idle\">(\"idle\");\r\n\r\n    \/\/ Register slide count\r\n    useEffect(() => {\r\n      registerSlides(slides.length);\r\n    }, [slides.length, registerSlides]);\r\n\r\n    \/\/ Register slide names from the `name` prop on each Carousel.Slide\r\n    useEffect(() => {\r\n      const names = slides.map((slide) => {\r\n        if (isValidElement<CarouselSlideProps>(slide) && slide.props.name) {\r\n          return slide.props.name;\r\n        }\r\n        return \"\";\r\n      });\r\n      registerSlideNames(names);\r\n    }, [slides.length, registerSlideNames]); \/\/ eslint-disable-line react-hooks\/exhaustive-deps\r\n\r\n    \/\/ Handle fade transition when activeIndex changes\r\n    useEffect(() => {\r\n      if (transition !== \"fade\") {\r\n        return;\r\n      }\r\n\r\n      if (prevIndexRef.current !== activeIndex) {\r\n        setTransitionState(\"enter\");\r\n        const timer = setTimeout(() => {\r\n          setTransitionState(\"idle\");\r\n        }, 300);\r\n        prevIndexRef.current = activeIndex;\r\n        return () => clearTimeout(timer);\r\n      }\r\n    }, [activeIndex, transition]);\r\n\r\n    if (transition === \"fade\") {\r\n      return (\r\n        <div ref={ref} data-react-fancy-carousel-panels=\"\" className={cn(\"relative overflow-hidden\", className)}>\r\n          <div\r\n            key={activeIndex}\r\n            className={cn(\r\n              transitionState === \"enter\" && \"fancy-fade-in\",\r\n            )}\r\n            style={{\r\n              animationDuration: transitionState === \"enter\" ? \"300ms\" : undefined,\r\n            }}\r\n          >\r\n            {slides[activeIndex]}\r\n          <\/div>\r\n        <\/div>\r\n      );\r\n    }\r\n\r\n    return (\r\n      <div ref={ref} data-react-fancy-carousel-panels=\"\" className={cn(\"relative overflow-hidden\", className)}>\r\n        {slides[activeIndex]}\r\n      <\/div>\r\n    );\r\n  },\r\n);\r\n\r\nCarouselPanels.displayName = \"CarouselPanels\";\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/CarouselPanels.tsx"},{"path":"components\/fancy\/carousel\/CarouselSlide.tsx","content":"import { forwardRef } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport type { CarouselSlideProps } from \".\/Carousel.types\";\r\n\r\nexport const CarouselSlide = forwardRef<HTMLDivElement, CarouselSlideProps>(\r\n  ({ children, className, name: _name }, ref) => {\r\n    return (\r\n      <div ref={ref} data-react-fancy-carousel-slide=\"\" className={cn(\"w-full\", className)}>\r\n        {children}\r\n      <\/div>\r\n    );\r\n  },\r\n);\r\n\r\nCarouselSlide.displayName = \"CarouselSlide\";\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/CarouselSlide.tsx"},{"path":"components\/fancy\/carousel\/CarouselSteps.tsx","content":"import { forwardRef } from \"react\";\r\nimport { cn } from \"..\/..\/utils\/cn\";\r\nimport { useCarousel } from \".\/Carousel.context\";\r\nimport type { CarouselStepsProps } from \".\/Carousel.types\";\r\n\r\nexport const CarouselSteps = forwardRef<HTMLDivElement, CarouselStepsProps>(\r\n  ({ className }, ref) => {\r\n    const { activeIndex, totalSlides, goTo, variant, slideNames, headless } = useCarousel();\r\n\r\n    if (headless) {\r\n      return null;\r\n    }\r\n\r\n    if (variant === \"wizard\") {\r\n      return (\r\n        <div ref={ref} data-react-fancy-carousel-steps=\"\" className={cn(\"flex items-center gap-3\", className)}>\r\n          {Array.from({ length: totalSlides }, (_, i) => {\r\n            const isActive = i === activeIndex;\r\n            const isCompleted = i < activeIndex;\r\n            const name = slideNames[i];\r\n\r\n            return (\r\n              <button\r\n                key={i}\r\n                type=\"button\"\r\n                onClick={() => goTo(i)}\r\n                className={cn(\r\n                  \"flex items-center gap-1.5 text-sm font-medium transition-colors\",\r\n                  isActive && \"text-blue-600 dark:text-blue-400\",\r\n                  isCompleted && \"text-green-600 dark:text-green-400\",\r\n                  !isActive && !isCompleted && \"text-zinc-400 dark:text-zinc-500\",\r\n                )}\r\n                aria-label={name || `Step ${i + 1}`}\r\n                aria-current={isActive ? \"step\" : undefined}\r\n              >\r\n                <span\r\n                  className={cn(\r\n                    \"flex h-6 w-6 items-center justify-center rounded-full text-xs\",\r\n                    isActive && \"bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300\",\r\n                    isCompleted && \"bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300\",\r\n                    !isActive && !isCompleted && \"bg-zinc-100 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-500\",\r\n                  )}\r\n                >\r\n                  {isCompleted ? \"\\u2713\" : i + 1}\r\n                <\/span>\r\n                {name && <span>{name}<\/span>}\r\n              <\/button>\r\n            );\r\n          })}\r\n        <\/div>\r\n      );\r\n    }\r\n\r\n    \/\/ Directional variant: dot indicators (default)\r\n    return (\r\n      <div ref={ref} data-react-fancy-carousel-steps=\"\" className={cn(\"flex items-center gap-1.5\", className)}>\r\n        {Array.from({ length: totalSlides }, (_, i) => (\r\n          <button\r\n            key={i}\r\n            type=\"button\"\r\n            onClick={() => goTo(i)}\r\n            className={cn(\r\n              \"h-2 w-2 rounded-full transition-colors\",\r\n              i === activeIndex ? \"bg-zinc-800 dark:bg-white\" : \"bg-zinc-300 dark:bg-zinc-600\",\r\n            )}\r\n            aria-label={`Go to slide ${i + 1}`}\r\n          \/>\r\n        ))}\r\n      <\/div>\r\n    );\r\n  },\r\n);\r\n\r\nCarouselSteps.displayName = \"CarouselSteps\";\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/CarouselSteps.tsx"},{"path":"components\/fancy\/carousel\/index.ts","content":"export { Carousel } from \".\/Carousel\";\r\nexport { CarouselPanels } from \".\/CarouselPanels\";\r\nexport { CarouselSlide } from \".\/CarouselSlide\";\r\nexport { CarouselControls } from \".\/CarouselControls\";\r\nexport { CarouselSteps } from \".\/CarouselSteps\";\r\nexport { useCarousel } from \".\/Carousel.context\";\r\nexport type {\r\n  CarouselProps,\r\n  CarouselSlideProps,\r\n  CarouselControlsProps,\r\n  CarouselStepsProps,\r\n  CarouselPanelsProps,\r\n  CarouselContextValue,\r\n  CarouselVariant,\r\n} from \".\/Carousel.types\";\r\n","type":"registry:ui","target":"components\/fancy\/carousel\/index.ts"}]}