diff --git a/packages/react/src/ProgressBar/ProgressBar.tsx b/packages/react/src/ProgressBar/ProgressBar.tsx index 6c760bbd77d..92158e87247 100644 --- a/packages/react/src/ProgressBar/ProgressBar.tsx +++ b/packages/react/src/ProgressBar/ProgressBar.tsx @@ -17,7 +17,7 @@ const shimmer = keyframes` to { mask-position: 0%; } ` -export const Item = styled.span` +const ProgressItem = styled.span` width: ${props => (props.progress ? `${props.progress}%` : 0)}; background-color: ${props => get(`colors.${props.bg || 'success.emphasis'}`)}; @@ -34,8 +34,6 @@ export const Item = styled.span` ${sx}; ` -Item.displayName = 'ProgressBar.Item' - const sizeMap = { small: '5px', large: '10px', @@ -60,36 +58,77 @@ const ProgressContainer = styled.span` ${sx}; ` -export type ProgressBarProps = React.HTMLAttributes & {bg?: string} & StyledProgressContainerProps & - ProgressProp +export type ProgressBarItems = React.HTMLAttributes & {'aria-label'?: string} & ProgressProp & SxProp -export const ProgressBar = forwardRef( +export const Item = forwardRef( ( - {animated, progress, bg = 'success.emphasis', barSize = 'default', children, ...rest}: ProgressBarProps, + {progress, 'aria-label': ariaLabel, 'aria-valuenow': ariaValueNow, 'aria-valuetext': ariaValueText, ...rest}, forwardRef, ) => { - if (children && progress) { - throw new Error('You should pass `progress` or children, not both.') + const progressAsNumber = typeof progress === 'string' ? parseInt(progress, 10) : progress + + const ariaAttributes = { + 'aria-valuenow': + ariaValueNow || (progressAsNumber && progressAsNumber >= 0 ? Math.round(progressAsNumber) : undefined), + 'aria-valuemin': 0, + 'aria-valuemax': 100, + 'aria-valuetext': ariaValueText, } warning( - children && - typeof (rest as React.AriaAttributes)['aria-valuenow'] === 'undefined' && - typeof (rest as React.AriaAttributes)['aria-valuetext'] === 'undefined', + ariaAttributes['aria-valuenow'] === undefined && ariaAttributes['aria-valuetext'] === undefined, 'Expected `aria-valuenow` or `aria-valuetext` to be provided to . Provide one of these values so screen reader users can determine the current progress. This warning will become an error in the next major release.', ) - const progressAsNumber = typeof progress === 'string' ? parseInt(progress, 10) : progress + return ( + + ) + }, +) - const ariaAttributes = { - 'aria-valuenow': progressAsNumber ? Math.round(progressAsNumber) : undefined, - 'aria-valuemin': 0, - 'aria-valuemax': 100, +Item.displayName = 'ProgressBar.Item' + +export type ProgressBarProps = React.HTMLAttributes & {bg?: string} & StyledProgressContainerProps & + ProgressProp + +export const ProgressBar = forwardRef( + ( + { + animated, + progress, + bg = 'success.emphasis', + barSize = 'default', + children, + 'aria-label': ariaLabel, + 'aria-valuenow': ariaValueNow, + 'aria-valuetext': ariaValueText, + ...rest + }: ProgressBarProps, + forwardRef, + ) => { + if (children && progress) { + throw new Error('You should pass `progress` or children, not both.') } return ( - - {children ?? } + + {children ?? ( + + )} ) }, diff --git a/packages/react/src/__tests__/ProgressBar.test.tsx b/packages/react/src/__tests__/ProgressBar.test.tsx index be373f9feb2..d2eb9e6d0bd 100644 --- a/packages/react/src/__tests__/ProgressBar.test.tsx +++ b/packages/react/src/__tests__/ProgressBar.test.tsx @@ -5,7 +5,7 @@ import {render as HTMLRender} from '@testing-library/react' import axe from 'axe-core' describe('ProgressBar', () => { - behavesAsComponent({Component: ProgressBar}) + behavesAsComponent({Component: ProgressBar, toRender: () => }) checkExports('ProgressBar', { default: undefined, @@ -50,4 +50,53 @@ describe('ProgressBar', () => { it('respects the "progress" prop', () => { expect(render()).toMatchSnapshot() }) + + it('passed the `aria-label` down to the progress bar', () => { + const {getByRole, getByLabelText} = HTMLRender() + expect(getByRole('progressbar')).toHaveAttribute('aria-label', 'Upload test.png') + expect(getByLabelText('Upload test.png')).toBeInTheDocument() + }) + + it('passed the `aria-valuenow` down to the progress bar', () => { + const {getByRole} = HTMLRender() + expect(getByRole('progressbar')).toHaveAttribute('aria-valuenow', '80') + }) + + it('passed the `aria-valuetext` down to the progress bar', () => { + const {getByRole} = HTMLRender() + expect(getByRole('progressbar')).toHaveAttribute('aria-valuetext', '80 percent') + }) + + it('does not pass the `aria-label` down to the progress bar if there are multiple items', () => { + const {getByRole} = HTMLRender( + + + , + ) + expect(getByRole('progressbar')).not.toHaveAttribute('aria-label') + }) + + it('passes aria attributes to the progress bar item', () => { + const {getByRole} = HTMLRender( + + + , + ) + expect(getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50') + expect(getByRole('progressbar')).toHaveAttribute('aria-label', 'Progress') + }) + + it('provides `aria-valuenow` to the progress bar item if it is not already provided', () => { + const {getByRole} = HTMLRender() + expect(getByRole('progressbar')).toHaveAttribute('aria-valuenow', '50') + }) + + it('should warn users if they do not pass the correct props or ARIA attributes', async () => { + const spy = jest.spyOn(console, 'warn').mockImplementationOnce(() => {}) + + render() + + expect(spy).toHaveBeenCalledTimes(1) + spy.mockRestore() + }) }) diff --git a/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap b/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap index e8f28759a8b..f47852ca0bc 100644 --- a/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap +++ b/packages/react/src/__tests__/__snapshots__/ProgressBar.test.tsx.snap @@ -34,15 +34,15 @@ exports[`ProgressBar respects the "progress" prop 1`] = ` } `;