2023-08-03 15:38:06 +02:00
|
|
|
import React, {
|
|
|
|
|
PropsWithChildren,
|
|
|
|
|
RefObject,
|
|
|
|
|
useLayoutEffect,
|
|
|
|
|
useRef,
|
|
|
|
|
useState,
|
|
|
|
|
} from "react";
|
2023-06-02 12:04:36 +02:00
|
|
|
import classNames from "classnames";
|
|
|
|
|
import { useHandleClickOutside } from ":/hooks/useHandleClickOutside";
|
|
|
|
|
|
|
|
|
|
type PopoverProps = PropsWithChildren & {
|
|
|
|
|
parentRef: RefObject<HTMLDivElement>;
|
|
|
|
|
onClickOutside: () => void;
|
|
|
|
|
borderless?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const Popover = ({
|
|
|
|
|
parentRef,
|
|
|
|
|
children,
|
|
|
|
|
onClickOutside,
|
|
|
|
|
borderless = false,
|
|
|
|
|
}: PopoverProps) => {
|
2023-08-03 15:38:06 +02:00
|
|
|
const popoverRef = useRef<HTMLDivElement>(null);
|
2023-06-02 12:04:36 +02:00
|
|
|
useHandleClickOutside(parentRef, onClickOutside);
|
2023-08-03 15:38:06 +02:00
|
|
|
const timeout = useRef<ReturnType<typeof setTimeout>>();
|
|
|
|
|
const [topPosition, setTopPosition] = useState<number | undefined>();
|
|
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
const setPopoverTopPosition = () => {
|
|
|
|
|
if (!parentRef.current || !popoverRef.current) return;
|
|
|
|
|
|
|
|
|
|
const parentBounds = parentRef.current.getBoundingClientRect();
|
|
|
|
|
const popoverBounds = popoverRef.current.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
const hasNotEnoughBottomPlace =
|
|
|
|
|
window.innerHeight - parentBounds.bottom < popoverBounds.height;
|
|
|
|
|
|
|
|
|
|
const hasEnoughTopPlace = parentBounds.top >= popoverBounds.height;
|
|
|
|
|
|
|
|
|
|
if (hasNotEnoughBottomPlace && hasEnoughTopPlace) {
|
|
|
|
|
setTopPosition(-popoverBounds.height);
|
|
|
|
|
} else {
|
|
|
|
|
setTopPosition(undefined);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleWindowResize = () => {
|
|
|
|
|
if (timeout.current) clearTimeout(timeout.current);
|
|
|
|
|
timeout.current = setTimeout(setPopoverTopPosition, 1000 / 30);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener("resize", handleWindowResize);
|
|
|
|
|
setPopoverTopPosition();
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener("resize", handleWindowResize);
|
|
|
|
|
if (timeout.current) clearTimeout(timeout.current);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
2023-06-02 12:04:36 +02:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
2023-08-03 15:38:06 +02:00
|
|
|
ref={popoverRef}
|
2023-06-02 12:04:36 +02:00
|
|
|
className={classNames("c__popover", {
|
|
|
|
|
"c__popover--borderless": borderless,
|
|
|
|
|
})}
|
2023-08-03 15:38:06 +02:00
|
|
|
style={{
|
|
|
|
|
top: topPosition,
|
|
|
|
|
}}
|
|
|
|
|
role="dialog"
|
2023-06-02 12:04:36 +02:00
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|