Commit 32294451 authored by Robert Knight's avatar Robert Knight

Add CircularProgress component

The implementation approach follows MUI's component of the same name.
parent 469988ed
export type CircularProgressProps = {
/** Width and height of the indicator in pixels. */
size: number;
/** Progress value between 0 and 100. */
value: number;
};
/**
* A compact circular progress indicator.
*/
export default function CircularProgress({
size,
value,
}: CircularProgressProps) {
const strokeWidth = 2;
// Internal diameter of circle.
const diameter = size - 2 * strokeWidth;
const circumference = Math.PI * diameter;
return (
<span
// This assumes a dark background. We'll need a variant for light
// backgrounds at some point.
className="text-grey-3"
role="progressbar"
aria-valuenow={value}
style={{
width: `${size}px`,
height: `${size}px`,
// Orient circle so that stroke is drawn starting from the top. By
// default it starts from 3 o'clock and goes clockwise.
transform: 'rotate(-90deg)',
}}
>
<svg viewBox={`0 0 ${size} ${size}`}>
<circle
cx={size / 2}
cy={size / 2}
r={diameter / 2}
fill="none"
stroke="currentColor"
// eslint-disable-next-line
stroke-width={strokeWidth}
style={{
// Stroke circle with a single dash, shortened by an offset that
// depends on the value.
strokeDasharray: circumference,
strokeDashoffset: `${
circumference - circumference * (value / 100)
}px`,
transitionDuration: '300ms',
transitionProperty: 'stroke-dashoffset',
}}
/>
</svg>
</span>
);
}
import { mount } from 'enzyme';
import CircularProgress from '../CircularProgress';
describe('CircularProgress', () => {
function renderProgress(props = {}) {
return mount(<CircularProgress size={40} value={0} {...props} />);
}
it('should display at specified size', () => {
const wrapper = renderProgress({ size: 40 });
assert.equal(wrapper.getDOMNode().style.width, '40px');
assert.equal(wrapper.getDOMNode().style.height, '40px');
});
it('should display expected completion', () => {
const value = 75;
const wrapper = renderProgress({ size: 40, value });
const circle = wrapper.find('circle').getDOMNode();
const dashLength = parseFloat(circle.style.strokeDasharray);
const dashOffset = parseFloat(circle.style.strokeDashoffset);
assert.approximately(dashOffset / dashLength, 1 - value / 100, 1e-4);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment