Spaces:
Sleeping
Sleeping
| import React, { useEffect, useRef } from 'react'; | |
| import { X } from 'lucide-react'; | |
| interface ModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| title: string; | |
| children: React.ReactNode; | |
| primaryAction?: { | |
| label: string; | |
| onClick: () => void; | |
| danger?: boolean; | |
| }; | |
| secondaryAction?: { | |
| label: string; | |
| onClick: () => void; | |
| }; | |
| } | |
| export default function Modal({ | |
| isOpen, | |
| onClose, | |
| title, | |
| children, | |
| primaryAction, | |
| secondaryAction | |
| }: ModalProps) { | |
| const modalRef = useRef<HTMLDivElement>(null); | |
| useEffect(() => { | |
| const handleEscape = (e: KeyboardEvent) => { | |
| if (e.key === 'Escape') onClose(); | |
| }; | |
| if (isOpen) { | |
| document.addEventListener('keydown', handleEscape); | |
| document.body.style.overflow = 'hidden'; | |
| } | |
| return () => { | |
| document.removeEventListener('keydown', handleEscape); | |
| document.body.style.overflow = 'unset'; | |
| }; | |
| }, [isOpen, onClose]); | |
| if (!isOpen) return null; | |
| return ( | |
| <div className="fixed inset-0 z-[200] flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm transition-opacity duration-200"> | |
| <div | |
| ref={modalRef} | |
| className="bg-white dark:bg-gray-900 rounded-xl shadow-2xl w-full max-w-md border border-gray-200 dark:border-gray-800 transform transition-all duration-200 scale-100" | |
| onClick={(e) => e.stopPropagation()} | |
| > | |
| <div className="flex items-center justify-between p-4 border-b border-gray-100 dark:border-gray-800"> | |
| <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{title}</h3> | |
| <button | |
| onClick={onClose} | |
| className="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" | |
| > | |
| <X size={20} /> | |
| </button> | |
| </div> | |
| <div className="p-6"> | |
| {children} | |
| </div> | |
| <div className="flex items-center justify-end gap-3 p-4 border-t border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-900/50 rounded-b-xl"> | |
| {secondaryAction && ( | |
| <button | |
| onClick={secondaryAction.onClick} | |
| className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors" | |
| > | |
| {secondaryAction.label} | |
| </button> | |
| )} | |
| {primaryAction && ( | |
| <button | |
| onClick={primaryAction.onClick} | |
| className={`px-4 py-2 text-sm font-medium text-white rounded-lg shadow-sm transition-colors ${ | |
| primaryAction.danger | |
| ? 'bg-red-500 hover:bg-red-600' | |
| : 'bg-blue-600 hover:bg-blue-700' | |
| }`} | |
| > | |
| {primaryAction.label} | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |