{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "RotatingText-TS-TW",
	"title": "RotatingText",
	"description": "Cycles through multiple phrases with 3D rotate / flip transitions.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "RotatingText/RotatingText.tsx",
			"content": "import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';\nimport {\n  motion,\n  AnimatePresence,\n  type Transition,\n  type VariantLabels,\n  type Target,\n  type TargetAndTransition\n} from 'motion/react';\n\nfunction cn(...classes: (string | undefined | null | boolean)[]): string {\n  return classes.filter(Boolean).join(' ');\n}\n\nexport interface RotatingTextRef {\n  next: () => void;\n  previous: () => void;\n  jumpTo: (index: number) => void;\n  reset: () => void;\n}\n\nexport interface RotatingTextProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<typeof motion.span>,\n    'children' | 'transition' | 'initial' | 'animate' | 'exit'\n  > {\n  texts: string[];\n  transition?: Transition;\n  initial?: boolean | Target | VariantLabels;\n  animate?: boolean | VariantLabels | TargetAndTransition;\n  exit?: Target | VariantLabels;\n  animatePresenceMode?: 'sync' | 'wait';\n  animatePresenceInitial?: boolean;\n  rotationInterval?: number;\n  staggerDuration?: number;\n  staggerFrom?: 'first' | 'last' | 'center' | 'random' | number;\n  loop?: boolean;\n  auto?: boolean;\n  splitBy?: string;\n  onNext?: (index: number) => void;\n  mainClassName?: string;\n  splitLevelClassName?: string;\n  elementLevelClassName?: string;\n}\n\nconst RotatingText = forwardRef<RotatingTextRef, RotatingTextProps>(\n  (\n    {\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    },\n    ref\n  ) => {\n    const [currentTextIndex, setCurrentTextIndex] = useState<number>(0);\n\n    const splitIntoCharacters = (text: string): string[] => {\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: string = 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: number, totalChars: number): number => {\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 as number) - index) * staggerDuration;\n      },\n      [staggerFrom, staggerDuration]\n    );\n\n    const handleIndexChange = useCallback(\n      (newIndex: number) => {\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: number) => {\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\n                .slice(0, wordIndex)\n                .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);\n\nRotatingText.displayName = 'RotatingText';\nexport default RotatingText;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}