1- import { useState , useEffect , useMemo } from "react" ;
1+ import { useState , useEffect , useMemo , useCallback , useRef } from "react" ;
22import type { MostCommentedPR } from "../types.ts" ;
33
44interface MostCommentedPRsProps {
@@ -78,6 +78,7 @@ const EMOJI_MAP: Record<string, string> = {
7878 money_with_wings : "\u{1F4B8}" ,
7979 thread : "\u{1F9F5}" ,
8080 safety_vest : "\u{1F9BA}" ,
81+ mag_right : "\u{1F50E}" ,
8182} ;
8283
8384function replaceEmojiShortcodes ( text : string ) : string {
@@ -99,29 +100,49 @@ export function MostCommentedPRs({ prs }: MostCommentedPRsProps) {
99100 const shuffled = useMemo ( ( ) => shuffle ( prs ) , [ prs ] ) ;
100101 const [ index , setIndex ] = useState ( 0 ) ;
101102 const [ visible , setVisible ] = useState ( true ) ;
103+ const cycleRef = useRef < ReturnType < typeof setInterval > | null > ( null ) ;
104+ const fadingRef = useRef ( false ) ;
105+
106+ const cycle = useCallback ( ( ) => {
107+ if ( fadingRef . current ) return ;
108+ fadingRef . current = true ;
109+ setVisible ( false ) ;
110+ setTimeout ( ( ) => {
111+ setIndex ( ( prev ) => ( prev + 1 ) % shuffled . length ) ;
112+ setVisible ( true ) ;
113+ fadingRef . current = false ;
114+ } , FADE_MS ) ;
115+ } , [ shuffled . length ] ) ;
116+
117+ const resetTimer = useCallback ( ( ) => {
118+ if ( cycleRef . current ) clearInterval ( cycleRef . current ) ;
119+ cycleRef . current = setInterval ( cycle , CYCLE_MS ) ;
120+ } , [ cycle ] ) ;
102121
103122 useEffect ( ( ) => {
104123 if ( shuffled . length <= 1 ) return ;
124+ cycleRef . current = setInterval ( cycle , CYCLE_MS ) ;
125+ return ( ) => {
126+ if ( cycleRef . current ) clearInterval ( cycleRef . current ) ;
127+ } ;
128+ } , [ shuffled . length , cycle ] ) ;
105129
106- const interval = setInterval ( ( ) => {
107- // fade out
108- setVisible ( false ) ;
109- // after fade-out, switch item and fade in
110- setTimeout ( ( ) => {
111- setIndex ( ( prev ) => ( prev + 1 ) % shuffled . length ) ;
112- setVisible ( true ) ;
113- } , FADE_MS ) ;
114- } , CYCLE_MS ) ;
115-
116- return ( ) => clearInterval ( interval ) ;
117- } , [ shuffled . length ] ) ;
130+ const handleClick = useCallback (
131+ ( e : React . MouseEvent ) => {
132+ if ( ( e . target as HTMLElement ) . closest ( "a" ) ) return ;
133+ if ( shuffled . length <= 1 ) return ;
134+ cycle ( ) ;
135+ resetTimer ( ) ;
136+ } ,
137+ [ cycle , resetTimer , shuffled . length ] ,
138+ ) ;
118139
119140 if ( shuffled . length === 0 ) return null ;
120141
121142 const pr = shuffled [ index ] ;
122143
123144 return (
124- < section className = "relative overflow-hidden bg-gradient-to-br from-blue-800 via-blue-900 to-red-800 text-white rounded-xl p-6 sm:p-8 shadow-lg shadow-blue-500/25" >
145+ < section onClick = { handleClick } className = "relative overflow-hidden bg-gradient-to-br from-blue-800 via-blue-900 to-red-800 text-white rounded-xl p-6 sm:p-8 shadow-lg shadow-blue-500/25 cursor-pointer " >
125146 { /* Animated tricolore glow */ }
126147 < div
127148 className = "absolute inset-0 opacity-30"
0 commit comments