{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "motion-text-loop",
  "title": "Text loop",
  "description": "A component that cycles through different text phrases with animations.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://ui.ndekocode.dev/r/theme.json",
    "https://ui.ndekocode.dev/r/use-prefers-reduced-motion.json",
    "https://ui.ndekocode.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "components/core/text-loop.tsx",
      "content": "'use client';\nimport { cn } from '@/lib/utils';\nimport { usePrefersReducedMotion } from '@/registry/ndeko/hooks/use-prefers-reduced-motion';\nimport {\n  motion,\n  AnimatePresence,\n  Transition,\n  Variants,\n  AnimatePresenceProps,\n  MotionConfig,\n} from 'motion/react';\nimport { useState, useEffect, useRef, Children } from 'react';\n\nexport type TextLoopProps = {\n  children: React.ReactNode[];\n  className?: string;\n  interval?: number;\n  transition?: Transition;\n  variants?: Variants;\n  onIndexChange?: (index: number) => void;\n  trigger?: boolean;\n  mode?: AnimatePresenceProps['mode'];\n};\n\nfunction ReducedMotionConfig({ children }: { children: React.ReactNode }) {\n  const prefersReducedMotion = usePrefersReducedMotion();\n  if (!prefersReducedMotion) {\n    return children;\n  }\n  return <MotionConfig reducedMotion='always'>{children}</MotionConfig>;\n}\n\nexport function TextLoop({\n  children,\n  className,\n  interval = 2,\n  transition = { duration: 0.3 },\n  variants,\n  onIndexChange,\n  trigger = true,\n  mode = 'popLayout',\n}: TextLoopProps) {\n  const [currentIndex, setCurrentIndex] = useState(0);\n  const latestIndex = useRef(0);\n  const items = Children.toArray(children);\n\n  useEffect(() => {\n    if (!trigger) return;\n\n    const intervalMs = interval * 1000;\n    // The parent is told from the timer, never from inside a state update: a parent that sets its own state there would be updated while this component renders.\n    const timer = setInterval(() => {\n      const next = (latestIndex.current + 1) % items.length;\n      latestIndex.current = next;\n      setCurrentIndex(next);\n      onIndexChange?.(next);\n    }, intervalMs);\n    return () => clearInterval(timer);\n  }, [items.length, interval, onIndexChange, trigger]);\n\n  const motionVariants: Variants = {\n    initial: { y: 20, opacity: 0 },\n    animate: { y: 0, opacity: 1 },\n    exit: { y: -20, opacity: 0 },\n  };\n\n  return (\n    <ReducedMotionConfig>\n      <span\n        className={cn('relative inline-block whitespace-nowrap', className)}\n      >\n        <AnimatePresence mode={mode} initial={false}>\n          <motion.span\n            key={currentIndex}\n            className='inline-block'\n            initial='initial'\n            animate='animate'\n            exit='exit'\n            transition={transition}\n            variants={variants || motionVariants}\n          >\n            {items[currentIndex]}\n          </motion.span>\n        </AnimatePresence>\n      </span>\n    </ReducedMotionConfig>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/core/text-loop.tsx"
    }
  ],
  "categories": [
    "motion"
  ],
  "type": "registry:component"
}