@spaced-out/ui-design-system
Version:
Sense UI components library
112 lines (100 loc) • 3.29 kB
Flow
// @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>
);
},
);