new-personal-site/components/lib/elements/Modal.tsx
2024-12-09 16:36:17 +01:00

72 lines
1.9 KiB
TypeScript

import React, { DetailedHTMLProps, HTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
import { createRoot } from "react-dom/client";
import Paper from "./Paper";
type Props = DetailedHTMLProps<
HTMLAttributes<HTMLDivElement>,
HTMLDivElement
> & {
target: React.ReactNode;
};
/**
* # Modal Component
* @className_wrapper twui-modal-root
* @className_wrapper twui-modal
*/
export default function Modal({ target, ...props }: Props) {
const [wrapper, setWrapper] = React.useState<HTMLDivElement | null>(null);
React.useEffect(() => {
const wrapperEl = document.createElement("div");
wrapperEl.className = twMerge(
"fixed z-[200000] top-0 left-0 w-screen h-screen",
"flex flex-col items-center justify-center",
"twui-modal-root"
);
setWrapper(wrapperEl);
}, []);
const modalEl = (
<React.Fragment>
<div
className={twMerge(
"absolute top-0 left-0 bg-slate-900/80 z-0",
"w-screen h-screen"
)}
onClick={(e) => {
closeModal({ wrapperEl: wrapper });
}}
></div>
<Paper
{...props}
className={twMerge("z-10 max-w-[500px]", props.className)}
>
{props.children}
</Paper>
</React.Fragment>
);
const targetEl = (
<div
onClick={(e) => {
if (!wrapper) return;
document.body.appendChild(wrapper);
const root = createRoot(wrapper);
root.render(modalEl);
}}
>
{target}
</div>
);
return targetEl;
}
function closeModal({ wrapperEl }: { wrapperEl: HTMLDivElement | null }) {
if (!wrapperEl) return;
wrapperEl.parentElement?.removeChild(wrapperEl);
}