UNPKG

@atlaskit/navigation-system

Version:

The latest navigation system for Atlassian apps.

117 lines (114 loc) • 4.01 kB
/* skip-links-container.tsx generated by @compiled/babel-plugin v3.0.2 */ import "./skip-links-container.compiled.css"; import * as React from 'react'; import { ax, ix } from "@compiled/react/runtime"; import { useMemo } from 'react'; import { fg } from '@atlaskit/platform-feature-flags/fg'; import { Text } from '@atlaskit/primitives/compiled/text'; import { SkipLink } from './skip-link'; import { SkipLinksPopup } from './skip-links-popup'; const styles = { root: "_zulp1b66 _2rko12b0 _1rjcutpp _18zrutpp _1e0c1txw _2lx21bp4 _kqsw1n9t _1e02v47k _152tv47k _bfhk1bhr _16qs130s _1pby1mrw _tzy4idpf _lcxvglyw _1digjh3g _1mygkb7n _18eu1wug", skipLinkList: "_zulp1b66 _1e0c1txw _2lx21bp4 _1x8w11lx _2mzuglyw _1pfhze3t _bozgze3t" }; /** * Keydown handler to hide the skip links when escape is pressed. * * TODO: consider removing this, it doesn't make a lot of sense. * The skip links look like a popup but in reality they aren't. * Even though we hide them when the user presses Escape, when * they press TAB it will reopen the list and move to the next one... * * One potential option is to use a tree walker to focus on the * next focusable element in the DOM (outside the skip links * container). */ const closeOnEscape = event => { if (event.key !== 'Escape') { return; } const activeElement = document.activeElement; if (activeElement instanceof HTMLElement) { activeElement.blur(); } }; const assignIndex = (num, arr) => { if (!arr.includes(num)) { return num; } return assignIndex(num + 1, arr); }; /** * Sorts an array of skip links by list indexes. * * Skip links with custom list indexes are positioned first, followed by regular skip links, * which are automatically assigned available index positions to avoid conflicts. */ function sortSkipLinks(arr) { const customLinks = arr.filter(link => Number.isInteger(link.listIndex)); if (customLinks.length === 0) { return arr; } const usedIndexes = customLinks.map(a => a.listIndex); const regularLinksWithIndex = arr.filter(link => link.listIndex === undefined).map((link, index) => { const listIndex = assignIndex(index, usedIndexes); usedIndexes.push(listIndex); return { ...link, listIndex }; }); return [...customLinks, ...regularLinksWithIndex].sort((a, b) => a.listIndex - b.listIndex); } const isOnlyWhitespaceRegex = /^\s*$/; /** * A container element for the skip links. * * The container element is always rendered in the DOM, but is only visible when * focus is within the container. * * The label is used as the heading of the skip links container. If the provided label is a string * comprised only of only whitespace (e.g. '' or ' '), the skip link heading element will be removed. * * When `platform_dst_nav4_skip_link_a11y_1` is enabled, rendering is delegated to `SkipLinksPopup`. */ export function SkipLinksContainer({ label, triggerLabel, testId, links }) { const sortedLinks = useMemo(() => { return sortSkipLinks(links); }, [links]); if (sortedLinks.length === 0) { return null; } const isEmptyLabel = isOnlyWhitespaceRegex.test(label); if (fg('platform_dst_nav4_skip_link_a11y_1')) { return /*#__PURE__*/React.createElement(SkipLinksPopup, { label: label, triggerLabel: triggerLabel, testId: testId, links: sortedLinks }); } return /*#__PURE__*/React.createElement("div", { onKeyDown: closeOnEscape, "data-testid": testId ? `${testId}--skip-links-container` : undefined, className: ax([styles.root]) }, !isEmptyLabel && /*#__PURE__*/React.createElement(Text, { weight: "bold", testId: testId ? `${testId}--skip-links-container--label` : undefined }, label), /*#__PURE__*/React.createElement("ol", { className: ax([styles.skipLinkList]) }, sortedLinks.map(({ id, label, onBeforeNavigate }) => /*#__PURE__*/React.createElement(SkipLink, { key: id, id: id, onBeforeNavigate: onBeforeNavigate }, label)))); }