From 73153fdeef8f465d417761904d20cbf22c4337c5 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Mon, 2 May 2022 13:41:48 -0500 Subject: [PATCH 1/2] feat(progress-bar): add support for statuses in v11 --- .../__snapshots__/PublicAPI-test.js.snap | 10 ++++ .../ProgressBar/ProgressBar-test.js | 42 +++++++++++++- .../src/components/ProgressBar/ProgressBar.js | 49 ++++++++++++++-- .../ProgressBar/next/ProgressBar.stories.js | 1 + .../progress-bar/_progress-bar.scss | 58 ++++++++++++++++++- 5 files changed, 149 insertions(+), 11 deletions(-) diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index e4614de9a5b5..6a8563890e75 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -5149,6 +5149,16 @@ Map { ], "type": "oneOf", }, + "status": Object { + "args": Array [ + Array [ + "active", + "finished", + "error", + ], + ], + "type": "oneOf", + }, "type": Object { "args": Array [ Array [ diff --git a/packages/react/src/components/ProgressBar/ProgressBar-test.js b/packages/react/src/components/ProgressBar/ProgressBar-test.js index 4c9bb1ec7893..80ae729752ea 100644 --- a/packages/react/src/components/ProgressBar/ProgressBar-test.js +++ b/packages/react/src/components/ProgressBar/ProgressBar-test.js @@ -24,7 +24,9 @@ describe('ProgressBar', () => { describe('renders as expected', () => { it('progress bar and label ids match', () => { const bar = wrapper.getByRole('progressbar'); - const label = wrapper.container.querySelector('span'); + const label = wrapper.container.querySelector( + `.${prefix}--progress-bar__label` + ); expect(bar.getAttribute('aria-labelledby')).toBe(label.id); }); @@ -41,9 +43,11 @@ describe('ProgressBar', () => { ).toBe(helperText.id); }); - it('still renders accessible when hideLabel is passed', () => { + it('still renders accessible label when hideLabel is passed', () => { wrapper.rerender(); - const label = wrapper.container.querySelector('span'); + const label = wrapper.container.querySelector( + `.${prefix}--progress-bar__label` + ); expect(label.textContent).toBe(props.label); expect(label.classList.contains(`${prefix}--visually-hidden`)).toBe(true); @@ -90,6 +94,38 @@ describe('ProgressBar', () => { .classList.contains(className) ).toBe(true); }); + + it('supports finished status', () => { + wrapper.rerender(); + + expect( + wrapper.container + .querySelector(`.${prefix}--progress-bar`) + .classList.contains(`${prefix}--progress-bar--finished`) + ).toBe(true); + + expect( + wrapper.getByRole('progressbar').getAttribute('aria-valuenow') + ).toBe('100'); + }); + + it('supports error status', () => { + wrapper.rerender(); + + expect( + wrapper.container + .querySelector(`.${prefix}--progress-bar`) + .classList.contains(`${prefix}--progress-bar--error`) + ).toBe(true); + + expect( + wrapper.getByRole('progressbar').getAttribute('aria-valuenow') + ).toBe('0'); + + expect( + wrapper.getByRole('progressbar').getAttribute('aria-invalid') + ).toBe('true'); + }); }); describe('behaves as expected', () => { diff --git a/packages/react/src/components/ProgressBar/ProgressBar.js b/packages/react/src/components/ProgressBar/ProgressBar.js index e8470a11d99f..c669c5e982e5 100644 --- a/packages/react/src/components/ProgressBar/ProgressBar.js +++ b/packages/react/src/components/ProgressBar/ProgressBar.js @@ -8,6 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; +import { CheckmarkFilled, ErrorFilled } from '@carbon/icons-react'; import { useId } from '../../internal/useId'; import { usePrefix } from '../../internal/usePrefix'; @@ -18,6 +19,7 @@ function ProgressBar({ label, max = 100, size = 'big', + status = 'active', type = 'default', value, }) { @@ -25,7 +27,11 @@ function ProgressBar({ const helperId = useId('progress-bar-helper'); const prefix = usePrefix(); - const indeterminate = value === null || value === undefined; + const isFinished = status === 'finished'; + const isError = status === 'error'; + + const indeterminate = + !isFinished && !isError && (value === null || value === undefined); let cappedValue = value; if (cappedValue > max) { @@ -34,6 +40,11 @@ function ProgressBar({ if (cappedValue < 0) { cappedValue = 0; } + if (isError) { + cappedValue = 0; + } else if (isFinished) { + cappedValue = max; + } const percentage = cappedValue / max; @@ -43,6 +54,8 @@ function ProgressBar({ `${prefix}--progress-bar--${type}`, { [`${prefix}--progress-bar--indeterminate`]: indeterminate, + [`${prefix}--progress-bar--finished`]: isFinished, + [`${prefix}--progress-bar--error`]: isError, }, className ); @@ -51,14 +64,31 @@ function ProgressBar({ [`${prefix}--visually-hidden`]: hideLabel, }); + let StatusIcon = null; + + if (isError) { + StatusIcon = React.forwardRef(function ErrorFilled16(props, ref) { + return ; + }); + } else if (isFinished) { + StatusIcon = React.forwardRef(function CheckmarkFilled16(props, ref) { + return ; + }); + } + return (
- - {label} - +
+ {label} + {StatusIcon && ( + + )} +
+ {/* eslint-disable-next-line jsx-a11y/role-supports-aria-props */}
{helperText && ( @@ -109,6 +143,11 @@ ProgressBar.propTypes = { */ size: PropTypes.oneOf(['small', 'big']), + /** + * Specify the status. + */ + status: PropTypes.oneOf(['active', 'finished', 'error']), + /** * Defines the alignment variant of the progress bar. */ diff --git a/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js b/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js index 3a421b8b5d94..a7a50ff25408 100644 --- a/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js +++ b/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js @@ -59,6 +59,7 @@ export const Example = () => { diff --git a/packages/styles/scss/components/progress-bar/_progress-bar.scss b/packages/styles/scss/components/progress-bar/_progress-bar.scss index fcb776109007..e6716aefb72f 100644 --- a/packages/styles/scss/components/progress-bar/_progress-bar.scss +++ b/packages/styles/scss/components/progress-bar/_progress-bar.scss @@ -20,14 +20,23 @@ .#{$prefix}--progress-bar__label { @include type-style('body-compact-01'); - display: block; + display: flex; + min-width: rem(48px); + justify-content: space-between; margin-bottom: $spacing-03; color: $text-primary; } + .#{$prefix}--progress-bar__label-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .#{$prefix}--progress-bar__track { position: relative; width: 100%; + min-width: rem(48px); height: rem(8px); background-color: $layer; } @@ -44,7 +53,8 @@ display: block; width: 100%; height: 100%; - background-color: $interactive; + background-color: currentColor; + color: $interactive; transform: scaleX(0); transform-origin: 0 center #{'/*rtl:100% center*/'}; transition: transform $duration-fast-02 motion(standard, productive); @@ -74,10 +84,47 @@ .#{$prefix}--progress-bar__helper-text { @include type-style('helper-text-01'); - margin-top: $spacing-02; + margin-top: $spacing-03; color: $text-secondary; } + .#{$prefix}--progress-bar__status-icon { + flex-shrink: 0; + margin-left: $spacing-05; + } + + .#{$prefix}--progress-bar--finished .#{$prefix}--progress-bar__bar, + .#{$prefix}--progress-bar--finished .#{$prefix}--progress-bar__status-icon { + color: $support-success; + } + + .#{$prefix}--progress-bar--error .#{$prefix}--progress-bar__bar, + .#{$prefix}--progress-bar--error .#{$prefix}--progress-bar__status-icon, + .#{$prefix}--progress-bar--error .#{$prefix}--progress-bar__helper-text { + color: $support-error; + } + + .#{$prefix}--progress-bar--finished .#{$prefix}--progress-bar__bar, + .#{$prefix}--progress-bar--error .#{$prefix}--progress-bar__bar { + transform: scaleX(1); + } + + .#{$prefix}--progress-bar--finished.#{$prefix}--progress-bar--inline + .#{$prefix}--progress-bar__track, + .#{$prefix}--progress-bar--error.#{$prefix}--progress-bar--inline + .#{$prefix}--progress-bar__track { + @include visually-hidden; + } + + .#{$prefix}--progress-bar--finished.#{$prefix}--progress-bar--inline + .#{$prefix}--progress-bar__label, + .#{$prefix}--progress-bar--error.#{$prefix}--progress-bar--inline + .#{$prefix}--progress-bar__label { + flex-shrink: 1; + justify-content: flex-start; + margin-right: 0; + } + @keyframes progress-bar-indeterminate { 0% { background-position-x: 25%; @@ -100,6 +147,11 @@ margin-inline-end: $spacing-05; } + .#{$prefix}--progress-bar--inline .#{$prefix}--progress-bar__track { + flex-basis: 0; + flex-grow: 1; + } + .#{$prefix}--progress-bar--inline .#{$prefix}--progress-bar__helper-text { @include visually-hidden; } From ad2aa50e4e9e47a9dfaa408a35cb12366ce02977 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Mon, 2 May 2022 15:35:59 -0500 Subject: [PATCH 2/2] docs(progressbar): add storybook playground story --- .../ProgressBar/next/ProgressBar.stories.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js b/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js index a7a50ff25408..1780f1f95638 100644 --- a/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js +++ b/packages/react/src/components/ProgressBar/next/ProgressBar.stories.js @@ -22,6 +22,23 @@ export const _ProgressBar = () => ( /> ); +const PlaygroundStory = (args) => ( + +); + +export const Playground = PlaygroundStory.bind({}); + +Playground.argTypes = { + status: { + options: ['active', 'finished', 'error'], + control: { type: 'select' }, + }, +}; + export const Indeterminate = () => ( );