Skip to content

Commit

Permalink
feat(Popup): add aria-modal prop (#1827)
Browse files Browse the repository at this point in the history
  • Loading branch information
ValeraS authored Sep 2, 2024
1 parent ee2d040 commit 7d8dff0
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 43 deletions.
13 changes: 11 additions & 2 deletions src/components/Popup/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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<HTMLDivElement>(null);

Expand Down Expand Up @@ -152,6 +155,11 @@ export function Popup({
restoreFocusRef,
});

let role = roleProp;
if ((ariaModal === true || ariaModal === 'true') && !role) {
role = 'dialog';
}

return (
<CSSTransition
nodeRef={containerRef}
Expand Down Expand Up @@ -186,8 +194,9 @@ export function Popup({
role={role}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
aria-modal={ariaModal && open ? ariaModal : undefined}
>
<FocusTrap enabled={focusTrap && open} disableAutoFocus={!autoFocus}>
<FocusTrap enabled={focusTrap && open} autoFocus={autoFocus}>
{/* 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 */}
<div
Expand Down
83 changes: 42 additions & 41 deletions src/components/Popup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,47 +83,48 @@ LANDING_BLOCK-->

## 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<PopupPlacement>` | |
| 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<PopupPlacement>` | |
| 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

Expand Down
32 changes: 32 additions & 0 deletions src/components/Popup/__tests__/Popup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<Popup open qa={qaId} />);
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(<Popup open focusTrap />);
const popup = screen.getByRole('dialog');
expect(popup).toHaveAttribute('aria-modal', 'true');
});

test('should use role from props if focusTrap is true', async () => {
render(<Popup open focusTrap role="alertdialog" />);
const popup = screen.getByRole('alertdialog');
expect(popup).toHaveAttribute('aria-modal', 'true');
});

test('should use aria-modal from props if focusTrap is true', async () => {
render(<Popup open qa={qaId} focusTrap aria-modal={false} />);
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(<Popup aria-modal keepMounted />);
const popup = screen.getByRole('dialog');
expect(popup).not.toHaveAttribute('aria-modal');
});
});

0 comments on commit 7d8dff0

Please sign in to comment.