UNPKG

@spaced-out/ui-design-system

Version:
112 lines (100 loc) 3.29 kB
// @flow strict import * as React from 'react'; import * as COLORS from '../../styles/variables/_color'; import classify from '../../utils/classify'; import {BodyLargeBold} from '../Text'; import css from './ProgressDonut.module.css'; const STROKE_WIDTH = 8; const MIN_PERCENTAGE = 0; const DIVISOR_TWO = 2; const MAX_PERCENTAGE = 100; const SUCCESS_THRESHOLD = 80; const LARGE_DONUT_SIZE = 72; const INFO_THRESHOLD = 50; const WARNING_THRESHOLD = 30; type ClassNames = $ReadOnly<{ wrapper?: string, }>; export type ProgressDonutProps = { classNames?: ClassNames, color?: $Keys<typeof COLORS>, percentage: number, }; const getProgressDonutColor = (percentage: number): string => { if (percentage >= SUCCESS_THRESHOLD) { return COLORS.colorSuccess; } else if (percentage >= INFO_THRESHOLD && percentage < SUCCESS_THRESHOLD) { return COLORS.colorInformation; } else if (percentage >= WARNING_THRESHOLD && percentage < INFO_THRESHOLD) { return COLORS.colorWarning; } else { return COLORS.colorDanger; } }; export const ProgressDonut: React$AbstractComponent< ProgressDonutProps, HTMLDivElement, > = React.forwardRef<ProgressDonutProps, HTMLDivElement>( ({classNames, percentage = 0, color = ''}: ProgressDonutProps, ref) => { const donutPercentage = React.useMemo( () => Math.min(Math.max(percentage, MIN_PERCENTAGE), MAX_PERCENTAGE), [percentage], ); const donutColor = React.useMemo( () => getProgressDonutColor(donutPercentage), [donutPercentage], ); const radius = React.useMemo( () => (LARGE_DONUT_SIZE - STROKE_WIDTH) / DIVISOR_TWO, [LARGE_DONUT_SIZE], ); const circumference = React.useMemo( () => DIVISOR_TWO * Math.PI * radius, [radius], ); const offset = React.useMemo( () => circumference - (donutPercentage / MAX_PERCENTAGE) * circumference, [circumference, donutPercentage], ); return ( <div ref={ref} data-testid="ProgressDonut" className={classify(css.wrapper, classNames?.wrapper)} > <div className={css.donutChart}> <svg width={LARGE_DONUT_SIZE} height={LARGE_DONUT_SIZE} viewBox={`0 0 ${LARGE_DONUT_SIZE} ${LARGE_DONUT_SIZE}`} className={css.donutChartSvg} > <circle className={css.donutChartBackground} cx={LARGE_DONUT_SIZE / DIVISOR_TWO} cy={LARGE_DONUT_SIZE / DIVISOR_TWO} r={radius} strokeWidth={STROKE_WIDTH} /> <circle className={css.donutChartForeground} cx={LARGE_DONUT_SIZE / DIVISOR_TWO} cy={LARGE_DONUT_SIZE / DIVISOR_TWO} r={radius} strokeWidth={STROKE_WIDTH} strokeDasharray={circumference} strokeDashoffset={ donutPercentage === MIN_PERCENTAGE ? circumference : offset } stroke={COLORS[color] || donutColor} strokeLinecap="round" /> </svg> <BodyLargeBold className={css.donutChartPercentage}> {`${donutPercentage}%`} </BodyLargeBold> </div> </div> ); }, );