From 2b32385fab10674fb894aa477015c35b287d89a3 Mon Sep 17 00:00:00 2001 From: Alex Kessaris Date: Wed, 26 Jul 2023 13:47:52 -0400 Subject: [PATCH] leverage discriminating union and move down LiveRegion --- pages/progress-bar/permutations-utils.tsx | 1 + .../__tests__/progress-bar.test.tsx | 37 ++++++++- src/progress-bar/index.tsx | 29 ++----- src/progress-bar/internal.tsx | 78 +++++++++++++------ 4 files changed, 96 insertions(+), 49 deletions(-) diff --git a/pages/progress-bar/permutations-utils.tsx b/pages/progress-bar/permutations-utils.tsx index 81a5870d8a..11ce2c4080 100644 --- a/pages/progress-bar/permutations-utils.tsx +++ b/pages/progress-bar/permutations-utils.tsx @@ -51,6 +51,7 @@ const permutations = createPermutations([ resultButtonText: [undefined, 'Result button text'], label: [undefined, 'Label'], description: [undefined, 'description'], + type: ['percentage'], additionalInfo: [undefined, 'additional info'], }, { diff --git a/src/progress-bar/__tests__/progress-bar.test.tsx b/src/progress-bar/__tests__/progress-bar.test.tsx index 95cd653379..926cf1df50 100644 --- a/src/progress-bar/__tests__/progress-bar.test.tsx +++ b/src/progress-bar/__tests__/progress-bar.test.tsx @@ -1,7 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import { render } from '@testing-library/react'; +import { render, within } from '@testing-library/react'; import ProgressBarWrapper from '../../../lib/components/test-utils/dom/progress-bar'; import createWrapper from '../../../lib/components/test-utils/dom'; import ProgressBar, { ProgressBarProps } from '../../../lib/components/progress-bar'; @@ -213,3 +213,38 @@ describe('Progress updates', () => { expect(wrapper.find(`.${liveRegionStyles.root}`)?.getElement().textContent).toBe(`${label}: 2%`); }); }); + +describe('ARIA value text', () => { + const setup = (props: Partial) => { + const wrapper = renderProgressBar(props); + return within(wrapper.getElement()).getByRole('progressbar'); + }; + + describe('percentage', () => { + test('default', () => { + const progressBar = setup({ type: 'percentage', value: 1 }); + expect(progressBar.getAttribute('aria-valuetext')).toEqual('1%'); + expect(progressBar.getAttribute('aria-valuenow')).toEqual('1'); + }); + }); + + describe('ratio', () => { + test('default', () => { + const progressBar = setup({ type: 'ratio', value: 1 }); + expect(progressBar.getAttribute('aria-valuetext')).toEqual('1/100'); + expect(progressBar.getAttribute('aria-valuenow')).toEqual('1'); + }); + + test('maxValue provided', () => { + const progressBar = setup({ type: 'ratio', value: 1, maxValue: 10 }); + expect(progressBar.getAttribute('aria-valuetext')).toEqual('1/10'); + expect(progressBar.getAttribute('aria-valuenow')).toEqual('1'); + }); + + test('ariaValueText provided', () => { + const progressBar = setup({ type: 'ratio', value: 1, maxValue: 10, ariaValueText: '1 of 10 tasks' }); + expect(progressBar.getAttribute('aria-valuetext')).toEqual('1 of 10 tasks'); + expect(progressBar.getAttribute('aria-valuenow')).toEqual('1'); + }); + }); +}); diff --git a/src/progress-bar/index.tsx b/src/progress-bar/index.tsx index 292a705dab..21271c8c57 100644 --- a/src/progress-bar/index.tsx +++ b/src/progress-bar/index.tsx @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React, { useEffect, useMemo, useState } from 'react'; +import React from 'react'; import clsx from 'clsx'; import styles from './styles.css.js'; @@ -12,18 +12,11 @@ import { useUniqueId } from '../internal/hooks/use-unique-id'; import { Progress, ResultState, SmallText } from './internal'; import { applyDisplayName } from '../internal/utils/apply-display-name'; import useBaseComponent from '../internal/hooks/use-base-component'; -import { throttle } from '../internal/utils/throttle'; -import LiveRegion from '../internal/components/live-region'; - -const ASSERTION_FREQUENCY = 5000; // interval in ms between progress announcements export { ProgressBarProps }; export default function ProgressBar({ value = 0, - type = 'percentage', - maxValue = 100, - ariaValueText, status = 'in-progress', variant = 'standalone', resultButtonText, @@ -42,18 +35,6 @@ export default function ProgressBar({ const isInFlash = variant === 'flash'; const isInProgressState = status === 'in-progress'; - const [assertion, setAssertion] = useState(''); - const throttledAssertion = useMemo(() => { - return throttle((value: ProgressBarProps['value']) => { - const announcement = type === 'ratio' ? `${value} of ${maxValue}}` : `${value}%`; - setAssertion(`${label ?? ''}: ${announcement}`); - }, ASSERTION_FREQUENCY); - }, [label, maxValue, type]); - - useEffect(() => { - throttledAssertion(value); - }, [throttledAssertion, value]); - if (isInFlash && resultButtonText) { warnOnce( 'ProgressBar', @@ -76,14 +57,14 @@ export default function ProgressBar({ {isInProgressState ? ( <> - {assertion} ) : ( { return Math.max(Math.min(value, upperLimit), lowerLimit); }; interface ProgressProps { + label: ReactNode; type: 'percentage' | 'ratio'; value: number; maxValue: number; @@ -25,33 +30,58 @@ interface ProgressProps { labelId: string; ariaValueText?: string; } -export const Progress = ({ value, maxValue = MAX_VALUE, type, isInFlash, labelId, ariaValueText }: ProgressProps) => { +export const Progress = ({ + label, + value, + maxValue = MAX_VALUE, + type, + isInFlash, + labelId, + ariaValueText, +}: ProgressProps) => { const roundedValue = Math.round(value); const progressValue = clamp(roundedValue, 0, maxValue); - const valueText = ariaValueText || type === 'ratio' ? `${progressValue}/${maxValue}` : `${progressValue}%`; + const isRatio = type === 'ratio'; + const percentage = isRatio && maxValue !== 100 ? Math.round(((progressValue * 1.0) / maxValue) * 100) : progressValue; + const valueText = isRatio ? ariaValueText || `${progressValue}/${maxValue}` : `${percentage}%`; + + const [assertion, setAssertion] = useState(''); + const throttledAssertion = useMemo(() => { + return throttle((value: ProgressBarProps['value']) => { + const announcement = type === 'ratio' ? `${value} of ${maxValue}}` : `${value}%`; + setAssertion(`${label ?? ''}: ${announcement}`); + }, ASSERTION_FREQUENCY); + }, [label, type, maxValue]); + + useEffect(() => { + throttledAssertion(value); + }, [throttledAssertion, value]); return ( -
- = maxValue && styles.complete, - isInFlash && styles['progress-in-flash'] - )} - max={maxValue} - value={progressValue} - aria-valuemin={0} - aria-valuemax={maxValue} - aria-labelledby={labelId} - aria-valuenow={progressValue} - aria-valuetext={valueText} - /> - -
+ <> +
+ = maxValue && styles.complete, + isInFlash && styles['progress-in-flash'] + )} + max={maxValue} + value={progressValue} + aria-valuemin={0} + aria-valuemax={maxValue} + aria-labelledby={labelId} + aria-valuenow={isRatio ? progressValue : percentage} + aria-valuetext={valueText} + /> + +
+ {assertion} + ); };