You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
68 lines
2.1 KiB
JavaScript
68 lines
2.1 KiB
JavaScript
import { useEffect, useRef } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import './FormOverlay.css';
|
|
|
|
export default function FormOverlay({ isOpen, onClose, title, children }) {
|
|
const overlayRef = useRef(null);
|
|
const mouseDownTargetRef = useRef(null);
|
|
|
|
// Escape key to close
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
document.addEventListener('keydown', handleKeyDown);
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
}, [isOpen, onClose]);
|
|
|
|
// Prevent body scroll when open
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
document.body.style.overflow = 'hidden';
|
|
return () => { document.body.style.overflow = ''; };
|
|
}
|
|
}, [isOpen]);
|
|
|
|
if (!isOpen) return null;
|
|
|
|
// Smart click handling: only close if mousedown AND mouseup both on backdrop
|
|
// This prevents closing when user selects text and drags to backdrop
|
|
const handleMouseDown = (e) => {
|
|
mouseDownTargetRef.current = e.target;
|
|
};
|
|
|
|
const handleMouseUp = (e) => {
|
|
if (mouseDownTargetRef.current === overlayRef.current && e.target === overlayRef.current) {
|
|
onClose();
|
|
}
|
|
mouseDownTargetRef.current = null;
|
|
};
|
|
|
|
return createPortal(
|
|
<div
|
|
className="nui-form-overlay"
|
|
ref={overlayRef}
|
|
onMouseDown={handleMouseDown}
|
|
onMouseUp={handleMouseUp}
|
|
>
|
|
<div className="nui-form-modal" onClick={(e) => e.stopPropagation()}>
|
|
<div className="nui-form-modal-header">
|
|
<h3>{title}</h3>
|
|
<button
|
|
className="nui-form-modal-close"
|
|
onClick={onClose}
|
|
type="button"
|
|
>
|
|
×
|
|
</button>
|
|
</div>
|
|
<div className="nui-form-modal-content">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body
|
|
);
|
|
}
|