import { useEffect, useRef } from 'react' import type { ReactNode } from 'react' import { Button } from './Button' interface ModalProps { children: ReactNode isOpen: boolean onClose: () => void title?: string size?: 'small' | 'medium' | 'large' | 'full' showCloseButton?: boolean 'aria-label'?: string } function getSizeClasses(size: ModalProps['size']): string { switch (size) { case 'small': return 'max-w-md' case 'medium': return 'max-w-lg' case 'large': return 'max-w-xl' case 'full': return 'max-w-full mx-4' default: return 'max-w-md' } } function CloseButton({ onClose }: { onClose: () => void }): React.ReactElement { return ( ) } function ModalHeader({ title, showCloseButton, onClose, }: { title?: string | undefined showCloseButton: boolean onClose: () => void }): React.ReactElement | null { if (!title && !showCloseButton) { return null } return (
{title &&

{title}

} {showCloseButton && }
) } function useModalFocus(modalRef: React.RefObject, isOpen: boolean): void { const previousFocusRef = useRef(null) useEffect(() => { if (isOpen) { const { activeElement } = document if (activeElement instanceof HTMLElement) { previousFocusRef.current = activeElement } const firstFocusable = modalRef.current?.querySelector( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ) if (firstFocusable instanceof HTMLElement) { firstFocusable.focus() } } else { previousFocusRef.current?.focus() } }, [isOpen, modalRef]) } function useModalKeyboard(isOpen: boolean, onClose: () => void): void { useEffect(() => { const handleEscape = (e: KeyboardEvent): void => { if (e.key === 'Escape' && isOpen) { onClose() } } if (isOpen) { document.addEventListener('keydown', handleEscape) document.body.style.overflow = 'hidden' } return () => { document.removeEventListener('keydown', handleEscape) document.body.style.overflow = '' } }, [isOpen, onClose]) } function ModalOverlay({ onClose, ariaLabel, children, }: { onClose: () => void ariaLabel: string | undefined children: ReactNode }): React.ReactElement { return (
{ if (e.target === e.currentTarget) { onClose() } }} role="dialog" aria-modal="true" aria-label={ariaLabel} > {children}
) } function ModalContent({ modalRef, sizeClasses, title, showCloseButton, onClose, children, }: { modalRef: React.RefObject sizeClasses: string title?: string | undefined showCloseButton: boolean onClose: () => void children: ReactNode }): React.ReactElement { return (
e.stopPropagation()} > {children}
) } export function Modal({ children, isOpen, onClose, title, size = 'medium', showCloseButton = true, 'aria-label': ariaLabel, }: ModalProps): React.ReactElement | null { const modalRef = useRef(null) useModalFocus(modalRef, isOpen) useModalKeyboard(isOpen, onClose) if (!isOpen) { return null } const sizeClasses = getSizeClasses(size) return ( {children} ) }