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