UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

129 lines (125 loc) • 4.73 kB
/* skip-links-popup.tsx generated by @compiled/babel-plugin v3.0.2 */ import _extends from "@babel/runtime/helpers/extends"; import "./skip-links-popup.compiled.css"; import * as React from 'react'; import { ax, ix } from "@compiled/react/runtime"; import { useCallback, useId, useRef, useState } from 'react'; import { flushSync } from 'react-dom'; import Button from '@atlaskit/button/default/button'; import mergeRefs from '@atlaskit/ds-lib/merge-refs'; import { Popup } from '@atlaskit/popup/popup'; import { focusElement } from './focus-element'; import { SkipLink } from './skip-link'; const contentStyles = { root: "_1rjcu2gc _18zrpxbi _1e0c1txw _2lx21bp4 _1pbyjh3g", skipLinkList: "_1e0c1txw _2lx21bp4 _1x8w11lx _2mzuglyw _1pfhze3t _bozgze3t", label: "_11c8i4vh _1rjcu2gc _syazazsu" }; const triggerStyles = { root: "_2rko12b0 _1dqoglyw _kqsw1n9t _1e02utpp _152tutpp _1pby1mrw _tzy4idpf _bfhk1bhr _4ojojh3g _c8j0kb7n", isOpen: "_tzy4idpf _t9ecz6y5" }; const popupOffset = [0, 0]; /** * Skip links shown behind `platform_dst_nav4_skip_link_a11y_1`: a single focus-revealed * control opens a dialog listing skip targets. */ export function SkipLinksPopup({ label: title, triggerLabel, testId, links }) { const titleId = useId(); const [isOpen, setIsOpen] = useState(false); const triggerRef = useRef(null); const openPopup = useCallback(() => { setIsOpen(true); }, []); /** * Closes the popup. * * Uses `flushSync` to avoid issues with focus management. * This ensures the popup's focus lock is fully released by the time skip links or our `onClose` try to move focus. */ const closePopup = useCallback(() => { flushSync(() => { setIsOpen(false); }); }, []); /** * Restores focus to the trigger on close, if it hasn't moved to another element, such as when Esc is pressed instead of a skip link. * * We need this because using the standard `shouldReturnFocus` on the popup would break the skip link focus behaviour, * as focus would always move back to the trigger. */ const onClose = useCallback(() => { closePopup(); // Focus on the body implies a skip link was not clicked if (document.activeElement === document.body) { var _triggerRef$current; (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.focus(); } }, [closePopup]); return /*#__PURE__*/React.createElement(Popup, { isOpen: isOpen, onClose: onClose, placement: "bottom-start", shouldRenderToParent: true, shouldReturnFocus: false, role: "dialog", titleId: titleId, testId: testId ? `${testId}--skip-links-container` : undefined, offset: popupOffset, content: () => /*#__PURE__*/React.createElement("div", { className: ax([contentStyles.root]) }, /*#__PURE__*/React.createElement("div", { "data-testid": testId ? `${testId}--skip-links-container--label` : undefined, id: titleId, className: ax([contentStyles.label]) }, title), /*#__PURE__*/React.createElement("ol", { className: ax([contentStyles.skipLinkList]) }, links.map(({ id, label, navigate }) => /*#__PURE__*/React.createElement(SkipLink, { key: id, id: id /** * The popup always owns the navigation effect under the * `platform_dst_nav4_skip_link_a11y_1` gate (the only path * that renders `SkipLinksPopup`). It first closes itself * synchronously so its focus lock is released, then either: * * - delegates to the consumer's `navigate` (e.g. SideNav * expanding and focusing its first nav item), or * - falls back to focusing the slot element with `id`. * * This means `SkipLink`'s `onBeforeNavigate` hook is never * needed under the gate and can be removed on cleanup. */, navigate: () => { closePopup(); if (navigate) { navigate(); return; } // Intentionally not using `document.querySelector` because many valid IDs are not valid selectors. const target = document.getElementById(id); if (target) { focusElement(target); } } }, label)))), trigger: triggerProps => /*#__PURE__*/React.createElement("div", { className: ax([triggerStyles.root, isOpen && triggerStyles.isOpen]) }, /*#__PURE__*/React.createElement(Button, _extends({}, triggerProps, { ref: mergeRefs([triggerProps.ref, triggerRef]), appearance: "primary", isSelected: isOpen, testId: testId ? `${testId}--skip-links-trigger` : undefined, onClick: openPopup }), triggerLabel)) }); }