Skip to content

Commit

Permalink
feat: add ProgressBar component
Browse files Browse the repository at this point in the history
  • Loading branch information
JC Estibariz authored and jcestibariz committed Nov 29, 2023
1 parent fb9294e commit 1fc66f6
Show file tree
Hide file tree
Showing 6 changed files with 506 additions and 0 deletions.
40 changes: 40 additions & 0 deletions packages/pelagos/__tests__/components/ProgressBar-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {shallow} from 'enzyme';

import ProgressBar from '../../src/components/ProgressBar';
import useRandomId from '../../src/hooks/useRandomId';

jest.unmock('../../src/components/ProgressBar');

useRandomId.mockReturnValue('random-id');

describe('ProgressBar', () => {
describe('rendering', () => {
it('renders expected elements', () => {
const wrapper = shallow(<ProgressBar label="Test label" value={20} />);
expect(wrapper.getElement()).toMatchSnapshot();
});

it('renders expected elements when optional properties are set', () => {
const wrapper = shallow(
<ProgressBar id="test" className="TestClass" label="Test label" helperText="Test helper" value={20} />
);
expect(wrapper.getElement()).toMatchSnapshot();
expect(useRandomId.mock.calls).toEqual([['test']]);
});

it('renders expected elements when value is not set', () => {
const wrapper = shallow(<ProgressBar label="Test label" />);
expect(wrapper.getElement()).toMatchSnapshot();
});

it('renders expected elements when status is finished', () => {
const wrapper = shallow(<ProgressBar label="Test label" value={20} status="finished" />);
expect(wrapper.getElement()).toMatchSnapshot();
});

it('renders expected elements when status is error', () => {
const wrapper = shallow(<ProgressBar label="Test label" value={20} status="error" />);
expect(wrapper.getElement()).toMatchSnapshot();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ProgressBar rendering renders expected elements 1`] = `
<div
className="ProgressBar ProgressBar--default ProgressBar--big ProgressBar--active"
>
<div
className="ProgressBar__label"
id="random-id-label"
>
<span
className="ProgressBar__labelText"
>
Test label
</span>
</div>
<Layer
aria-invalid={false}
aria-labelledby="random-id-label"
aria-valuemax={100}
aria-valuemin="0"
aria-valuenow={20}
className="ProgressBar__track"
id="random-id"
role="progressbar"
>
<div
className="ProgressBar__bar"
style={
{
"transform": "scaleX(0.2)",
}
}
/>
</Layer>
</div>
`;

exports[`ProgressBar rendering renders expected elements when optional properties are set 1`] = `
<div
className="ProgressBar ProgressBar--default ProgressBar--big ProgressBar--active TestClass"
>
<div
className="ProgressBar__label"
id="random-id-label"
>
<span
className="ProgressBar__labelText"
>
Test label
</span>
</div>
<Layer
aria-describedby="random-id-helper"
aria-invalid={false}
aria-labelledby="random-id-label"
aria-valuemax={100}
aria-valuemin="0"
aria-valuenow={20}
className="ProgressBar__track"
id="random-id"
role="progressbar"
>
<div
className="ProgressBar__bar"
style={
{
"transform": "scaleX(0.2)",
}
}
/>
</Layer>
<div
className="ProgressBar__helper"
id="random-id-helper"
>
Test helper
</div>
</div>
`;

exports[`ProgressBar rendering renders expected elements when status is error 1`] = `
<div
className="ProgressBar ProgressBar--default ProgressBar--big ProgressBar--error"
>
<div
className="ProgressBar__label"
id="random-id-label"
>
<span
className="ProgressBar__labelText"
>
Test label
</span>
<SvgIcon
aria-hidden={true}
className="ProgressBar__icon"
icon={
{
"icon": [],
"iconName": "rhombus-exclamation",
}
}
/>
</div>
<Layer
aria-invalid={true}
aria-labelledby="random-id-label"
aria-valuemax={100}
aria-valuemin="0"
aria-valuenow={0}
className="ProgressBar__track"
id="random-id"
role="progressbar"
>
<div
className="ProgressBar__bar"
style={null}
/>
</Layer>
</div>
`;

exports[`ProgressBar rendering renders expected elements when status is finished 1`] = `
<div
className="ProgressBar ProgressBar--default ProgressBar--big ProgressBar--finished"
>
<div
className="ProgressBar__label"
id="random-id-label"
>
<span
className="ProgressBar__labelText"
>
Test label
</span>
<SvgIcon
aria-hidden={true}
className="ProgressBar__icon"
icon={
{
"icon": [],
"iconName": "circle-check",
"prefix": "fas",
}
}
/>
</div>
<Layer
aria-invalid={false}
aria-labelledby="random-id-label"
aria-valuemax={100}
aria-valuemin="0"
aria-valuenow={100}
className="ProgressBar__track"
id="random-id"
role="progressbar"
>
<div
className="ProgressBar__bar"
style={null}
/>
</Layer>
</div>
`;

exports[`ProgressBar rendering renders expected elements when value is not set 1`] = `
<div
className="ProgressBar ProgressBar--default ProgressBar--big ProgressBar--indeterminate"
>
<div
className="ProgressBar__label"
id="random-id-label"
>
<span
className="ProgressBar__labelText"
>
Test label
</span>
</div>
<Layer
aria-invalid={false}
aria-labelledby="random-id-label"
aria-valuemax={100}
aria-valuemin="0"
className="ProgressBar__track"
id="random-id"
role="progressbar"
>
<div
className="ProgressBar__bar"
style={null}
/>
</Layer>
</div>
`;
80 changes: 80 additions & 0 deletions packages/pelagos/src/components/ProgressBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import PropTypes from 'prop-types';
import {faCircleCheck} from '@fortawesome/free-solid-svg-icons';

import useRandomId from '../hooks/useRandomId';
import rhombusExclamation from '../icons/rhombusExclamation';

import SvgIcon from './SvgIcon';
import Layer from './Layer';

import './ProgressBar.less';

const icons = {finished: faCircleCheck, error: rhombusExclamation};

/** Displays the progress status for tasks that take a long time. */
const ProgressBar = ({id, className, label, helperText, type, size, status, max, value}) => {
id = useRandomId(id);
const labelId = `${id}-label`;
const helperId = `${id}-helper`;
if (status === 'active' && (value === null || value === undefined)) {
status = 'indeterminate';
}
return (
<div
className={`ProgressBar ProgressBar--${type} ProgressBar--${size} ProgressBar--${status}${
className ? ` ${className}` : ''
}`}>
<div id={labelId} className="ProgressBar__label">
<span className="ProgressBar__labelText">{label}</span>
{status in icons && <SvgIcon className="ProgressBar__icon" icon={icons[status]} aria-hidden />}
</div>
<Layer
id={id}
className="ProgressBar__track"
role="progressbar"
aria-invalid={status === 'error'}
aria-labelledby={labelId}
aria-describedby={helperText && helperId}
aria-valuemin="0"
aria-valuemax={max}
aria-valuenow={status === 'finished' ? max : status === 'error' ? 0 : value}>
<div className="ProgressBar__bar" style={status === 'active' ? {transform: `scaleX(${value / max})`} : null} />
</Layer>
{helperText && (
<div id={helperId} className="ProgressBar__helper">
{helperText}
</div>
)}
</div>
);
};

ProgressBar.propTypes = {
/** The component id. */
id: PropTypes.string,
/** The component class name(s). */
className: PropTypes.string,
/** The label text. */
label: PropTypes.string,
/** The textual representation of the current progress. */
helperText: PropTypes.string,
/** The alignment type. */
type: PropTypes.oneOf(['default', 'inline', 'indented']),
/** The size of the progress bar. */
size: PropTypes.oneOf(['small', 'big']),
/** The progress bar status. */
status: PropTypes.oneOf(['active', 'finished', 'error']),
/** The maximum value. */
max: PropTypes.number,
/** The current value. */
value: PropTypes.number,
};

ProgressBar.defaultProps = {
type: 'default',
size: 'big',
status: 'active',
max: 100,
};

export default ProgressBar;
Loading

0 comments on commit 1fc66f6

Please sign in to comment.