1"use client";
2
3import { cn } from "@falnix/ui";
4import { motion, stagger, useAnimate, useInView } from "framer-motion";
5import { useEffect } from "react";
6
7export const TypewriterEffect = ({
8 words,
9 className,
10 cursorClassName,
11}: {
12 words: {
13 text: string;
14 className?: string;
15 }[];
16 className?: string;
17 cursorClassName?: string;
18}) => {
19
20 const wordsArray = words.map((word) => {
21 return {
22 ...word,
23 text: word.text.split(""),
24 };
25 });
26
27 const [scope, animate] = useAnimate();
28 const isInView = useInView(scope);
29 useEffect(() => {
30 if (isInView) {
31 animate(
32 "span",
33 {
34 display: "inline-block",
35 opacity: 1,
36 width: "fit-content",
37 },
38 {
39 duration: 0.3,
40 delay: stagger(0.1),
41 ease: "easeInOut",
42 }
43 );
44 }
45 }, [isInView]);
46
47 const renderWords = () => {
48 return (
49 <motion.div ref={scope} className="inline">
50 {wordsArray.map((word, idx) => {
51 return (
52 <div key={`word-${idx}`} className="inline-block">
53 {word.text.map((char, index) => (
54 <motion.span
55 initial={{}}
56 key={`char-${index}`}
57 className={cn(
58 "dark:text-white text-black opacity-0 hidden",
59 word.className
60 )}
61 >
62 {char}
63 </motion.span>
64 ))}
65
66 </div>
67 );
68 })}
69 </motion.div>
70 );
71 };
72 return (
73 <div
74 className={cn(
75 "text-base md:text-xl lg:text-3xl font-bold text-center",
76 className
77 )}
78 >
79 {renderWords()}
80 <motion.span
81 initial={{
82 opacity: 0,
83 }}
84 animate={{
85 opacity: 1,
86 }}
87 transition={{
88 duration: 0.8,
89 repeat: Infinity,
90 repeatType: "reverse",
91 }}
92 className={cn(
93 "inline-block rounded-sm w-[4px] h-4 md:h-6 lg:h-10 bg-blue-500",
94 cursorClassName
95 )}
96 ></motion.span>
97 </div>
98 );
99};