UNPKG

@dnanpm/styleguide

Version:

DNA Styleguide repository provides the set of components and theme object used in various DNA projects.

365 lines (350 loc) 17.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tslib = require('tslib'); var icons = require('@dnanpm/icons'); var React = require('react'); var useDebounce = require('../../hooks/useDebounce.js'); var useOutsideClick = require('../../hooks/useOutsideClick.js'); var useResizeObserver = require('../../hooks/useResizeObserver.js'); var useWindowSize = require('../../hooks/useWindowSize.js'); var styledComponents = require('styled-components'); var theme = require('../../themes/theme.js'); var common = require('../../utils/common.js'); var styledUtils = require('../../utils/styledUtils.js'); var ButtonIcon = require('../ButtonIcon/ButtonIcon.js'); var Icon = require('../Icon/Icon.js'); var PriorityNavigationItem = require('../PriorityNavigationItem/PriorityNavigationItem.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefaultCompat(React); const Container = styledComponents.styled.div ` width: 100%; ${styledUtils.media.md ` border-bottom: 1px solid ${theme.default.color.line.L03}; `} `; const Category = styledComponents.styled.span ` margin: 0; color: ${theme.default.color.text.black}; font-size: ${theme.default.fontSize.h4}; font-weight: ${theme.default.fontWeight.bold}; line-height: ${theme.default.lineHeight.h4}; display: block; ${styledUtils.media.md ` padding: 1.25rem 0.25rem 0; background: linear-gradient( ${theme.default.color.default.black}05 0%, ${theme.default.color.default.black}00 50%, ${theme.default.color.default.black}00 100%), ${theme.default.color.background.white.default}; `} `; const ListsContainer = styledComponents.styled.div ` display: flex; flex-direction: column; position: relative; background-color: ${theme.default.color.background.white.default}; ${styledUtils.media.md ` justify-content: space-between; flex-direction: row; height: 100%; align-items: center; margin: 0 auto; `} `; const MobileDropdown = styledComponents.styled.button ` display: flex; align-items: center; justify-content: space-between; cursor: pointer; background: none; border: none; padding: ${({ $isCategoryLabel }) => ($isCategoryLabel ? '0.5rem' : '0.75rem')} 1.25rem; color: ${theme.default.color.text.pink}; font-size: ${theme.default.fontSize.default}; line-height: ${theme.default.lineHeight.default}; font-weight: ${theme.default.fontWeight.bold}; border-bottom: 3px solid ${theme.default.color.default.pink}; & svg { pointer-events: none; } `; const MobileDropdownContent = styledComponents.styled.div ` display: flex; flex-direction: column; align-items: baseline; text-align: left; `; const CoreULStyles = styledComponents.styled.ul ` list-style: none; margin: 0; padding: 0; overflow: hidden; `; const NavigationList = styledComponents.styled(CoreULStyles) ` display: flex; flex-direction: column; justify-content: flex-start; z-index: 1; width: 100%; background-color: ${theme.default.color.background.white.default}; position: absolute; top: ${styledUtils.getMultipliedSize(theme.default.base.baseHeight, 5.6)}; visibility: ${({ $isMobileNavigationOpen }) => $isMobileNavigationOpen ? 'visible' : 'hidden'}; clip-path: inset( 0% 0% ${({ $isMobileNavigationOpen }) => ($isMobileNavigationOpen ? '0%' : '100%')} 0% ); transition: all 0.2s ease-in-out; ${styledUtils.media.md ` position: static; flex-direction: row; visibility: visible; clip-path: none; width: auto; height: 100%; padding: 0 2px; & > li:first-child { margin-left: 0; } `} `; const DropdownList = styledComponents.styled(CoreULStyles) ` position: absolute; top: ${styledUtils.getMultipliedSize(theme.default.base.baseHeight, 6)}; right: 0; z-index: 1; padding-bottom: 0.5rem; background-color: ${theme.default.color.background.white.default}; border: 1px solid ${theme.default.color.line.L04}; border-radius: 0 0 ${theme.default.radius.default} ${theme.default.radius.default}; visibility: ${({ $isDropdownListOpen }) => ($isDropdownListOpen ? 'visible' : 'hidden')}; clip-path: inset( 0% 0% ${({ $isDropdownListOpen }) => ($isDropdownListOpen ? '0%' : '100%')} 0% ); transition: all 0.2s ease-in-out; ${styledUtils.media.md ` & > li { margin: auto 1.25rem; } `} ${common.default({ elevation: 'low' })} `; const VisuallyHidden = styledComponents.styled.span ` position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; `; const reducer = (state, action) => { var _a, _b; switch (action.type) { case 'resetNavigationState': { const navigationItems = React.Children.toArray((_a = action === null || action === void 0 ? void 0 : action.payload) === null || _a === void 0 ? void 0 : _a.navigationItems); return Object.assign(Object.assign({}, state), { navigationItems: navigationItems.slice(0, navigationItems.length - state.lastItemWidth.length), dropdownItems: navigationItems.slice(navigationItems.length - state.lastItemWidth.length) }); } case 'moveItemToDropdown': { const lastChild = state.navigationItems[state.navigationItems.length - 1]; return Object.assign(Object.assign(Object.assign({}, state), { navigationItems: state.navigationItems.slice(0, -1), dropdownItems: [lastChild, ...state.dropdownItems] }), (((_b = action === null || action === void 0 ? void 0 : action.payload) === null || _b === void 0 ? void 0 : _b.lastItem) && { lastItemWidth: [ ...state.lastItemWidth, action.payload.lastItem.offsetWidth + 40, ], })); } case 'moveItemToNavigation': { const [firstItemFromList, ...dropdownItems] = state.dropdownItems; const [, ...lastItemWidth] = state.lastItemWidth; return { navigationItems: [...state.navigationItems, firstItemFromList], dropdownItems, lastItemWidth, }; } default: return state; } }; const getReactNodeText = (reactNode) => { if (reactNode === null) { return ''; } switch (typeof reactNode) { case 'string': case 'number': return reactNode.toString(); case 'boolean': return ''; case 'object': { if (reactNode instanceof Array) { return reactNode.map(getReactNodeText).join(''); } if ('props' in reactNode) { // Unsafe member access .children on an `any` value (@typescript-eslint/no-unsafe-member-access) // eslint-disable-next-line return getReactNodeText(reactNode.props.children); } return ''; } default: return ''; } }; /** * @visibleName Priority Navigation */ const PriorityNavigation = (_a) => { var { dropdownButtonLabel = 'Lisää', 'data-testid': dataTestId, mobileDropdownAriaLabel, currentPageAriaLabel, openMoreSubpagesAriaLabel } = _a, props = tslib.__rest(_a, ["dropdownButtonLabel", 'data-testid', "mobileDropdownAriaLabel", "currentPageAriaLabel", "openMoreSubpagesAriaLabel"]); const listsContainerRef = React.useRef(null); const navigationListRef = React.useRef(null); const dropdownButtonRef = React.useRef(null); const categoryId = React.useId(); const { isMobile } = useWindowSize.default(theme.default.breakpoints.md); const { width: wrapperContainerWidth } = useResizeObserver.default(listsContainerRef); const [isMobileNavigationOpen, setIsMobileNavigationOpen] = React.useState(false); const toggleMobileNavigation = () => setIsMobileNavigationOpen(!isMobileNavigationOpen); const [isDropdownListOpen, setIsDropdownListOpen] = React.useState(false); const toggleDropdown = () => { if (props.onDropdownListToggle) { props.onDropdownListToggle(!isDropdownListOpen); } setIsDropdownListOpen(!isDropdownListOpen); }; const navigationItems = React.useRef(new Map()).current; const initialState = { navigationItems: React.Children.toArray(props.children), dropdownItems: [], lastItemWidth: [], }; const [state, dispatch] = React.useReducer(reducer, initialState); const checkHorizontalOverflow = useDebounce.default(() => { var _a, _b; if (navigationListRef.current && listsContainerRef.current) { const navigationListWidth = navigationListRef.current.scrollWidth; const dropdownButtonWidth = ((_b = (_a = dropdownButtonRef.current) === null || _a === void 0 ? void 0 : _a.offsetWidth) !== null && _b !== void 0 ? _b : 80) + 20; if (state.navigationItems.length > 0 && navigationListWidth + dropdownButtonWidth > wrapperContainerWidth) { dispatch({ type: 'moveItemToDropdown', payload: { lastItem: navigationItems.get(state.navigationItems.length - 1), }, }); } else if (state.dropdownItems.length > 0 && wrapperContainerWidth > navigationListWidth + state.lastItemWidth[state.lastItemWidth.length - 1] + dropdownButtonWidth + 20) { dispatch({ type: 'moveItemToNavigation', }); } } }, 100); const activeItem = [...state.navigationItems, ...state.dropdownItems].find(child => React.isValidElement(child) && child.type === PriorityNavigationItem.default && child.props.isActive); const selectedItem = isMobile ? getReactNodeText(activeItem === null || activeItem === void 0 ? void 0 : activeItem.props.children) || '' : props.categoryLabel; useOutsideClick.default(listsContainerRef, () => { if (isMobileNavigationOpen) { setIsMobileNavigationOpen(false); } if (isDropdownListOpen) { if (props.onDropdownListToggle) { props.onDropdownListToggle(false); } setIsDropdownListOpen(false); } }); React.useEffect(() => { if (!isMobile) { requestAnimationFrame(() => { dispatch({ type: 'resetNavigationState', payload: { navigationItems: props.children, }, }); setTimeout(() => { checkHorizontalOverflow(); }, 0); }); } setIsMobileNavigationOpen(false); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isMobile, props.children]); React.useLayoutEffect(() => { if (!isMobile) { requestAnimationFrame(() => { checkHorizontalOverflow(); }); } }, [ isMobile, wrapperContainerWidth, state.navigationItems.length, state.dropdownItems.length, checkHorizontalOverflow, ]); const handleNavigationListKeyDown = React.useCallback((e) => { var _a, _b; if (isMobile && isMobileNavigationOpen && e.key === 'Tab') { const focusableElements = (_a = navigationListRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('a, button, input, [tabindex]:not([tabindex="-1"])'); const lastElement = focusableElements === null || focusableElements === void 0 ? void 0 : focusableElements[focusableElements.length - 1]; const goingForward = !e.shiftKey; if (goingForward && document.activeElement === lastElement) { e.preventDefault(); setIsMobileNavigationOpen(false); (_b = dropdownButtonRef.current) === null || _b === void 0 ? void 0 : _b.focus(); } } }, [isMobile, isMobileNavigationOpen]); const handleItemClick = (e) => { setIsMobileNavigationOpen(false); if (props.onClick) { props.onClick(e); } }; return (React__default.default.createElement(Container, { id: props.id, className: props.className, "data-testid": dataTestId }, !isMobile && props.categoryLabel && (React__default.default.createElement(Category, { id: categoryId }, props.categoryLabel)), React__default.default.createElement("nav", { "aria-labelledby": categoryId }, React__default.default.createElement(ListsContainer, { ref: listsContainerRef }, isMobile && selectedItem && (React__default.default.createElement(MobileDropdown, { onClick: toggleMobileNavigation, "aria-label": mobileDropdownAriaLabel !== null && mobileDropdownAriaLabel !== void 0 ? mobileDropdownAriaLabel : `${props.categoryLabel ? `${props.categoryLabel}, ` : ''}${selectedItem}.`, "aria-expanded": isMobileNavigationOpen, "$isCategoryLabel": Boolean(props.categoryLabel) }, React__default.default.createElement(MobileDropdownContent, null, props.categoryLabel && React__default.default.createElement(Category, null, props.categoryLabel), selectedItem), React__default.default.createElement(Icon.default, { icon: isMobileNavigationOpen ? icons.OvalChevronUp : icons.OvalChevronDown, size: "2.5rem" }))), React__default.default.createElement(NavigationList, { ref: navigationListRef, "$isMobileNavigationOpen": isMobileNavigationOpen, onKeyDown: handleNavigationListKeyDown }, React.Children.map([...state.navigationItems, ...(isMobile ? state.dropdownItems : [])], (navigationItem, index) => { if (React.isValidElement(navigationItem) && navigationItem.type === PriorityNavigationItem.default) { return (React__default.default.createElement(PriorityNavigationItem.default, { id: navigationItem.props.id, key: navigationItem.key, onClick: handleItemClick, onKeyDown: navigationItem.props.onKeyDown || props.onKeyDown, isActive: navigationItem.props.isActive, className: navigationItem.props.className, "data-testid": navigationItem.props['data-testid'], ref: instance => { if (instance) { navigationItems.set(index, instance); } } }, navigationItem.props.isActive ? React.cloneElement(navigationItem.props.children, { 'aria-current': 'page', }) : navigationItem.props.children, navigationItem.props.isActive && isMobile && (React__default.default.createElement(React__default.default.Fragment, null, React__default.default.createElement(Icon.default, { icon: icons.Check, "aria-hidden": true, color: theme.default.color.default.pink }), React__default.default.createElement(VisuallyHidden, null, currentPageAriaLabel))))); } return null; })), !isMobile && Boolean(state.dropdownItems.length) && (React__default.default.createElement(React__default.default.Fragment, null, React__default.default.createElement("div", null, React__default.default.createElement(ButtonIcon.default, { ref: dropdownButtonRef, ariaLabel: openMoreSubpagesAriaLabel, ariaExpanded: isDropdownListOpen, onClick: toggleDropdown, icon: isDropdownListOpen ? icons.ChevronUp : icons.ChevronDown, isReversed: true, "data-testid": "dropdown-button" }, dropdownButtonLabel)), isDropdownListOpen && (React__default.default.createElement(DropdownList, { "$isDropdownListOpen": isDropdownListOpen }, state.dropdownItems.map(dropdownItem => React.isValidElement(dropdownItem) && dropdownItem.type === PriorityNavigationItem.default && (React__default.default.createElement(PriorityNavigationItem.default, { id: dropdownItem.props.id, key: dropdownItem.key, onClick: dropdownItem.props.onClick || props.onClick, onKeyDown: dropdownItem.props.onKeyDown || props.onKeyDown, isActive: dropdownItem.props.isActive, className: dropdownItem.props.className, "data-testid": dropdownItem.props['data-testid'] }, dropdownItem.props.children))))))))))); }; exports.default = PriorityNavigation;