maestro/ui/src/components/embed/EmbedModal.tsx
oss-sync 3848b5efd7
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (63a6e76)
2026-06-09 03:17:43 +00:00

70 lines
2.0 KiB
TypeScript

import { useEffect, useCallback, type ReactNode } from 'react';
import { createPortal } from 'react-dom';
import { useBackdropClose } from '../../lib/useBackdropClose';
interface EmbedModalProps {
open: boolean;
onClose: () => void;
children: ReactNode;
}
export function EmbedModal({ open, onClose, children }: EmbedModalProps) {
const backdrop = useBackdropClose(onClose);
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
}, [onClose]);
useEffect(() => {
if (open) {
document.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = '';
};
}
}, [open, handleKeyDown]);
if (!open) return null;
return createPortal(
<div
className="fixed inset-0 z-50 flex items-center justify-center"
>
{/* Backdrop — the close handler lives here (the actual clicked element),
not the transparent outer div, so target===currentTarget holds. */}
<div className="absolute inset-0 bg-black/50" {...backdrop} />
{/* Modal content */}
<div
className="
relative bg-surface overflow-y-auto
w-full h-full
sm:w-auto sm:h-auto sm:max-w-[720px] sm:max-h-[85vh] sm:min-w-[400px]
sm:rounded-2xl sm:shadow-2xl sm:m-4
"
onClick={(e) => e.stopPropagation()}
>
{/* Close button */}
<button
onClick={onClose}
className="
sticky top-0 float-right z-10
m-3 w-8 h-8
flex items-center justify-center
bg-slate-100 hover:bg-slate-200
rounded-full text-slate-500 hover:text-slate-700
transition-colors cursor-pointer border-none text-lg
"
aria-label="閉じる"
>
&#10005;
</button>
{children}
</div>
</div>,
document.body,
);
}