import React, { useState, useEffect, useRef, MutableRefObject } from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { OlTooltip } from "./oltooltip"; export function OlDropdown(props: { disableAutoClose?: boolean; className?: string; leftIcon?: IconProp; rightIcon?: IconProp; label?: string; children?: JSX.Element | JSX.Element[]; buttonRef?: MutableRefObject | null; open?: boolean; tooltip?: string | (() => JSX.Element | JSX.Element[]); tooltipPosition?: string; tooltipRelativeToParent?: boolean; }) { const [open, setOpen] = props.open !== undefined ? [props.open, () => {}] : useState(false); const [hover, setHover] = useState(false); const [hoverTimeout, setHoverTimeout] = useState(null as number | null); var contentRef = useRef(null); var buttonRef = props.buttonRef !== undefined ? props.buttonRef : useRef(null); useEffect(() => { window.addEventListener("click", (e) => { setHover(false); }); }, []); function setPosition(content: HTMLDivElement, button: HTMLButtonElement) { /* Reset the position of the content */ content.style.left = "0px"; content.style.top = "0px"; content.style.height = ""; /* Get the position and size of the button and the content elements */ let [cxl, cyt, cxr, cyb, cw, ch] = [ content.getBoundingClientRect().x, content.getBoundingClientRect().y, content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight, content.clientWidth, content.clientHeight, ]; let [bxl, byt, bxr, byb, bbw, bh] = [ button.getBoundingClientRect().x, button.getBoundingClientRect().y, button.getBoundingClientRect().x + button.clientWidth, button.getBoundingClientRect().y + button.clientHeight, button.clientWidth, button.clientHeight, ]; /* Limit the maximum height */ if (ch > 400) { ch = 400; content.style.height = `${ch}px`; } /* Compute the horizontal position of the center of the button and the content */ var cxc = (cxl + cxr) / 2; var bxc = (bxl + bxr) / 2; /* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */ var offsetX = bxc - cxc; var offsetY = byb - cyt + 8; /* Compute the new position of the left and right margins of the content */ cxl += offsetX; cxr += offsetX; cyb += offsetY; /* Try and move the content so it is inside the screen */ if (cxl < 0) offsetX -= cxl; if (cxr > window.innerWidth) offsetX -= cxr - window.innerWidth; if (cyb > window.innerHeight) offsetY -= bh + ch + 16; /* Apply the offset */ content.style.left = `${offsetX}px`; content.style.top = `${offsetY}px`; content.style.width = `${bbw}px`; } useEffect(() => { if (contentRef.current && buttonRef?.current) { const content = contentRef.current as HTMLDivElement; const button = buttonRef.current as HTMLButtonElement; setPosition(content, button); /* Register click events to automatically close the dropdown when clicked anywhere outside of it */ document.addEventListener("click", function (event) { const target = event.target; if (target && !content.contains(target as HTMLElement) && !button.contains(target as HTMLElement)) { setOpen(false); } }); } }); return ( <>
{props.buttonRef === undefined && ( )}
{ props.disableAutoClose !== true && setOpen(false); }} > {props.children}
{hover && !open && buttonRef && props.tooltip && ( )} ); } /* Conveniency Component for dropdown elements */ export function OlDropdownItem(props: { onClick?: () => void; className?: string; borderColor?: string; children?: string | JSX.Element | JSX.Element[], disabled?: boolean }) { return ( ); }