/** * Modal 弹窗组件 * 设计稿参考: UIDesignSpec.md */ import React, { useEffect, useCallback } from 'react'; import { X } from 'lucide-react'; import { Button } from './Button'; export interface ModalProps { isOpen: boolean; onClose: () => void; title?: string; children: React.ReactNode; footer?: React.ReactNode; size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'; closeOnOverlay?: boolean; closeOnEsc?: boolean; showCloseButton?: boolean; className?: string; } const sizeStyles = { sm: 'max-w-sm', md: 'max-w-md', lg: 'max-w-lg', xl: 'max-w-xl', full: 'max-w-[90vw] max-h-[90vh]', }; export const Modal: React.FC = ({ isOpen, onClose, title, children, footer, size = 'md', closeOnOverlay = true, closeOnEsc = true, showCloseButton = true, className = '', }) => { // Handle ESC key const handleKeyDown = useCallback((e: KeyboardEvent) => { if (closeOnEsc && e.key === 'Escape') { onClose(); } }, [closeOnEsc, onClose]); useEffect(() => { if (isOpen) { document.addEventListener('keydown', handleKeyDown); document.body.style.overflow = 'hidden'; } return () => { document.removeEventListener('keydown', handleKeyDown); document.body.style.overflow = ''; }; }, [isOpen, handleKeyDown]); if (!isOpen) return null; return (
{/* Overlay */}
{/* Modal Content */}
{/* Header */} {(title || showCloseButton) && (
{title && (

{title}

)} {showCloseButton && ( )}
)} {/* Body */}
{children}
{/* Footer */} {footer && (
{footer}
)}
); }; // 确认弹窗 export interface ConfirmModalProps { isOpen: boolean; onClose: () => void; onConfirm: () => void; title: string; message: string; confirmText?: string; cancelText?: string; variant?: 'default' | 'danger'; loading?: boolean; } export const ConfirmModal: React.FC = ({ isOpen, onClose, onConfirm, title, message, confirmText = '确认', cancelText = '取消', variant = 'default', loading = false, }) => { return ( } >

{message}

); }; export default Modal;