99 lines
2.7 KiB
TypeScript
99 lines
2.7 KiB
TypeScript
"use client";
|
|
import React, { useCallback, useEffect, useState } from "react";
|
|
import { AnimatePresence, motion } from "framer-motion";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
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);
|
|
|
|
// 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]);
|
|
|
|
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-[#f5f5f5] dark:text-neutral-100 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(8px)" }}
|
|
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
|
transition={{
|
|
delay: wordIndex * 0.3,
|
|
duration: 0.3,
|
|
}}
|
|
className="inline-block whitespace-nowrap"
|
|
>
|
|
{word.split("").map((letter, letterIndex) => (
|
|
<motion.span
|
|
key={word + letterIndex}
|
|
initial={{ opacity: 0, y: 10, filter: "blur(8px)" }}
|
|
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
|
transition={{
|
|
delay: wordIndex * 0.3 + letterIndex * 0.05,
|
|
duration: 0.2,
|
|
}}
|
|
className="inline-block"
|
|
>
|
|
{letter}
|
|
</motion.span>
|
|
))}
|
|
<span className="inline-block"> </span>
|
|
</motion.span>
|
|
))}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
);
|
|
};
|