@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
JavaScript
'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;