12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |
- import React, { useLayoutEffect, useRef, useEffect } from "react";
- import "./Popover.scss";
- import { unstable_batchedUpdates } from "react-dom";
- type Props = {
- top?: number;
- left?: number;
- children?: React.ReactNode;
- onCloseRequest?(event: PointerEvent): void;
- fitInViewport?: boolean;
- offsetLeft?: number;
- offsetTop?: number;
- viewportWidth?: number;
- viewportHeight?: number;
- };
- export const Popover = ({
- children,
- left,
- top,
- onCloseRequest,
- fitInViewport = false,
- offsetLeft = 0,
- offsetTop = 0,
- viewportWidth = window.innerWidth,
- viewportHeight = window.innerHeight,
- }: Props) => {
- const popoverRef = useRef<HTMLDivElement>(null);
- // ensure the popover doesn't overflow the viewport
- useLayoutEffect(() => {
- if (fitInViewport && popoverRef.current) {
- const element = popoverRef.current;
- const { x, y, width, height } = element.getBoundingClientRect();
- if (x + width - offsetLeft > viewportWidth) {
- element.style.left = `${viewportWidth - width}px`;
- }
- if (y + height - offsetTop > viewportHeight) {
- element.style.top = `${viewportHeight - height}px`;
- }
- }
- }, [fitInViewport, viewportWidth, viewportHeight, offsetLeft, offsetTop]);
- useEffect(() => {
- if (onCloseRequest) {
- const handler = (event: PointerEvent) => {
- if (!popoverRef.current?.contains(event.target as Node)) {
- unstable_batchedUpdates(() => onCloseRequest(event));
- }
- };
- document.addEventListener("pointerdown", handler, false);
- return () => document.removeEventListener("pointerdown", handler, false);
- }
- }, [onCloseRequest]);
- return (
- <div className="popover" style={{ top, left }} ref={popoverRef}>
- {children}
- </div>
- );
- };
|