2
2
3
3
import { useEffect , useRef , useState } from "react"
4
4
5
+ import { cn } from "@/lib/utils/cn"
6
+
5
7
import { usePrefersReducedMotion } from "@/hooks/usePrefersReducedMotion"
6
8
7
9
type MorpherProps = {
@@ -15,11 +17,12 @@ const Morpher = ({
15
17
} : MorpherProps ) => {
16
18
const [ currentText , setCurrentText ] = useState ( words [ 0 ] )
17
19
const [ isAnimating , setIsAnimating ] = useState ( false )
20
+ const [ isFading , setIsFading ] = useState ( false )
18
21
const { prefersReducedMotion } = usePrefersReducedMotion ( )
19
22
20
23
const morphTimeoutRef = useRef < NodeJS . Timeout | null > ( null )
21
24
const morphIntervalRef = useRef < NodeJS . Timeout | null > ( null )
22
- const counterRef = useRef ( 1 )
25
+ const counterRef = useRef ( 0 )
23
26
const wordsRef = useRef ( words )
24
27
const currentTextRef = useRef ( currentText )
25
28
const isAnimatingRef = useRef ( false )
@@ -114,13 +117,18 @@ const Morpher = ({
114
117
}
115
118
116
119
useEffect ( ( ) => {
117
- // If reduced motion is preferred, show static text cycling
120
+ // If reduced motion is preferred, show static text cycling with fade
118
121
if ( prefersReducedMotion ) {
119
122
morphIntervalRef . current = setInterval ( ( ) => {
120
- const nextWord = wordsRef . current [ counterRef . current ]
121
- setCurrentText ( nextWord )
122
- currentTextRef . current = nextWord
123
- counterRef . current = ( counterRef . current + 1 ) % wordsRef . current . length
123
+ setIsFading ( true )
124
+ setTimeout ( ( ) => {
125
+ counterRef . current =
126
+ ( counterRef . current + 1 ) % wordsRef . current . length
127
+ const nextWord = wordsRef . current [ counterRef . current ]
128
+ setCurrentText ( nextWord )
129
+ currentTextRef . current = nextWord
130
+ setIsFading ( false )
131
+ } , 150 ) // Half of the fade duration
124
132
} , 3000 )
125
133
} else {
126
134
// Defer animation start by 2 seconds to improve initial page load
@@ -159,7 +167,16 @@ const Morpher = ({
159
167
// eslint-disable-next-line react-hooks/exhaustive-deps
160
168
} , [ prefersReducedMotion , charSet ] )
161
169
162
- return currentText
170
+ return (
171
+ < span
172
+ className = { cn (
173
+ "transition-opacity duration-300 ease-in-out" ,
174
+ prefersReducedMotion && isFading && "opacity-0"
175
+ ) }
176
+ >
177
+ { currentText }
178
+ </ span >
179
+ )
163
180
}
164
181
165
182
export default Morpher
0 commit comments