[ObsUX][A11y] Add announcement keyboard elements improvements (#216592)

Closes https://github.com/elastic/kibana/issues/216029

## Summary

Some elements on the Infrastructure Inventory were not accessible or
their interaction with the keyboard was not properly announced to the
users, see issue for steps to reproduce, use a screen reader to test.

### What was done

#### Groupby buttons 

- Replace link element by button
- Add announcement when interacting with the button, i.e., when
clicking/selecting the `All` button nothing changes


https://github.com/user-attachments/assets/da7a973d-26a4-4288-8aea-e1dd7a43ea6f

#### Legend options

- Replace `EuiColorPickerSwatch` button by and icon, the button didn't
have any action, it only shows the selected color for minimum and
maximum for the selected color palette
- Fix ui alignment of checkbox elements

Before

<img width="452" alt="Screenshot 2025-04-01 at 08 58 40"
src="https://github.com/user-attachments/assets/66d7e733-8f95-4d76-9eb2-549b8421b888"
/>

After

<img width="585" alt="Screenshot 2025-04-01 at 08 54 31"
src="https://github.com/user-attachments/assets/825a9f84-ddaf-43dc-b136-e268592818c1"
/>
This commit is contained in:
Miriam 2025-04-02 12:24:26 +01:00 committed by GitHub
parent 7275d2e8bd
commit ecd83ce211
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 40 deletions

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import { EuiLink, EuiToolTip, useEuiFontSize } from '@elastic/eui';
import React from 'react';
import { EuiButtonEmpty, EuiScreenReaderOnly, EuiToolTip, useEuiFontSize } from '@elastic/eui';
import React, { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
import styled from '@emotion/styled';
import type {
InfraWaffleMapGroup,
@ -20,43 +21,72 @@ interface Props {
options: InfraWaffleMapOptions;
}
export class GroupName extends React.PureComponent<Props, {}> {
public render() {
const { group, isChild } = this.props;
const linkStyle = {
fontSize: isChild ? '0.85em' : '1em',
};
return (
export const GroupName: React.FC<Props> = ({ onDrilldown, group, isChild, options }) => {
const [a11yAnnouncement, setA11yAnnouncement] = useState('');
const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if (options.groupBy.length === 0) {
setA11yAnnouncement(
i18n.translate('xpack.infra.inventory.groupBy.noChangeMessage', {
defaultMessage: 'No changes were made when selecting {group}.',
values: { group: group.name },
})
);
return;
}
setA11yAnnouncement(
i18n.translate('xpack.infra.inventory.groupBy.groupingByMessage', {
defaultMessage: 'Grouping by {group}...',
values: { group: group.name },
})
);
const currentPath =
isChild && options.groupBy.length > 1 ? options.groupBy[1] : options.groupBy[0];
onDrilldown(`${currentPath.field}: "${group.name}"`);
},
[group.name, isChild, onDrilldown, options.groupBy]
);
const buttonStyle = {
fontSize: isChild ? '0.85em' : '1em',
};
return (
<>
<EuiScreenReaderOnly>
<div aria-live="polite" role="status">
{a11yAnnouncement}
</div>
</EuiScreenReaderOnly>
<GroupNameContainer>
<Inner isChild={isChild}>
<Name>
<EuiToolTip position="top" content={group.name}>
<EuiLink
style={linkStyle}
onClickCapture={this.handleClick}
data-test-subj="groupNameLink"
<EuiButtonEmpty
aria-label={i18n.translate('xpack.infra.inventory.groupBySelectorButtonLabel', {
defaultMessage: 'Group by {group}',
values: { group: group.name },
})}
style={buttonStyle}
onClick={handleClick}
data-test-subj="groupNameButton"
>
{group.name}
</EuiLink>
</EuiButtonEmpty>
</EuiToolTip>
</Name>
<Count>{group.count}</Count>
</Inner>
</GroupNameContainer>
);
}
private handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
const { groupBy } = this.props.options;
// When groupBy is empty that means there is nothing todo so let's just do nothing.
if (groupBy.length === 0) {
return;
}
const currentPath = this.props.isChild && groupBy.length > 1 ? groupBy[1] : groupBy[0];
this.props.onDrilldown(`${currentPath.field}: "${this.props.group.name}"`);
};
}
</>
);
};
const GroupNameContainer = styled.div`
position: relative;

View file

@ -259,10 +259,6 @@ export const LegendControls = ({
checked={draftLegend.reverseColors}
onChange={handleReverseColors}
compressed
style={{
position: 'relative',
top: '8px',
}}
/>
</EuiFormRow>
<EuiFormRow
@ -281,10 +277,6 @@ export const LegendControls = ({
checked={draftAuto}
onChange={handleAutoChange}
compressed
style={{
position: 'relative',
top: '8px',
}}
/>
</EuiFormRow>
<EuiFormRow

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { EuiColorPickerSwatch, EuiText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiText, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
export interface Props {
@ -17,7 +18,15 @@ export const SwatchLabel = ({ label, color }: Props) => {
return (
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<EuiColorPickerSwatch color={color} />
<EuiIcon
type="stopFilled"
color={color}
size="xl"
aria-label={i18n.translate('xpack.infra.legendControls.iconColorLabel', {
defaultMessage: '{label} color',
values: { label },
})}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiText size="xs">

View file

@ -129,8 +129,8 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide
await comboBox.setElement(groupByCustomField, field);
await testSubjects.click('groupByCustomFieldAddButton');
await this.waitForLoading();
const groupNameLinks = await testSubjects.findAll('groupNameLink');
return Promise.all(groupNameLinks.map(async (link) => link.getVisibleText()));
const groupNameButtons = await testSubjects.findAll('groupNameButton');
return Promise.all(groupNameButtons.map((link) => link.getVisibleText()));
},
async enterSearchTerm(query: string) {