@kiwicom/orbit-components
Version:
Orbit-components is a React component library which provides developers with the easiest possible way of building Kiwi.com's products.
145 lines (144 loc) • 4.52 kB
JavaScript
"use client";
import * as React from "react";
import { FloatingPortal } from "@floating-ui/react";
import useStateWithTimeout from "../hooks/useStateWithTimeout";
import { PLACEMENTS } from "../common/placements";
import PopoverContent from "./components/ContentWrapper";
import useRandomId from "../hooks/useRandomId";
import handleKeyDown from "../utils/handleKeyDown";
const Popover = ({
children,
renderInPortal = true,
opened,
zIndex,
content,
onClose,
id,
onOpen,
offset,
placement = PLACEMENTS.BOTTOM_START,
fixed,
lockScrolling = true,
noFlip,
labelClose = "Close",
renderTimeout = 0,
allowOverflow,
noPadding,
width,
maxHeight,
actions,
overlapped,
dataTest,
ariaLabel,
ariaLabelledby,
role = "dialog",
tabIndex,
triggerRole
}) => {
const ref = React.useRef(null);
const popoverId = useRandomId();
const overlayId = useRandomId();
const [shown, setShown, setShownWithTimeout, clearShownTimeout] = useStateWithTimeout(false, renderTimeout);
const [render, setRender, setRenderWithTimeout, clearRenderTimeout] = useStateWithTimeout(false, renderTimeout);
const resolveCallback = React.useCallback(state => {
if (onClose && !state) onClose();
if (onOpen && state) onOpen();
}, [onClose, onOpen]);
const closePopover = React.useCallback(() => {
// If open prop is present ignore custom handler
if (typeof opened === "undefined") {
setShown(false);
clearShownTimeout();
resolveCallback(false);
setRenderWithTimeout(false);
} else if (onClose) onClose();
}, [setShown, clearShownTimeout, onClose, opened, resolveCallback, setRenderWithTimeout]);
const handleEsc = React.useCallback(ev => {
if (ev.key === "Escape") {
closePopover();
}
}, [closePopover]);
const handleOut = React.useCallback(ev => {
ev?.stopPropagation();
if (ev && ref.current && !ref.current.contains(ev.target)) {
closePopover();
}
}, [closePopover]);
const handleClick = React.useCallback(() => {
// If open prop is present ignore custom handler
if (typeof opened === "undefined") {
if (shown) {
setShown(false);
clearShownTimeout();
setRenderWithTimeout(false);
resolveCallback(false);
} else {
setRender(true);
clearRenderTimeout();
setShownWithTimeout(true);
resolveCallback(true);
}
} else if (opened) {
resolveCallback(false);
} else if (!opened) {
resolveCallback(true);
}
}, [clearRenderTimeout, clearShownTimeout, opened, resolveCallback, setRender, setRenderWithTimeout, setShown, setShownWithTimeout, shown]);
React.useEffect(() => {
if (opened || shown) {
document.addEventListener("keydown", handleEsc);
}
if (typeof opened !== "undefined") {
if (opened) {
setRender(true);
clearRenderTimeout();
setShownWithTimeout(true);
} else {
setShown(false);
clearShownTimeout();
setRenderWithTimeout(false);
}
}
return () => {
document.removeEventListener("keydown", handleEsc);
};
}, [shown, handleEsc, opened, clearRenderTimeout, clearShownTimeout, setRender, setShown, setShownWithTimeout, setRenderWithTimeout]);
const popover = /*#__PURE__*/React.createElement(PopoverContent, {
shown: shown,
id: id || popoverId,
overlayId: overlayId,
labelClose: labelClose,
dataTest: dataTest,
zIndex: zIndex,
overlapped: overlapped,
fixed: fixed,
noFlip: noFlip,
allowOverflow: allowOverflow,
lockScrolling: lockScrolling,
noPadding: noPadding,
actions: actions,
width: width,
maxHeight: maxHeight,
offset: offset,
referenceElement: ref.current,
onClose: handleOut,
placement: placement,
ariaLabel: ariaLabel,
ariaLabelledby: ariaLabelledby,
role: role
}, content);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
className: "relative inline-block focus:outline-offset-1",
ref: ref,
role: triggerRole || "button",
tabIndex: tabIndex ? Number(tabIndex) : 0,
onClick: handleClick,
onKeyDown: handleKeyDown(handleClick),
"aria-controls": shown ? id || popoverId : undefined,
"aria-haspopup": role,
"aria-expanded": shown
}, children), render && (renderInPortal ? /*#__PURE__*/React.createElement(FloatingPortal, {
id: "popovers"
}, popover) : popover));
};
export default Popover;