UNPKG

@dnanpm/styleguide

Version:

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

332 lines (325 loc) 17.1 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var tslib = require('tslib'); var icons = require('@dnanpm/icons'); var ramda = require('ramda'); var React = require('react'); var useScrollPosition = require('../../hooks/useScrollPosition.js'); var useWindowSize = require('../../hooks/useWindowSize.js'); var styledComponents = require('styled-components'); var theme = require('../../themes/theme.js'); var navigation = require('../../themes/themeComponents/navigation.js'); var styledUtils = require('../../utils/styledUtils.js'); var DnaLogo = require('../DnaLogo/DnaLogo.js'); var Notification = require('../Notification/Notification.js'); var BusinessMenu = require('./ChildComponents/BusinessMenu.js'); var DesktopMenu = require('./ChildComponents/DesktopMenu.js'); var LanguageSelector = require('./ChildComponents/LanguageSelector.js'); var LoginTooltip = require('./ChildComponents/LoginTooltip.js'); var MainNavTooltipMenu = require('./ChildComponents/MainNavTooltipMenu.js'); var MinicartTooltip = require('./ChildComponents/MinicartTooltip.js'); var MobileMenu = require('./ChildComponents/MobileMenu.js'); var PageSearch = require('./ChildComponents/PageSearch.js'); var NavContext = require('./context/NavContext.js'); var globalNavStyles = require('./globalNavStyles.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefaultCompat(React); const GlobalStyle = styledComponents.createGlobalStyle ` body.overflow-hidden { overflow: hidden; } `; // detect common touch screen devices const isIosDevice = () => { const validate = /iPad|iPhone|iPod|iPad Simulator|iPhone Simulator/i; if (typeof navigator !== 'undefined' && typeof window !== 'undefined') { return (validate.test(navigator.userAgent) && !window.MSStream); } return false; }; const iosDevice = isIosDevice(); const fadeIn = styledComponents.keyframes ` 0% { opacity: 0; transform: translateY(-5px); } 5% { opacity: 0; transform: translateY(-5px); } 100% { opacity: 1; transform: translateY(0px); } `; const preventBodyScroll = () => { const { scrollY } = window; document.body.style.position = 'fixed'; document.body.style.top = `-${scrollY}px`; }; const resumeBodyScroll = () => { const scrollY = document.body.style.top; document.body.style.position = ''; document.body.style.top = ''; // if the call to close overlay is done multiple times, scroll only the first time if (scrollY) { setTimeout(() => { window.scrollTo(0, parseInt(scrollY, 10) * -1); }); } }; const PageOverlay = styledComponents.styled.div ` position: fixed; animation: ${fadeIn} 0.2s ease-in-out; top: 0; left: 0; width: 100%; height: 100%; background: ${theme.default.color.background.plum.default + theme.default.color.transparency.T10}; z-index: ${({ $navZIndex }) => $navZIndex - 1}; `; const scrollThreshold = 82; const checkThreshold = (prevPos, currPos) => { if (prevPos >= currPos) { return prevPos - currPos > scrollThreshold; } return currPos - prevPos > scrollThreshold; }; const GlobalNavigationContainer = styledComponents.styled.header ` z-index: ${({ $navZIndex }) => $navZIndex}; position: sticky; top: 0; left: 0; right: 0; width: 100%; background-color: ${theme.default.color.background.white.default}; color: ${theme.default.color.text.black}; max-width: 2560px; margin: 0 auto; > nav { background-color: ${theme.default.color.background.plum.E02}; } `; const NavigationWrapper = styledComponents.styled.div ` display: flex; width: 100%; max-width: ${styledUtils.getMultipliedSize(theme.default.base.baseWidth, 256)}; margin: 0 auto; height: ${navigation.navMaxHeight}; position: relative; flex-wrap: nowrap; gap: 0; background-color: ${theme.default.color.background.white.default}; border-bottom: 1px solid ${theme.default.color.line.L04}; @media (min-width: ${p => p.$collapseSize + 1}px) { flex-wrap: nowrap; gap: 0 ${styledUtils.getMultipliedSize(theme.default.base.baseWidth, 1.5)}; } `; let previousValue = false; const isStickySupported = typeof CSS !== 'undefined' && typeof CSS.supports !== 'undefined' && (CSS.supports('position', 'sticky') || CSS.supports('position', '-webkit-sticky')); const initiateScrollToHeader = (componentRef) => { if ((componentRef === null || componentRef === void 0 ? void 0 : componentRef.current) && isStickySupported && typeof window !== 'undefined') { window.scrollTo({ top: componentRef.current.offsetTop, left: 0 }); } }; /** @visibleName MainHeaderNavigation */ const MainHeaderNavigation = (_a) => { var _b, _c, _d; var { backToPreviousCategoryLabel = '', businessMenuAriaLabel = 'Sivustot', categoryCollectionText = 'Muut kategoriat', closeMainMenuAriaLabel, className, collapseSize = 767, currentUrl = '', dnaLogoLinkAriaLabel, featuredItemsAriaLabel = '', isLoggedIn = false, items = {}, language = 'fi', languageSelector = false, languageSelectorText = 'Vaihda kieltä', login = true, loginComponent, loginText = 'Kirjaudu', mainMenuAriaLabel = 'Päänavigaatio', minicart = true, minicartAmount = 0, minicartAmountLabel, minicartComponent = false, minicartText = 'Ostoskori', nextJSLinkComponent = false, nextJSLinkLegacyBehavior = true, notificationText = '', openMainMenuAriaLabel, search = true, searchComponent = false, searchText = 'Haku', showLoginTooltip = false, showMinicart = false, zIndex = 1030 } = _a, props = tslib.__rest(_a, ["backToPreviousCategoryLabel", "businessMenuAriaLabel", "categoryCollectionText", "closeMainMenuAriaLabel", "className", "collapseSize", "currentUrl", "dnaLogoLinkAriaLabel", "featuredItemsAriaLabel", "isLoggedIn", "items", "language", "languageSelector", "languageSelectorText", "login", "loginComponent", "loginText", "mainMenuAriaLabel", "minicart", "minicartAmount", "minicartAmountLabel", "minicartComponent", "minicartText", "nextJSLinkComponent", "nextJSLinkLegacyBehavior", "notificationText", "openMainMenuAriaLabel", "search", "searchComponent", "searchText", "showLoginTooltip", "showMinicart", "zIndex"]); const { isMobile } = useWindowSize.default(collapseSize); const navigationEl = React.useRef(null); const key = ramda.prop('id'); // Index & memoize menu levels const level1 = React.useMemo(() => { var _a, _b; return (_b = (_a = items.mainNavigation) === null || _a === void 0 ? void 0 : _a.pages) !== null && _b !== void 0 ? _b : []; }, [items.mainNavigation]); const level1Items = React.useMemo(() => ramda.indexBy(key, level1), [level1, key]); const level2 = React.useMemo(() => level1.map(item => ramda.indexBy(key, item.pages)), [level1, key]); const level2Items = Object.assign({}, ...level2); // All states to for handling menus const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false); const [menuLevel, setMenuLevel] = React.useState({ level1: null, animate1: true, level2: null, animate2: true, level1Mobile: null, level2Mobile: null, scrollPosition: 0, hideOnScroll: false, }); const [lang] = React.useState(language); // As scroll position cannot be always retained, close mobile menu when orientation changes const initRotation = () => { setIsMobileMenuOpen(false); }; React.useEffect(() => { if (iosDevice && isMobileMenuOpen) { window.addEventListener('orientationchange', initRotation, false); preventBodyScroll(); } if (iosDevice && !isMobileMenuOpen) { window.removeEventListener('orientationchange', initRotation, false); resumeBodyScroll(); } return () => { window.removeEventListener('orientationchange', initRotation, false); resumeBodyScroll(); }; }, [isMobileMenuOpen]); const [tooltipItems, setTooltipItems] = React.useState(React.useMemo(() => ({ minicart: false, login: false, search: false, }), [])); const tooltipMenuActive = tooltipItems.minicart || tooltipItems.login || tooltipItems.search; const freezeOverflow = isMobileMenuOpen || (tooltipMenuActive && isMobile); const isPageOverlay = isMobileMenuOpen || tooltipMenuActive || menuLevel.level1 || menuLevel.level2; React.useEffect(() => { const bodyClasses = ['overflow-shown', 'overflow-hidden']; document.body.classList.remove(...bodyClasses); document.body.classList.add(bodyClasses[Number(Boolean(freezeOverflow))]); }, [freezeOverflow]); const navigationContext = React.useMemo(() => { const handleNavMenuClick = (newValue, updatedLevel) => { let clickedItself = false; switch (updatedLevel) { case 'level1': clickedItself = newValue === menuLevel.level1; setMenuLevel(Object.assign(Object.assign({}, menuLevel), { level1: !clickedItself ? newValue : null, animate1: !clickedItself, level2: null, animate2: true })); break; case 'level2': clickedItself = newValue === menuLevel.level2; setMenuLevel(Object.assign(Object.assign({}, menuLevel), { level2: newValue !== menuLevel.level2 ? newValue : null, animate1: false, animate2: !clickedItself })); break; case 'level1Mobile': setMenuLevel(Object.assign(Object.assign({}, menuLevel), { level1Mobile: newValue || menuLevel.level1Mobile, scrollPosition: newValue ? 1 : 0 })); break; case 'level2Mobile': setMenuLevel(Object.assign(Object.assign({}, menuLevel), { level2Mobile: newValue || menuLevel.level2Mobile, scrollPosition: newValue ? 2 : 1 })); break; } }; const resetMenuEvents = () => { setMenuLevel({ level2: null, animate2: true, level1: null, animate1: true, level1Mobile: null, level2Mobile: null, scrollPosition: 0, hideOnScroll: false, }); if (isMobileMenuOpen) { setIsMobileMenuOpen(false); } setTooltipItems({ minicart: false, login: false, search: false, }); }; const getBackLink = (menuElements, i) => { const idValue = menuLevel[`level${i}Mobile`]; return menuElements[idValue]; }; // Resets menu states when menu content alters between mobile and desktop const menuChanged = previousValue !== isMobile; if (menuChanged) { resetMenuEvents(); previousValue = isMobile; } return { backToPreviousCategoryLabel, categoryCollectionText, collapseSize, currentUrl, getBackLink, featuredItemsAriaLabel, handleNavMenuClick, isLoggedIn, isMobileMenu: isMobile, isMobileMenuOpen, items, lang, level1Items, level2Items, loginComponent, menuLevel, minicartComponent, navZIndex: zIndex, nextJSLinkComponent, nextJSLinkLegacyBehavior, resetMenuEvents, searchComponent, setTooltipItems, showLoginTooltip, showMinicart, tooltipItems, }; }, [ backToPreviousCategoryLabel, categoryCollectionText, collapseSize, currentUrl, featuredItemsAriaLabel, isLoggedIn, isMobile, isMobileMenuOpen, items, lang, level1Items, level2Items, loginComponent, menuLevel, minicartComponent, nextJSLinkComponent, nextJSLinkLegacyBehavior, searchComponent, showLoginTooltip, showMinicart, tooltipItems, zIndex, ]); useScrollPosition.default(({ prevPos, currPos }) => { const overThreshold = checkThreshold(prevPos.y, currPos.y); const isShow = currPos.y < prevPos.y; if (isShow !== menuLevel.hideOnScroll && overThreshold && !menuLevel.level1) { setMenuLevel(Object.assign(Object.assign({}, menuLevel), { animate1: false, animate2: false, hideOnScroll: isShow })); } }, undefined, false, 200); const currentBusinessSite = (_b = items === null || items === void 0 ? void 0 : items.businessSelector) === null || _b === void 0 ? void 0 : _b.items.find(item => item.businessId === items.businessId); const languagesObject = (_c = items === null || items === void 0 ? void 0 : items.languageSelector) === null || _c === void 0 ? void 0 : _c.urls; const { resetMenuEvents } = navigationContext; const handleToggleClick = () => { resetMenuEvents(); initiateScrollToHeader(navigationEl); setIsMobileMenuOpen(!isMobileMenuOpen); }; return (React__default.default.createElement(React__default.default.Fragment, null, isPageOverlay && React__default.default.createElement(PageOverlay, { "data-testid": "navigation-overlay", "$navZIndex": zIndex }), React__default.default.createElement(GlobalNavigationContainer, { className: className, "$collapseSize": collapseSize, "data-testid": "dna-main-header", "$navZIndex": zIndex, ref: navigationEl }, React__default.default.createElement(NavContext.default.Provider, { value: navigationContext }, React__default.default.createElement(GlobalStyle, null), !items.mainNavigation && notificationText && (React__default.default.createElement(Notification.default, { type: "info" }, notificationText)), React__default.default.createElement(BusinessMenu.default, { ariaLabel: businessMenuAriaLabel, currentBusinessId: items.businessId, items: (_d = items.businessSelector) === null || _d === void 0 ? void 0 : _d.items, lang: language }), React__default.default.createElement(NavigationWrapper, { "$collapseSize": collapseSize }, React__default.default.createElement(globalNavStyles.LogoLink, { "data-testid": "dna-logo-link", href: currentBusinessSite === null || currentBusinessSite === void 0 ? void 0 : currentBusinessSite.urls[language], "aria-label": dnaLogoLinkAriaLabel }, React__default.default.createElement(DnaLogo.default, { size: navigation.headerLogoSize })), React__default.default.createElement(globalNavStyles.DesktopMenuContainer, { "aria-label": mainMenuAriaLabel, "$collapseSize": collapseSize }, React__default.default.createElement(DesktopMenu.default, null)), React__default.default.createElement(globalNavStyles.HeaderIconContainer, { "$collapseSize": collapseSize }, languagesObject && languageSelector && (React__default.default.createElement(LanguageSelector.default, { currentLanguage: language, languagesObject: languagesObject, languageSelectorLabel: languageSelectorText })), search && React__default.default.createElement(PageSearch.default, { searchLabel: searchText }), minicart && (React__default.default.createElement(MinicartTooltip.default, { minicartAmount: minicartAmount, minicartAmountLabel: minicartAmountLabel !== null && minicartAmountLabel !== void 0 ? minicartAmountLabel : '', minicartLabel: minicartText, onCartButtonClick: props.onCartButtonClick })), login && React__default.default.createElement(LoginTooltip.default, { loginLabel: loginText, isLogged: isLoggedIn }), React__default.default.createElement(MainNavTooltipMenu.IconContainer, { "$collapseSize": collapseSize, "data-testid": "header-mobile-menu-container" }, React__default.default.createElement(globalNavStyles.MobileMenuButton, { "aria-expanded": isMobileMenuOpen, "aria-label": isMobileMenuOpen ? closeMainMenuAriaLabel : openMainMenuAriaLabel, "data-testid": "header-mobile-menu-toggle", onClick: handleToggleClick }, isMobileMenuOpen ? React__default.default.createElement(icons.Close, null) : React__default.default.createElement(icons.Menu, null)))), isMobile && React__default.default.createElement(MobileMenu.default, null)))))); }; exports.default = MainHeaderNavigation;