diff --git a/src/components/Popup/Popup.tsx b/src/components/Popup/Popup.tsx index 15e48f3b2d..15768bf02b 100644 --- a/src/components/Popup/Popup.tsx +++ b/src/components/Popup/Popup.tsx @@ -64,6 +64,8 @@ export interface PopupProps extends DOMProps, LayerExtendableProps, PopperProps, 'aria-label'?: React.AriaAttributes['aria-label']; /** `aria-labelledby` attribute, prefer this attribute if you have visible caption */ 'aria-labelledby'?: React.AriaAttributes['aria-labelledby']; + /** `aria-modal` attribute, default value is equal to focusTrap */ + 'aria-modal'?: React.AriaAttributes['aria-modal']; /** `aria-role` attribute */ role?: React.AriaRole; /** HTML `id` attribute */ @@ -111,10 +113,11 @@ export function Popup({ restoreFocusRef, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, - role, + role: roleProp, id, focusTrap = false, autoFocus = false, + 'aria-modal': ariaModal = focusTrap, }: PopupProps) { const containerRef = React.useRef(null); @@ -152,6 +155,11 @@ export function Popup({ restoreFocusRef, }); + let role = roleProp; + if ((ariaModal === true || ariaModal === 'true') && !role) { + role = 'dialog'; + } + return ( - + {/* FIXME The onClick event handler is deprecated and should be removed */} {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */}
## Properties -| Name | Description | Type | Default | -| :------------------- | :--------------------------------------------------------------------------------- | :--------------------------------------: | :-------------: | -| altBoundary | `altBoundary` parameter for `Popper.js` `offset` modifier | `boolean` | `false` | -| anchorRef | `Popper.js` anchor element. Can also be `popper.VirtualElement` | `PopupAnchorRef` | | -| autoFocus | While open, the focus will be set to the first interactive element in the content | `boolean` | `false` | -| children | Any React content | `React.ReactNode` | | -| className | HTML `class` attribute for root node | `string` | | -| container | DOM element children to be mounted to | `HTMLElement` | `document.body` | -| contentClassName | HTML `class` attribute for content node | `string` | | -| disableEscapeKeyDown | Do not trigger close on `Esc` | `boolean` | `false` | -| disableLayer | Do not use `LayerManager` on stacking popups | `boolean` | `false` | -| disableOutsideClick | Do not trigger close on outside clicks | `boolean` | `false` | -| disablePortal | Do not use `Portal` for children | `boolean` | `false` | -| focusTrap | Enable focus trapping behavior | `boolean` | `false` | -| hasArrow | Render an arrow pointing to the anchor | `boolean` | `false` | -| id | HTML `id` attribute | `string` | | -| keepMounted | `Popup` will not be removed from the DOM upon hiding | `boolean` | `false` | -| modifiers | `Popper.js` modifiers in addition to default: `arrow`, `offset`, `flip` | `Array` | `[0, 4]` | -| offset | `Popper.js` offset | `[number, number]` | `[0, 4]` | -| onBlur | `blur` event handler | `Function` | | -| onClose | Handle `Popup` close event | `Function` | | -| onEnterKeyDown | `Enter` press event handler | `Function` | | -| onEscapeKeyDown | `Esc` press event handler | `Function` | | -| onFocus | `focus` event handler | `Function` | | -| onMouseEnter | `mouseenter` event handler | `Function` | | -| onMouseLeave | `mouseleave` event handler | `Function` | | -| onOutsideClick | Outside click event handler | `Function` | | -| onTransitionEnter | On start open popup animation | `Function` | | -| onTransitionEntered | On finish open popup animation | `Function` | | -| onTransitionExit | On start close popup animation | `Function` | | -| onTransitionExited | On finish close popup animation | `Function` | | -| open | Manages `Popup` visibility | `boolean` | `false` | -| placement | `Popper.js` placement | `PopupPlacement` `Array` | | -| qa | Test attribute (`data-qa`) | `string` | | -| restoreFocus | If true, the focus will return to the anchor element | `boolean` | `false` | -| restoreFocusRef | Element the focus will be restored to | `React.RefObject` | | -| aria-labelledby | `aria-labelledby` attribute, prefer this attribute if you have visible caption | `string` | | -| aria-label | `aria-label` attribute, use this attribute only if you didn't have visible caption | `string` | | -| role | `aria-role` attribute | `string` | | -| strategy | `Popper.js` positioning strategy | `popper.PositioningStrategy` | `[0, 4]` | -| style | HTML `style` atribute for root node | `string` | | +| Name | Description | Type | Default | +| :------------------- | :--------------------------------------------------------------------------------- | :--------------------------------------: | :------------------------------: | +| altBoundary | `altBoundary` parameter for `Popper.js` `offset` modifier | `boolean` | `false` | +| anchorRef | `Popper.js` anchor element. Can also be `popper.VirtualElement` | `PopupAnchorRef` | | +| autoFocus | While open, the focus will be set to the first interactive element in the content | `boolean` | `false` | +| children | Any React content | `React.ReactNode` | | +| className | HTML `class` attribute for root node | `string` | | +| container | DOM element children to be mounted to | `HTMLElement` | `document.body` | +| contentClassName | HTML `class` attribute for content node | `string` | | +| disableEscapeKeyDown | Do not trigger close on `Esc` | `boolean` | `false` | +| disableLayer | Do not use `LayerManager` on stacking popups | `boolean` | `false` | +| disableOutsideClick | Do not trigger close on outside clicks | `boolean` | `false` | +| disablePortal | Do not use `Portal` for children | `boolean` | `false` | +| focusTrap | Enable focus trapping behavior | `boolean` | `false` | +| hasArrow | Render an arrow pointing to the anchor | `boolean` | `false` | +| id | HTML `id` attribute | `string` | | +| keepMounted | `Popup` will not be removed from the DOM upon hiding | `boolean` | `false` | +| modifiers | `Popper.js` modifiers in addition to default: `arrow`, `offset`, `flip` | `Array` | `[0, 4]` | +| offset | `Popper.js` offset | `[number, number]` | `[0, 4]` | +| onBlur | `blur` event handler | `Function` | | +| onClose | Handle `Popup` close event | `Function` | | +| onEnterKeyDown | `Enter` press event handler | `Function` | | +| onEscapeKeyDown | `Esc` press event handler | `Function` | | +| onFocus | `focus` event handler | `Function` | | +| onMouseEnter | `mouseenter` event handler | `Function` | | +| onMouseLeave | `mouseleave` event handler | `Function` | | +| onOutsideClick | Outside click event handler | `Function` | | +| onTransitionEnter | On start open popup animation | `Function` | | +| onTransitionEntered | On finish open popup animation | `Function` | | +| onTransitionExit | On start close popup animation | `Function` | | +| onTransitionExited | On finish close popup animation | `Function` | | +| open | Manages `Popup` visibility | `boolean` | `false` | +| placement | `Popper.js` placement | `PopupPlacement` `Array` | | +| qa | Test attribute (`data-qa`) | `string` | | +| restoreFocus | If true, the focus will return to the anchor element | `boolean` | `false` | +| restoreFocusRef | Element the focus will be restored to | `React.RefObject` | | +| aria-labelledby | `aria-labelledby` attribute, prefer this attribute if you have visible caption | `string` | | +| aria-label | `aria-label` attribute, use this attribute only if you didn't have visible caption | `string` | | +| aria-modal | The `aria-modal` attribute indicates whether an element is modal when displayed. | `Booleanish` | value of `focusTrap` | +| role | The accessibility role for popup | `string` | `dialog` if `aria-modal` is true | +| strategy | `Popper.js` positioning strategy | `popper.PositioningStrategy` | `[0, 4]` | +| style | HTML `style` attribute for root node | `string` | | ## CSS API diff --git a/src/components/Popup/__tests__/Popup.test.tsx b/src/components/Popup/__tests__/Popup.test.tsx index 3304296b4c..b69f964328 100644 --- a/src/components/Popup/__tests__/Popup.test.tsx +++ b/src/components/Popup/__tests__/Popup.test.tsx @@ -49,4 +49,36 @@ describe('Popup', () => { const popup = await screen.findByText(sampleText); expect(popup).toBeVisible(); }); + + test('should not set aria-modal and role by default', async () => { + render(); + const popup = screen.getByTestId(qaId); + expect(popup).not.toHaveAttribute('aria-modal'); + expect(popup).not.toHaveAttribute('role'); + }); + + test('should set aria-modal to true and role to dialog if focusTrap is true', async () => { + render(); + const popup = screen.getByRole('dialog'); + expect(popup).toHaveAttribute('aria-modal', 'true'); + }); + + test('should use role from props if focusTrap is true', async () => { + render(); + const popup = screen.getByRole('alertdialog'); + expect(popup).toHaveAttribute('aria-modal', 'true'); + }); + + test('should use aria-modal from props if focusTrap is true', async () => { + render(); + const popup = screen.getByTestId(qaId); + expect(popup).not.toHaveAttribute('aria-modal'); + expect(popup).not.toHaveAttribute('role'); + }); + + test('should remove aria-modal if popup is closed', async () => { + render(); + const popup = screen.getByRole('dialog'); + expect(popup).not.toHaveAttribute('aria-modal'); + }); });