Spaces:
Sleeping
Sleeping
| import { AlertTriangle, CheckCircle, XCircle, Info } from 'lucide-react'; | |
| import { createPortal } from 'react-dom'; | |
| export type ModalType = 'confirm' | 'success' | 'error' | 'info'; | |
| interface ModalDialogProps { | |
| isOpen: boolean; | |
| title: string; | |
| message: string; | |
| type?: ModalType; | |
| onConfirm: () => void; | |
| onCancel?: () => void; | |
| confirmText?: string; | |
| cancelText?: string; | |
| isDestructive?: boolean; | |
| } | |
| export default function ModalDialog({ | |
| isOpen, | |
| title, | |
| message, | |
| type = 'confirm', | |
| onConfirm, | |
| onCancel, | |
| confirmText = '确定', | |
| cancelText = '取消', | |
| isDestructive = false | |
| }: ModalDialogProps) { | |
| if (!isOpen) return null; | |
| const getIcon = () => { | |
| switch (type) { | |
| case 'success': | |
| return <CheckCircle className="w-7 h-7 text-green-500" />; | |
| case 'error': | |
| return <XCircle className="w-7 h-7 text-red-500" />; | |
| case 'info': | |
| return <Info className="w-7 h-7 text-blue-500" />; | |
| case 'confirm': | |
| default: | |
| return isDestructive ? <AlertTriangle className="w-7 h-7 text-red-500" /> : <AlertTriangle className="w-7 h-7 text-blue-500" />; | |
| } | |
| }; | |
| const getIconBg = () => { | |
| switch (type) { | |
| case 'success': return 'bg-green-50 dark:bg-green-900/20'; | |
| case 'error': return 'bg-red-50 dark:bg-red-900/20'; | |
| case 'info': return 'bg-blue-50 dark:bg-blue-900/20'; | |
| case 'confirm': default: return isDestructive ? 'bg-red-50 dark:bg-red-900/20' : 'bg-blue-50 dark:bg-blue-900/20'; | |
| } | |
| }; | |
| const showCancel = type === 'confirm' && onCancel; | |
| return createPortal( | |
| <div className="modal modal-open z-[100]"> | |
| {/* Draggable Top Region */} | |
| <div data-tauri-drag-region className="fixed top-0 left-0 right-0 h-8 z-[110]" /> | |
| <div className="modal-box relative max-w-sm bg-white dark:bg-base-100 shadow-2xl rounded-2xl p-0 overflow-hidden transform transition-all animate-in fade-in zoom-in-95 duration-200"> | |
| <div className="flex flex-col items-center text-center p-6 pt-8"> | |
| <div className={`w-14 h-14 rounded-full flex items-center justify-center mb-4 shadow-sm ${getIconBg()}`}> | |
| {getIcon()} | |
| </div> | |
| <h3 className="text-xl font-bold text-gray-900 dark:text-base-content mb-2">{title}</h3> | |
| <p className="text-gray-500 dark:text-gray-400 text-sm mb-8 leading-relaxed px-4">{message}</p> | |
| <div className="flex gap-3 w-full"> | |
| {showCancel && ( | |
| <button | |
| className="flex-1 px-4 py-2.5 bg-gray-100 dark:bg-base-200 text-gray-700 dark:text-gray-300 font-medium rounded-xl hover:bg-gray-200 dark:hover:bg-base-300 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-200 dark:focus:ring-base-300" | |
| onClick={onCancel} | |
| > | |
| {cancelText} | |
| </button> | |
| )} | |
| <button | |
| className={`flex-1 px-4 py-2.5 text-white font-medium rounded-xl shadow-md transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 ${isDestructive && type === 'confirm' | |
| ? 'bg-red-500 hover:bg-red-600 focus:ring-red-500 shadow-red-100' | |
| : 'bg-blue-500 hover:bg-blue-600 focus:ring-blue-500 shadow-blue-100' | |
| }`} | |
| onClick={onConfirm} | |
| > | |
| {confirmText} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="modal-backdrop bg-black/40 backdrop-blur-sm fixed inset-0 z-[-1]" onClick={showCancel ? onCancel : undefined}></div> | |
| </div>, | |
| document.body | |
| ); | |
| } | |