@atlaskit/navigation-system
Version:
The latest navigation system for Atlassian apps.
146 lines (141 loc) • 6.53 kB
JavaScript
/* skip-links-popup.tsx generated by @compiled/babel-plugin v3.0.2 */
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SkipLinksPopup = SkipLinksPopup;
require("./skip-links-popup.compiled.css");
var _react = _interopRequireWildcard(require("react"));
var React = _react;
var _runtime = require("@compiled/react/runtime");
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _reactDom = require("react-dom");
var _button = _interopRequireDefault(require("@atlaskit/button/default/button"));
var _mergeRefs = _interopRequireDefault(require("@atlaskit/ds-lib/merge-refs"));
var _popup = require("@atlaskit/popup/popup");
var _focusElement = require("./focus-element");
var _skipLink = require("./skip-link");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
var contentStyles = {
root: "_1rjcu2gc _18zrpxbi _1e0c1txw _2lx21bp4 _1pbyjh3g",
skipLinkList: "_1e0c1txw _2lx21bp4 _1x8w11lx _2mzuglyw _1pfhze3t _bozgze3t",
label: "_11c8i4vh _1rjcu2gc _syazazsu"
};
var triggerStyles = {
root: "_2rko12b0 _1dqoglyw _kqsw1n9t _1e02utpp _152tutpp _1pby1mrw _tzy4idpf _bfhk1bhr _4ojojh3g _c8j0kb7n",
isOpen: "_tzy4idpf _t9ecz6y5"
};
var 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.
*/
function SkipLinksPopup(_ref) {
var title = _ref.label,
triggerLabel = _ref.triggerLabel,
testId = _ref.testId,
links = _ref.links;
var titleId = (0, _react.useId)();
var _useState = (0, _react.useState)(false),
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
isOpen = _useState2[0],
setIsOpen = _useState2[1];
var triggerRef = (0, _react.useRef)(null);
var openPopup = (0, _react.useCallback)(function () {
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.
*/
var closePopup = (0, _react.useCallback)(function () {
(0, _reactDom.flushSync)(function () {
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.
*/
var onClose = (0, _react.useCallback)(function () {
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 || _triggerRef$current.focus();
}
}, [closePopup]);
return /*#__PURE__*/React.createElement(_popup.Popup, {
isOpen: isOpen,
onClose: onClose,
placement: "bottom-start",
shouldRenderToParent: true,
shouldReturnFocus: false,
role: "dialog",
titleId: titleId,
testId: testId ? "".concat(testId, "--skip-links-container") : undefined,
offset: popupOffset,
content: function content() {
return /*#__PURE__*/React.createElement("div", {
className: (0, _runtime.ax)([contentStyles.root])
}, /*#__PURE__*/React.createElement("div", {
"data-testid": testId ? "".concat(testId, "--skip-links-container--label") : undefined,
id: titleId,
className: (0, _runtime.ax)([contentStyles.label])
}, title), /*#__PURE__*/React.createElement("ol", {
className: (0, _runtime.ax)([contentStyles.skipLinkList])
}, links.map(function (_ref2) {
var id = _ref2.id,
label = _ref2.label,
_navigate = _ref2.navigate;
return /*#__PURE__*/React.createElement(_skipLink.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: function navigate() {
closePopup();
if (_navigate) {
_navigate();
return;
}
// Intentionally not using `document.querySelector` because many valid IDs are not valid selectors.
var target = document.getElementById(id);
if (target) {
(0, _focusElement.focusElement)(target);
}
}
}, label);
})));
},
trigger: function trigger(triggerProps) {
return /*#__PURE__*/React.createElement("div", {
className: (0, _runtime.ax)([triggerStyles.root, isOpen && triggerStyles.isOpen])
}, /*#__PURE__*/React.createElement(_button.default, (0, _extends2.default)({}, triggerProps, {
ref: (0, _mergeRefs.default)([triggerProps.ref, triggerRef]),
appearance: "primary",
isSelected: isOpen,
testId: testId ? "".concat(testId, "--skip-links-trigger") : undefined,
onClick: openPopup
}), triggerLabel));
}
});
}