movie-web/src/components/overlays/OverlayDisplay.tsx

88 lines
2.6 KiB
TypeScript
Raw Normal View History

import classNames from "classnames";
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { Transition } from "@/components/Transition";
2023-10-09 21:00:58 +02:00
import { useInternalOverlayRouter } from "@/hooks/useOverlayRouter";
export interface OverlayProps {
id: string;
children?: ReactNode;
darken?: boolean;
}
export function OverlayDisplay(props: { children: ReactNode }) {
2023-10-09 21:02:18 +02:00
const router = useInternalOverlayRouter("hello world :)");
2023-10-09 21:00:58 +02:00
const refRouter = useRef(router);
2023-10-09 21:00:58 +02:00
// close router on first mount, we dont want persist routes for overlays
useEffect(() => {
const r = refRouter.current;
r.close();
return () => {
r.close();
};
}, []);
2023-10-09 21:02:18 +02:00
return <div className="popout-location">{props.children}</div>;
}
export function Overlay(props: OverlayProps) {
const router = useInternalOverlayRouter(props.id);
const [portalElement, setPortalElement] = useState<Element | null>(null);
const ref = useRef<HTMLDivElement>(null);
const target = useRef<Element | null>(null);
2023-10-09 21:00:58 +02:00
useEffect(() => {
function listen(e: MouseEvent) {
target.current = e.target as Element;
}
document.addEventListener("mousedown", listen);
return () => {
document.removeEventListener("mousedown", listen);
};
});
const click = useCallback(
(e: React.MouseEvent) => {
const startedTarget = target.current;
target.current = null;
if (e.currentTarget !== e.target) return;
if (!startedTarget) return;
if (!startedTarget.isEqualNode(e.currentTarget as Element)) return;
router.close();
},
[router]
);
useEffect(() => {
const element = ref.current?.closest(".popout-location");
setPortalElement(element ?? document.body);
}, []);
return (
<div ref={ref}>
{portalElement
? createPortal(
<Transition show={router.isOverlayActive()} animation="none">
<div className="popout-wrapper pointer-events-auto fixed inset-0 z-[999] select-none">
<Transition animation="fade" isChild>
<div
onClick={click}
className={classNames({
"absolute inset-0": true,
"bg-black opacity-90": props.darken,
})}
/>
</Transition>
<Transition animation="slide-up" className="h-0" isChild>
{props.children}
</Transition>
</div>
</Transition>,
portalElement
)
: null}
</div>
);
}