UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

82 lines (78 loc) 3.33 kB
import { useContext, useRef, useEffect, useCallback } from 'react'; import { AutocompleteContext } from './AutocompleteContext.js'; import { useMergedRefs } from '../hooks/useMergedRefs.js'; import VisuallyHidden from '../_VisuallyHidden.js'; import classes from './AutocompleteOverlay.module.css.js'; import { clsx } from 'clsx'; import { jsx } from 'react/jsx-runtime'; import { useAnchoredPosition } from '../hooks/useAnchoredPosition.js'; import Overlay from '../Overlay/Overlay.js'; // TODO: consider making 'aria-labelledby' required function AutocompleteOverlay({ menuAnchorRef, overlayProps: oldOverlayProps, children, className, ...newOverlayProps }) { const autocompleteContext = useContext(AutocompleteContext); if (autocompleteContext === null) { throw new Error('AutocompleteContext returned null values'); } const overlayProps = { ...oldOverlayProps, ...newOverlayProps }; const { inputRef, scrollContainerRef, selectedItemLength, setShowMenu, showMenu = false } = autocompleteContext; const computedAnchorRef = useRef(null); useEffect(() => { var _menuAnchorRef$curren, _tokensContainer$pare, _ref; const explicit = (_menuAnchorRef$curren = menuAnchorRef === null || menuAnchorRef === void 0 ? void 0 : menuAnchorRef.current) !== null && _menuAnchorRef$curren !== void 0 ? _menuAnchorRef$curren : null; const tokensContainer = inputRef.current ? inputRef.current.closest('[data-prevent-token-wrapping]') : null; const tokensRoot = (_tokensContainer$pare = tokensContainer === null || tokensContainer === void 0 ? void 0 : tokensContainer.parentElement) !== null && _tokensContainer$pare !== void 0 ? _tokensContainer$pare : null; computedAnchorRef.current = (_ref = explicit !== null && explicit !== void 0 ? explicit : tokensRoot) !== null && _ref !== void 0 ? _ref : inputRef.current; }, [menuAnchorRef, inputRef]); const { floatingElementRef, position } = useAnchoredPosition({ side: 'outside-bottom', align: 'start', anchorElementRef: computedAnchorRef }, [showMenu, selectedItemLength]); const mergedScrollContainerRef = useMergedRefs(scrollContainerRef, floatingElementRef); const closeOptionList = useCallback(() => { setShowMenu(false); }, [setShowMenu]); if (typeof window === 'undefined') { return null; } return showMenu ? /*#__PURE__*/jsx(Overlay, { returnFocusRef: inputRef, preventFocusOnOpen: true, onClickOutside: closeOptionList, onEscape: closeOptionList, ref: mergedScrollContainerRef, top: position === null || position === void 0 ? void 0 : position.top, left: position === null || position === void 0 ? void 0 : position.left, className: clsx(classes.Overlay, className), ...overlayProps, children: children }) : /*#__PURE__*/ // HACK: This ensures AutocompleteMenu is still mounted when closing the menu and all of the hooks inside of it are still called. // A better way to do this would be to move the hooks to AutocompleteOverlay or somewhere that won't get unmounted. jsx(VisuallyHidden, { "aria-hidden": "true", children: children }); } AutocompleteOverlay.displayName = 'AutocompleteOverlay'; AutocompleteOverlay.__SLOT__ = Symbol('Autocomplete.Overlay'); export { AutocompleteOverlay as default };