70 lines
2.0 KiB
TypeScript
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="閉じる"
|
|
>
|
|
✕
|
|
</button>
|
|
|
|
{children}
|
|
</div>
|
|
</div>,
|
|
document.body,
|
|
);
|
|
}
|