Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: Introduce a basic ProgressBar component #53030

Merged
merged 26 commits into from Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fec0532
Components: Introduce a basic ProgressBar
tyxla Jul 26, 2023
5452f8a
Add a CHANGELOG entry
tyxla Jul 27, 2023
cafb1d8
Expose as a private API
tyxla Jul 27, 2023
43d2dc2
Add missing className in type
tyxla Jul 27, 2023
f1d5c80
Render progressbar element invisibly
tyxla Jul 27, 2023
c6273f4
Update appearance
jameskoster Jul 27, 2023
b28b7b5
Mark the component story as experimental
tyxla Jul 28, 2023
ac38177
Add an experimental alert in the component README
tyxla Jul 28, 2023
a4d5de9
Remove usage from README
tyxla Jul 28, 2023
5065164
Remove README from manifest
tyxla Jul 28, 2023
bbfbfcc
Add border radius to indicator as well
tyxla Jul 28, 2023
166ae8d
Refactor from SCSS to Emotion
tyxla Jul 28, 2023
e00dc4f
Remove color props
tyxla Jul 28, 2023
05245ac
Update tests
tyxla Jul 28, 2023
d6d7044
Add an aria-label to the underlying progress element
tyxla Jul 28, 2023
fef81d2
Update snapshots
tyxla Jul 28, 2023
3b2b625
Intentionally ignore ProgressBar README from docs manifest
tyxla Jul 28, 2023
2bda78e
Use the components gray color variable
tyxla Jul 31, 2023
453f5d1
Add TODO for using the :indeterminate pseudo-class
tyxla Jul 31, 2023
7b63099
Remove default className
tyxla Jul 31, 2023
4c1c86e
Add a max-width
tyxla Jul 31, 2023
2e16c12
Update snapshots
tyxla Jul 31, 2023
f772032
Add support for ID and ref
tyxla Jul 31, 2023
664a4de
Fix docs
tyxla Jul 31, 2023
2df2d48
Use emotion for indeterminate and value-based styling
tyxla Aug 1, 2023
f0c3be6
Remove ID, pass rest props down to the progress element
tyxla Aug 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/manifest.json
Expand Up @@ -1115,6 +1115,12 @@
"markdown_source": "../packages/components/src/popover/README.md",
"parent": "components"
},
{
"title": "ProgressBar",
"slug": "progress-bar",
"markdown_source": "../packages/components/src/progress-bar/README.md",
"parent": "components"
},
{
"title": "QueryControls",
"slug": "query-controls",
Expand Down
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New Feature

- Add a new `ProgressBar` component. ([#53030](https://github.com/WordPress/gutenberg/pull/53030)).

### Enhancements

- `ColorPalette`, `BorderControl`: Don't hyphenate hex value in `aria-label` ([#52932](https://github.com/WordPress/gutenberg/pull/52932)).
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Expand Up @@ -128,6 +128,7 @@ export { default as PanelHeader } from './panel/header';
export { default as PanelRow } from './panel/row';
export { default as Placeholder } from './placeholder';
export { default as Popover } from './popover';
export { default as __experimentalProgressBar } from './progress-bar';
ntsekouras marked this conversation as resolved.
Show resolved Hide resolved
export { default as QueryControls } from './query-controls';
export { default as __experimentalRadio } from './radio-group/radio';
export { default as __experimentalRadioGroup } from './radio-group';
Expand Down
47 changes: 47 additions & 0 deletions packages/components/src/progress-bar/README.md
@@ -0,0 +1,47 @@
# ProgressBar

A simple horizontal progress bar component.
tyxla marked this conversation as resolved.
Show resolved Hide resolved

Supports two modes: determinate and indeterminate. A progress bar is determinate when a specific progress value has been specified (from 0 to 100), and indeterminate when a value hasn't been specified.

### Usage

```jsx
/**
* WordPress dependencies
*/
import { __experimentalProgressBar as ProgressBar } from '@wordpress/components';
tyxla marked this conversation as resolved.
Show resolved Hide resolved

export const MyProgressBar = () => {
return <ProgressBar value={ 30 } />;
};
```

### Props

The component accepts the following props:

#### `value`: `number`

The progress value, a number from 0 to 100.
If a `value` is not specified, the progress bar will be considered indeterminate.

- Required: No

#### `trackColor`: `string`

Optional color of the progress bar track.

- Required: No

#### `indicatorColor`: `string`

Optional color of the progress bar indicator.
tyxla marked this conversation as resolved.
Show resolved Hide resolved

- Required: No

##### `className`: `string`

A CSS class to apply to the underlying `div` element, serving as a progress bar track.

- Required: No
42 changes: 42 additions & 0 deletions packages/components/src/progress-bar/index.tsx
@@ -0,0 +1,42 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* Internal dependencies
*/
import type { ProgressBarProps } from './types';
import type { WordPressComponentProps } from '../ui/context';

// Width of the indicator for the indeterminate progress bar
export const INDETERMINATE_TRACK_WIDTH = 50;

export function ProgressBar(
props: WordPressComponentProps< ProgressBarProps, 'div', false >
) {
const { className, value, indicatorColor, trackColor } = props;
const isIndeterminate = ! Number.isFinite( value );
const wrapperClasses = classnames( 'components-progress-bar', className, {
tyxla marked this conversation as resolved.
Show resolved Hide resolved
'is-indeterminate': isIndeterminate,
tyxla marked this conversation as resolved.
Show resolved Hide resolved
} );
const trackStyle = {
backgroundColor: trackColor ? trackColor : undefined,
tyxla marked this conversation as resolved.
Show resolved Hide resolved
};
const indicatorStyle = {
width: `${ isIndeterminate ? INDETERMINATE_TRACK_WIDTH : value }%`,
backgroundColor: indicatorColor ? indicatorColor : undefined,
};

return (
<div className={ wrapperClasses } style={ trackStyle }>
<div
className="components-progress-bar__indicator"
style={ indicatorStyle }
/>
<progress max={ 100 } value={ value } />
tyxla marked this conversation as resolved.
Show resolved Hide resolved
</div>
);
}

export default ProgressBar;
35 changes: 35 additions & 0 deletions packages/components/src/progress-bar/stories/index.tsx
@@ -0,0 +1,35 @@
/**
* External dependencies
*/
import type { ComponentMeta, ComponentStory } from '@storybook/react';

/**
* Internal dependencies
*/
import { ProgressBar } from '..';

const meta: ComponentMeta< typeof ProgressBar > = {
component: ProgressBar,
title: 'Components/ProgressBar',
tyxla marked this conversation as resolved.
Show resolved Hide resolved
argTypes: {
indicatorColor: { control: { type: 'color' } },
trackColor: { control: { type: 'color' } },
value: { control: { type: 'number', min: 0, max: 100, step: 1 } },
},
parameters: {
controls: {
expanded: true,
},
docs: { source: { state: 'open' } },
},
};
export default meta;

const Template: ComponentStory< typeof ProgressBar > = ( { ...args } ) => {
return <ProgressBar { ...args } />;
};

export const Default: ComponentStory< typeof ProgressBar > = Template.bind(
{}
);
Default.args = {};
37 changes: 37 additions & 0 deletions packages/components/src/progress-bar/style.scss
@@ -0,0 +1,37 @@
.components-progress-bar {
tyxla marked this conversation as resolved.
Show resolved Hide resolved
position: relative;
overflow: hidden;
width: 100%;
height: 5px;
background-color: $components-color-gray-100;

progress {
display: none;
}

.components-progress-bar__indicator {
display: inline-block;
position: absolute;
top: 0;
height: 100%;
background-color: $components-color-accent;
}

&.is-indeterminate .components-progress-bar__indicator {
animation-duration: 1.5s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-name: progress-bar-indication;

@include reduce-motion("animation");
}
}

@keyframes progress-bar-indication {
from {
left: -50%;
}
to {
left: 100%;
}
}
@@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ProgressBar should be able to specify custom track and indicator colors 1`] = `
<div>
<div
class="components-progress-bar is-indeterminate"
style="background-color: rgb(217, 217, 217);"
>
<div
class="components-progress-bar__indicator"
style="width: 50%; background-color: rgb(153, 0, 0);"
/>
<progress
max="100"
/>
</div>
</div>
`;

exports[`ProgressBar should have an \`is-indeterminate\` className if \`value\` is not provided 1`] = `
<div>
<div
class="components-progress-bar is-indeterminate"
>
<div
class="components-progress-bar__indicator"
style="width: 50%;"
/>
<progress
max="100"
/>
</div>
</div>
`;

exports[`ProgressBar should not have an \`is-indeterminate\` className if \`value\` is provided 1`] = `
<div>
<div
class="components-progress-bar"
>
<div
class="components-progress-bar__indicator"
style="width: 55%;"
/>
<progress
max="100"
value="55"
/>
</div>
</div>
`;
83 changes: 83 additions & 0 deletions packages/components/src/progress-bar/test/index.tsx
@@ -0,0 +1,83 @@
/**
* External dependencies
*/
import { render, screen } from '@testing-library/react';

/**
* Internal dependencies
*/
import { ProgressBar, INDETERMINATE_TRACK_WIDTH } from '..';

describe( 'ProgressBar', () => {
it( 'should render an indeterminate semantic progress bar element', () => {
render( <ProgressBar /> );

const progressBar = screen.getByRole( 'progressbar' );

expect( progressBar ).toBeVisible();
expect( progressBar ).not.toHaveValue();
} );

it( 'should render a determinate semantic progress bar element', () => {
render( <ProgressBar value={ 55 } /> );

const progressBar = screen.getByRole( 'progressbar' );

expect( progressBar ).toBeVisible();
expect( progressBar ).toHaveValue( 55 );
} );

it( 'should use `INDETERMINATE_TRACK_WIDTH`% as track width for indeterminate progress bar', () => {
const { container } = render( <ProgressBar /> );

/**
* We're intentionally not using an accessible selector, because
* the track is an intentionally non-interactive presentation element.
*/
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const indicator = container.querySelector(
'.components-progress-bar__indicator'
);

expect( indicator ).toHaveStyle( {
width: `${ INDETERMINATE_TRACK_WIDTH }%`,
} );
} );

it( 'should use `value`% as width for determinate progress bar', () => {
const { container } = render( <ProgressBar value={ 55 } /> );

/**
* We're intentionally not using an accessible selector, because
* the track is an intentionally non-interactive presentation element.
*/
// eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
const indicator = container.querySelector(
'.components-progress-bar__indicator'
);

expect( indicator ).toHaveStyle( {
width: '55%',
} );
} );

it( 'should have an `is-indeterminate` className if `value` is not provided', () => {
const { container } = render( <ProgressBar /> );

expect( container ).toMatchSnapshot();
} );

it( 'should not have an `is-indeterminate` className if `value` is provided', () => {
const { container } = render( <ProgressBar value={ 55 } /> );

expect( container ).toMatchSnapshot();
} );
tyxla marked this conversation as resolved.
Show resolved Hide resolved

it( 'should be able to specify custom track and indicator colors', () => {
const { container } = render(
<ProgressBar indicatorColor="#900" trackColor="#d9d9d9" />
);

expect( container ).toMatchSnapshot();
} );
} );
21 changes: 21 additions & 0 deletions packages/components/src/progress-bar/types.ts
@@ -0,0 +1,21 @@
/**
* External dependencies
*/
import type { CSSProperties } from 'react';

export type ProgressBarProps = {
/**
* Value of the progress bar.
*/
value?: number;

/**
* Color of the progress bar indicator.
*/
indicatorColor?: CSSProperties[ 'color' ];

/**
* Color of the progress bar track.
*/
trackColor?: CSSProperties[ 'color' ];
tyxla marked this conversation as resolved.
Show resolved Hide resolved
};
1 change: 1 addition & 0 deletions packages/components/src/style.scss
Expand Up @@ -39,6 +39,7 @@
@import "./panel/style.scss";
@import "./placeholder/style.scss";
@import "./popover/style.scss";
@import "./progress-bar/style.scss";
@import "./radio-control/style.scss";
@import "./resizable-box/style.scss";
@import "./responsive-wrapper/style.scss";
Expand Down