@navinc/base-react-components
Version:
Nav's Pattern Library
131 lines (116 loc) • 3.71 kB
JavaScript
import React, { useState } from 'react'
import styled, { css, keyframes } from 'styled-components'
import Copy from './copy'
import Icon from './icon.js'
const pop = keyframes`
0% { transform: scale(0.9); }
70% { transform: scale(1.1); }
100% { transform: scale(1); }
`
const popAnimation = () => css`
animation: ${pop} 0.3s ${({ theme }) => theme.materialTransitionTiming};
transform-origin: center;
`
const dash = keyframes`
to { stroke-dashoffset: 0; }
`
const IconBase = styled.circle`
fill: ${({ theme }) => theme.neutral100};
stroke: ${({ theme }) => theme.neutral200};
stroke-width: ${({ strokeWidth }) => strokeWidth};
${({ iconState }) => iconState === 'complete' && popAnimation}
`
const ActiveStatus = styled.circle`
stroke-linecap: round;
stroke-width: ${({ strokeWidth }) => strokeWidth};
transform: rotate(-90deg);
transform-origin: center;
stroke-dasharray: ${({ lineLength }) => lineLength};
stroke-dashoffset: ${({ lineLength }) => lineLength};
stroke: ${({ theme }) => theme.bubbleBlue500};
animation: ${dash} 1s forwards;
fill: none;
`
const StyledIcon = styled(Icon)`
fill: ${({ iconState, theme }) => (iconState === 'active' ? theme.bubbleBlue500 : theme.neutral300)};
`
const FlowIcon = ({ icon, currentState }) => {
const [lineLength, setLineLength] = useState(0)
const strokeWidth = 4
const circleContainer = 80
const circleDiameter = circleContainer * 0.75
const circleRadius = circleDiameter / 2 - strokeWidth / 2
const centerPoint = circleContainer / 2
const iconCenter = centerPoint - 12
const pathLength = (node) => node !== null && setLineLength(node?.getTotalLength?.() ?? 0)
return (
<svg viewBox={`0 0 ${circleContainer} ${circleContainer}`}>
<IconBase strokeWidth={strokeWidth} cx={centerPoint} cy={centerPoint} r={circleRadius} iconState={currentState} />
{currentState === 'active' && (
<ActiveStatus
strokeWidth={strokeWidth}
cx={centerPoint}
cy={centerPoint}
r={circleRadius}
ref={pathLength}
lineLength={lineLength}
/>
)}
<StyledIcon
name={currentState !== 'complete' ? icon : 'actions/check-circle'}
x={iconCenter}
y={iconCenter}
iconState={currentState}
/>
</svg>
)
}
const IconStepContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
max-width: 80px;
z-index: 1;
`
const iconWidth = 80
const StyledStatusBar = styled.div`
width: 100%;
position: relative;
display: grid;
grid-template-columns: repeat(auto-fit, ${iconWidth}px);
justify-items: stretch;
justify-content: space-between;
&::before {
content: '';
display: ${(props) => props.iconCount <= 1 && 'none'};
position: absolute;
width: calc(100% - 40px);
left: 20px;
height: 2px;
background: ${({ theme }) => theme.neutral200};
top: ${iconWidth / 2}px;
}
`
export default ({ currentPath, flow = [], endLocation }) => {
const step = flow.findIndex((path) => path.route === currentPath)
return (
<StyledStatusBar iconCount={flow.length}>
{flow.map((item, i) => (
<IconStepContainer key={i}>
<FlowIcon
icon={item.icon}
currentState={item.route === currentPath ? 'active' : i >= step ? 'incomplete' : 'complete'}
/>
<Copy size="sm">{item.name}</Copy>
</IconStepContainer>
))}
{endLocation && (
<IconStepContainer>
<FlowIcon icon={endLocation.icon} />
<Copy size="sm">{endLocation.name}</Copy>
</IconStepContainer>
)}
</StyledStatusBar>
)
}