@@ -89,6 +89,7 @@ export default function Home() {
8989 const [ filterLogins , setFilterLogins ] = useState < Set < string > > ( new Set ( ) ) ;
9090 const [ filterQuery , setFilterQuery ] = useState ( '' ) ;
9191 const [ filterOpen , setFilterOpen ] = useState ( false ) ;
92+ const [ filterHighlight , setFilterHighlight ] = useState ( 0 ) ;
9293 const generationRef = useRef ( 0 ) ;
9394 const lastCompletedDevsRef = useRef ( 0 ) ;
9495
@@ -1046,43 +1047,62 @@ export default function Home() {
10461047 ) ;
10471048 } ) }
10481049 < div className = "relative" >
1049- < input
1050- type = "text"
1051- value = { filterQuery }
1052- onChange = { e => { setFilterQuery ( e . target . value ) ; setFilterOpen ( true ) ; } }
1053- onFocus = { ( ) => setFilterOpen ( true ) }
1054- placeholder = { filterLogins . size > 0 ? 'Add more...' : 'Filter by developer...' }
1055- className = "bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-sm text-white placeholder-gray-600 focus:outline-none focus:border-blue-500 w-48"
1056- />
1057- { filterOpen && filterQuery . length > 0 && ( ( ) => {
1050+ { ( ( ) => {
10581051 const q = filterQuery . toLowerCase ( ) ;
1059- const matches = developers . filter ( d =>
1060- ! filterLogins . has ( d . github_login ) && (
1061- d . github_login . toLowerCase ( ) . includes ( q ) ||
1062- ( d . github_name || '' ) . toLowerCase ( ) . includes ( q )
1063- )
1064- ) . slice ( 0 , 8 ) ;
1065- if ( matches . length === 0 ) return null ;
1052+ const matches = filterOpen && q . length > 0
1053+ ? developers . filter ( d =>
1054+ ! filterLogins . has ( d . github_login ) && (
1055+ d . github_login . toLowerCase ( ) . includes ( q ) ||
1056+ ( d . github_name || '' ) . toLowerCase ( ) . includes ( q )
1057+ )
1058+ ) . slice ( 0 , 8 )
1059+ : [ ] ;
1060+ const selectMatch = ( login : string ) => {
1061+ setFilterLogins ( prev => new Set ( prev ) . add ( login ) ) ;
1062+ setFilterQuery ( '' ) ;
1063+ setFilterOpen ( false ) ;
1064+ setFilterHighlight ( 0 ) ;
1065+ } ;
10661066 return (
1067- < div className = "absolute z-40 top-full mt-1 left-0 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-xl overflow-hidden" >
1068- { matches . map ( d => (
1069- < button
1070- key = { d . github_login }
1071- className = "w-full flex items-center gap-2.5 px-3 py-2 text-left text-sm hover:bg-gray-700 transition-colors"
1072- onClick = { ( ) => {
1073- setFilterLogins ( prev => new Set ( prev ) . add ( d . github_login ) ) ;
1074- setFilterQuery ( '' ) ;
1075- setFilterOpen ( false ) ;
1076- } }
1077- >
1078- { d . avatar_url && < img src = { d . avatar_url } alt = "" className = "w-5 h-5 rounded-full" /> }
1079- < div >
1080- < span className = "text-white" > { d . github_name || d . github_login } </ span >
1081- { d . github_name && < span className = "text-gray-500 ml-1.5" > @{ d . github_login } </ span > }
1082- </ div >
1083- </ button >
1084- ) ) }
1085- </ div >
1067+ < >
1068+ < input
1069+ type = "text"
1070+ value = { filterQuery }
1071+ onChange = { e => { setFilterQuery ( e . target . value ) ; setFilterOpen ( true ) ; setFilterHighlight ( 0 ) ; } }
1072+ onFocus = { ( ) => { setFilterOpen ( true ) ; setFilterHighlight ( 0 ) ; } }
1073+ onBlur = { ( ) => setTimeout ( ( ) => setFilterOpen ( false ) , 150 ) }
1074+ onKeyDown = { e => {
1075+ if ( e . key === 'ArrowDown' ) { e . preventDefault ( ) ; setFilterHighlight ( h => Math . min ( h + 1 , matches . length - 1 ) ) ; }
1076+ else if ( e . key === 'ArrowUp' ) { e . preventDefault ( ) ; setFilterHighlight ( h => Math . max ( h - 1 , 0 ) ) ; }
1077+ else if ( e . key === 'Enter' && matches . length > 0 ) { e . preventDefault ( ) ; selectMatch ( matches [ filterHighlight ] ?. github_login ) ; }
1078+ else if ( e . key === 'Escape' ) { setFilterOpen ( false ) ; }
1079+ else if ( e . key === 'Backspace' && filterQuery === '' && filterLogins . size > 0 ) {
1080+ const last = [ ...filterLogins ] . pop ( ) ! ;
1081+ setFilterLogins ( prev => { const n = new Set ( prev ) ; n . delete ( last ) ; return n ; } ) ;
1082+ }
1083+ } }
1084+ placeholder = { filterLogins . size > 0 ? 'Add more...' : 'Filter by developer...' }
1085+ className = "bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-sm text-white placeholder-gray-600 focus:outline-none focus:border-blue-500 w-48"
1086+ />
1087+ { matches . length > 0 && (
1088+ < div className = "absolute z-40 top-full mt-1 left-0 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-xl overflow-hidden" >
1089+ { matches . map ( ( d , idx ) => (
1090+ < button
1091+ key = { d . github_login }
1092+ className = { `w-full flex items-center gap-2.5 px-3 py-2 text-left text-sm transition-colors ${ idx === filterHighlight ? 'bg-gray-700' : 'hover:bg-gray-700' } ` }
1093+ onMouseEnter = { ( ) => setFilterHighlight ( idx ) }
1094+ onClick = { ( ) => selectMatch ( d . github_login ) }
1095+ >
1096+ { d . avatar_url && < img src = { d . avatar_url } alt = "" className = "w-5 h-5 rounded-full" /> }
1097+ < div >
1098+ < span className = "text-white" > { d . github_name || d . github_login } </ span >
1099+ { d . github_name && < span className = "text-gray-500 ml-1.5" > @{ d . github_login } </ span > }
1100+ </ div >
1101+ </ button >
1102+ ) ) }
1103+ </ div >
1104+ ) }
1105+ </ >
10861106 ) ;
10871107 } ) ( ) }
10881108 </ div >
0 commit comments