UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

104 lines (101 loc) 4.03 kB
import React__default, { useCallback, useEffect } from 'react'; import { useFocusTrap } from '../hooks/useFocusTrap.js'; import { useFocusZone } from '../hooks/useFocusZone.js'; import { useId } from '../hooks/useId.js'; import { useProvidedRefOrCreate } from '../hooks/useProvidedRefOrCreate.js'; import { useRenderForcingRef } from '../hooks/useRenderForcingRef.js'; import { useAnchoredPosition } from '../hooks/useAnchoredPosition.js'; import Overlay from '../Overlay/Overlay.js'; function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /** * An `AnchoredOverlay` provides an anchor that will open a floating overlay positioned relative to the anchor. * The overlay can be opened and navigated using keyboard or mouse. */ const AnchoredOverlay = ({ renderAnchor, anchorRef: externalAnchorRef, anchorId: externalAnchorId, children, open, onOpen, onClose, height, width, overlayProps, focusTrapSettings, focusZoneSettings, side = 'outside-bottom', align = 'start' }) => { const anchorRef = useProvidedRefOrCreate(externalAnchorRef); const [overlayRef, updateOverlayRef] = useRenderForcingRef(); const anchorId = useId(externalAnchorId); const onClickOutside = useCallback(() => onClose === null || onClose === void 0 ? void 0 : onClose('click-outside'), [onClose]); const onEscape = useCallback(() => onClose === null || onClose === void 0 ? void 0 : onClose('escape'), [onClose]); const onAnchorKeyDown = useCallback(event => { if (!event.defaultPrevented) { if (!open && ['ArrowDown', 'ArrowUp', ' ', 'Enter'].includes(event.key)) { onOpen === null || onOpen === void 0 ? void 0 : onOpen('anchor-key-press', event); event.preventDefault(); } } }, [open, onOpen]); const onAnchorClick = useCallback(event => { if (event.defaultPrevented || event.button !== 0) { return; } if (!open) { onOpen === null || onOpen === void 0 ? void 0 : onOpen('anchor-click'); } else { onClose === null || onClose === void 0 ? void 0 : onClose('anchor-click'); } }, [open, onOpen, onClose]); const { position } = useAnchoredPosition({ anchorElementRef: anchorRef, floatingElementRef: overlayRef, side, align }, [overlayRef.current]); useEffect(() => { // ensure overlay ref gets cleared when closed, so position can reset between closing/re-opening if (!open && overlayRef.current) { updateOverlayRef(null); } }, [open, overlayRef, updateOverlayRef]); useFocusZone({ containerRef: overlayRef, disabled: !open || !position, ...focusZoneSettings }); useFocusTrap({ containerRef: overlayRef, disabled: !open || !position, ...focusTrapSettings }); return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, renderAnchor && renderAnchor({ ref: anchorRef, id: anchorId, 'aria-haspopup': 'true', 'aria-expanded': open ? 'true' : undefined, tabIndex: 0, onClick: onAnchorClick, onKeyDown: onAnchorKeyDown }), open ? /*#__PURE__*/React__default.createElement(Overlay, _extends({ returnFocusRef: anchorRef, onClickOutside: onClickOutside, ignoreClickRefs: [anchorRef], onEscape: onEscape, ref: updateOverlayRef, role: "none", visibility: position ? 'visible' : 'hidden', height: height, width: width, top: (position === null || position === void 0 ? void 0 : position.top) || 0, left: (position === null || position === void 0 ? void 0 : position.left) || 0, anchorSide: position === null || position === void 0 ? void 0 : position.anchorSide }, overlayProps), children) : null); }; AnchoredOverlay.displayName = 'AnchoredOverlay'; export { AnchoredOverlay };