{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Carousel-TS-CSS",
	"title": "Carousel",
	"description": "Responsive carousel with touch gestures, looping and transitions.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "Carousel.css",
			"target": "@components/Carousel.css",
			"content": ".carousel-container {\n  position: relative;\n  overflow: hidden;\n  border: 1px solid #555;\n  border-radius: 24px;\n  padding: 16px;\n  --outer-r: 24px;\n  --p-distance: 12px;\n}\n\n.carousel-track {\n  display: flex;\n}\n\n.carousel-item {\n  position: relative;\n  display: flex;\n  flex-shrink: 0;\n  flex-direction: column;\n  align-items: flex-start;\n  justify-content: space-between;\n  border: 1px solid #555;\n  border-radius: calc(var(--outer-r) - var(--p-distance));\n  background-color: #0d0d0d;\n  overflow: hidden;\n  cursor: grab;\n}\n\n.carousel-item:active {\n  cursor: grabbing;\n}\n\n.carousel-container.round {\n  border: 1px solid #555;\n}\n\n.carousel-item.round {\n  background-color: #0d0d0d;\n  position: relative;\n  bottom: 0.1em;\n  border: 1px solid #555;\n  justify-content: center;\n  align-items: center;\n  text-align: center;\n}\n\n.carousel-item-header.round {\n  padding: 0;\n  margin: 0;\n}\n\n.carousel-indicators-container.round {\n  position: absolute;\n  z-index: 2;\n  bottom: 3em;\n  left: 50%;\n  transform: translateX(-50%);\n}\n\n.carousel-indicator.active {\n  background-color: #333333;\n}\n\n.carousel-indicator.inactive {\n  background-color: rgba(51, 51, 51, 0.4);\n}\n\n.carousel-item-header {\n  margin-bottom: 16px;\n  padding: 20px;\n  padding-top: 20px;\n}\n\n.carousel-icon-container {\n  display: flex;\n  height: 28px;\n  width: 28px;\n  align-items: center;\n  justify-content: center;\n  border-radius: 50%;\n  background-color: #fff;\n}\n\n.carousel-icon {\n  height: 16px;\n  width: 16px;\n  color: #120F17;\n}\n\n.carousel-item-content {\n  padding: 20px;\n  padding-bottom: 20px;\n}\n\n.carousel-item-title {\n  margin-bottom: 4px;\n  font-weight: 900;\n  font-size: 18px;\n  color: #fff;\n}\n\n.carousel-item-description {\n  font-size: 14px;\n  color: #fff;\n}\n\n.carousel-indicators-container {\n  display: flex;\n  width: 100%;\n  justify-content: center;\n}\n\n.carousel-indicators {\n  margin-top: 16px;\n  display: flex;\n  width: 150px;\n  justify-content: space-between;\n  padding: 0 32px;\n}\n\n.carousel-indicator {\n  height: 8px;\n  width: 8px;\n  border: none;\n  padding: 0;\n  appearance: none;\n  border-radius: 50%;\n  cursor: pointer;\n  transition: background-color 150ms;\n}\n\n.carousel-indicator:focus-visible {\n  outline: 2px solid #fff;\n  outline-offset: 2px;\n}\n\n.carousel-indicator.active {\n  background-color: #fff;\n}\n\n.carousel-indicator.inactive {\n  background-color: #555;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "Carousel.tsx",
			"content": "import { useEffect, useMemo, useRef, useState } from 'react';\nimport { motion, type PanInfo, useMotionValue, useTransform } from 'motion/react';\n// replace icons with your own if needed\nimport { FiCircle, FiCode, FiFileText, FiLayers, FiLayout } from 'react-icons/fi';\nimport './Carousel.css';\n\nexport interface CarouselItem {\n  title: string;\n  description: string;\n  id: number;\n  icon: React.ReactElement;\n}\n\nexport interface CarouselProps {\n  items?: CarouselItem[];\n  baseWidth?: number;\n  autoplay?: boolean;\n  autoplayDelay?: number;\n  pauseOnHover?: boolean;\n  loop?: boolean;\n  round?: boolean;\n}\n\nconst DEFAULT_ITEMS: CarouselItem[] = [\n  {\n    title: 'Text Animations',\n    description: 'Cool text animations for your projects.',\n    id: 1,\n    icon: <FiFileText className=\"carousel-icon\" />\n  },\n  {\n    title: 'Animations',\n    description: 'Smooth animations for your projects.',\n    id: 2,\n    icon: <FiCircle className=\"carousel-icon\" />\n  },\n  {\n    title: 'Components',\n    description: 'Reusable components for your projects.',\n    id: 3,\n    icon: <FiLayers className=\"carousel-icon\" />\n  },\n  {\n    title: 'Backgrounds',\n    description: 'Beautiful backgrounds and patterns for your projects.',\n    id: 4,\n    icon: <FiLayout className=\"carousel-icon\" />\n  },\n  {\n    title: 'Common UI',\n    description: 'Common UI components are coming soon!',\n    id: 5,\n    icon: <FiCode className=\"carousel-icon\" />\n  }\n];\n\nconst DRAG_BUFFER = 0;\nconst VELOCITY_THRESHOLD = 500;\nconst GAP = 16;\nconst SPRING_OPTIONS = { type: 'spring' as const, stiffness: 300, damping: 30 };\n\ninterface CarouselItemProps {\n  item: CarouselItem;\n  index: number;\n  itemWidth: number;\n  round: boolean;\n  trackItemOffset: number;\n  x: any;\n  transition: any;\n}\n\nfunction CarouselItem({ item, index, itemWidth, round, trackItemOffset, x, transition }: CarouselItemProps) {\n  const range = [-(index + 1) * trackItemOffset, -index * trackItemOffset, -(index - 1) * trackItemOffset];\n  const outputRange = [90, 0, -90];\n  const rotateY = useTransform(x, range, outputRange, { clamp: false });\n\n  return (\n    <motion.div\n      key={`${item?.id ?? index}-${index}`}\n      className={`carousel-item ${round ? 'round' : ''}`}\n      style={{\n        width: itemWidth,\n        height: round ? itemWidth : '100%',\n        rotateY: rotateY,\n        ...(round && { borderRadius: '50%' })\n      }}\n      transition={transition}\n    >\n      <div className={`carousel-item-header ${round ? 'round' : ''}`}>\n        <span className=\"carousel-icon-container\">{item.icon}</span>\n      </div>\n      <div className=\"carousel-item-content\">\n        <div className=\"carousel-item-title\">{item.title}</div>\n        <p className=\"carousel-item-description\">{item.description}</p>\n      </div>\n    </motion.div>\n  );\n}\n\nexport default function Carousel({\n  items = DEFAULT_ITEMS,\n  baseWidth = 300,\n  autoplay = false,\n  autoplayDelay = 3000,\n  pauseOnHover = false,\n  loop = false,\n  round = false\n}: CarouselProps): React.JSX.Element {\n  const containerPadding = 16;\n  const itemWidth = baseWidth - containerPadding * 2;\n  const trackItemOffset = itemWidth + GAP;\n  const itemsForRender = useMemo(() => {\n    if (!loop) return items;\n    if (items.length === 0) return [];\n    return [items[items.length - 1], ...items, items[0]];\n  }, [items, loop]);\n\n  const [position, setPosition] = useState<number>(loop ? 1 : 0);\n  const x = useMotionValue(0);\n  const [isHovered, setIsHovered] = useState<boolean>(false);\n  const [isJumping, setIsJumping] = useState<boolean>(false);\n  const [isAnimating, setIsAnimating] = useState<boolean>(false);\n\n  const containerRef = useRef<HTMLDivElement>(null);\n  useEffect(() => {\n    if (pauseOnHover && containerRef.current) {\n      const container = containerRef.current;\n      const handleMouseEnter = () => setIsHovered(true);\n      const handleMouseLeave = () => setIsHovered(false);\n      container.addEventListener('mouseenter', handleMouseEnter);\n      container.addEventListener('mouseleave', handleMouseLeave);\n      return () => {\n        container.removeEventListener('mouseenter', handleMouseEnter);\n        container.removeEventListener('mouseleave', handleMouseLeave);\n      };\n    }\n  }, [pauseOnHover]);\n\n  useEffect(() => {\n    if (!autoplay || itemsForRender.length <= 1) return undefined;\n    if (pauseOnHover && isHovered) return undefined;\n\n    const timer = setInterval(() => {\n      setPosition(prev => Math.min(prev + 1, itemsForRender.length - 1));\n    }, autoplayDelay);\n\n    return () => clearInterval(timer);\n  }, [autoplay, autoplayDelay, isHovered, pauseOnHover, itemsForRender.length]);\n\n  useEffect(() => {\n    const startingPosition = loop ? 1 : 0;\n    setPosition(startingPosition);\n    x.set(-startingPosition * trackItemOffset);\n  }, [items.length, loop, trackItemOffset, x]);\n\n  useEffect(() => {\n    if (!loop && position > itemsForRender.length - 1) {\n      setPosition(Math.max(0, itemsForRender.length - 1));\n    }\n  }, [itemsForRender.length, loop, position]);\n\n  const effectiveTransition = isJumping ? { duration: 0 } : SPRING_OPTIONS;\n\n  const handleAnimationStart = () => {\n    setIsAnimating(true);\n  };\n\n  const handleAnimationComplete = () => {\n    if (!loop || itemsForRender.length <= 1) {\n      setIsAnimating(false);\n      return;\n    }\n    const lastCloneIndex = itemsForRender.length - 1;\n\n    if (position === lastCloneIndex) {\n      setIsJumping(true);\n      const target = 1;\n      setPosition(target);\n      x.set(-target * trackItemOffset);\n      requestAnimationFrame(() => {\n        setIsJumping(false);\n        setIsAnimating(false);\n      });\n      return;\n    }\n\n    if (position === 0) {\n      setIsJumping(true);\n      const target = items.length;\n      setPosition(target);\n      x.set(-target * trackItemOffset);\n      requestAnimationFrame(() => {\n        setIsJumping(false);\n        setIsAnimating(false);\n      });\n      return;\n    }\n\n    setIsAnimating(false);\n  };\n\n  const handleDragEnd = (_: MouseEvent | TouchEvent | PointerEvent, info: PanInfo): void => {\n    const { offset, velocity } = info;\n    const direction =\n      offset.x < -DRAG_BUFFER || velocity.x < -VELOCITY_THRESHOLD\n        ? 1\n        : offset.x > DRAG_BUFFER || velocity.x > VELOCITY_THRESHOLD\n          ? -1\n          : 0;\n\n    if (direction === 0) return;\n\n    setPosition(prev => {\n      const next = prev + direction;\n      const max = itemsForRender.length - 1;\n      return Math.max(0, Math.min(next, max));\n    });\n  };\n\n  const dragProps = loop\n    ? {}\n    : {\n        dragConstraints: {\n          left: -trackItemOffset * Math.max(itemsForRender.length - 1, 0),\n          right: 0\n        }\n      };\n\n  const activeIndex =\n    items.length === 0 ? 0 : loop ? (position - 1 + items.length) % items.length : Math.min(position, items.length - 1);\n\n  return (\n    <div\n      ref={containerRef}\n      className={`carousel-container ${round ? 'round' : ''}`}\n      style={{\n        width: `${baseWidth}px`,\n        ...(round && { height: `${baseWidth}px`, borderRadius: '50%' })\n      }}\n    >\n      <motion.div\n        className=\"carousel-track\"\n        drag={isAnimating ? false : 'x'}\n        {...dragProps}\n        style={{\n          width: itemWidth,\n          gap: `${GAP}px`,\n          perspective: 1000,\n          perspectiveOrigin: `${position * trackItemOffset + itemWidth / 2}px 50%`,\n          x\n        }}\n        onDragEnd={handleDragEnd}\n        animate={{ x: -(position * trackItemOffset) }}\n        transition={effectiveTransition}\n        onAnimationStart={handleAnimationStart}\n        onAnimationComplete={handleAnimationComplete}\n      >\n        {itemsForRender.map((item, index) => (\n          <CarouselItem\n            key={`${item?.id ?? index}-${index}`}\n            item={item}\n            index={index}\n            itemWidth={itemWidth}\n            round={round}\n            trackItemOffset={trackItemOffset}\n            x={x}\n            transition={effectiveTransition}\n          />\n        ))}\n      </motion.div>\n      <div className={`carousel-indicators-container ${round ? 'round' : ''}`}>\n        <div className=\"carousel-indicators\">\n          {items.map((_, index) => (\n            <motion.button\n              type=\"button\"\n              key={index}\n              className={`carousel-indicator ${activeIndex === index ? 'active' : 'inactive'}`}\n              aria-label={`Go to slide ${index + 1}`}\n              aria-current={activeIndex === index}\n              animate={{\n                scale: activeIndex === index ? 1.2 : 1\n              }}\n              onClick={() => setPosition(loop ? index + 1 : index)}\n              transition={{ duration: 0.15 }}\n            />\n          ))}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12",
		"react-icons@^5.5.0"
	]
}