Skip to content

Commit b2438a5

Browse files
authored
[EuiListItemLayout] Add support for focusable disabled items via hasAriaDisabled (#9584)
1 parent bdd2337 commit b2438a5

8 files changed

Lines changed: 252 additions & 119 deletions

File tree

packages/eui/src/components/combo_box/combo_box_options_list/combo_box_options_list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export class EuiComboBoxOptionsList<T> extends Component<
273273
showIndicator={!!singleSelection}
274274
textWrap={rowHeight !== 'auto' ? 'truncate' : 'wrap'}
275275
tooltipProps={
276-
toolTipContent
276+
toolTipContent && !optionIsDisabled
277277
? {
278278
...toolTipProps,
279279
content: toolTipContent,

packages/eui/src/components/list_group/list_group_item.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,20 @@ const meta: Meta<EuiListGroupItemProps> = {
2323
iconType: {
2424
control: { type: 'text' },
2525
},
26+
hasAriaDisabled: {
27+
description: `NOTE: Beta feature, may be changed or removed in the future.<br/>
28+
Changes the native \`disabled\` attribute for \`element="button"\` usages to \`aria-disabled\` to preserve focusability.
29+
This results in a semantically disabled button without the default browser handling of the disabled state.<br/>
30+
Use e.g. when a disabled button element should have a tooltip.
31+
`,
32+
},
2633
},
2734
args: {
2835
color: 'text',
2936
showToolTip: false,
3037
isActive: false,
3138
isDisabled: false,
39+
hasAriaDisabled: false,
3240
},
3341
};
3442
disableStorybookControls(meta, ['buttonRef']);

packages/eui/src/components/list_group/list_group_item.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,26 @@ describe('EuiListGroupItem', () => {
9090
});
9191
});
9292

93+
describe('hasAriaDisabled', () => {
94+
it('renders interactive items with `aria-disabled` when `isDisabled=true`', () => {
95+
const { getByTestSubject } = render(
96+
<EuiListGroupItem
97+
label="Label"
98+
hasAriaDisabled
99+
isDisabled
100+
onClick={() => {}}
101+
data-test-subj="euiListGroupItem"
102+
/>
103+
);
104+
105+
const button = getByTestSubject('euiListGroupItem');
106+
107+
expect(button).toBeEuiDisabled();
108+
expect(button).toHaveAttribute('aria-disabled', 'true');
109+
expect(button).not.toHaveAttribute('disabled');
110+
});
111+
});
112+
93113
describe('iconType', () => {
94114
test('is rendered', () => {
95115
const { container } = render(

packages/eui/src/components/list_group/list_group_item.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
getSecureRelForTarget,
2222
useEuiMemoizedStyles,
2323
cloneElementWithCss,
24+
EuiDisabledProps,
2425
} from '../../services';
2526
import { validateHref } from '../../services/security/href_validator';
2627
import { ExclusiveUnion, CommonProps } from '../common';
@@ -58,7 +59,8 @@ export type EuiListGroupItemProps = CommonProps &
5859
HTMLAttributes<HTMLSpanElement>
5960
>,
6061
'onClick' | 'color' | 'target' | 'rel'
61-
> & {
62+
> &
63+
EuiDisabledProps & {
6264
/**
6365
* By default the item will get the color `text`.
6466
* You can customize the color of the item by passing a color name.
@@ -76,11 +78,6 @@ export type EuiListGroupItemProps = CommonProps &
7678
*/
7779
isActive?: boolean;
7880

79-
/**
80-
* Apply styles indicating an item is disabled
81-
*/
82-
isDisabled?: boolean;
83-
8481
/**
8582
* Make the list item label a link.
8683
* While permitted, `href` and `onClick` should not be used together in most cases and may create problems.

packages/eui/src/components/list_item_layout/_list_item_layout.stories.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,21 @@ const meta: Meta<EuiListItemLayoutProps> = {
125125
role: {
126126
control: 'text',
127127
},
128+
hasAriaDisabled: {
129+
description: `NOTE: Beta feature, may be changed or removed in the future.<br/>
130+
Changes the native \`disabled\` attribute for \`element="button"\` usages to \`aria-disabled\` to preserve focusability.
131+
This results in a semantically disabled button without the default browser handling of the disabled state.<br/>
132+
Use e.g. when a disabled button element should have a tooltip.
133+
`,
134+
},
128135
},
129136
args: {
130137
element: 'li',
131138
checked: undefined,
132139
prepend: undefined,
133140
append: undefined,
134141
isDisabled: false,
142+
hasAriaDisabled: false,
135143
isFocused: false,
136144
isSelected: false,
137145
isSingleSelection: false,
@@ -205,12 +213,19 @@ export const TooltipProps: Story = {
205213
name: 'tooltipProps (prop)',
206214
parameters: {
207215
controls: {
208-
include: ['element', 'tooltipProps', 'isFocused', 'children'],
216+
include: [
217+
'element',
218+
'tooltipProps',
219+
'isFocused',
220+
'children',
221+
'isDisabled',
222+
'hasAriaDisabled',
223+
],
209224
},
210225
},
211226
args: {
212227
children: 'List item',
213-
component: 'button',
228+
element: 'button',
214229
isFocused: true,
215230
tooltipProps: {
216231
title: 'Tooltip',
@@ -301,7 +316,13 @@ export const KitchenSink: Story = {
301316
tags: ['vrt-only'],
302317
parameters: {
303318
controls: {
304-
include: ['isDisabled', 'isFocused', 'isSelected', 'children'],
319+
include: [
320+
'isDisabled',
321+
'hasAriaDisabled',
322+
'isFocused',
323+
'isSelected',
324+
'children',
325+
],
305326
},
306327
},
307328
args: {

packages/eui/src/components/list_item_layout/_list_item_layout.styles.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@ export const euiListItemLayoutStyles = (euiThemeContext: UseEuiTheme) => {
137137
cursor: not-allowed;
138138
background-color: transparent;
139139
`,
140+
buttonIsDisabled: css`
141+
/* prevent user (mouse) interactions for custom disabled buttons.
142+
Covers user interaction only. Programmatic event handling is done in the \`useEuiDisabledElement\` hook */
143+
&[aria-disabled='true'] {
144+
pointer-events: none;
145+
146+
> * {
147+
pointer-events: none;
148+
}
149+
}
150+
`,
140151
isFocused: css`
141152
${highlightedStyles}
142153
`,
@@ -168,6 +179,11 @@ export const euiListItemLayoutStyles = (euiThemeContext: UseEuiTheme) => {
168179
.backgroundBaseInteractiveSelectHover};
169180
}
170181
`,
182+
tooltip: {
183+
isDisabled: css`
184+
cursor: not-allowed;
185+
`,
186+
},
171187
};
172188
};
173189

packages/eui/src/components/list_item_layout/_list_item_layout.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,36 @@ describe('EuiListItemLayout', () => {
571571
});
572572
});
573573

574+
describe('hasAriaDisabled', () => {
575+
it('renders `aria-disabled` when `isDisabled=true` for `element="button"`', () => {
576+
const { getByTestSubject } = render(
577+
<EuiListItemLayout element="button" isDisabled hasAriaDisabled>
578+
Option
579+
</EuiListItemLayout>
580+
);
581+
582+
const button = getByTestSubject('euiListItemLayout');
583+
584+
expect(button).toBeEuiDisabled();
585+
expect(button).toHaveAttribute('aria-disabled', 'true');
586+
expect(button).not.toHaveAttribute('disabled');
587+
});
588+
589+
it('renders `aria-disabled` when `isDisabled=true` for `element="a"`', () => {
590+
const { getByTestSubject } = render(
591+
<EuiListItemLayout element="a" isDisabled hasAriaDisabled>
592+
Option
593+
</EuiListItemLayout>
594+
);
595+
596+
const button = getByTestSubject('euiListItemLayout');
597+
598+
expect(button).toBeEuiDisabled();
599+
expect(button).toHaveAttribute('aria-disabled', 'true');
600+
expect(button).not.toHaveAttribute('disabled');
601+
});
602+
});
603+
574604
describe('checked', () => {
575605
it('applies a checked state for multi-selection items when `checked="on"`', () => {
576606
const { getByTestSubject } = render(

0 commit comments

Comments
 (0)