UNPKG

@massds/mayflower-react

Version:

React versions of Mayflower design system UI components

326 lines (324 loc) 12.9 kB
"use strict"; exports.__esModule = true; exports.HeaderNavItem = exports.HeaderMainNavContext = exports.HeaderMainNav = void 0; var _react = _interopRequireDefault(require("react")); var _classnames = _interopRequireDefault(require("classnames")); var _propTypes = _interopRequireDefault(require("prop-types")); var _hooks = require("../HeaderNav/hooks.js"); var _useWindowWidth = _interopRequireDefault(require("../hooks/use-window-width.js")); var _getFallbackComponent = _interopRequireDefault(require("../utilities/getFallbackComponent.js")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } var HeaderMainNavContext = exports.HeaderMainNavContext = /*#__PURE__*/_react["default"].createContext(); var HeaderMainNav = exports.HeaderMainNav = function HeaderMainNav(_ref) { var NavItem = _ref.NavItem, _ref$items = _ref.items, items = _ref$items === void 0 ? [] : _ref$items; var RenderedNavItem = (0, _getFallbackComponent["default"])(NavItem, HeaderNavItem); var mainNavRef = _react["default"].useRef(); // All items passed will become part of HeaderMainNav's context. var state = (0, _hooks.useHeaderMainNav)(items); return /*#__PURE__*/_react["default"].createElement(HeaderMainNavContext.Provider, { value: state }, /*#__PURE__*/_react["default"].createElement("div", { className: "ma__header__main-nav" }, /*#__PURE__*/_react["default"].createElement("div", { className: "ma__main-nav" }, /*#__PURE__*/_react["default"].createElement("ul", { ref: mainNavRef, role: "menubar", className: "ma__main-nav__items js-main-nav" }, items.map(function (item, itemIndex) { return ( /*#__PURE__*/ // eslint-disable-next-line react/no-array-index-key _react["default"].createElement(RenderedNavItem, _extends({ key: "main-nav-navitem--" + itemIndex }, item, { index: itemIndex, mainNav: mainNavRef })) ); }))))); }; HeaderMainNav.propTypes = process.env.NODE_ENV !== "production" ? { /** An uninstantiated component which handles displaying individual menu items within the menu. */ NavItem: _propTypes["default"].elementType, /** An array of items used to create the menu. */ items: _propTypes["default"].arrayOf(_propTypes["default"].shape({ href: _propTypes["default"].string, text: _propTypes["default"].string, // Active main nav item eccentuated with an styled underline active: _propTypes["default"].bool, subNav: _propTypes["default"].arrayOf(_propTypes["default"].shape({ href: _propTypes["default"].string, text: _propTypes["default"].string })) })) } : {}; var HeaderNavItem = exports.HeaderNavItem = /*#__PURE__*/_react["default"].memo(function (_ref2) { var href = _ref2.href, text = _ref2.text, active = _ref2.active, _ref2$subNav = _ref2.subNav, subNav = _ref2$subNav === void 0 ? [] : _ref2$subNav, index = _ref2.index, mainNav = _ref2.mainNav, id = _ref2.id; var mainContext = _react["default"].useContext(HeaderMainNavContext); var windowWidth = (0, _useWindowWidth["default"])(); var itemRef = _react["default"].useRef(); var buttonRef = _react["default"].useRef(); var contentRef = _react["default"].useRef(); var breakpoint = 840; var items = mainContext.items, hide = mainContext.hide, show = mainContext.show, setIsOpen = mainContext.setIsOpen, setButtonExpanded = mainContext.setButtonExpanded; var state = items[index]; var buttonExpanded = state.buttonExpanded, isItemOpen = state.isOpen; var hasSubNav = subNav && subNav.length > 0; var classes = (0, _classnames["default"])('ma__main-nav__item js-main-nav-toggle', { 'has-subnav': hasSubNav, 'is-active': active }); var contentClasses = (0, _classnames["default"])('ma__main-nav__subitems js-main-nav-content', { 'is-open': isItemOpen, 'is-closed': !isItemOpen }); // This is the same logic as twig for when covid background displays. var isCovid = text.toLowerCase().includes('covid'); var topNavLinkclasses = (0, _classnames["default"])('ma__main-nav__top-link', { ' cv-alternate-style': isCovid }); var onMouseEnter = _react["default"].useCallback(function () { show({ index: index }); }, [show, index]); var onMouseLeave = _react["default"].useCallback(function () { hide(); }, [hide]); var onButtonLinkClick = _react["default"].useCallback(function (e) { if (windowWidth) { // mobile if (windowWidth <= breakpoint) { e.preventDefault(); // add open class to this item setIsOpen({ index: index, status: true }); show({ index: index }); setButtonExpanded({ index: index, status: true }); } else { if (isItemOpen) { hide({ index: index }); } setButtonExpanded({ index: index, status: false }); if (!isItemOpen) { show({ index: index }); setButtonExpanded({ index: index, status: true }); } } } }, [show, windowWidth, isItemOpen, setIsOpen, setButtonExpanded, index]); var onKeyDown = _react["default"].useCallback(function (e) { var item = itemRef.current; var $parent = mainNav.current; if (item && windowWidth && $parent) { // Grab all the DOM info we need... var hasFocus = 'has-focus'; var $link = item; var $topLevelLinks = $parent.querySelectorAll('.ma__main-nav__top-link'); var $focusedElement = document.activeElement; var menuFlipped = windowWidth < breakpoint; var $otherLinks = Array.from($parent.childNodes).filter(function (child) { return item !== child; }); // relevant if open.. var $topLevelItem = $focusedElement.closest('.ma__main-nav__item'); var $topLevelLink = $topLevelItem.querySelector('.ma__main-nav__top-link'); var $dropdownLinks = $link.querySelectorAll('.ma__main-nav__subitem .ma__main-nav__link'); var dropdownLinksLength = $dropdownLinks && $dropdownLinks.length; var focusIndexInDropdown = Array.from($dropdownLinks).findIndex(function (link) { return link === $focusedElement; }); // Easy access to the key that was pressed. var key = e.key, code = e.code; var action = { tab: key === 'Tab', // tab esc: key === 'Esc' || key === 'Escape', // esc left: key === 'Left' || key === 'ArrowLeft', // left arrow right: key === 'Right' || key === 'ArrowRight', // right arrow up: key === 'Up' || key === 'ArrowUp', // up arrow down: key === 'Down' || key === 'ArrowDown', // down arrow space: key === ' ' || code === 'Space', // space enter: key === 'Enter' // enter }; // Default behavior is prevented for all actions except 'tab'. if (action.esc || action.left || action.right || action.up || action.down) { e.preventDefault(); } if (action.enter || action.space) { $link.classList.add(hasFocus); $otherLinks.forEach(function (link) { return link.classList.remove(hasFocus); }); } if (action.tab && dropdownLinksLength === focusIndexInDropdown + 1) { if (isItemOpen) { hide(); } $topLevelLink.setAttribute('aria-expanded', 'false'); $link.classList.remove(hasFocus); return; } // Navigate into or within a submenu using the up/down arrow keys. if ((action.up || action.down) && dropdownLinksLength > 0) { // Open submenu if it's not open already. if (!isItemOpen && !$link.classList.contains(hasFocus)) { show({ index: index }); if (action.up) { focusIndexInDropdown = dropdownLinksLength - 1; } else { focusIndexInDropdown = 0; } $dropdownLinks[focusIndexInDropdown].focus(); } else { // Adjust index of active menu item based on performed action. focusIndexInDropdown += action.up ? -1 : 1; // Wrap around if at the end of the submenu. focusIndexInDropdown = (focusIndexInDropdown % dropdownLinksLength + dropdownLinksLength) % dropdownLinksLength; $dropdownLinks[focusIndexInDropdown].focus(); } } // Close menu and return focus to menubar if (action.esc || menuFlipped && action.left) { if (isItemOpen) { hide(); } $link.classList.remove(hasFocus); $topLevelLink.focus(); } // Navigate between submenus. This is needed for left/right actions in // normal layout, or up/down actions in flipped layout (when nav is closed). if ((action.left || action.right) && !menuFlipped || (action.up || action.down) && menuFlipped && !isItemOpen) { var idx = Array.from($topLevelLinks).findIndex(function (link) { return link === $topLevelLink; }); var prev = action.left || action.up; var linkCount = $topLevelLinks.length; // hide content // If menubar focus // - Change menubar item // // If dropdown focus // - Open previous pull down menu and select first item if (isItemOpen) { hide(); } $link.classList.remove(hasFocus); // Get previous item if left arrow, next item if right arrow. idx += prev ? -1 : 1; // Wrap around if at the end of the set of menus. idx = (idx % linkCount + linkCount) % linkCount; $topLevelLinks[idx].focus(); } } }, [index, itemRef, windowWidth, mainNav, isItemOpen]); // Adds keyboard support. (0, _hooks.useHeaderNavKeydown)(itemRef.current, onKeyDown); // Adds mouse events. (0, _hooks.useHeaderNavMouseEvents)(itemRef.current, onMouseEnter, onMouseLeave); // Adds button events. (0, _hooks.useHeaderNavButtonEffects)(buttonRef.current, onButtonLinkClick); return /*#__PURE__*/_react["default"].createElement("li", { ref: itemRef, role: "menuitem", className: classes, tabIndex: "-1" }, hasSubNav ? /*#__PURE__*/_react["default"].createElement(_react["default"].Fragment, null, /*#__PURE__*/_react["default"].createElement("button", { ref: buttonRef, type: "button", id: "button" + index, className: "ma__main-nav__top-link", "aria-haspopup": "true", tabIndex: "0", "aria-expanded": buttonExpanded }, /*#__PURE__*/_react["default"].createElement("span", { className: "visually-hidden show-label" }, "Show the sub topics of "), text), /*#__PURE__*/_react["default"].createElement("div", { ref: contentRef, className: contentClasses }, /*#__PURE__*/_react["default"].createElement("ul", { id: id || "menu" + index, role: "menu", "aria-labelledby": "button" + index, className: "ma__main-nav__container" }, subNav.map(function (item, itemIndex) { return ( /*#__PURE__*/ // eslint-disable-next-line react/no-array-index-key _react["default"].createElement("li", { key: "main-nav-subitem--" + index + "-" + itemIndex, role: "none", className: "ma__main-nav__subitem" }, /*#__PURE__*/_react["default"].createElement("a", { onClick: onButtonLinkClick, href: item.href, role: "menuitem", className: "ma__main-nav__link" }, item.text)) ); })))) : /*#__PURE__*/_react["default"].createElement("a", { href: href, className: topNavLinkclasses, tabIndex: "0" }, text)); }); HeaderNavItem.propTypes = process.env.NODE_ENV !== "production" ? { id: _propTypes["default"].string, hide: _propTypes["default"].func, show: _propTypes["default"].func, href: _propTypes["default"].string, text: _propTypes["default"].string, active: _propTypes["default"].bool, mainNav: _propTypes["default"].shape({ /* eslint-disable-next-line react/forbid-prop-types */ current: _propTypes["default"].object // Element doesn't exist for SSR, so we check for it. }), subNav: _propTypes["default"].arrayOf(_propTypes["default"].shape({ href: _propTypes["default"].string, text: _propTypes["default"].string })), index: _propTypes["default"].oneOfType([_propTypes["default"].number, _propTypes["default"].string]) } : {};