UNPKG

@navinc/base-react-components

Version:
138 lines (123 loc) 4.18 kB
import React, { useState } from 'react' import styled, { css, keyframes } from 'styled-components' import Copy from './copy.js' import Icon from './icon.js' import isRebrand from './is-rebrand.js' const setIconStateFill = (iconState, theme) => { if (isRebrand(theme)) { return ['active', 'complete'].includes(iconState) ? theme.navPrimary : theme.navNeutral400 } return ['active', 'complete'].includes(iconState) ? theme.bubbleBlue500 : theme.neutral300 } 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 }) => (isRebrand(theme) ? theme.navNeutral100 : theme.neutral100)}; stroke: ${({ theme }) => (isRebrand(theme) ? theme.navNeutral300 : 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 }) => (isRebrand(theme) ? theme.navPrimary400 : theme.bubbleBlue500)}; animation: ${dash} 1s forwards; fill: none; ` const StyledIcon = styled(Icon)` fill: ${({ iconState, theme }) => setIconStateFill(iconState, theme)}; ` const FlowIcon = ({ icon, currentState }) => { const [lineLength, setLineLength] = useState(0) const strokeWidth = 3 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} /> {['active', 'complete'].includes(currentState) && ( <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 }) => (isRebrand(theme) ? theme.navNeutral300 : 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> ) }