1- import { useEffect , useRef , useState } from ' react'
2- import { motion } from ' framer-motion'
1+ import { useEffect , useRef , useState } from " react" ;
2+ import { motion } from " framer-motion" ;
33
44interface TrueFocusProps {
5- sentence ?: string
6- manualMode ?: boolean
7- blurAmount ?: number
8- borderColor ?: string
9- glowColor ?: string
10- animationDuration ?: number
11- pauseBetweenAnimations ?: number
5+ sentence ?: string ;
6+ manualMode ?: boolean ;
7+ blurAmount ?: number ;
8+ borderColor ?: string ;
9+ glowColor ?: string ;
10+ animationDuration ?: number ;
11+ pauseBetweenAnimations ?: number ;
1212}
1313
1414interface FocusRect {
15- x : number
16- y : number
17- width : number
18- height : number
15+ x : number ;
16+ y : number ;
17+ width : number ;
18+ height : number ;
1919}
2020
2121const TrueFocus : React . FC < TrueFocusProps > = ( {
22- sentence = ' True Focus' ,
23- manualMode = false ,
24- blurAmount = 5 ,
25- borderColor = ' green' ,
26- glowColor = ' rgba(0, 255, 0, 0.6)' ,
27- animationDuration = 0.5 ,
28- pauseBetweenAnimations = 1 ,
22+ sentence = " True Focus" ,
23+ manualMode = false ,
24+ blurAmount = 5 ,
25+ borderColor = " green" ,
26+ glowColor = " rgba(0, 255, 0, 0.6)" ,
27+ animationDuration = 0.5 ,
28+ pauseBetweenAnimations = 1 ,
2929} ) => {
30- const words = sentence . split ( ' ' )
31- const [ currentIndex , setCurrentIndex ] = useState < number > ( 0 )
32- const [ lastActiveIndex , setLastActiveIndex ] = useState < number | null > ( null )
33- const containerRef = useRef < HTMLDivElement | null > ( null )
34- const wordRefs = useRef < ( HTMLSpanElement | null ) [ ] > ( [ ] )
35- const [ focusRect , setFocusRect ] = useState < FocusRect > ( { x : 0 , y : 0 , width : 0 , height : 0 } )
30+ const words = sentence . split ( " " ) ;
31+ const [ currentIndex , setCurrentIndex ] = useState < number > ( 0 ) ;
32+ const [ lastActiveIndex , setLastActiveIndex ] = useState < number | null > ( null ) ;
33+ const containerRef = useRef < HTMLDivElement | null > ( null ) ;
34+ const wordRefs = useRef < ( HTMLSpanElement | null ) [ ] > ( [ ] ) ;
35+ const [ focusRect , setFocusRect ] = useState < FocusRect > ( { x : 0 , y : 0 , width : 0 , height : 0 } ) ;
3636
37- useEffect ( ( ) => {
38- if ( ! manualMode ) {
39- const interval = setInterval (
40- ( ) => {
41- setCurrentIndex ( ( prev ) => ( prev + 1 ) % words . length )
42- } ,
43- ( animationDuration + pauseBetweenAnimations ) * 1000
44- )
37+ useEffect ( ( ) => {
38+ if ( ! manualMode ) {
39+ const interval = setInterval ( ( ) => {
40+ setCurrentIndex ( ( prev ) => ( prev + 1 ) % words . length ) ;
41+ } , ( animationDuration + pauseBetweenAnimations ) * 1000 ) ;
4542
46- return ( ) => clearInterval ( interval )
47- }
48- } , [ manualMode , animationDuration , pauseBetweenAnimations , words . length ] )
43+ return ( ) => clearInterval ( interval ) ;
44+ }
45+ } , [ manualMode , animationDuration , pauseBetweenAnimations , words . length ] ) ;
4946
50- useEffect ( ( ) => {
51- if ( currentIndex === null || currentIndex === - 1 ) return
52- if ( ! wordRefs . current [ currentIndex ] || ! containerRef . current ) return
47+ useEffect ( ( ) => {
48+ if ( currentIndex === null || currentIndex === - 1 ) return ;
49+ if ( ! wordRefs . current [ currentIndex ] || ! containerRef . current ) return ;
5350
54- const parentRect = containerRef . current . getBoundingClientRect ( )
55- const activeRect = wordRefs . current [ currentIndex ] ! . getBoundingClientRect ( )
51+ const parentRect = containerRef . current . getBoundingClientRect ( ) ;
52+ const activeRect = wordRefs . current [ currentIndex ] ! . getBoundingClientRect ( ) ;
5653
57- setFocusRect ( {
58- x : activeRect . left - parentRect . left ,
59- y : activeRect . top - parentRect . top ,
60- width : activeRect . width ,
61- height : activeRect . height ,
62- } )
63- } , [ currentIndex , words . length ] )
54+ setFocusRect ( {
55+ x : activeRect . left - parentRect . left ,
56+ y : activeRect . top - parentRect . top ,
57+ width : activeRect . width ,
58+ height : activeRect . height ,
59+ } ) ;
60+ } , [ currentIndex , words . length ] ) ;
6461
65- const handleMouseEnter = ( index : number ) => {
66- if ( manualMode ) {
67- setLastActiveIndex ( index )
68- setCurrentIndex ( index )
69- }
70- }
62+ const handleMouseEnter = ( index : number ) => {
63+ if ( manualMode ) {
64+ setLastActiveIndex ( index ) ;
65+ setCurrentIndex ( index ) ;
66+ }
67+ } ;
7168
72- const handleMouseLeave = ( ) => {
73- if ( manualMode ) {
74- setCurrentIndex ( lastActiveIndex ! )
75- }
76- }
69+ const handleMouseLeave = ( ) => {
70+ if ( manualMode ) {
71+ setCurrentIndex ( lastActiveIndex ! ) ;
72+ }
73+ } ;
7774
78- return (
79- < div className = "relative flex flex-wrap items-center justify-center gap-4" ref = { containerRef } >
80- { words . map ( ( word , index ) => {
81- const isActive = index === currentIndex
82- return (
83- < span
84- key = { index }
85- ref = { ( el ) => {
86- wordRefs . current [ index ] = el
87- } }
88- style = {
89- {
90- filter : manualMode
91- ? isActive
92- ? `blur(0px)`
93- : `blur(${ blurAmount } px)`
94- : isActive
95- ? `blur(0px)`
96- : `blur(${ blurAmount } px)` ,
97- transition : `filter ${ animationDuration } s ease` ,
98- } as React . CSSProperties
99- }
100- onMouseEnter = { ( ) => handleMouseEnter ( index ) }
101- onMouseLeave = { handleMouseLeave }
102- >
103- { word }
104- </ span >
105- )
106- } ) }
75+ return (
76+ < div
77+ className = "relative flex gap-4 justify-center items-center flex-wrap"
78+ ref = { containerRef }
79+ >
80+ { words . map ( ( word , index ) => {
81+ const isActive = index === currentIndex ;
82+ return (
83+ < span
84+ key = { index }
85+ ref = { ( el ) => {
86+ wordRefs . current [ index ] = el ; } }
87+ className = "relative text-[3rem] font-black cursor-pointer"
88+ style = { {
89+ filter : manualMode
90+ ? isActive
91+ ? `blur(0px)`
92+ : `blur(${ blurAmount } px)`
93+ : isActive
94+ ? `blur(0px)`
95+ : `blur(${ blurAmount } px)` ,
96+ transition : `filter ${ animationDuration } s ease` ,
97+ } as React . CSSProperties }
98+ onMouseEnter = { ( ) => handleMouseEnter ( index ) }
99+ onMouseLeave = { handleMouseLeave }
100+ >
101+ { word }
102+ </ span >
103+ ) ;
104+ } ) }
107105
108- < motion . div
109- className = "pointer-events-none absolute left-0 top-0 box-border border-0"
110- animate = { {
111- x : focusRect . x ,
112- y : focusRect . y ,
113- width : focusRect . width ,
114- height : focusRect . height ,
115- opacity : currentIndex >= 0 ? 1 : 0 ,
116- } }
117- transition = { {
118- duration : animationDuration ,
119- } }
120- style = {
121- {
122- '--border-color' : borderColor ,
123- '--glow-color' : glowColor ,
124- } as React . CSSProperties
125- }
126- >
127- < span
128- className = "absolute left-[-10px] top-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-b-0 border-r-0"
129- style = { {
130- borderColor : 'var(--border-color)' ,
131- filter : 'drop-shadow(0 0 4px var(--border-color))' ,
132- } }
133- > </ span >
134- < span
135- className = "absolute right-[-10px] top-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-b-0 border-l-0"
136- style = { {
137- borderColor : 'var(--border-color)' ,
138- filter : 'drop-shadow(0 0 4px var(--border-color))' ,
139- } }
140- > </ span >
141- < span
142- className = "absolute bottom-[-10px] left-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-r-0 border-t-0"
143- style = { {
144- borderColor : 'var(--border-color)' ,
145- filter : 'drop-shadow(0 0 4px var(--border-color))' ,
146- } }
147- > </ span >
148- < span
149- className = "absolute bottom-[-10px] right-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-l-0 border-t-0"
150- style = { {
151- borderColor : 'var(--border-color)' ,
152- filter : 'drop-shadow(0 0 4px var(--border-color))' ,
153- } }
154- > </ span >
155- </ motion . div >
156- </ div >
157- )
158- }
106+ < motion . div
107+ className = "absolute top-0 left-0 pointer-events-none box-border border-0"
108+ animate = { {
109+ x : focusRect . x ,
110+ y : focusRect . y ,
111+ width : focusRect . width ,
112+ height : focusRect . height ,
113+ opacity : currentIndex >= 0 ? 1 : 0 ,
114+ } }
115+ transition = { {
116+ duration : animationDuration ,
117+ } }
118+ style = { {
119+ "--border-color" : borderColor ,
120+ "--glow-color" : glowColor ,
121+ } as React . CSSProperties }
122+ >
123+ < span
124+ className = "absolute w-4 h-4 border-[3px] rounded-[3px] top-[-10px] left-[-10px] border-r-0 border-b-0"
125+ style = { {
126+ borderColor : "var(--border-color)" ,
127+ filter : "drop-shadow(0 0 4px var(--border-color))" ,
128+ } }
129+ > </ span >
130+ < span
131+ className = "absolute w-4 h-4 border-[3px] rounded-[3px] top-[-10px] right-[-10px] border-l-0 border-b-0"
132+ style = { {
133+ borderColor : "var(--border-color)" ,
134+ filter : "drop-shadow(0 0 4px var(--border-color))" ,
135+ } }
136+ > </ span >
137+ < span
138+ className = "absolute w-4 h-4 border-[3px] rounded-[3px] bottom-[-10px] left-[-10px] border-r-0 border-t-0"
139+ style = { {
140+ borderColor : "var(--border-color)" ,
141+ filter : "drop-shadow(0 0 4px var(--border-color))" ,
142+ } }
143+ > </ span >
144+ < span
145+ className = "absolute w-4 h-4 border-[3px] rounded-[3px] bottom-[-10px] right-[-10px] border-l-0 border-t-0"
146+ style = { {
147+ borderColor : "var(--border-color)" ,
148+ filter : "drop-shadow(0 0 4px var(--border-color))" ,
149+ } }
150+ > </ span >
151+ </ motion . div >
152+ </ div >
153+ ) ;
154+ } ;
159155
160- export default TrueFocus
156+ export default TrueFocus ;
0 commit comments