56 lines
1.6 KiB
TypeScript
56 lines
1.6 KiB
TypeScript
'use client'
|
|||
|
|
|
||
|
|
import { motion } from 'framer-motion'
|
||
|
|
import { useState, useEffect } from 'react'
|
||
|
|
|
||
|
|
const phrases = [
|
||
|
|
'a Senior Systems engineer',
|
||
|
|
'Rock Liang',
|
||
|
|
'a Senior Full-stack builder',
|
||
|
|
'a Senior Infrastructure architect',
|
||
|
|
'a Senior Backend SDE',
|
||
|
|
]
|
||
|
|
|
||
|
|
export function TypewriterText() {
|
||
|
|
const [currentPhrase, setCurrentPhrase] = useState(0)
|
||
|
|
const [displayText, setDisplayText] = useState('')
|
||
|
|
const [isDeleting, setIsDeleting] = useState(false)
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const phrase = phrases[currentPhrase]
|
||
|
|
const delay = isDeleting ? 50 : 100
|
||
|
|
|
||
|
|
const timer = setTimeout(() => {
|
||
|
|
if (!isDeleting) {
|
||
|
|
if (displayText.length < phrase.length) {
|
||
|
|
setDisplayText(phrase.substring(0, displayText.length + 1))
|
||
|
|
} else {
|
||
|
|
setTimeout(() => setIsDeleting(true), 1500)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (displayText.length > 0) {
|
||
|
|
setDisplayText(phrase.substring(0, displayText.length - 1))
|
||
|
|
} else {
|
||
|
|
setIsDeleting(false)
|
||
|
|
setCurrentPhrase((prev) => (prev + 1) % phrases.length)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, delay)
|
||
|
|
|
||
|
|
return () => clearTimeout(timer)
|
||
|
|
}, [displayText, isDeleting, currentPhrase])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="relative z-10 mt-6 h-10 flex items-center justify-center">
|
||
|
|
<span className="text-2xl font-semibold text-gray-800 dark:text-gray-200">
|
||
|
|
I'm <span className="text-blue-600 dark:text-blue-400">{displayText}</span>
|
||
|
|
<motion.span
|
||
|
|
animate={{ opacity: [1, 0] }}
|
||
|
|
transition={{ duration: 0.6, repeat: Infinity }}
|
||
|
|
className="inline-block w-0.5 h-7 ml-1 bg-blue-600 dark:bg-blue-400"
|
||
|
|
/>
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|