@@ -215,15 +215,62 @@ const Projects = () => {
215
215
const sectionRef = useRef ( null ) ;
216
216
const modalRef = useRef ( null ) ;
217
217
218
+ // Add scroll lock effect
219
+ useEffect ( ( ) => {
220
+ const body = document . body ;
221
+ if ( modalOpen ) {
222
+ // Store current scroll position
223
+ const scrollY = window . scrollY ;
224
+ body . style . position = 'fixed' ;
225
+ body . style . width = '100%' ;
226
+ body . style . top = `-${ scrollY } px` ;
227
+ // Add class to prevent background interactions
228
+ body . classList . add ( 'modal-open' ) ;
229
+ } else {
230
+ // Restore scroll position
231
+ const scrollY = body . style . top ;
232
+ body . style . position = '' ;
233
+ body . style . top = '' ;
234
+ body . style . width = '' ;
235
+ window . scrollTo ( 0 , parseInt ( scrollY || '0' ) * - 1 ) ;
236
+ // Remove class preventing background interactions
237
+ body . classList . remove ( 'modal-open' ) ;
238
+ }
239
+ } , [ modalOpen ] ) ;
240
+
241
+ // Handle keyboard interactions
218
242
useEffect ( ( ) => {
219
243
const handleKeyDown = ( event ) => {
220
244
if ( event . key === 'Escape' ) {
221
245
closeModal ( ) ;
246
+ } else if ( event . key === 'Tab' && modalOpen ) {
247
+ // Get all focusable elements in modal
248
+ const focusableElements = modalRef . current ?. querySelectorAll (
249
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
250
+ ) ;
251
+
252
+ if ( focusableElements ?. length ) {
253
+ const firstElement = focusableElements [ 0 ] ;
254
+ const lastElement = focusableElements [ focusableElements . length - 1 ] ;
255
+
256
+ // If shift+tab and first element is focused, move to last element
257
+ if ( event . shiftKey && document . activeElement === firstElement ) {
258
+ event . preventDefault ( ) ;
259
+ lastElement . focus ( ) ;
260
+ }
261
+ // If tab and last element is focused, move to first element
262
+ else if ( ! event . shiftKey && document . activeElement === lastElement ) {
263
+ event . preventDefault ( ) ;
264
+ firstElement . focus ( ) ;
265
+ }
266
+ }
222
267
}
223
268
} ;
224
269
225
270
if ( modalOpen ) {
226
271
window . addEventListener ( 'keydown' , handleKeyDown ) ;
272
+ // Focus the modal when it opens
273
+ modalRef . current ?. focus ( ) ;
227
274
} else {
228
275
window . removeEventListener ( 'keydown' , handleKeyDown ) ;
229
276
}
@@ -304,7 +351,7 @@ const Projects = () => {
304
351
id = "projects"
305
352
>
306
353
< GradientHeading visibleSection = { visibleSection } >
307
- Featured Projects
354
+ Featured Projects
308
355
</ GradientHeading >
309
356
310
357
< div className = "grid grid-cols-1 lg:grid-cols-2 gap-12 max-w-7xl mx-auto" >
@@ -396,15 +443,21 @@ const Projects = () => {
396
443
onClick = { closeModal }
397
444
ref = { modalRef }
398
445
tabIndex = "-1"
446
+ role = "dialog"
447
+ aria-modal = "true"
448
+ aria-labelledby = "modal-title"
399
449
>
400
450
< div
401
451
className = { `modal-content ${ modalOpen ? 'open' : '' } ` }
402
452
onClick = { ( e ) => e . stopPropagation ( ) }
403
- tabIndex = "-1"
404
453
>
405
454
< div className = "modal-header" >
406
- < h3 className = "modal-title" > { selectedProject . title } </ h3 >
407
- < button className = "modal-close" onClick = { closeModal } >
455
+ < h3 className = "modal-title" id = "modal-title" > { selectedProject . title } </ h3 >
456
+ < button
457
+ className = "modal-close"
458
+ onClick = { closeModal }
459
+ aria-label = "Close modal"
460
+ >
408
461
×
409
462
</ button >
410
463
</ div >
0 commit comments