mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Improves screen reader features of combo boxes in edit role screen (#153808)
## Summary Related to #27749 While the [EuiComboBox rebuild](https://github.com/elastic/eui/issues/2841) is in progress, this PR addresses missing aria properties and fixes option announcements for the `run as user` combo. It additionally adds an aria-label for the Spaces Navigation EuiSelectable that I found was missing. ### Testing - Add all sample data - Edit a non-reserved role - Turn on a screen reader (NVDA is recommended, but VoiceOver will do) - Tab through the controls on the Edit role screen. When you arrive in a combo box you may or may not hear an announcement declaring the name of the combo box (this is expected, especially with VoiceOver). - With focus in a combo box press the down arrow key. Verify the options are announced as you traverse them with the down and up arrow keys. - If there was success in option announcement, press escape key and verify that the name of the combo box is announced. You may have to press escape twice when using NVDA. Note: It seems to be the consensus that this is best we can do with the existing implementation of EuiComboBox, which is partly why it is being rebuilt with the EuiSelectable component.
This commit is contained in:
parent
327dd493bf
commit
b3d78e800e
6 changed files with 72 additions and 72 deletions
|
@ -6,6 +6,7 @@ exports[`it renders without crashing 1`] = `
|
|||
key="clusterPrivs"
|
||||
>
|
||||
<EuiComboBox
|
||||
aria-label="Cluster privileges"
|
||||
async={false}
|
||||
compressed={false}
|
||||
data-test-subj="cluster-privileges-combobox"
|
||||
|
|
|
@ -100,28 +100,21 @@ exports[`it renders without crashing 1`] = `
|
|||
</h3>
|
||||
}
|
||||
>
|
||||
<EuiFormRow
|
||||
describedByIds={Array []}
|
||||
display="row"
|
||||
hasChildLabel={true}
|
||||
hasEmptyLabelSpace={true}
|
||||
labelType="label"
|
||||
>
|
||||
<EuiComboBox
|
||||
async={false}
|
||||
compressed={false}
|
||||
fullWidth={false}
|
||||
isClearable={true}
|
||||
isDisabled={false}
|
||||
onChange={[Function]}
|
||||
onCreateOption={[Function]}
|
||||
options={Array []}
|
||||
placeholder="Add a user…"
|
||||
selectedOptions={Array []}
|
||||
singleSelection={false}
|
||||
sortMatchesBy="none"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiComboBox
|
||||
aria-label="Run as privileges"
|
||||
async={false}
|
||||
compressed={false}
|
||||
fullWidth={false}
|
||||
isClearable={true}
|
||||
isDisabled={false}
|
||||
onChange={[Function]}
|
||||
onCreateOption={[Function]}
|
||||
options={Array []}
|
||||
placeholder="Add a user…"
|
||||
selectedOptions={Array []}
|
||||
singleSelection={false}
|
||||
sortMatchesBy="none"
|
||||
/>
|
||||
</EuiDescribedFormGroup>
|
||||
<EuiSpacer />
|
||||
<EuiTitle
|
||||
|
|
|
@ -9,6 +9,8 @@ import { EuiComboBox, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import type { Role } from '../../../../../../common/model';
|
||||
import { isRoleReadOnly } from '../../../../../../common/model';
|
||||
|
||||
|
@ -41,6 +43,10 @@ export class ClusterPrivileges extends Component<Props, {}> {
|
|||
return (
|
||||
<EuiFlexItem key={'clusterPrivs'}>
|
||||
<EuiComboBox
|
||||
aria-label={i18n.translate(
|
||||
'xpack.security.management.editRole.clusterPrivilegeForm.clusterPrivilegesAriaLabel',
|
||||
{ defaultMessage: 'Cluster privileges' }
|
||||
)}
|
||||
data-test-subj={'cluster-privileges-combobox'}
|
||||
options={options}
|
||||
selectedOptions={selectedOptions}
|
||||
|
|
|
@ -118,27 +118,28 @@ export class ElasticsearchPrivileges extends Component<Props, {}> {
|
|||
</p>
|
||||
}
|
||||
>
|
||||
<EuiFormRow hasEmptyLabelSpace>
|
||||
<EuiComboBox
|
||||
placeholder={
|
||||
this.props.editable
|
||||
? i18n.translate(
|
||||
'xpack.security.management.editRole.elasticSearchPrivileges.addUserTitle',
|
||||
{ defaultMessage: 'Add a user…' }
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
options={this.props.runAsUsers.map((username) => ({
|
||||
id: username,
|
||||
label: username,
|
||||
isGroupLabelOption: false,
|
||||
}))}
|
||||
selectedOptions={this.props.role.elasticsearch.run_as.map((u) => ({ label: u }))}
|
||||
onCreateOption={this.onCreateRunAsOption}
|
||||
onChange={this.onRunAsUserChange}
|
||||
isDisabled={!editable}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiComboBox
|
||||
aria-label={i18n.translate(
|
||||
'xpack.security.management.editRole.elasticSearchPrivileges.runAsPrivilegesAriaLabel',
|
||||
{ defaultMessage: 'Run as privileges' }
|
||||
)}
|
||||
placeholder={
|
||||
this.props.editable
|
||||
? i18n.translate(
|
||||
'xpack.security.management.editRole.elasticSearchPrivileges.addUserTitle',
|
||||
{ defaultMessage: 'Add a user…' }
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
options={this.props.runAsUsers.map((username) => ({
|
||||
label: username,
|
||||
isGroupLabelOption: false,
|
||||
}))}
|
||||
selectedOptions={this.props.role.elasticsearch.run_as.map((u) => ({ label: u }))}
|
||||
onCreateOption={this.onCreateRunAsOption}
|
||||
onChange={this.onRunAsUserChange}
|
||||
isDisabled={!editable}
|
||||
/>
|
||||
</EuiDescribedFormGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
|
|
@ -241,18 +241,16 @@ export class IndexPrivilegeForm extends Component<Props, State> {
|
|||
) : undefined
|
||||
}
|
||||
>
|
||||
<Fragment>
|
||||
<EuiComboBox
|
||||
data-test-subj={`fieldInput${this.props.formIndex}`}
|
||||
options={this.state.flsOptions.map(toOption)}
|
||||
selectedOptions={grant.map(toOption)}
|
||||
onCreateOption={this.onCreateGrantedField}
|
||||
onChange={this.onGrantedFieldsChange}
|
||||
isDisabled={this.props.isRoleReadOnly}
|
||||
async={true}
|
||||
isLoading={this.state.isFieldListLoading}
|
||||
/>
|
||||
</Fragment>
|
||||
<EuiComboBox
|
||||
data-test-subj={`fieldInput${this.props.formIndex}`}
|
||||
options={this.state.flsOptions.map(toOption)}
|
||||
selectedOptions={grant.map(toOption)}
|
||||
onCreateOption={this.onCreateGrantedField}
|
||||
onChange={this.onGrantedFieldsChange}
|
||||
isDisabled={this.props.isRoleReadOnly}
|
||||
async={true}
|
||||
isLoading={this.state.isFieldListLoading}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -266,18 +264,16 @@ export class IndexPrivilegeForm extends Component<Props, State> {
|
|||
fullWidth={true}
|
||||
className="indexPrivilegeForm__deniedFieldsRow"
|
||||
>
|
||||
<Fragment>
|
||||
<EuiComboBox
|
||||
data-test-subj={`deniedFieldInput${this.props.formIndex}`}
|
||||
options={this.state.flsOptions.map(toOption)}
|
||||
selectedOptions={except.map(toOption)}
|
||||
onCreateOption={this.onCreateDeniedField}
|
||||
onChange={this.onDeniedFieldsChange}
|
||||
isDisabled={isRoleReadOnly}
|
||||
async={true}
|
||||
isLoading={this.state.isFieldListLoading}
|
||||
/>
|
||||
</Fragment>
|
||||
<EuiComboBox
|
||||
data-test-subj={`deniedFieldInput${this.props.formIndex}`}
|
||||
options={this.state.flsOptions.map(toOption)}
|
||||
selectedOptions={except.map(toOption)}
|
||||
onCreateOption={this.onCreateDeniedField}
|
||||
onChange={this.onDeniedFieldsChange}
|
||||
isDisabled={isRoleReadOnly}
|
||||
async={true}
|
||||
isLoading={this.state.isFieldListLoading}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -20,7 +20,7 @@ import type {
|
|||
EuiSelectableOnChangeEvent,
|
||||
EuiSelectableSearchableSearchProps,
|
||||
} from '@elastic/eui/src/components/selectable/selectable';
|
||||
import React, { Component, lazy, Suspense } from 'react';
|
||||
import React, { Component, Fragment, lazy, Suspense } from 'react';
|
||||
|
||||
import type { ApplicationStart, Capabilities } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -85,8 +85,11 @@ class SpacesMenuUI extends Component<Props> {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Fragment>
|
||||
<EuiSelectable
|
||||
aria-label={i18n.translate('xpack.spaces.navControl.spacesMenu.spacesAriaLabel', {
|
||||
defaultMessage: 'Spaces',
|
||||
})}
|
||||
id={this.props.id}
|
||||
className={'spcMenu'}
|
||||
title={i18n.translate('xpack.spaces.navControl.spacesMenu.changeCurrentSpaceTitle', {
|
||||
|
@ -105,7 +108,7 @@ class SpacesMenuUI extends Component<Props> {
|
|||
}}
|
||||
>
|
||||
{(list, search) => (
|
||||
<>
|
||||
<Fragment>
|
||||
<EuiPopoverTitle paddingSize="s">
|
||||
{search ||
|
||||
i18n.translate('xpack.spaces.navControl.spacesMenu.selectSpacesTitle', {
|
||||
|
@ -113,11 +116,11 @@ class SpacesMenuUI extends Component<Props> {
|
|||
})}
|
||||
</EuiPopoverTitle>
|
||||
{list}
|
||||
</>
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
<EuiPopoverFooter paddingSize="s">{this.renderManageButton()}</EuiPopoverFooter>
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue