{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "classname-slider",
  "dependencies": [
    "embla-carousel",
    "lucide-react",
    "embla-carousel-class-names",
    "embla-carousel-react",
    "embla-carousel-autoplay",
    "motion"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "./registry/components/carousel/classname-slider.tsx",
      "content": "'use client';\nimport { Carousel, Slider, SliderContainer, SliderDotButton } from '@/components/ui/carousel';\nimport type { EmblaOptionsType } from 'embla-carousel';\nimport ClassNames from 'embla-carousel-class-names';\nimport React, { ReactNode } from 'react';\n\nfunction ClassName() {\n  const OPTIONS: EmblaOptionsType = { loop: true };\n  return (\n    <>\n      <Carousel options={OPTIONS} plugins={[ClassNames()]}>\n        <SliderContainer className='gap-2'>\n          <Slider\n            className='\n    w-4/5\n    transition-opacity duration-700 ease-out\n    [&.is-in-view]:opacity-20\n    [&.is-snapped]:opacity-100\n  '\n          >\n            <div className='h-[28em] pl-2 bg-red-500 rounded-xl'></div>\n          </Slider>\n\n          <Slider\n            className='\n    w-4/5 \n    transition-opacity duration-700 ease-out\n    [&.is-in-view]:opacity-20\n    [&.is-snapped]:opacity-100\n  '\n          >\n            <div className='h-[28em] bg-blue-500 rounded-xl'></div>\n          </Slider>\n\n          <Slider\n            className='\n    w-4/5\n    transition-opacity duration-700 ease-out\n    [&.is-in-view]:opacity-20\n    [&.is-snapped]:opacity-100\n  '\n          >\n            <div className='h-[28em] bg-green-500 rounded-xl'></div>\n          </Slider>\n\n          <Slider\n            className='\n    w-4/5\n    transition-opacity duration-700 ease-out\n    [&.is-in-view]:opacity-20\n    [&.is-snapped]:opacity-100\n  '\n          >\n            <div className='h-[28em] bg-yellow-500 rounded-xl'></div>\n          </Slider>\n        </SliderContainer>\n        <div className='flex justify-center py-2'>\n          <SliderDotButton />\n        </div>\n      </Carousel>\n    </>\n  );\n}\n\nexport default ClassName;\n",
      "type": "registry:component"
    },
    {
      "path": "./components/ui/carousel.tsx",
      "content": "'use client';\n\nimport { cn } from '@/lib/utils';\nimport type { EmblaCarouselType, EmblaEventType, EmblaOptionsType } from 'embla-carousel';\nimport useEmblaCarousel from 'embla-carousel-react';\nimport { AnimatePresence, motion } from 'motion/react';\nimport type React from 'react';\nimport {\n  createContext,\n  forwardRef,\n  useCallback,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n} from 'react';\n\n// ============= TYPES =============\ninterface CarouselProps extends React.HTMLAttributes<HTMLDivElement> {\n  options?: EmblaOptionsType;\n  plugins?: Parameters<typeof useEmblaCarousel>[1];\n  isScale?: boolean;\n}\n\ninterface CarouselContextType {\n  emblaApi: EmblaCarouselType | undefined;\n  emblaThumbsApi: EmblaCarouselType | undefined;\n  emblaRef: ReturnType<typeof useEmblaCarousel>[0];\n  emblaThumbsRef: ReturnType<typeof useEmblaCarousel>[0];\n  prevBtnDisabled: boolean;\n  nextBtnDisabled: boolean;\n  onPrevButtonClick: () => void;\n  onNextButtonClick: () => void;\n  selectedIndex: number;\n  scrollSnaps: number[];\n  onDotButtonClick: (index: number) => void;\n  scrollProgress: number;\n  selectedSnap: number;\n  snapCount: number;\n  isScale: boolean;\n  slidesArr: string[];\n  setSlidesArr: React.Dispatch<React.SetStateAction<string[]>>;\n  onThumbClick: (index: number) => void;\n  carouselId: string;\n  orientation: 'vertical' | 'horizontal';\n  direction: 'ltr' | 'rtl' | undefined;\n  handleKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n}\n\n// ============= CONTEXT =============\nconst CarouselContext = createContext<CarouselContextType | undefined>(undefined);\n\nexport const useCarousel = () => {\n  const context = useContext(CarouselContext);\n  if (!context) {\n    throw new Error('useCarousel must be used within a Carousel component');\n  }\n  return context;\n};\n\n// ============= UTILITIES =============\nconst TWEEN_FACTOR_BASE = 0.52;\nconst numberWithinRange = (number: number, min: number, max: number): number =>\n  Math.min(Math.max(number, min), max);\n\n// ============= MAIN CAROUSEL COMPONENT =============\nexport const Carousel = forwardRef<HTMLDivElement, CarouselProps>(\n  ({ children, options = {}, plugins = [], className, isScale = false, dir, ...props }, ref) => {\n    const carouselId = useId();\n    const [slidesArr, setSlidesArr] = useState<string[]>([]);\n\n    const orientation = options.axis === 'y' ? 'vertical' : 'horizontal';\n    const direction = options.direction ?? (dir as 'ltr' | 'rtl' | undefined);\n\n    // Main carousel\n    const [emblaRef, emblaApi] = useEmblaCarousel(\n      {\n        ...options,\n        axis: orientation === 'vertical' ? 'y' : 'x',\n        direction,\n      },\n      plugins\n    );\n\n    // Thumbnails carousel\n    const [emblaThumbsRef, emblaThumbsApi] = useEmblaCarousel({\n      containScroll: 'keepSnaps',\n      dragFree: true,\n      axis: orientation === 'vertical' ? 'y' : 'x',\n      direction,\n    });\n\n    // State\n    const [prevBtnDisabled, setPrevBtnDisabled] = useState(true);\n    const [nextBtnDisabled, setNextBtnDisabled] = useState(true);\n    const [selectedIndex, setSelectedIndex] = useState(0);\n    const [scrollSnaps, setScrollSnaps] = useState<number[]>([]);\n    const [scrollProgress, setScrollProgress] = useState(0);\n    const [snapCount, setSnapCount] = useState(0);\n\n    // Navigation callbacks\n    const onPrevButtonClick = useCallback(() => {\n      emblaApi?.scrollPrev();\n    }, [emblaApi]);\n\n    const onNextButtonClick = useCallback(() => {\n      emblaApi?.scrollNext();\n    }, [emblaApi]);\n\n    const onDotButtonClick = useCallback(\n      (index: number) => {\n        emblaApi?.scrollTo(index);\n      },\n      [emblaApi]\n    );\n\n    const onThumbClick = useCallback(\n      (index: number) => {\n        if (!emblaApi || !emblaThumbsApi) return;\n        emblaApi.scrollTo(index);\n      },\n      [emblaApi, emblaThumbsApi]\n    );\n\n    // Keyboard navigation\n    const handleKeyDown = useCallback(\n      (event: React.KeyboardEvent<HTMLDivElement>) => {\n        if (!emblaApi) return;\n        switch (event.key) {\n          case 'ArrowLeft':\n            event.preventDefault();\n            if (orientation === 'horizontal') {\n              direction === 'rtl' ? onNextButtonClick() : onPrevButtonClick();\n            }\n            break;\n          case 'ArrowRight':\n            event.preventDefault();\n            if (orientation === 'horizontal') {\n              direction === 'rtl' ? onPrevButtonClick() : onNextButtonClick();\n            }\n            break;\n          case 'ArrowUp':\n            event.preventDefault();\n            if (orientation === 'vertical') onPrevButtonClick();\n            break;\n          case 'ArrowDown':\n            event.preventDefault();\n            if (orientation === 'vertical') onNextButtonClick();\n            break;\n        }\n      },\n      [emblaApi, orientation, direction, onPrevButtonClick, onNextButtonClick]\n    );\n\n    // Selection handler\n    const onSelect = useCallback(() => {\n      if (!emblaApi) return;\n      setSelectedIndex(emblaApi.selectedScrollSnap());\n      setPrevBtnDisabled(!emblaApi.canScrollPrev());\n      setNextBtnDisabled(!emblaApi.canScrollNext());\n      emblaThumbsApi?.scrollTo(emblaApi.selectedScrollSnap());\n    }, [emblaApi, emblaThumbsApi]);\n\n    // Scroll progress handler\n    const onScroll = useCallback((emblaApi: EmblaCarouselType) => {\n      const progress = Math.max(0, Math.min(1, emblaApi.scrollProgress()));\n      setScrollProgress(progress * 100);\n    }, []);\n\n    // Scale animation for isScale mode\n    const tweenFactor = useRef(0);\n    const tweenNodes = useRef<HTMLElement[]>([]);\n\n    const setTweenNodes = useCallback(\n      (emblaApi: EmblaCarouselType): void => {\n        if (!isScale) return;\n        tweenNodes.current = emblaApi\n          .slideNodes()\n          .map((slideNode) => slideNode.querySelector('.slider_content')) as HTMLElement[];\n      },\n      [isScale]\n    );\n\n    const setTweenFactor = useCallback(\n      (emblaApi: EmblaCarouselType) => {\n        if (!isScale) return;\n        tweenFactor.current = TWEEN_FACTOR_BASE * emblaApi.scrollSnapList().length;\n      },\n      [isScale]\n    );\n\n    const tweenScale = useCallback(\n      (emblaApi: EmblaCarouselType, eventName?: EmblaEventType) => {\n        if (!isScale) return;\n        const engine = emblaApi.internalEngine();\n        const scrollProgress = emblaApi.scrollProgress();\n        const slidesInView = emblaApi.slidesInView();\n        const isScrollEvent = eventName === 'scroll';\n\n        emblaApi.scrollSnapList().forEach((scrollSnap, snapIndex) => {\n          let diffToTarget = scrollSnap - scrollProgress;\n          const slidesInSnap = engine.slideRegistry[snapIndex];\n\n          slidesInSnap.forEach((slideIndex) => {\n            if (isScrollEvent && !slidesInView.includes(slideIndex)) return;\n\n            if (engine.options.loop) {\n              engine.slideLooper.loopPoints.forEach((loopItem) => {\n                const target = loopItem.target();\n                if (slideIndex === loopItem.index && target !== 0) {\n                  const sign = Math.sign(target);\n                  if (sign === -1) {\n                    diffToTarget = scrollSnap - (1 + scrollProgress);\n                  }\n                  if (sign === 1) {\n                    diffToTarget = scrollSnap + (1 - scrollProgress);\n                  }\n                }\n              });\n            }\n\n            const tweenValue = 1 - Math.abs(diffToTarget * tweenFactor.current);\n            const scale = numberWithinRange(tweenValue, 0, 1).toString();\n            const tweenNode = tweenNodes.current[slideIndex];\n            if (tweenNode) {\n              tweenNode.style.transform = `scale(${scale})`;\n            }\n          });\n        });\n      },\n      [isScale]\n    );\n\n    // Effects\n    useEffect(() => {\n      if (!emblaApi) return;\n      setScrollSnaps(emblaApi.scrollSnapList());\n      setSnapCount(emblaApi.scrollSnapList().length);\n      onSelect();\n      onScroll(emblaApi);\n\n      emblaApi\n        .on('reInit', onSelect)\n        .on('select', onSelect)\n        .on('reInit', onScroll)\n        .on('scroll', onScroll);\n\n      if (isScale) {\n        setTweenNodes(emblaApi);\n        setTweenFactor(emblaApi);\n        tweenScale(emblaApi);\n        emblaApi\n          .on('reInit', setTweenNodes)\n          .on('reInit', setTweenFactor)\n          .on('reInit', tweenScale)\n          .on('scroll', tweenScale);\n      }\n    }, [emblaApi, onSelect, onScroll, isScale, setTweenNodes, setTweenFactor, tweenScale]);\n\n    return (\n      <CarouselContext.Provider\n        value={{\n          emblaApi,\n          emblaThumbsApi,\n          emblaRef,\n          emblaThumbsRef,\n          prevBtnDisabled,\n          nextBtnDisabled,\n          onPrevButtonClick,\n          onNextButtonClick,\n          selectedIndex,\n          scrollSnaps,\n          onDotButtonClick,\n          scrollProgress,\n          selectedSnap: selectedIndex,\n          snapCount,\n          isScale,\n          slidesArr,\n          setSlidesArr,\n          onThumbClick,\n          carouselId,\n          orientation,\n          direction,\n          handleKeyDown,\n        }}\n      >\n        <div\n          ref={ref}\n          tabIndex={0}\n          onKeyDownCapture={handleKeyDown}\n          className={cn('relative w-full focus:outline-hidden', className)}\n          dir={direction}\n          {...props}\n        >\n          {children}\n        </div>\n      </CarouselContext.Provider>\n    );\n  }\n);\n\nCarousel.displayName = 'Carousel';\n\n// ============= SLIDER CONTAINER =============\nexport const SliderContainer = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, children, ...props }, ref) => {\n    const { emblaRef, orientation } = useCarousel();\n\n    return (\n      <div ref={emblaRef} className='overflow-hidden' {...props}>\n        <div\n          ref={ref}\n          className={cn('flex', orientation === 'vertical' ? 'flex-col' : 'flex-row', className)}\n          style={{ touchAction: 'pan-y pinch-zoom' }}\n        >\n          {children}\n        </div>\n      </div>\n    );\n  }\n);\n\nSliderContainer.displayName = 'SliderContainer';\n\n// ============= SLIDER ITEM =============\ninterface SliderProps extends React.HTMLAttributes<HTMLDivElement> {\n  thumbnailSrc?: string;\n}\n\nexport const Slider = forwardRef<HTMLDivElement, SliderProps>(\n  ({ children, className, thumbnailSrc, ...props }, ref) => {\n    const { isScale, setSlidesArr, orientation } = useCarousel();\n\n    useEffect(() => {\n      if (thumbnailSrc) {\n        setSlidesArr((prev) => {\n          if (!prev.includes(thumbnailSrc)) {\n            return [...prev, thumbnailSrc];\n          }\n          return prev;\n        });\n      }\n    }, [thumbnailSrc, setSlidesArr]);\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          'min-w-0 shrink-0 grow-0',\n          // orientation === 'vertical' ? 'pb-1' : 'pr-1',\n          className\n        )}\n        {...props}\n      >\n        {isScale ? <div className='slider_content'>{children}</div> : children}\n      </div>\n    );\n  }\n);\n\nSlider.displayName = 'Slider';\n\n// ============= NAVIGATION BUTTONS =============\nexport const SliderPrevButton = forwardRef<\n  HTMLButtonElement,\n  React.ButtonHTMLAttributes<HTMLButtonElement>\n>(({ children, className, ...props }, ref) => {\n  const { onPrevButtonClick, prevBtnDisabled } = useCarousel();\n\n  return (\n    <button\n      ref={ref}\n      type='button'\n      onClick={onPrevButtonClick}\n      disabled={prevBtnDisabled}\n      className={cn('', className)}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n});\n\nSliderPrevButton.displayName = 'SliderPrevButton';\n\nexport const SliderNextButton = forwardRef<\n  HTMLButtonElement,\n  React.ButtonHTMLAttributes<HTMLButtonElement>\n>(({ children, className, ...props }, ref) => {\n  const { onNextButtonClick, nextBtnDisabled } = useCarousel();\n\n  return (\n    <button\n      ref={ref}\n      type='button'\n      onClick={onNextButtonClick}\n      disabled={nextBtnDisabled}\n      className={cn('', className)}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n});\n\nSliderNextButton.displayName = 'SliderNextButton';\n\n// ============= PROGRESS BAR =============\nexport const SliderProgress = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => {\n    const { scrollProgress } = useCarousel();\n\n    return (\n      <div\n        ref={ref}\n        className={cn(\n          'bg-neutral-500 relative rounded-md h-2 w-96 max-w-full overflow-hidden',\n          className\n        )}\n        {...props}\n      >\n        <div\n          className='dark:bg-white bg-black absolute w-full top-0 -left-full bottom-0 transition-transform'\n          style={{ transform: `translate3d(${scrollProgress}%,0px,0px)` }}\n        />\n      </div>\n    );\n  }\n);\n\nSliderProgress.displayName = 'SliderProgress';\n\n// ============= SNAP DISPLAY =============\nexport const SliderSnapDisplay = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  ({ className, ...props }, ref) => {\n    const { selectedSnap, snapCount } = useCarousel();\n    const prevSnapRef = useRef(selectedSnap);\n    const direction = selectedSnap > prevSnapRef.current ? 1 : -1;\n\n    useEffect(() => {\n      prevSnapRef.current = selectedSnap;\n    }, [selectedSnap]);\n\n    return (\n      <div\n        ref={ref}\n        className={cn('mix-blend-difference overflow-hidden flex gap-1 items-center', className)}\n        {...props}\n      >\n        <AnimatePresence mode='wait'>\n          <motion.div\n            key={selectedSnap}\n            custom={direction}\n            // @ts-expect-error\n            initial={(d: number) => ({ y: d * 20, opacity: 0 })}\n            animate={{ y: 0, opacity: 1 }}\n            // @ts-expect-error\n            exit={(d: number) => ({ y: d * -20, opacity: 0 })}\n          >\n            {selectedSnap + 1}\n          </motion.div>\n        </AnimatePresence>\n        <span>/ {snapCount}</span>\n      </div>\n    );\n  }\n);\n\nSliderSnapDisplay.displayName = 'SliderSnapDisplay';\n\n// ============= DOT BUTTONS =============\ninterface SliderDotButtonProps extends React.HTMLAttributes<HTMLDivElement> {\n  activeClass?: string;\n}\n\nexport const SliderDotButton = forwardRef<HTMLDivElement, SliderDotButtonProps>(\n  ({ className, activeClass, ...props }, ref) => {\n    const { selectedIndex, scrollSnaps, orientation, onDotButtonClick, carouselId } = useCarousel();\n\n    return (\n      <div ref={ref} className={cn('flex gap-2', className)} {...props}>\n        {scrollSnaps.map((_, index) => (\n          <button\n            key={`${carouselId}-dot-${_}`}\n            type='button'\n            onClick={() => onDotButtonClick(index)}\n            className={cn(\n              'relative inline-flex p-0 m-0',\n              orientation === 'vertical' ? 'h-6 w-1' : 'w-6 h-1'\n            )}\n          >\n            <div\n              className={cn(\n                'bg-neutral-500/40 rounded-full ',\n                orientation === 'vertical' ? 'h-6 w-1' : 'w-6 h-1'\n              )}\n            />\n            {index === selectedIndex && (\n              <AnimatePresence mode='wait'>\n                <motion.div\n                  transition={{\n                    layout: {\n                      duration: 0.4,\n                      ease: 'easeInOut',\n                      delay: 0.04,\n                    },\n                  }}\n                  layoutId={`hover-${carouselId}`}\n                  className={cn(\n                    'absolute z-3 w-full h-full left-0 top-0 dark:bg-white bg-black rounded-full',\n                    orientation === 'vertical' ? 'h-6 w-1' : 'w-6 h-1',\n                    activeClass\n                  )}\n                />\n              </AnimatePresence>\n            )}\n          </button>\n        ))}\n      </div>\n    );\n  }\n);\n\nSliderDotButton.displayName = 'SliderDotButton';\n\n// ============= CAROUSEL INDICATORS =============\ninterface CarouselIndicatorProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  index: number;\n}\n\nexport const CarouselIndicator = forwardRef<HTMLButtonElement, CarouselIndicatorProps>(\n  ({ className, index, ...props }, ref) => {\n    const { selectedIndex, onDotButtonClick } = useCarousel();\n    const isActive = selectedIndex === index;\n\n    return (\n      <button\n        ref={ref}\n        type='button'\n        onClick={() => onDotButtonClick(index)}\n        className={cn(\n          'h-1.5 w-6 rounded-full transition-colors',\n          isActive ? 'bg-primary' : 'bg-primary/50',\n          className\n        )}\n        aria-label={`Go to slide ${index + 1}`}\n        {...props}\n      >\n        <span className='sr-only'>Slide {index + 1}</span>\n      </button>\n    );\n  }\n);\n\nCarouselIndicator.displayName = 'CarouselIndicator';\n\n// Auto-generate thumbnails from slides\nexport const ThumbsSlider = forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement> & {\n    thumbsClassName?: string;\n    thumbsSliderClassName?: string;\n  }\n>(({ className, thumbsClassName, thumbsSliderClassName, ...props }, ref) => {\n  const { slidesArr, selectedIndex, onThumbClick, orientation, emblaThumbsRef } = useCarousel();\n\n  if (slidesArr.length === 0) return null;\n\n  return (\n    <div ref={emblaThumbsRef} className={cn('overflow-hidden', className)} {...props}>\n      <div\n        ref={ref}\n        className={cn(\n          'flex gap-2 h-[300px]',\n          orientation === 'vertical' ? 'flex-col' : 'flex-row',\n          thumbsClassName\n        )}\n      >\n        {slidesArr.map((src, index) => (\n          <div\n            key={src}\n            onClick={() => onThumbClick(index)}\n            className={cn(\n              'shrink-0 cursor-pointer transition-opacity',\n              'border-2 rounded-md',\n              orientation === 'vertical' ? 'basis-[15%] h-20' : 'basis-[15%] h-24',\n              selectedIndex === index\n                ? 'opacity-100 border-primary'\n                : 'opacity-30 border-transparent',\n              thumbsSliderClassName\n            )}\n          >\n            <img\n              src={src}\n              alt={`Thumbnail ${index + 1}`}\n              className='w-full h-full object-cover rounded-md'\n            />\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n});\n\nThumbsSlider.displayName = 'ThumbsSlider';\n\n// Alias for backward compatibility\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}