diff --git a/src/components/ProgressBar.cy.tsx b/src/components/ProgressBar.cy.tsx index 29455601..45cef286 100644 --- a/src/components/ProgressBar.cy.tsx +++ b/src/components/ProgressBar.cy.tsx @@ -90,4 +90,48 @@ describe('ProgressBar', () => { cy.findByRole('progressbar').should('have.attr', 'style').and('include', 'scaleX(0.7)'); }); + + it('has ARIA attributes for accessibility', () => { + cy.mount( + + + + ); + + cy.findByRole('progressbar') + .should('have.attr', 'aria-valuemin', '0') + .should('have.attr', 'aria-valuemax', '1') + .should('not.have.attr', 'aria-valuenow'); + }); + + it('has aria-valuenow for controlled progress bar', () => { + cy.mount( + + + + ); + + cy.findByRole('progressbar') + .should('have.attr', 'aria-valuemin', '0') + .should('have.attr', 'aria-valuemax', '1') + .should('have.attr', 'aria-valuenow', '0.5'); + }); + + it('clamps aria-valuenow between 0 and 1', () => { + cy.mount( + + + + ); + + cy.findByRole('progressbar').should('have.attr', 'aria-valuenow', '1'); + + cy.mount( + + + + ); + + cy.findByRole('progressbar').should('have.attr', 'aria-valuenow', '0'); + }); }); diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx index 6414f4e5..2c741a3c 100644 --- a/src/components/ProgressBar.tsx +++ b/src/components/ProgressBar.tsx @@ -112,7 +112,21 @@ export function ProgressBar({ } }; - // TODO: add aria-valuenow, aria-valuemax, aria-valuemin + // ARIA attributes for progress bar accessibility + // Only provide aria-valuenow for controlled progress bars where we know the exact value + // For animated progress bars, we omit aria-valuenow as the value changes continuously + const ariaProps: { + 'aria-valuemin': number; + 'aria-valuemax': number; + 'aria-valuenow'?: number; + } = { + 'aria-valuemin': 0, + 'aria-valuemax': 1 + }; + + if (controlledProgress && typeof progress === 'number') { + ariaProps['aria-valuenow'] = Math.max(0, Math.min(1, progress)); + } return (
@@ -125,6 +139,7 @@ export function ProgressBar({ aria-label="notification timer" className={classNames} style={style} + {...ariaProps} {...animationEvent} />