Spaces:
Sleeping
Sleeping
| "use client"; | |
| import { useCallback, useEffect, useState } from "react"; | |
| import { AnimatePresence, motion } from "framer-motion"; | |
| import { cn } from "@/lib/utils"; | |
| import { useMounted } from "@/hooks/use-mounted"; | |
| export const FlipWords = ({ | |
| words, | |
| duration = 3000, | |
| className, | |
| }: { | |
| words: string[]; | |
| duration?: number; | |
| className?: string; | |
| }) => { | |
| const [currentWord, setCurrentWord] = useState(words[0]); | |
| const [isAnimating, setIsAnimating] = useState<boolean>(false); | |
| const mounted = useMounted(); | |
| // thanks for the fix Julian - https://github.com/Julian-AT | |
| const startAnimation = useCallback(() => { | |
| const word = words[words.indexOf(currentWord) + 1] || words[0]; | |
| setCurrentWord(word); | |
| setIsAnimating(true); | |
| }, [currentWord, words]); | |
| useEffect(() => { | |
| if (!isAnimating) | |
| setTimeout(() => { | |
| startAnimation(); | |
| }, duration); | |
| }, [isAnimating, duration, startAnimation]); | |
| if (!mounted) return null; | |
| return ( | |
| <AnimatePresence | |
| onExitComplete={() => { | |
| setIsAnimating(false); | |
| }} | |
| > | |
| <motion.div | |
| initial={{ | |
| opacity: 0, | |
| y: 10, | |
| }} | |
| animate={{ | |
| opacity: 1, | |
| y: 0, | |
| }} | |
| transition={{ | |
| type: "spring", | |
| stiffness: 100, | |
| damping: 10, | |
| }} | |
| exit={{ | |
| opacity: 0, | |
| y: -40, | |
| x: 40, | |
| filter: "blur(8px)", | |
| scale: 2, | |
| position: "absolute", | |
| }} | |
| className={cn( | |
| "z-10 inline-block relative text-left text-foreground px-2", | |
| className, | |
| )} | |
| key={currentWord} | |
| > | |
| {/* edit suggested by Sajal: https://x.com/DewanganSajal */} | |
| {currentWord.split(" ").map((word, wordIndex) => ( | |
| <motion.span | |
| key={word + wordIndex} | |
| initial={{ opacity: 0, y: 10, filter: "blur(4px)" }} | |
| animate={{ opacity: 1, y: 0, filter: "blur(0px)" }} | |
| transition={{ | |
| delay: wordIndex * 0.01, | |
| duration: 0.03, | |
| }} | |
| className="inline-block whitespace-nowrap" | |
| > | |
| {word.split("").map((letter, letterIndex) => ( | |
| <motion.span | |
| key={word + letterIndex} | |
| initial={{ opacity: 0, y: 10, filter: "blur(4px)" }} | |
| animate={{ opacity: 1, y: 0, filter: "blur(0px)" }} | |
| transition={{ | |
| delay: wordIndex * 0.2 + letterIndex * 0.08, | |
| duration: 0.2, | |
| }} | |
| className="inline-block" | |
| > | |
| {letter} | |
| </motion.span> | |
| ))} | |
| <span className="inline-block"> </span> | |
| </motion.span> | |
| ))} | |
| </motion.div> | |
| </AnimatePresence> | |
| ); | |
| }; | |