Files
portfolio/components/ui/Modal.tsx
T

72 lines
2.0 KiB
TypeScript

'use client';
import { IconXFilled } from "@tabler/icons-react";
import { useEffect, useState } from "react";
import { createPortal } from 'react-dom';
type ModalProps = {
children?: React.ReactNode;
headline?: string;
open?: boolean;
setOpen?: (open: boolean) => void;
};
export default function Modal({ children, headline, open, setOpen }: ModalProps) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (!open) return;
const original = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.body.style.overflow = original;
};
}, [open]);
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && handleClose();
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [open]);
const handleClose = () => {
if (setOpen) setOpen(false);
};
if (!mounted || !open) return null;
return createPortal(
<div
onClick={handleClose}
className="fixed inset-0 z-50 bg-black/50 backdrop-blur-xl"
>
<div
onClick={(e) => e.stopPropagation()}
className="fixed inset-0 m-2 sm:m-24 sm:mx-64 xs:mx-64 flex flex-col rounded-md border border-neutral-700 bg-neutral-900/50 p-2"
>
<nav className="flex justify-between items-center p-2">
<div className="text-lg font-medium text-center w-full">
{headline}
</div>
<button
onClick={handleClose}
className="text-neutral-400 hover:text-neutral-200 rounded-md p-1 transition-colors duration-200 cursor-pointer"
aria-label="Close modal"
>
<IconXFilled />
</button>
</nav>
<div className="flex min-h-0 flex-1 items-center justify-center overflow-auto p-5">
{children}
</div>
</div>
</div>,
document.body
);
}