@atlaskit/navigation-system
Version:
The latest navigation system for Atlassian apps.
129 lines (125 loc) • 4.73 kB
JavaScript
/* 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))
});
}