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- setCurrentIndex ( ( prev ) => ( prev + 1 ) % words . length ) ;
41- } , ( animationDuration + pauseBetweenAnimations ) * 1000 ) ;
37+ useEffect ( ( ) => {
38+ if ( ! manualMode ) {
39+ const interval = setInterval (
40+ ( ) => {
41+ setCurrentIndex ( ( prev ) => ( prev + 1 ) % words . length )
42+ } ,
43+ ( animationDuration + pauseBetweenAnimations ) * 1000
44+ )
4245
43- return ( ) => clearInterval ( interval ) ;
44- }
45- } , [ manualMode , animationDuration , pauseBetweenAnimations , words . length ] ) ;
46+ return ( ) => clearInterval ( interval )
47+ }
48+ } , [ manualMode , animationDuration , pauseBetweenAnimations , words . length ] )
4649
47- useEffect ( ( ) => {
48- if ( currentIndex === null || currentIndex === - 1 ) return ;
49- if ( ! wordRefs . current [ currentIndex ] || ! containerRef . current ) return ;
50+ useEffect ( ( ) => {
51+ if ( currentIndex === null || currentIndex === - 1 ) return
52+ if ( ! wordRefs . current [ currentIndex ] || ! containerRef . current ) return
5053
51- const parentRect = containerRef . current . getBoundingClientRect ( ) ;
52- const activeRect = wordRefs . current [ currentIndex ] ! . getBoundingClientRect ( ) ;
54+ const parentRect = containerRef . current . getBoundingClientRect ( )
55+ const activeRect = wordRefs . current [ currentIndex ] ! . getBoundingClientRect ( )
5356
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 ] ) ;
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 ] )
6164
62- const handleMouseEnter = ( index : number ) => {
63- if ( manualMode ) {
64- setLastActiveIndex ( index ) ;
65- setCurrentIndex ( index ) ;
66- }
67- } ;
65+ const handleMouseEnter = ( index : number ) => {
66+ if ( manualMode ) {
67+ setLastActiveIndex ( index )
68+ setCurrentIndex ( index )
69+ }
70+ }
6871
69- const handleMouseLeave = ( ) => {
70- if ( manualMode ) {
71- setCurrentIndex ( lastActiveIndex ! ) ;
72- }
73- } ;
72+ const handleMouseLeave = ( ) => {
73+ if ( manualMode ) {
74+ setCurrentIndex ( lastActiveIndex ! )
75+ }
76+ }
7477
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- } ) }
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+ className = "relative cursor-pointer text-[3rem] font-black"
89+ style = {
90+ {
91+ filter : manualMode
92+ ? isActive
93+ ? `blur(0px)`
94+ : `blur(${ blurAmount } px )`
95+ : isActive
96+ ? `blur(0px)`
97+ : `blur(${ blurAmount } px)` ,
98+ transition : `filter ${ animationDuration } s ease ` ,
99+ } as React . CSSProperties
100+ }
101+ onMouseEnter = { ( ) => handleMouseEnter ( index ) }
102+ onMouseLeave = { handleMouseLeave }
103+ >
104+ { word }
105+ </ span >
106+ )
107+ } ) }
105108
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- } ;
109+ < motion . div
110+ className = "pointer-events-none absolute left-0 top-0 box-border border-0"
111+ animate = { {
112+ x : focusRect . x ,
113+ y : focusRect . y ,
114+ width : focusRect . width ,
115+ height : focusRect . height ,
116+ opacity : currentIndex >= 0 ? 1 : 0 ,
117+ } }
118+ transition = { {
119+ duration : animationDuration ,
120+ } }
121+ style = {
122+ {
123+ '--border-color' : borderColor ,
124+ '--glow-color' : glowColor ,
125+ } as React . CSSProperties
126+ }
127+ >
128+ < span
129+ className = "absolute left-[-10px] top-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-b-0 border-r-0"
130+ style = { {
131+ borderColor : 'var(--border-color)' ,
132+ filter : 'drop-shadow(0 0 4px var(--border-color))' ,
133+ } }
134+ > </ span >
135+ < span
136+ className = "absolute right-[-10px] top-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-b-0 border-l-0"
137+ style = { {
138+ borderColor : 'var(--border-color)' ,
139+ filter : 'drop-shadow(0 0 4px var(--border-color))' ,
140+ } }
141+ > </ span >
142+ < span
143+ className = "absolute bottom-[-10px] left-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-r-0 border-t-0"
144+ style = { {
145+ borderColor : 'var(--border-color)' ,
146+ filter : 'drop-shadow(0 0 4px var(--border-color))' ,
147+ } }
148+ > </ span >
149+ < span
150+ className = "absolute bottom-[-10px] right-[-10px] h-4 w-4 rounded-[3px] border-[3px] border-l-0 border-t-0"
151+ style = { {
152+ borderColor : 'var(--border-color)' ,
153+ filter : 'drop-shadow(0 0 4px var(--border-color))' ,
154+ } }
155+ > </ span >
156+ </ motion . div >
157+ </ div >
158+ )
159+ }
155160
156- export default TrueFocus ;
161+ export default TrueFocus
0 commit comments