@axeptio/design-system
Version:
Design System for Axeptio
273 lines (242 loc) • 6.92 kB
JSX
import React, { useState } from 'react';
import styled from 'styled-components';
import { RoughNotation } from 'react-rough-notation';
import { Flipper } from 'react-flip-toolkit';
import PropTypes from 'prop-types';
import Icon from '../../Icon';
import DropdownContainer from './dropDownContainer';
import generateNavBar from './dropDown';
const menuColors = [
'#fac6a1',
'#a9ddd7',
'#afc2e7',
'#f7daa6',
'#f9a6a2',
'#f8cfb6',
'#fac6a1',
'#a9ddd7',
'#afc2e7',
'#f7daa6',
'#f9a6a2',
'#f8cfb6'
];
const NavItemIconDropDown = styled.span`
width: 24px;
display: flex;
justify-content: center;
align-items: center;
transition: transform 0.3s ease-in-out;
${props =>
props.show &&
`
transform: rotate(180deg);
`}
`;
const NavItemButtonContent = styled.span`
display: flex;
flex-direction: row;
flex-wrap: no-wrap;
height: 100%;
-webkit-box-pack: center;
justify-content: center;
-webkit-box-align: center;
align-items: center;
`;
const NavItemButtonContainer = styled.span`
display: flex;
height: 100%;
-webkit-box-pack: center;
justify-content: center;
-webkit-box-align: center;
align-items: center;
`;
const NavItemButton = styled.div`
font-weight: 400;
display: flex;
align-items: center;
background: transparent;
border: 0;
font-weight: 400;
font-family: inherit;
font-size: 17px;
padding: 0 20px;
height: 100%;
color: ${props => props.theme.colors.secondary};
justify-content: center;
white-space: nowrap;
transition: opacity 250ms;
cursor: pointer;
position: relative;
z-index: 2;
&:focus {
outline: none;
color: ${props => props.theme.colors.grey.v500};
}
&:visited {
color: ${props => props.theme.colors.secondary};
}
&:active {
outline: none;
color: ${props => props.theme.colors.secondary};
}
span {
display: flex;
height: 100%;
justify-content: center;
align-items: center;
}
&:link {
text-decoration: none;
}
&:visited {
text-decoration: none;
}
&:hover {
text-decoration: none;
}
&:active {
text-decoration: none;
}
`;
const NavItem = styled.li`
position: relative;
margin: 0px;
font-family: inherit;
`;
const Main = styled.div`
display: none;
font-family: ${props => props.theme.fonts.text};
@media (min-width: ${props => props.theme.breakpoints.xlarge}px) {
display: flex;
justify-content: center;
}
`;
const Root = styled.ul`
display: flex;
-webkit-box-pack: center;
justify-content: center;
list-style: none;
margin: 0px;
padding: 0px;
font-family: inherit;
`;
const DropdownSlot = styled.div`
position: absolute;
left: 50%;
transform: translateX(-50%);
perspective: 1500px;
z-index: 10;
`;
const Menu = ({ menu, test, id }) => {
let navbar = generateNavBar(menu, test);
let [hover, setHover] = useState(null);
const [activeIndices, setActiveIndices] = React.useState([]);
const [animatingOut, setAnimatingOut] = React.useState(false);
const [animatingOutTimeout, setAnimatingOutTimeout] = React.useState(null);
const resetDropdownState = i => {
setActiveIndices(typeof i === 'number' ? [i] : []);
setAnimatingOut(false);
setAnimatingOutTimeout(null);
};
const onMouseEnter = i => {
setHover(i);
if (animatingOutTimeout) {
clearTimeout(animatingOutTimeout);
resetDropdownState(i);
return;
}
if (activeIndices[activeIndices.length - 1] === i) {
return;
}
// If there is no dropdown for the item
if (!navbar[i]?.dropdown) {
onMouseLeave(i);
return;
}
setActiveIndices(prevState => prevState.concat(i));
setAnimatingOut(false);
};
const onMouseLeave = i => {
setHover(i ? i : null);
setAnimatingOut(true);
const timer = setTimeout(resetDropdownState, duration);
setAnimatingOutTimeout(timer);
};
let CurrentDropdown;
let PrevDropdown;
let direction;
const currentIndex = activeIndices[activeIndices.length - 1];
const prevIndex = activeIndices.length > 1 && activeIndices[activeIndices.length - 2];
// current
if (typeof currentIndex === 'number') {
CurrentDropdown = navbar[currentIndex].dropdown;
}
// previous
if (typeof prevIndex === 'number') {
PrevDropdown = navbar[prevIndex].dropdown;
direction = currentIndex > prevIndex ? 'right' : 'left';
}
const showDropdown = !!CurrentDropdown;
const duration = 300;
return (
<Main id={id ? id : 'main_menu'}>
<Flipper flipKey={currentIndex} spring={duration === 300 ? 'noWobble' : { stiffness: 10, damping: 10 }}>
<Root onMouseLeave={onMouseLeave}>
{menu.map((elem, index) => {
const roughNotationProps = {
show: index === hover && (elem?.noRoughAnimation ? !elem?.noRoughAnimation : true),
type: 'underline',
padding: elem?.submenu ? -25 : 5,
iterations: 1,
strokeWidth: 3,
color: menuColors[index],
animationDuration: 300
};
return (
<NavItem index={index} key={index}>
<NavItemButton
id={elem.name}
onMouseEnter={() => {
onMouseEnter(index);
}}
href={elem?.submenu?.length > 0 ? '' : elem?.link}
as={elem?.submenu?.length > 0 ? 'button' : 'a'}
>
<NavItemButtonContainer>
<RoughNotation {...roughNotationProps}>
<NavItemButtonContent>
{elem.name}
{elem?.submenu ? (
<NavItemIconDropDown
show={index === hover && (elem?.noAnimatedIcon ? !elem?.noAnimatedIcon : true)}
name={test ? elem.icon : 'ArrowDown'}
>
<Icon iconSize={24} iconWidth={15} name={test ? elem.icon : 'ArrowDown'} />
</NavItemIconDropDown>
) : null}
</NavItemButtonContent>
</RoughNotation>
</NavItemButtonContainer>
</NavItemButton>
{currentIndex === index && showDropdown ? (
<DropdownSlot>
<DropdownContainer direction={direction} animatingOut={animatingOut} duration={duration}>
<CurrentDropdown />
{PrevDropdown && <PrevDropdown />}
</DropdownContainer>
</DropdownSlot>
) : null}
</NavItem>
);
})}
</Root>
</Flipper>
</Main>
);
};
Menu.propTypes = {
menu: PropTypes.array.isRequired,
test: PropTypes.bool,
id: PropTypes.string
};
export default Menu;