{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "RotatingText-TS-CSS",
	"title": "RotatingText",
	"description": "Cycles through multiple phrases with 3D rotate / flip transitions.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "RotatingText.css",
			"target": "@components/RotatingText.css",
			"content": ".text-rotate {\n  display: flex;\n  flex-wrap: wrap;\n  white-space: pre-wrap;\n  position: relative;\n}\n\n.text-rotate-sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  padding: 0;\n  margin: -1px;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  white-space: nowrap;\n  border: 0;\n}\n\n.text-rotate-word {\n  display: inline-flex;\n}\n\n.text-rotate-lines {\n  display: flex;\n  flex-direction: column;\n  width: 100%;\n}\n\n.text-rotate-element {\n  display: inline-block;\n}\n\n.text-rotate-space {\n  white-space: pre;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "RotatingText.tsx",
			"content": "'use client';\n\nimport 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\nimport './RotatingText.css';\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>((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<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 className={cn('text-rotate', mainClassName)} {...rest} layout transition={transition}>\n      <span className=\"text-rotate-sr-only\">{texts[currentTextIndex]}</span>\n      <AnimatePresence mode={animatePresenceMode} initial={animatePresenceInitial}>\n        <motion.span\n          key={currentTextIndex}\n          className={cn(splitBy === 'lines' ? 'text-rotate-lines' : 'text-rotate')}\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('text-rotate-word', 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('text-rotate-element', elementLevelClassName)}\n                  >\n                    {char}\n                  </motion.span>\n                ))}\n                {wordObj.needsSpace && <span className=\"text-rotate-space\"> </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"
	]
}