@@ -134,22 +134,55 @@ export default function OnboardingFlow({ visible, onClose, onFinish, activeStep,
134134
135135 // Escape key closes
136136 useEffect ( ( ) => {
137+ if ( ! visible ) {
138+ if ( schoolDebounceRef . current ) {
139+ clearTimeout ( schoolDebounceRef . current ) ;
140+ schoolDebounceRef . current = null ;
141+ }
142+ return ;
143+ }
144+
137145 const handler = ( e : KeyboardEvent ) => { if ( e . key === 'Escape' ) onClose ( ) ; } ;
138146 window . addEventListener ( 'keydown' , handler ) ;
139- return ( ) => window . removeEventListener ( 'keydown' , handler ) ;
140- } , [ onClose ] ) ;
147+ return ( ) => {
148+ window . removeEventListener ( 'keydown' , handler ) ;
149+ if ( schoolDebounceRef . current ) {
150+ clearTimeout ( schoolDebounceRef . current ) ;
151+ schoolDebounceRef . current = null ;
152+ }
153+ } ;
154+ } , [ visible , onClose ] ) ;
155+
156+ function handleOptionKeyDown ( event : React . KeyboardEvent < HTMLButtonElement > , onSelect : ( ) => void ) {
157+ if ( event . key === 'Enter' || event . key === ' ' ) {
158+ event . preventDefault ( ) ;
159+ onSelect ( ) ;
160+ }
161+ }
162+
163+ function selectSchool ( name : string ) {
164+ setFormData ( prev => ( { ...prev , school : name } ) ) ;
165+ setSchoolSuggestions ( [ ] ) ;
166+ }
167+
168+ function selectYear ( option : string ) {
169+ setFormData ( prev => ( { ...prev , year : option . toLowerCase ( ) } ) ) ;
170+ setYearOpen ( false ) ;
171+ }
141172
142173 function handleSchoolInput ( value : string ) {
143174 setFormData ( prev => ( { ...prev , school : value } ) ) ;
144175 if ( schoolDebounceRef . current ) clearTimeout ( schoolDebounceRef . current ) ;
145176 if ( value . trim ( ) . length < 2 ) { setSchoolSuggestions ( [ ] ) ; return ; }
146177 schoolDebounceRef . current = setTimeout ( async ( ) => {
147178 try {
148- const res = await fetch ( `http ://universities.hipolabs.com/search?name=${ encodeURIComponent ( value ) } &country=United+States` ) ;
179+ const res = await fetch ( `https ://universities.hipolabs.com/search?name=${ encodeURIComponent ( value ) } &country=United+States` ) ;
149180 const data : { name : string } [ ] = await res . json ( ) ;
150181 setSchoolSuggestions ( data . slice ( 0 , 10 ) . map ( u => u . name ) ) ;
151182 } catch {
152183 setSchoolSuggestions ( [ ] ) ;
184+ } finally {
185+ schoolDebounceRef . current = null ;
153186 }
154187 } , 300 ) ;
155188 }
@@ -409,27 +442,39 @@ export default function OnboardingFlow({ visible, onClose, onFinish, activeStep,
409442 maxHeight : '192px' , overflowY : 'auto' ,
410443 zIndex : 100 ,
411444 boxShadow : '0 8px 32px rgba(0,0,0,0.12)' ,
412- } } >
445+ } } role = "listbox" aria-label = "School suggestions" >
413446 { schoolSuggestions . map ( ( name , i ) => (
414- < div key = { i }
415- onMouseDown = { ( ) => {
416- setFormData ( prev => ( { ...prev , school : name } ) ) ;
417- setSchoolSuggestions ( [ ] ) ;
418- } }
447+ < button
448+ key = { i }
449+ type = "button"
450+ role = "option"
451+ aria-selected = { formData . school === name }
452+ onMouseDown = { ( ) => selectSchool ( name ) }
453+ onKeyDown = { e => handleOptionKeyDown ( e , ( ) => selectSchool ( name ) ) }
419454 style = { {
455+ width : '100%' ,
456+ textAlign : 'left' ,
420457 padding : '12px 18px' ,
421458 fontSize : '14px' ,
422459 color : '#111827' ,
423460 cursor : 'pointer' ,
424461 borderBottom : i < schoolSuggestions . length - 1 ? '1px solid rgba(0,0,0,0.06)' : 'none' ,
425462 fontFamily : "var(--font-dm-sans), 'DM Sans', sans-serif" ,
426463 transition : 'background 0.15s' ,
464+ background : formData . school === name ? 'rgba(27,108,66,0.06)' : 'transparent' ,
465+ borderLeft : 'none' ,
466+ borderRight : 'none' ,
467+ borderTop : 'none' ,
468+ } }
469+ onMouseEnter = { e => {
470+ if ( formData . school !== name ) e . currentTarget . style . background = 'rgba(27,108,66,0.08)' ;
471+ } }
472+ onMouseLeave = { e => {
473+ e . currentTarget . style . background = formData . school === name ? 'rgba(27,108,66,0.06)' : 'transparent' ;
427474 } }
428- onMouseEnter = { e => ( e . currentTarget . style . background = 'rgba(27,108,66,0.08)' ) }
429- onMouseLeave = { e => ( e . currentTarget . style . background = 'transparent' ) }
430475 >
431476 { name }
432- </ div >
477+ </ button >
433478 ) ) }
434479 </ div >
435480 ) }
@@ -459,14 +504,18 @@ export default function OnboardingFlow({ visible, onClose, onFinish, activeStep,
459504 maxHeight : '192px' , overflowY : 'auto' ,
460505 zIndex : 100 ,
461506 boxShadow : '0 8px 32px rgba(0,0,0,0.12)' ,
462- } } >
507+ } } role = "listbox" aria-label = "Class year options" >
463508 { YEAR_OPTIONS . map ( ( opt , i ) => (
464- < div key = { opt }
465- onMouseDown = { ( ) => {
466- setFormData ( prev => ( { ...prev , year : opt . toLowerCase ( ) } ) ) ;
467- setYearOpen ( false ) ;
468- } }
509+ < button
510+ key = { opt }
511+ type = "button"
512+ role = "option"
513+ aria-selected = { formData . year === opt . toLowerCase ( ) }
514+ onMouseDown = { ( ) => selectYear ( opt ) }
515+ onKeyDown = { e => handleOptionKeyDown ( e , ( ) => selectYear ( opt ) ) }
469516 style = { {
517+ width : '100%' ,
518+ textAlign : 'left' ,
470519 padding : '12px 18px' ,
471520 fontSize : '14px' ,
472521 color : formData . year === opt . toLowerCase ( ) ? '#1B6C42' : '#111827' ,
@@ -476,12 +525,15 @@ export default function OnboardingFlow({ visible, onClose, onFinish, activeStep,
476525 fontFamily : "var(--font-dm-sans), 'DM Sans', sans-serif" ,
477526 transition : 'background 0.15s' ,
478527 background : formData . year === opt . toLowerCase ( ) ? 'rgba(27,108,66,0.06)' : 'transparent' ,
528+ borderLeft : 'none' ,
529+ borderRight : 'none' ,
530+ borderTop : 'none' ,
479531 } }
480532 onMouseEnter = { e => { if ( formData . year !== opt . toLowerCase ( ) ) e . currentTarget . style . background = 'rgba(27,108,66,0.08)' ; } }
481533 onMouseLeave = { e => { e . currentTarget . style . background = formData . year === opt . toLowerCase ( ) ? 'rgba(27,108,66,0.06)' : 'transparent' ; } }
482534 >
483535 { opt }
484- </ div >
536+ </ button >
485537 ) ) }
486538 </ div >
487539 ) }
@@ -498,7 +550,6 @@ export default function OnboardingFlow({ visible, onClose, onFinish, activeStep,
498550 { ( [ 'majors' , 'minors' ] as const ) . map ( field => {
499551 const isMinor = field === 'minors' ;
500552 const input = isMinor ? minorInput : majorInput ;
501- const setInput = isMinor ? setMinorInput : setMajorInput ;
502553 const suggestions = isMinor ? minorSuggestions : majorSuggestions ;
503554 const focused = isMinor ? minorFocused : majorFocused ;
504555 const setFocused = isMinor ? setMinorFocused : setMajorFocused ;
@@ -530,16 +581,29 @@ export default function OnboardingFlow({ visible, onClose, onFinish, activeStep,
530581 background : '#ffffff' , border : '1px solid rgba(0,0,0,0.1)' ,
531582 borderRadius : '14px' , maxHeight : '180px' , overflowY : 'auto' ,
532583 zIndex : 100 , boxShadow : '0 8px 32px rgba(0,0,0,0.12)' ,
533- } } >
584+ } } role = "listbox" aria-label = { isMinor ? 'Minor suggestions' : 'Major suggestions' } >
534585 { suggestions . map ( ( s , i ) => (
535- < div key = { i } onMouseDown = { ( ) => addItem ( s ) } style = { {
586+ < button
587+ key = { i }
588+ type = "button"
589+ role = "option"
590+ aria-selected = { items . includes ( s ) }
591+ onMouseDown = { ( ) => addItem ( s ) }
592+ onKeyDown = { e => handleOptionKeyDown ( e , ( ) => addItem ( s ) ) }
593+ style = { {
594+ width : '100%' ,
595+ textAlign : 'left' ,
536596 padding : '11px 18px' , fontSize : '14px' , color : '#111827' , cursor : 'pointer' ,
537597 borderBottom : i < suggestions . length - 1 ? '1px solid rgba(0,0,0,0.06)' : 'none' ,
538598 fontFamily : "var(--font-dm-sans), 'DM Sans', sans-serif" , transition : 'background 0.15s' ,
599+ background : 'transparent' ,
600+ borderLeft : 'none' ,
601+ borderRight : 'none' ,
602+ borderTop : 'none' ,
539603 } }
540604 onMouseEnter = { e => ( e . currentTarget . style . background = 'rgba(27,108,66,0.08)' ) }
541605 onMouseLeave = { e => ( e . currentTarget . style . background = 'transparent' ) }
542- > { s } </ div >
606+ > { s } </ button >
543607 ) ) }
544608 </ div >
545609 ) }
0 commit comments