Skip to content

Commit

Permalink
Fix tooltip aria-describedby ID not correctly applying to buttons due…
Browse files Browse the repository at this point in the history
… to multiple children
  • Loading branch information
cee-chen committed Nov 17, 2023
1 parent 86af956 commit 202261c
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 41 deletions.
4 changes: 4 additions & 0 deletions changelogs/upcoming/7373.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@

- Deprecated `EuiContextMenuItem`'s `toolTipTitle` prop. Use `toolTipProps.title` instead
- Deprecated `EuiContextMenuItem`'s `toolTipPosition` prop. Use `toolTipProps.position` instead

**Accessibility**

- Fixed `EuiBasicTable` and `EuiInMemoryTable` actions not correctly reading out action descriptions to screen readers
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,24 @@ exports[`DefaultItemAction renders an EuiButtonEmpty when \`type="button" 1`] =
</span>
`;

exports[`DefaultItemAction renders an EuiButtonIcon when \`type="icon"\` 1`] = `
<span
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
>
<button
aria-labelledby="generated-id"
class="euiButtonIcon emotion-euiButtonIcon-xs-empty-primary"
type="button"
exports[`DefaultItemAction renders an EuiButtonIcon with screen reader text when \`type="icon"\` 1`] = `
<div>
<span
class="euiToolTipAnchor emotion-euiToolTipAnchor-inlineBlock"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="trash"
/>
</button>
<button
aria-labelledby="generated-id"
class="euiButtonIcon emotion-euiButtonIcon-xs-empty-primary"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="trash"
/>
</button>
</span>
<span
class="emotion-euiScreenReaderOnly"
id="generated-id"
Expand All @@ -45,5 +47,5 @@ exports[`DefaultItemAction renders an EuiButtonIcon when \`type="icon"\` 1`] = `
action1
</span>
</span>
</span>
</div>
`;
4 changes: 2 additions & 2 deletions src/components/basic_table/default_item_action.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('DefaultItemAction', () => {
expect(container.firstChild).toMatchSnapshot();
});

it('renders an EuiButtonIcon when `type="icon"`', () => {
it('renders an EuiButtonIcon with screen reader text when `type="icon"`', () => {
const action: IconButtonAction<Item> = {
name: <span>action1</span>,
description: 'action 1',
Expand All @@ -59,7 +59,7 @@ describe('DefaultItemAction', () => {

const { container } = render(<DefaultItemAction {...props} />);

expect(container.firstChild).toMatchSnapshot();
expect(container).toMatchSnapshot();
});

it('renders an EuiButtonEmpty if no type is specified', () => {
Expand Down
57 changes: 34 additions & 23 deletions src/components/basic_table/default_item_action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import React, { ReactElement } from 'react';
import React, { ReactElement, ReactNode } from 'react';

import { isString } from '../../services/predicate';
import {
Expand Down Expand Up @@ -63,29 +63,32 @@ export const DefaultItemAction = <T,>({
const dataTestSubj = callWithItemIfFunction(item)(action['data-test-subj']);

const ariaLabelId = useGeneratedHtmlId();
let ariaLabelledBy: ReactNode;

if (action.type === 'icon') {
if (!icon) {
throw new Error(`Cannot render item action [${action.name}]. It is configured to render as an icon but no
icon is provided. Make sure to set the 'icon' property of the action`);
}
button = (
<>
<EuiButtonIcon
className={className}
aria-labelledby={ariaLabelId}
isDisabled={!enabled}
color={color}
iconType={icon}
onClick={onClick}
href={href}
target={action.target}
data-test-subj={dataTestSubj}
/>
{/* actionContent (action.name) is a ReactNode and must be rendered to an element and referenced by ID for screen readers */}
<EuiScreenReaderOnly>
<span id={ariaLabelId}>{actionContent}</span>
</EuiScreenReaderOnly>
</>
<EuiButtonIcon
className={className}
aria-labelledby={ariaLabelId}
isDisabled={!enabled}
color={color}
iconType={icon}
onClick={onClick}
href={href}
target={action.target}
data-test-subj={dataTestSubj}
/>
);
// actionContent (action.name) is a ReactNode and must be rendered
// to an element and referenced by ID for screen readers
ariaLabelledBy = (
<EuiScreenReaderOnly>
<span id={ariaLabelId}>{actionContent}</span>
</EuiScreenReaderOnly>
);
} else {
button = (
Expand All @@ -106,11 +109,19 @@ export const DefaultItemAction = <T,>({
);
}

return enabled && tooltipContent ? (
<EuiToolTip content={tooltipContent} delay="long">
{button}
</EuiToolTip>
return enabled ? (
<>
<EuiToolTip content={tooltipContent} delay="long">
{button}
</EuiToolTip>
{/* SR text has to be rendered outside the tooltip,
otherwise EuiToolTip's own aria-labelledby won't properly clone */}
{ariaLabelledBy}
</>
) : (
button
<>
{button}
{ariaLabelledBy}
</>
);
};

0 comments on commit 202261c

Please sign in to comment.