Files
fiberops-web/src/components/ui/form-modal.tsx
2026-04-13 09:36:37 +08:00

56 lines
2.1 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
interface FormModalProps {
open: boolean;
onClose: () => void;
title: string;
description?: string;
children: React.ReactNode;
wide?: boolean;
}
export function FormModal({ open, onClose, title, description, children, wide }: FormModalProps) {
const overlayRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (open) document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = ''; };
}, [open]);
useEffect(() => {
function handleKey(e: KeyboardEvent) {
if (e.key === 'Escape' && open) onClose();
}
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [open, onClose]);
if (!open) return null;
return (
<div
ref={overlayRef}
className="fixed inset-0 z-50 flex items-start justify-center pt-[10vh] px-4 bg-surface-900/40 backdrop-blur-sm overflow-y-auto"
role="dialog"
aria-modal="true"
aria-labelledby="form-modal-title"
onClick={(e) => { if (e.target === overlayRef.current) onClose(); }}
>
<div className={`bg-white dark:bg-surface-800 rounded-xl shadow-xl w-full p-6 mb-10 animate-in fade-in slide-in-from-top-4 duration-200 ${wide ? 'max-w-2xl' : 'max-w-lg'}`}>
<div className="flex items-start justify-between mb-5">
<div>
<h2 id="form-modal-title" className="text-lg font-semibold text-surface-900 dark:text-surface-200">{title}</h2>
{description && <p className="mt-1 text-sm text-surface-500 dark:text-surface-400">{description}</p>}
</div>
<button onClick={onClose} className="p-1 text-surface-400 hover:text-surface-600 dark:hover:text-surface-300 transition-colors cursor-pointer rounded-lg hover:bg-surface-50 dark:hover:bg-surface-700" aria-label="Close">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M6 6l8 8M14 6l-8 8" /></svg>
</button>
</div>
{children}
</div>
</div>
);
}