{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "RotatingText-JS-TW",
	"title": "RotatingText",
	"description": "Cycles through multiple phrases with 3D rotate / flip transitions.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "RotatingText/RotatingText.jsx",
			"content": "'use client';\n\nimport { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';\nimport { motion, AnimatePresence } from 'motion/react';\n\nfunction cn(...classes) {\n  return classes.filter(Boolean).join(' ');\n}\n\nconst RotatingText = forwardRef((props, ref) => {\n  const {\n    texts,\n    transition = { type: 'spring', damping: 25, stiffness: 300 },\n    initial = { y: '100%', opacity: 0 },\n    animate = { y: 0, opacity: 1 },\n    exit = { y: '-120%', opacity: 0 },\n    animatePresenceMode = 'wait',\n    animatePresenceInitial = false,\n    rotationInterval = 2000,\n    staggerDuration = 0,\n    staggerFrom = 'first',\n    loop = true,\n    auto = true,\n    splitBy = 'characters',\n    onNext,\n    mainClassName,\n    splitLevelClassName,\n    elementLevelClassName,\n    ...rest\n  } = props;\n\n  const [currentTextIndex, setCurrentTextIndex] = useState(0);\n\n  const splitIntoCharacters = text => {\n    if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n      const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });\n      return Array.from(segmenter.segment(text), segment => segment.segment);\n    }\n    return Array.from(text);\n  };\n\n  const elements = useMemo(() => {\n    const currentText = texts[currentTextIndex];\n    if (splitBy === 'characters') {\n      const words = currentText.split(' ');\n      return words.map((word, i) => ({\n        characters: splitIntoCharacters(word),\n        needsSpace: i !== words.length - 1\n      }));\n    }\n    if (splitBy === 'words') {\n      return currentText.split(' ').map((word, i, arr) => ({\n        characters: [word],\n        needsSpace: i !== arr.length - 1\n      }));\n    }\n    if (splitBy === 'lines') {\n      return currentText.split('\\n').map((line, i, arr) => ({\n        characters: [line],\n        needsSpace: i !== arr.length - 1\n      }));\n    }\n\n    return currentText.split(splitBy).map((part, i, arr) => ({\n      characters: [part],\n      needsSpace: i !== arr.length - 1\n    }));\n  }, [texts, currentTextIndex, splitBy]);\n\n  const getStaggerDelay = useCallback(\n    (index, totalChars) => {\n      const total = totalChars;\n      if (staggerFrom === 'first') return index * staggerDuration;\n      if (staggerFrom === 'last') return (total - 1 - index) * staggerDuration;\n      if (staggerFrom === 'center') {\n        const center = Math.floor(total / 2);\n        return Math.abs(center - index) * staggerDuration;\n      }\n      if (staggerFrom === 'random') {\n        const randomIndex = Math.floor(Math.random() * total);\n        return Math.abs(randomIndex - index) * staggerDuration;\n      }\n      return Math.abs(staggerFrom - index) * staggerDuration;\n    },\n    [staggerFrom, staggerDuration]\n  );\n\n  const handleIndexChange = useCallback(\n    newIndex => {\n      setCurrentTextIndex(newIndex);\n      if (onNext) onNext(newIndex);\n    },\n    [onNext]\n  );\n\n  const next = useCallback(() => {\n    const nextIndex = currentTextIndex === texts.length - 1 ? (loop ? 0 : currentTextIndex) : currentTextIndex + 1;\n    if (nextIndex !== currentTextIndex) {\n      handleIndexChange(nextIndex);\n    }\n  }, [currentTextIndex, texts.length, loop, handleIndexChange]);\n\n  const previous = useCallback(() => {\n    const prevIndex = currentTextIndex === 0 ? (loop ? texts.length - 1 : currentTextIndex) : currentTextIndex - 1;\n    if (prevIndex !== currentTextIndex) {\n      handleIndexChange(prevIndex);\n    }\n  }, [currentTextIndex, texts.length, loop, handleIndexChange]);\n\n  const jumpTo = useCallback(\n    index => {\n      const validIndex = Math.max(0, Math.min(index, texts.length - 1));\n      if (validIndex !== currentTextIndex) {\n        handleIndexChange(validIndex);\n      }\n    },\n    [texts.length, currentTextIndex, handleIndexChange]\n  );\n\n  const reset = useCallback(() => {\n    if (currentTextIndex !== 0) {\n      handleIndexChange(0);\n    }\n  }, [currentTextIndex, handleIndexChange]);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      next,\n      previous,\n      jumpTo,\n      reset\n    }),\n    [next, previous, jumpTo, reset]\n  );\n\n  useEffect(() => {\n    if (!auto) return;\n    const intervalId = setInterval(next, rotationInterval);\n    return () => clearInterval(intervalId);\n  }, [next, rotationInterval, auto]);\n\n  return (\n    <motion.span\n      className={cn('flex flex-wrap whitespace-pre-wrap relative', mainClassName)}\n      {...rest}\n      layout\n      transition={transition}\n    >\n      <span className=\"sr-only\">{texts[currentTextIndex]}</span>\n      <AnimatePresence mode={animatePresenceMode} initial={animatePresenceInitial}>\n        <motion.span\n          key={currentTextIndex}\n          className={cn(splitBy === 'lines' ? 'flex flex-col w-full' : 'flex flex-wrap whitespace-pre-wrap relative')}\n          layout\n          aria-hidden=\"true\"\n        >\n          {elements.map((wordObj, wordIndex, array) => {\n            const previousCharsCount = array.slice(0, wordIndex).reduce((sum, word) => sum + word.characters.length, 0);\n            return (\n              <span key={wordIndex} className={cn('inline-flex', splitLevelClassName)}>\n                {wordObj.characters.map((char, charIndex) => (\n                  <motion.span\n                    key={charIndex}\n                    initial={initial}\n                    animate={animate}\n                    exit={exit}\n                    transition={{\n                      ...transition,\n                      delay: getStaggerDelay(\n                        previousCharsCount + charIndex,\n                        array.reduce((sum, word) => sum + word.characters.length, 0)\n                      )\n                    }}\n                    className={cn('inline-block', elementLevelClassName)}\n                  >\n                    {char}\n                  </motion.span>\n                ))}\n                {wordObj.needsSpace && <span className=\"whitespace-pre\"> </span>}\n              </span>\n            );\n          })}\n        </motion.span>\n      </AnimatePresence>\n    </motion.span>\n  );\n});\n\nRotatingText.displayName = 'RotatingText';\nexport default RotatingText;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}