1import React, { useEffect } from 'react';
2import {
3 animate,
4 Easing,
5 motion,
6 useMotionValue,
7 useTransform,
8} from 'framer-motion';
9import { Box, keyframes } from '@mui/system';
10
11export interface TypewriterProps {
12 text: string;
13 speed?: number;
14 delay?: number;
15 ease?: Easing;
16 showCursor?: boolean;
17 onComplete?: () => void;
18}
19
20const Typewriter = (props: TypewriterProps) => {
21 const {
22 text,
23 speed = 30,
24 delay = 0,
25 showCursor = false,
26 ease = 'easeInOut',
27 onComplete,
28 } = props;
29 const count = useMotionValue(0);
30 const rounded = useTransform(count, latest => Math.round(latest));
31 const displayedText: any = useTransform(rounded, latest =>
32 text.slice(0, latest)
33 );
34
35 useEffect(() => {
36 const controls = animate(count, text.length, {
37 delay: delay / 1000,
38 duration: (speed * text.length) / 1000,
39 ease: ease,
40 onComplete: () => {
41 onComplete?.();
42 },
43 });
44
45 return controls.stop;
46 }, [text, speed, delay, count, onComplete]);
47
48 return (
49 <>
50 <Box component={motion.span}>{displayedText}</Box>
51 {showCursor && <TypingCursor />}
52 </>
53 );
54};
55
56const blinkKeyframe = keyframes`
57 0% {
58 opacity: 0;
59 }
60
61 50% {
62 opacity: 1;
63 }
64
65 100% {
66 opacity: 0;
67 }
68`;
69
70const TypingCursor = () => {
71 return (
72 <Box
73 sx={{
74 width: '2px',
75 height: 16,
76 display: 'inline-block',
77 ml: '3px',
78 mb: '-2px',
79 background: '#c2e6ebd4',
80 animation: `${blinkKeyframe} 1.5s infinite`,
81 }}
82 />
83 );
84};
85
86export default Typewriter;