mirror of https://github.com/gabehf/Koito.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
85 lines
2.3 KiB
85 lines
2.3 KiB
import { useEffect, useRef, useState } from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
|
|
export function Modal({
|
|
isOpen,
|
|
onClose,
|
|
children,
|
|
maxW,
|
|
h
|
|
}: {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
children: React.ReactNode;
|
|
maxW?: number;
|
|
h?: number;
|
|
}) {
|
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
const [shouldRender, setShouldRender] = useState(isOpen);
|
|
const [isClosing, setIsClosing] = useState(false);
|
|
|
|
// Show/hide logic
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setShouldRender(true);
|
|
setIsClosing(false);
|
|
} else if (shouldRender) {
|
|
setIsClosing(true);
|
|
const timeout = setTimeout(() => {
|
|
setShouldRender(false);
|
|
}, 100); // Match fade-out duration
|
|
return () => clearTimeout(timeout);
|
|
}
|
|
}, [isOpen, shouldRender]);
|
|
|
|
// Close on Escape key
|
|
useEffect(() => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') onClose();
|
|
};
|
|
if (isOpen) document.addEventListener('keydown', handleKeyDown);
|
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
}, [isOpen, onClose]);
|
|
|
|
// Close on outside click
|
|
useEffect(() => {
|
|
const handleClick = (e: MouseEvent) => {
|
|
if (
|
|
modalRef.current &&
|
|
!modalRef.current.contains(e.target as Node)
|
|
) {
|
|
onClose();
|
|
}
|
|
};
|
|
if (isOpen) document.addEventListener('mousedown', handleClick);
|
|
return () => document.removeEventListener('mousedown', handleClick);
|
|
}, [isOpen, onClose]);
|
|
|
|
if (!shouldRender) return null;
|
|
|
|
return ReactDOM.createPortal(
|
|
<div
|
|
className={`fixed inset-0 z-50 flex items-center justify-center bg-black/50 transition-opacity duration-100 ${
|
|
isClosing ? 'animate-fade-out' : 'animate-fade-in'
|
|
}`}
|
|
>
|
|
<div
|
|
ref={modalRef}
|
|
className={`bg-secondary rounded-lg shadow-md p-6 w-full relative max-h-3/4 overflow-y-auto transition-all duration-100 ${
|
|
isClosing ? 'animate-fade-out-scale' : 'animate-fade-in-scale'
|
|
}`}
|
|
style={{ maxWidth: maxW ?? 600, height: h ?? '' }}
|
|
>
|
|
<button
|
|
onClick={onClose}
|
|
className="absolute top-2 right-2 color-fg-tertiary hover:cursor-pointer"
|
|
>
|
|
🞪
|
|
</button>
|
|
{children}
|
|
</div>
|
|
</div>,
|
|
document.body
|
|
);
|
|
}
|