Skip to content

Commit 487eb2e

Browse files
[Lens] accessibility screen reader issues (#84395)
* [Lens] accessibility screen reader issues * fix i18n * fix: no aria-label on divs * cr fixes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 3cb26eb commit 487eb2e

File tree

12 files changed

+184
-101
lines changed

12 files changed

+184
-101
lines changed

x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ function LayerPanels(
9494
{...props}
9595
key={layerId}
9696
layerId={layerId}
97-
dataTestSubj={`lns-layerPanel-${index}`}
97+
index={index}
9898
visualizationState={visualizationState}
9999
updateVisualization={setVisualizationState}
100100
updateDatasource={updateDatasource}

x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.scss

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,39 @@
1313
animation: euiFlyout $euiAnimSpeedNormal $euiAnimSlightResistance;
1414
}
1515

16-
.lnsDimensionContainer__footer,
17-
.lnsDimensionContainer__header {
16+
.lnsDimensionContainer__footer {
1817
padding: $euiSizeS;
1918
}
19+
20+
.lnsDimensionContainer__header {
21+
padding: $euiSizeS $euiSizeXS;
22+
}
23+
24+
.lnsDimensionContainer__headerTitle {
25+
padding: $euiSizeS $euiSizeXS;
26+
cursor: pointer;
27+
28+
&:hover {
29+
text-decoration: underline;
30+
}
31+
}
32+
33+
.lnsDimensionContainer__headerLink {
34+
&:focus-within {
35+
background-color: transparentize($euiColorVis1, .9);
36+
37+
.lnsDimensionContainer__headerTitle {
38+
text-decoration: underline;
39+
}
40+
}
41+
}
42+
43+
.lnsDimensionContainer__backIcon {
44+
&:hover {
45+
transform: none !important; // sass-lint:disable-line no-important
46+
}
47+
48+
&:focus {
49+
background-color: transparent;
50+
}
51+
}

x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_container.tsx

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {
1010
EuiFlyoutHeader,
1111
EuiFlyoutFooter,
1212
EuiTitle,
13+
EuiButtonIcon,
1314
EuiButtonEmpty,
15+
EuiFlexGroup,
1416
EuiFlexItem,
1517
EuiFocusTrap,
1618
EuiOutsideClickDetector,
@@ -54,24 +56,42 @@ export function DimensionContainer({
5456
className="lnsDimensionContainer"
5557
>
5658
<EuiFlyoutHeader hasBorder className="lnsDimensionContainer__header">
57-
<EuiTitle size="xs">
58-
<EuiButtonEmpty
59-
onClick={closeFlyout}
60-
data-test-subj="lns-indexPattern-dimensionContainerTitle"
61-
id="lnsDimensionContainerTitle"
62-
iconType="sortLeft"
63-
flush="left"
64-
>
65-
<strong>
66-
{i18n.translate('xpack.lens.configure.configurePanelTitle', {
67-
defaultMessage: '{groupLabel} configuration',
68-
values: {
69-
groupLabel,
70-
},
59+
<EuiFlexGroup
60+
gutterSize="none"
61+
alignItems="center"
62+
className="lnsDimensionContainer__headerLink"
63+
onClick={closeFlyout}
64+
>
65+
<EuiFlexItem grow={false}>
66+
<EuiButtonIcon
67+
color="text"
68+
data-test-subj="lns-indexPattern-dimensionContainerBack"
69+
className="lnsDimensionContainer__backIcon"
70+
onClick={closeFlyout}
71+
iconType="sortLeft"
72+
aria-label={i18n.translate('xpack.lens.dimensionContainer.closeConfiguration', {
73+
defaultMessage: 'Close configuration',
7174
})}
72-
</strong>
73-
</EuiButtonEmpty>
74-
</EuiTitle>
75+
/>
76+
</EuiFlexItem>
77+
<EuiFlexItem grow={true}>
78+
<EuiTitle size="xs">
79+
<h2
80+
id="lnsDimensionContainerTitle"
81+
className="lnsDimensionContainer__headerTitle"
82+
>
83+
<strong>
84+
{i18n.translate('xpack.lens.configure.configurePanelTitle', {
85+
defaultMessage: '{groupLabel} configuration',
86+
values: {
87+
groupLabel,
88+
},
89+
})}
90+
</strong>
91+
</h2>
92+
</EuiTitle>
93+
</EuiFlexItem>
94+
</EuiFlexGroup>
7595
</EuiFlyoutHeader>
7696
<EuiFlexItem className="eui-yScrollWithShadows" grow={1}>
7797
{panel}

x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('LayerPanel', () => {
5858
onRemoveLayer: jest.fn(),
5959
dispatch: jest.fn(),
6060
core: coreMock.createStart(),
61-
dataTestSubj: 'lns_layerPanel-0',
61+
index: 0,
6262
};
6363
}
6464

x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ import { DimensionContainer } from './dimension_container';
2929
import { ColorIndicator } from './color_indicator';
3030
import { PaletteIndicator } from './palette_indicator';
3131

32+
const triggerLinkA11yText = (label: string) =>
33+
i18n.translate('xpack.lens.configure.editConfig', {
34+
defaultMessage: 'Click to edit configuration for {label} or drag to move',
35+
values: { label },
36+
});
37+
3238
const initialActiveDimensionState = {
3339
isNew: false,
3440
};
@@ -58,7 +64,7 @@ function isSameConfiguration(config1: unknown, config2: unknown) {
5864
export function LayerPanel(
5965
props: Exclude<ConfigPanelWrapperProps, 'state' | 'setState'> & {
6066
layerId: string;
61-
dataTestSubj: string;
67+
index: number;
6268
isOnlyLayer: boolean;
6369
updateVisualization: StateSetter<unknown>;
6470
updateDatasource: (datasourceId: string, newState: unknown) => void;
@@ -75,7 +81,7 @@ export function LayerPanel(
7581
initialActiveDimensionState
7682
);
7783

78-
const { framePublicAPI, layerId, isOnlyLayer, onRemoveLayer, dataTestSubj } = props;
84+
const { framePublicAPI, layerId, isOnlyLayer, onRemoveLayer, index } = props;
7985
const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId];
8086

8187
useEffect(() => {
@@ -125,7 +131,11 @@ export function LayerPanel(
125131
const columnLabelMap = layerDatasource.uniqueLabels(layerDatasourceConfigProps.state);
126132
return (
127133
<ChildDragDropProvider {...dragDropContext}>
128-
<EuiPanel data-test-subj={dataTestSubj} className="lnsLayerPanel" paddingSize="s">
134+
<EuiPanel
135+
data-test-subj={`lns-layerPanel-${index}`}
136+
className="lnsLayerPanel"
137+
paddingSize="s"
138+
>
129139
<EuiFlexGroup gutterSize="s" alignItems="flexStart" responsive={false}>
130140
<EuiFlexItem grow={false} className="lnsLayerPanel__settingsFlexItem">
131141
<LayerSettings
@@ -180,14 +190,10 @@ export function LayerPanel(
180190

181191
<EuiSpacer size="m" />
182192

183-
{groups.map((group, index) => {
193+
{groups.map((group, groupIndex) => {
184194
const newId = generateId();
185195
const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0;
186196

187-
const triggerLinkA11yText = i18n.translate('xpack.lens.configure.editConfig', {
188-
defaultMessage: 'Click to edit configuration or drag to move',
189-
});
190-
191197
return (
192198
<EuiFormRow
193199
className={
@@ -198,7 +204,7 @@ export function LayerPanel(
198204
fullWidth
199205
label={<div className="lnsLayerPanel__groupLabel">{group.groupLabel}</div>}
200206
labelType="legend"
201-
key={index}
207+
key={groupIndex}
202208
isInvalid={isMissing}
203209
error={
204210
isMissing ? (
@@ -327,8 +333,8 @@ export function LayerPanel(
327333
});
328334
}
329335
}}
330-
aria-label={triggerLinkA11yText}
331-
title={triggerLinkA11yText}
336+
aria-label={triggerLinkA11yText(columnLabelMap[accessor])}
337+
title={triggerLinkA11yText(columnLabelMap[accessor])}
332338
>
333339
<ColorIndicator accessorConfig={accessorConfig}>
334340
<NativeRenderer
@@ -351,11 +357,13 @@ export function LayerPanel(
351357
aria-label={i18n.translate(
352358
'xpack.lens.indexPattern.removeColumnLabel',
353359
{
354-
defaultMessage: 'Remove configuration',
360+
defaultMessage: 'Remove configuration from "{groupLabel}"',
361+
values: { groupLabel: group.groupLabel },
355362
}
356363
)}
357364
title={i18n.translate('xpack.lens.indexPattern.removeColumnLabel', {
358-
defaultMessage: 'Remove configuration',
365+
defaultMessage: 'Remove configuration from "{groupLabel}"',
366+
values: { groupLabel: group.groupLabel },
359367
})}
360368
onClick={() => {
361369
trackUiEvent('indexpattern_dimension_removed');
@@ -435,6 +443,13 @@ export function LayerPanel(
435443
contentProps={{
436444
className: 'lnsLayerPanel__triggerTextContent',
437445
}}
446+
aria-label={i18n.translate(
447+
'xpack.lens.indexPattern.removeColumnAriaLabel',
448+
{
449+
defaultMessage: 'Drop a field or click to add to {groupLabel}',
450+
values: { groupLabel: group.groupLabel },
451+
}
452+
)}
438453
data-test-subj="lns-empty-dimension"
439454
onClick={() => {
440455
if (activeId) {
@@ -535,6 +550,17 @@ export function LayerPanel(
535550
iconType="trash"
536551
color="danger"
537552
data-test-subj="lnsLayerRemove"
553+
aria-label={
554+
isOnlyLayer
555+
? i18n.translate('xpack.lens.resetLayerAriaLabel', {
556+
defaultMessage: 'Reset layer {index}',
557+
values: { index: index + 1 },
558+
})
559+
: i18n.translate('xpack.lens.deleteLayerAriaLabel', {
560+
defaultMessage: `Delete layer {index}`,
561+
values: { index: index + 1 },
562+
})
563+
}
538564
onClick={() => {
539565
// If we don't blur the remove / clear button, it remains focused
540566
// which is a strange UX in this case. e.target.blur doesn't work
@@ -554,7 +580,7 @@ export function LayerPanel(
554580
defaultMessage: 'Reset layer',
555581
})
556582
: i18n.translate('xpack.lens.deleteLayer', {
557-
defaultMessage: 'Delete layer',
583+
defaultMessage: `Delete layer`,
558584
})}
559585
</EuiButtonEmpty>
560586
</EuiFlexItem>

x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ export function WorkspacePanelWrapper({
124124
<EuiScreenReaderOnly>
125125
<h1 id="lns_ChartTitle" data-test-subj="lns_ChartTitle">
126126
{title ||
127-
i18n.translate('xpack.lens.chartTitle.unsaved', { defaultMessage: 'Unsaved' })}
127+
i18n.translate('xpack.lens.chartTitle.unsaved', {
128+
defaultMessage: 'Unsaved visualization',
129+
})}
128130
</h1>
129131
</EuiScreenReaderOnly>
130132
<EuiPageContentBody className="lnsWorkspacePanelWrapper__pageContentBody">

x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -181,49 +181,56 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) {
181181
/>
182182
);
183183
return (
184-
<EuiPopover
185-
ownFocus
186-
className="lnsFieldItem__popoverAnchor"
187-
display="block"
188-
data-test-subj="lnsFieldListPanelField"
189-
container={document.querySelector<HTMLElement>('.application') || undefined}
190-
button={
191-
<DragDrop
192-
label={field.displayName}
193-
value={value}
194-
data-test-subj={`lnsFieldListPanelField-${field.name}`}
195-
draggable
196-
>
197-
<FieldButton
198-
className={`lnsFieldItem lnsFieldItem--${field.type} lnsFieldItem--${
199-
exists ? 'exists' : 'missing'
200-
}`}
201-
isActive={infoIsOpen}
202-
onClick={togglePopover}
203-
aria-label={i18n.translate('xpack.lens.indexPattern.fieldStatsButtonAriaLabel', {
204-
defaultMessage: '{fieldName}: {fieldType}. Hit enter for a field preview.',
205-
values: {
206-
fieldName: field.displayName,
207-
fieldType: field.type,
208-
},
209-
})}
210-
fieldIcon={lensFieldIcon}
211-
fieldName={
212-
<EuiHighlight search={wrapOnDot(highlight)}>
213-
{wrapOnDot(field.displayName)}
214-
</EuiHighlight>
215-
}
216-
fieldInfoIcon={lensInfoIcon}
217-
/>
218-
</DragDrop>
219-
}
220-
isOpen={infoIsOpen}
221-
closePopover={() => setOpen(false)}
222-
anchorPosition="rightUp"
223-
panelClassName="lnsFieldItem__fieldPanel"
224-
>
225-
<FieldItemPopoverContents {...state} {...props} />
226-
</EuiPopover>
184+
<li>
185+
<EuiPopover
186+
ownFocus
187+
className="lnsFieldItem__popoverAnchor"
188+
display="block"
189+
data-test-subj="lnsFieldListPanelField"
190+
container={document.querySelector<HTMLElement>('.application') || undefined}
191+
button={
192+
<DragDrop
193+
label={field.displayName}
194+
value={value}
195+
data-test-subj={`lnsFieldListPanelField-${field.name}`}
196+
draggable
197+
>
198+
<FieldButton
199+
className={`lnsFieldItem lnsFieldItem--${field.type} lnsFieldItem--${
200+
exists ? 'exists' : 'missing'
201+
}`}
202+
isActive={infoIsOpen}
203+
onClick={togglePopover}
204+
buttonProps={{
205+
['aria-label']: i18n.translate(
206+
'xpack.lens.indexPattern.fieldStatsButtonAriaLabel',
207+
{
208+
defaultMessage: '{fieldName}: {fieldType}. Hit enter for a field preview.',
209+
values: {
210+
fieldName: field.displayName,
211+
fieldType: field.type,
212+
},
213+
}
214+
),
215+
}}
216+
fieldIcon={lensFieldIcon}
217+
fieldName={
218+
<EuiHighlight search={wrapOnDot(highlight)}>
219+
{wrapOnDot(field.displayName)}
220+
</EuiHighlight>
221+
}
222+
fieldInfoIcon={lensInfoIcon}
223+
/>
224+
</DragDrop>
225+
}
226+
isOpen={infoIsOpen}
227+
closePopover={() => setOpen(false)}
228+
anchorPosition="rightUp"
229+
panelClassName="lnsFieldItem__fieldPanel"
230+
>
231+
<FieldItemPopoverContents {...state} {...props} />
232+
</EuiPopover>
233+
</li>
227234
);
228235
};
229236

0 commit comments

Comments
 (0)