11'use client' ;
22
33import { X } from 'lucide-react' ;
4- import { ReactNode } from 'react' ;
4+ import { ReactNode , useEffect , useState } from 'react' ;
5+ import { createPortal } from 'react-dom' ;
56
67interface ModalProps {
78 isOpen : boolean ;
@@ -13,19 +14,45 @@ interface ModalProps {
1314}
1415
1516export function Modal ( { isOpen, onClose, title, children, actions, flush = false } : ModalProps ) {
16- if ( ! isOpen ) return null ;
17+ const [ mounted , setMounted ] = useState ( false ) ;
1718
18- return (
19+ useEffect ( ( ) => {
20+ setMounted ( true ) ;
21+ if ( isOpen ) {
22+ document . body . style . overflow = 'hidden' ;
23+ }
24+ return ( ) => {
25+ document . body . style . overflow = 'unset' ;
26+ // Clean up potentially left-over strict overflow if unmounted while open
27+ if ( isOpen ) {
28+ document . body . style . overflow = 'unset' ;
29+ }
30+ } ;
31+ } , [ isOpen ] ) ;
32+
33+ if ( ! mounted || ! isOpen ) return null ;
34+
35+ return createPortal (
1936 < div className = "fixed inset-0 z-[110] flex items-center justify-center p-4" >
2037 < div
2138 className = "absolute inset-0 bg-black/60 backdrop-blur-[2px] transition-opacity"
2239 onClick = { onClose }
40+ aria-hidden = "true"
2341 />
24- < div className = { `bg-white w-full max-w-md rounded-2xl shadow-2xl relative z-10 overflow-hidden transform transition-all animate-in zoom-in duration-300 ${ flush ? 'mb-0' : 'mb-0' } ` } >
42+ < div
43+ className = { `bg-white w-full max-w-md rounded-2xl shadow-2xl relative z-10 overflow-hidden transform transition-all animate-in zoom-in duration-300 ${ flush ? 'mb-0' : 'mb-0' } ` }
44+ role = "dialog"
45+ aria-modal = "true"
46+ aria-labelledby = "modal-title"
47+ >
2548 < div className = { flush ? '' : 'p-6' } >
2649 < div className = { `flex justify-between items-center ${ flush ? 'p-6 pb-2' : 'mb-4' } ` } >
27- < h3 className = "text-xl font-black text-gray-900 tracking-tight" > { title } </ h3 >
28- < button onClick = { onClose } className = "p-2 hover:bg-gray-100 rounded-full transition-colors" >
50+ < h3 id = "modal-title" className = "text-xl font-black text-gray-900 tracking-tight" > { title } </ h3 >
51+ < button
52+ onClick = { onClose }
53+ className = "p-2 hover:bg-gray-100 rounded-full transition-colors"
54+ aria-label = "Close modal"
55+ >
2956 < X className = "w-5 h-5 text-gray-500" />
3057 </ button >
3158 </ div >
@@ -39,6 +66,7 @@ export function Modal({ isOpen, onClose, title, children, actions, flush = false
3966 ) }
4067 </ div >
4168 </ div >
42- </ div >
69+ </ div > ,
70+ document . body
4371 ) ;
4472}
0 commit comments