/** * Modal 组件 * * 统一的弹窗组件,遵循三层架构设计 * 支持多种尺寸(small、medium、large、xlarge、fullscreen) * 支持ESC键关闭、点击遮罩关闭、键盘导航 * 完整的ARIA标签支持,确保可访问性 * * @created 2025-11-22 * @component */ 'use client' import { useEffect, useRef, ReactNode } from 'react' import { useThemeStyles } from '@/lib/ui-styles' export type ModalSize = 'small' | 'medium' | 'large' | 'xlarge' | 'fullscreen' interface ModalProps { isOpen: boolean onClose: () => void title?: string children: ReactNode size?: ModalSize showCloseButton?: boolean closeOnOverlayClick?: boolean closeOnEscape?: boolean footer?: ReactNode className?: string } export default function Modal({ isOpen, onClose, title, children, size = 'medium', showCloseButton = true, closeOnOverlayClick = true, closeOnEscape = true, footer, className = '', }: ModalProps) { const styles = useThemeStyles() const modalRef = useRef(null) // ESC键关闭 useEffect(() => { if (!isOpen || !closeOnEscape) return const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose() } } document.addEventListener('keydown', handleEscape) return () => document.removeEventListener('keydown', handleEscape) }, [isOpen, closeOnEscape, onClose]) // 阻止body滚动 useEffect(() => { if (isOpen) { document.body.style.overflow = 'hidden' } else { document.body.style.overflow = '' } return () => { document.body.style.overflow = '' } }, [isOpen]) if (!isOpen) return null const handleOverlayClick = (e: React.MouseEvent) => { if (closeOnOverlayClick && e.target === e.currentTarget) { onClose() } } const sizeClass = styles.modalStyles.sizes[size] return (
e.stopPropagation()} >
{/* 头部 */} {(title || showCloseButton) && (
{title && ( )} {showCloseButton && ( )}
)} {/* 主体 */}
{children}
{/* 底部 */} {footer &&
{footer}
}
) }