{
  "name": "carousel-custom-indicator",
  "type": "registry:ui",
  "componentName": "carousel-custom-indicator",
  "description": "Carousel with custom indicators for navigation.",
  "files": [
    {
      "path": "carousel-custom-indicator.tsx",
      "content": "'use client';\nimport {\n  Carousel,\n  CarouselContent,\n  CarouselItem,\n} from '@/components/core/carousel';\nimport { useState } from 'react';\n\nconst ITEMS = new Array(4).fill(null).map((_, index) => index + 1);\n\nexport function CarouselCustomIndicator() {\n  const [index, setIndex] = useState(0);\n\n  return (\n    <div className='relative w-full max-w-xs py-8'>\n      <Carousel index={index} onIndexChange={setIndex}>\n        <CarouselContent className='relative'>\n          {ITEMS.map((item) => {\n            return (\n              <CarouselItem key={item} className='p-4'>\n                <div className='flex aspect-square items-center justify-center border border-zinc-200 dark:border-zinc-800'>\n                  {item}\n                </div>\n              </CarouselItem>\n            );\n          })}\n        </CarouselContent>\n      </Carousel>\n      <div className='flex w-full justify-center space-x-3 px-4'>\n        {ITEMS.map((item) => {\n          return (\n            <button\n              key={item}\n              type='button'\n              aria-label={`Go to slide ${item}`}\n              onClick={() => setIndex(item - 1)}\n              className='h-12 w-12 border border-zinc-200 dark:border-zinc-800'\n            >\n              {item}\n            </button>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/core/carousel.tsx",
      "content": "'use client';\nimport {\n  Children,\n  ReactNode,\n  createContext,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { motion, Transition, useMotionValue } from 'motion/react';\nimport { cn } from '@/lib/utils';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\n\nexport type CarouselContextType = {\n  index: number;\n  setIndex: (newIndex: number) => void;\n  itemsCount: number;\n  setItemsCount: (newItemsCount: number) => void;\n  disableDrag: boolean;\n};\n\nconst CarouselContext = createContext<CarouselContextType | undefined>(\n  undefined\n);\n\nfunction useCarousel() {\n  const context = useContext(CarouselContext);\n  if (!context) {\n    throw new Error('useCarousel must be used within an CarouselProvider');\n  }\n  return context;\n}\n\nexport type CarouselProviderProps = {\n  children: ReactNode;\n  initialIndex?: number;\n  onIndexChange?: (newIndex: number) => void;\n  disableDrag?: boolean;\n};\n\nfunction CarouselProvider({\n  children,\n  initialIndex = 0,\n  onIndexChange,\n  disableDrag = false,\n}: CarouselProviderProps) {\n  const [index, setIndex] = useState<number>(initialIndex);\n  const [itemsCount, setItemsCount] = useState<number>(0);\n\n  const handleSetIndex = (newIndex: number) => {\n    setIndex(newIndex);\n    onIndexChange?.(newIndex);\n  };\n\n  useEffect(() => {\n    setIndex(initialIndex);\n  }, [initialIndex]);\n\n  return (\n    <CarouselContext.Provider\n      value={{\n        index,\n        setIndex: handleSetIndex,\n        itemsCount,\n        setItemsCount,\n        disableDrag,\n      }}\n    >\n      {children}\n    </CarouselContext.Provider>\n  );\n}\n\nexport type CarouselProps = {\n  children: ReactNode;\n  className?: string;\n  initialIndex?: number;\n  index?: number;\n  onIndexChange?: (newIndex: number) => void;\n  disableDrag?: boolean;\n};\n\nfunction Carousel({\n  children,\n  className,\n  initialIndex = 0,\n  index: externalIndex,\n  onIndexChange,\n  disableDrag = false,\n}: CarouselProps) {\n  const [internalIndex, setInternalIndex] = useState<number>(initialIndex);\n  const isControlled = externalIndex !== undefined;\n  const currentIndex = isControlled ? externalIndex : internalIndex;\n\n  const handleIndexChange = (newIndex: number) => {\n    if (!isControlled) {\n      setInternalIndex(newIndex);\n    }\n    onIndexChange?.(newIndex);\n  };\n\n  return (\n    <CarouselProvider\n      initialIndex={currentIndex}\n      onIndexChange={handleIndexChange}\n      disableDrag={disableDrag}\n    >\n      <div className={cn('group/hover relative', className)}>\n        <div className='overflow-hidden'>{children}</div>\n      </div>\n    </CarouselProvider>\n  );\n}\n\nexport type CarouselNavigationProps = {\n  className?: string;\n  classNameButton?: string;\n  alwaysShow?: boolean;\n};\n\nfunction CarouselNavigation({\n  className,\n  classNameButton,\n  alwaysShow,\n}: CarouselNavigationProps) {\n  const { index, setIndex, itemsCount } = useCarousel();\n\n  return (\n    <div\n      className={cn(\n        'pointer-events-none absolute left-[-12.5%] top-1/2 flex w-[125%] -translate-y-1/2 justify-between px-2',\n        className\n      )}\n    >\n      <button\n        type='button'\n        aria-label='Previous slide'\n        className={cn(\n          'pointer-events-auto h-fit w-fit rounded-full bg-zinc-50 p-2 transition-opacity duration-300 dark:bg-zinc-950',\n          alwaysShow\n            ? 'opacity-100'\n            : 'opacity-0 group-hover/hover:opacity-100',\n          alwaysShow\n            ? 'disabled:opacity-40'\n            : 'group-hover/hover:disabled:opacity-40',\n          classNameButton\n        )}\n        disabled={index === 0}\n        onClick={() => {\n          if (index > 0) {\n            setIndex(index - 1);\n          }\n        }}\n      >\n        <ChevronLeft\n          className='stroke-zinc-600 dark:stroke-zinc-50'\n          size={16}\n        />\n      </button>\n      <button\n        type='button'\n        className={cn(\n          'pointer-events-auto h-fit w-fit rounded-full bg-zinc-50 p-2 transition-opacity duration-300 dark:bg-zinc-950',\n          alwaysShow\n            ? 'opacity-100'\n            : 'opacity-0 group-hover/hover:opacity-100',\n          alwaysShow\n            ? 'disabled:opacity-40'\n            : 'group-hover/hover:disabled:opacity-40',\n          classNameButton\n        )}\n        aria-label='Next slide'\n        disabled={index + 1 === itemsCount}\n        onClick={() => {\n          if (index < itemsCount - 1) {\n            setIndex(index + 1);\n          }\n        }}\n      >\n        <ChevronRight\n          className='stroke-zinc-600 dark:stroke-zinc-50'\n          size={16}\n        />\n      </button>\n    </div>\n  );\n}\n\nexport type CarouselIndicatorProps = {\n  className?: string;\n  classNameButton?: string;\n};\n\nfunction CarouselIndicator({\n  className,\n  classNameButton,\n}: CarouselIndicatorProps) {\n  const { index, itemsCount, setIndex } = useCarousel();\n\n  return (\n    <div\n      className={cn(\n        'absolute bottom-0 z-10 flex w-full items-center justify-center',\n        className\n      )}\n    >\n      <div className='flex space-x-2'>\n        {Array.from({ length: itemsCount }, (_, i) => (\n          <button\n            key={i}\n            type='button'\n            aria-label={`Go to slide ${i + 1}`}\n            onClick={() => setIndex(i)}\n            className={cn(\n              'h-2 w-2 rounded-full transition-opacity duration-300',\n              index === i\n                ? 'bg-zinc-950 dark:bg-zinc-50'\n                : 'bg-zinc-900/50 dark:bg-zinc-100/50',\n              classNameButton\n            )}\n          />\n        ))}\n      </div>\n    </div>\n  );\n}\n\nexport type CarouselContentProps = {\n  children: ReactNode;\n  className?: string;\n  transition?: Transition;\n};\n\nfunction CarouselContent({\n  children,\n  className,\n  transition,\n}: CarouselContentProps) {\n  const { index, setIndex, setItemsCount, disableDrag } = useCarousel();\n  const [visibleItemsCount, setVisibleItemsCount] = useState(1);\n  const dragX = useMotionValue(0);\n  const containerRef = useRef<HTMLDivElement>(null);\n  const itemsLength = Children.count(children);\n\n  useEffect(() => {\n    if (!containerRef.current) {\n      return;\n    }\n\n    const options = {\n      root: containerRef.current,\n      threshold: 0.5,\n    };\n\n    const observer = new IntersectionObserver((entries) => {\n      const visibleCount = entries.filter(\n        (entry) => entry.isIntersecting\n      ).length;\n      setVisibleItemsCount(visibleCount);\n    }, options);\n\n    const childNodes = containerRef.current.children;\n    Array.from(childNodes).forEach((child) => observer.observe(child));\n\n    return () => observer.disconnect();\n  }, [children, setItemsCount]);\n\n  useEffect(() => {\n    if (!itemsLength) {\n      return;\n    }\n\n    setItemsCount(itemsLength);\n  }, [itemsLength, setItemsCount]);\n\n  const onDragEnd = () => {\n    const x = dragX.get();\n\n    if (x <= -10 && index < itemsLength - 1) {\n      setIndex(index + 1);\n    } else if (x >= 10 && index > 0) {\n      setIndex(index - 1);\n    }\n  };\n\n  return (\n    <motion.div\n      drag={disableDrag ? false : 'x'}\n      dragConstraints={\n        disableDrag\n          ? undefined\n          : {\n              left: 0,\n              right: 0,\n            }\n      }\n      dragMomentum={disableDrag ? undefined : false}\n      style={{\n        x: disableDrag ? undefined : dragX,\n      }}\n      animate={{\n        translateX: `-${index * (100 / visibleItemsCount)}%`,\n      }}\n      onDragEnd={disableDrag ? undefined : onDragEnd}\n      transition={\n        transition || {\n          damping: 18,\n          stiffness: 90,\n          type: 'spring',\n          duration: 0.2,\n        }\n      }\n      className={cn(\n        'flex items-center',\n        !disableDrag && 'cursor-grab active:cursor-grabbing',\n        className\n      )}\n      ref={containerRef}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport type CarouselItemProps = {\n  children: ReactNode;\n  className?: string;\n};\n\nfunction CarouselItem({ children, className }: CarouselItemProps) {\n  return (\n    <motion.div\n      className={cn(\n        'w-full min-w-0 shrink-0 grow-0 overflow-hidden',\n        className\n      )}\n    >\n      {children}\n    </motion.div>\n  );\n}\n\nexport {\n  Carousel,\n  CarouselContent,\n  CarouselNavigation,\n  CarouselIndicator,\n  CarouselItem,\n  useCarousel,\n};\n",
      "type": "registry:ui"
    }
  ]
}