import React from "react"; import PropTypes from "prop-types"; import { keyframes } from "@emotion/core"; import { responsivePropType } from "../hooks/useResponsiveProp"; import useResponsivePropsCSS from "../hooks/useResponsivePropsCSS"; const SIZES = ["small", "medium", "large"]; const COLORS = ["highlight.blue.t100", "white", "black"]; const DEFAULT_PROPS = { size: "small", color: "highlight.blue.t100", }; const circleRadiusMap = { small: 4, medium: 8, large: 12, }; LoadingIcon.SIZES = SIZES; LoadingIcon.COLORS = COLORS; LoadingIcon.DEFAULT_PROPS = DEFAULT_PROPS; function LoadingIcon(_props) { const props = { ...DEFAULT_PROPS, ..._props }; const { size, testId } = props; const radius = circleRadiusMap[size] ?? 4; const stepPx = `${3 * radius}px`; const frames = 12; // 3 circles * 4 steps each const percantagePerFrame = 100 / frames; const duration = 3; // sec /* circle diameter + space between circles + circle diameter (2 * radius) + radius + (2 * radius) */ const svgSize = 5 * radius; const start = { transform: `translate3d(0, 0, 0)`, }; const right = { transform: `translate3d(${stepPx}, 0, 0)`, }; const bottom = { transform: `translate3d(0, ${stepPx}, 0)`, }; const left = { transform: `translate3d(-${stepPx}, 0, 0)`, }; const up = { transform: `translate3d(0, -${stepPx}, 0)`, }; const rightAndBottom = { transform: `translate3d(${stepPx}, ${stepPx}, 0)`, }; const upAndRight = { transform: `translate3d(${stepPx}, -${stepPx}, 0)`, }; const leftAndUp = { transform: `translate3d(-${stepPx}, -${stepPx}, 0)`, }; const getKeyframes = (steps) => keyframes( steps.reduce((acc, step, index) => { acc[`${index * percantagePerFrame}%`] = step; return acc; }, {}) ); const svgCSS = useResponsivePropsCSS(props, DEFAULT_PROPS, { color: ({ color }, theme) => { return { fill: theme.getColor(color) ?? theme.colors.black, }; }, }); const circleCSS = [ getKeyframes([ start, right, right, right, rightAndBottom, rightAndBottom, rightAndBottom, bottom, bottom, bottom, start, start, ]), getKeyframes([ start, start, up, up, up, upAndRight, upAndRight, upAndRight, right, right, right, start, ]), getKeyframes([ start, start, start, left, left, left, leftAndUp, leftAndUp, leftAndUp, up, up, up, ]), ].map((animation) => ({ animation: `${animation} ${duration}s ease infinite`, })); return ( <svg css={svgCSS} width={svgSize} height={svgSize} viewBox={`0 0 ${svgSize} ${svgSize}`} focusable="false" role="img" aria-label="Loading icon" data-testid={testId} > <circle cx={radius} cy={radius} r={radius} css={circleCSS[0]} /> <circle cx={radius} cy={4 * radius} r={radius} css={circleCSS[1]} /> <circle cx={4 * radius} cy={4 * radius} r={radius} css={circleCSS[2]} /> </svg> ); } LoadingIcon.propTypes = { ...responsivePropType("color", PropTypes.oneOf(COLORS)), size: PropTypes.oneOf(SIZES), testId: PropTypes.string, }; export default LoadingIcon;