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 24 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/tool/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const componentPaths = glob( 'packages/components/src/*/**/README.md', {
'packages/components/src/theme/README.md',
'packages/components/src/view/README.md',
'packages/components/src/dropdown-menu-v2/README.md',
'packages/components/src/progress-bar/README.md',
],
} );
const packagePaths = glob( 'packages/*/package.json' )
Expand Down
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
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
2 changes: 2 additions & 0 deletions packages/components/src/private-apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/pri
*/
import { default as CustomSelectControl } from './custom-select-control';
import { positionToPlacement as __experimentalPopoverLegacyPositionToPlacement } from './popover/utils';
import { default as ProgressBar } from './progress-bar';
import { createPrivateSlotFill } from './slot-fill';
import {
DropdownMenu as DropdownMenuV2,
Expand Down Expand Up @@ -45,4 +46,5 @@ lock( privateApis, {
DropdownMenuSeparatorV2,
DropdownSubMenuV2,
DropdownSubMenuTriggerV2,
ProgressBar,
} );
32 changes: 32 additions & 0 deletions packages/components/src/progress-bar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ProgressBar

<div class="callout callout-alert">
This feature is still experimental. “Experimental” means this is an early implementation subject to drastic and breaking changes.
</div>

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.

### 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

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

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

- Required: No

##### `id`: `string`

The HTML `id` of the `progress` element. This is necessary to be able to accessibly associate the label with that element.
tyxla marked this conversation as resolved.
Show resolved Hide resolved

- Required: No
62 changes: 62 additions & 0 deletions packages/components/src/progress-bar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import type { ForwardedRef } from 'react';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { forwardRef } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';

/**
* Internal dependencies
*/
import * as ProgressBarStyled from './styles';
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;

function UnforwardedProgressBar(
props: WordPressComponentProps< ProgressBarProps, 'div', false >,
tyxla marked this conversation as resolved.
Show resolved Hide resolved
ref: ForwardedRef< HTMLProgressElement >
) {
const { className, id: idProp, value } = props;
const id = useInstanceId( ProgressBar, 'progress-bar', idProp );
tyxla marked this conversation as resolved.
Show resolved Hide resolved
const isIndeterminate = ! Number.isFinite( value );
const wrapperClasses = classnames( className, {
/**
* @todo Use the `:indeterminate` pseudo-class once supported.
*
* @see https://caniuse.com/mdn-css_selectors_indeterminate_progress
*/
'is-indeterminate': isIndeterminate,
tyxla marked this conversation as resolved.
Show resolved Hide resolved
} );

return (
<ProgressBarStyled.Track className={ wrapperClasses }>
<ProgressBarStyled.Indicator
style={ {
width: `${
isIndeterminate ? INDETERMINATE_TRACK_WIDTH : value
}%`,
} }
/>
<ProgressBarStyled.ProgressElement
max={ 100 }
value={ value }
aria-label={ __( 'Loading …' ) }
id={ id }
ref={ ref }
/>
</ProgressBarStyled.Track>
);
}

export const ProgressBar = forwardRef( UnforwardedProgressBar );

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

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

const meta: ComponentMeta< typeof ProgressBar > = {
component: ProgressBar,
title: 'Components (Experimental)/ProgressBar',
argTypes: {
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 = {};
61 changes: 61 additions & 0 deletions packages/components/src/progress-bar/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* External dependencies
*/
import styled from '@emotion/styled';
import { keyframes } from '@emotion/react';

/**
* Internal dependencies
*/
import { COLORS, CONFIG } from '../utils';

const animateProgressBar = keyframes( {
'0%': {
left: '-50%',
},
'100%': {
left: '100%',
},
} );

export const Track = styled.div`
position: relative;
overflow: hidden;
width: 100%;
max-width: 160px;
height: ${ CONFIG.borderWidthFocus };
background-color: var(
--wp-components-color-gray-100,
${ COLORS.gray[ 100 ] }
);
border-radius: ${ CONFIG.radiusBlockUi };
`;

export const Indicator = styled.div`
display: inline-block;
position: absolute;
top: 0;
height: 100%;
border-radius: ${ CONFIG.radiusBlockUi };
background-color: ${ COLORS.ui.theme };

.is-indeterminate & {
animation-duration: 1.5s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-name: ${ animateProgressBar };

@media ( prefers-reduced-motion ) {
animation-duration: 0s;
}
}
`;

export const ProgressElement = styled.progress`
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 100%;
height: 100%;
`;
158 changes: 158 additions & 0 deletions packages/components/src/progress-bar/test/__snapshots__/index.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ProgressBar should have an \`is-indeterminate\` className if \`value\` is not provided 1`] = `
@keyframes animation-0 {
0% {
left: -50%;
}

100% {
left: 100%;
}
}

.emotion-0 {
position: relative;
overflow: hidden;
width: 100%;
max-width: 160px;
height: 1.5px;
background-color: var(
--wp-components-color-gray-100,
#f0f0f0
);
border-radius: 2px;
}

.emotion-2 {
display: inline-block;
position: absolute;
top: 0;
height: 100%;
border-radius: 2px;
background-color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
}

.is-indeterminate .emotion-2 {
-webkit-animation-duration: 1.5s;
animation-duration: 1.5s;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-name: animation-0;
animation-name: animation-0;
}

@media ( prefers-reduced-motion ) {
.is-indeterminate .emotion-2 {
-webkit-animation-duration: 0s;
animation-duration: 0s;
}
}

.emotion-4 {
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 100%;
height: 100%;
}

<div>
<div
class="is-indeterminate emotion-0 emotion-1"
>
<div
class="emotion-2 emotion-3"
style="width: 50%;"
/>
<progress
aria-label="Loading …"
class="emotion-4 emotion-5"
id="progress-bar-4"
max="100"
/>
</div>
</div>
`;

exports[`ProgressBar should not have an \`is-indeterminate\` className if \`value\` is provided 1`] = `
@keyframes animation-0 {
0% {
left: -50%;
}

100% {
left: 100%;
}
}

.emotion-0 {
position: relative;
overflow: hidden;
width: 100%;
max-width: 160px;
height: 1.5px;
background-color: var(
--wp-components-color-gray-100,
#f0f0f0
);
border-radius: 2px;
}

.emotion-2 {
display: inline-block;
position: absolute;
top: 0;
height: 100%;
border-radius: 2px;
background-color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
}

.is-indeterminate .emotion-2 {
-webkit-animation-duration: 1.5s;
animation-duration: 1.5s;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-name: animation-0;
animation-name: animation-0;
}

@media ( prefers-reduced-motion ) {
.is-indeterminate .emotion-2 {
-webkit-animation-duration: 0s;
animation-duration: 0s;
}
}

.emotion-4 {
position: absolute;
top: 0;
left: 0;
opacity: 0;
width: 100%;
height: 100%;
}

<div>
<div
class=" emotion-0 emotion-1"
>
<div
class="emotion-2 emotion-3"
style="width: 55%;"
/>
<progress
aria-label="Loading …"
class="emotion-4 emotion-5"
id="progress-bar-5"
max="100"
value="55"
/>
</div>
</div>
`;
Loading
Loading