Files
CHOMPStation2/tgui/packages/tgui/components/Popper.tsx
CHOMPStation2 eebf92d66f [MIRROR] TGUI 5.0 Patch 1 (#7701)
Co-authored-by: Selis <sirlionfur@hotmail.de>
2024-02-08 15:31:06 +01:00

89 lines
2.5 KiB
TypeScript

import { Placement } from '@popperjs/core';
import { PropsWithChildren, ReactNode, useEffect, useRef, useState } from 'react';
import { usePopper } from 'react-popper';
type RequiredProps = {
/** The content to display in the popper */
content: ReactNode;
/** Whether the popper is open */
isOpen: boolean;
};
type OptionalProps = Partial<{
/** Called when the user clicks outside the popper */
onClickOutside: () => void;
/** Where to place the popper relative to the reference element */
placement: Placement;
}>;
type Props = RequiredProps & OptionalProps;
/**
* ## Popper
* Popper lets you position elements so that they don't go out of the bounds of the window.
* @url https://popper.js.org/react-popper/ for more information.
*/
export function Popper(props: PropsWithChildren<Props>) {
const { children, content, isOpen, onClickOutside, placement } = props;
const [referenceElement, setReferenceElement] =
useState<HTMLDivElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(
null
);
// One would imagine we could just use useref here, but it's against react-popper documentation and causes a positioning bug
// We still need them to call focus and clickoutside events :(
const popperRef = useRef<HTMLDivElement | null>(null);
const parentRef = useRef<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement,
});
/** Close the popper when the user clicks outside */
function handleClickOutside(event: MouseEvent) {
if (
!popperRef.current?.contains(event.target as Node) &&
!parentRef.current?.contains(event.target as Node)
) {
onClickOutside?.();
}
}
useEffect(() => {
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen]);
return (
<>
<div
ref={(node) => {
setReferenceElement(node);
parentRef.current = node;
}}>
{children}
</div>
{isOpen && (
<div
ref={(node) => {
setPopperElement(node);
popperRef.current = node;
}}
style={{ ...styles.popper, zIndex: 5 }}
{...attributes.popper}>
{content}
</div>
)}
</>
);
}