Skip to content

Commit

Permalink
Add support for screen reader accessibility.
Browse files Browse the repository at this point in the history
  • Loading branch information
cjcenizal committed Mar 28, 2018
1 parent 3b118f3 commit dbab330
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ exports[`EuiComboBox is rendered 1`] = `
style="font-size:14px;display:inline-block"
>
<input
role="combobox"
aria-hidden="true"
style="box-sizing:content-box;width:1px"
value=""
/>
Expand Down
5 changes: 4 additions & 1 deletion src/components/combo_box/combo_box.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ export class EuiComboBox extends Component {
'euiComboBox-isOpen': this.state.isListOpen,
});

const value = selectedOptions.map(selectedOption => selectedOption.label).join(', ');

return (
<div
className={classes}
Expand All @@ -397,7 +399,8 @@ export class EuiComboBox extends Component {
onClick={this.onComboBoxClick}
onChange={this.onSearchChange}
onFocus={this.openList}
value={searchValue}
value={value}
searchValue={searchValue}
autoSizeInputRef={this.autoSizeInputRef}
inputRef={this.searchInputRef}
/>
Expand Down
164 changes: 111 additions & 53 deletions src/components/combo_box/combo_box_input/combo_box_input.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,124 @@
import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AutosizeInput from 'react-input-autosize';

import { EuiScreenReaderOnly } from '../../accessibility';
import { EuiFormControlLayout, EuiValidatableControl } from '../../form';
import { EuiComboBoxPill } from './combo_box_pill';
import { htmlIdGenerator } from '../../../services';

export const EuiComboBoxInput = ({
selectedOptions,
onRemoveOption,
onClick,
onFocus,
onChange,
value,
autoSizeInputRef,
inputRef,
}) => {
const pills = selectedOptions.map((option) => {
const makeId = htmlIdGenerator();

export class EuiComboBoxInput extends Component {
constructor(props) {
super(props);

this.state = {
hasFocus: false,
};
}

onFocus = () => {
this.props.onFocus();
this.setState({
hasFocus: true,
});
};

onBlur = () => {
this.setState({
hasFocus: false,
});
};

render() {
const {
selectedOptions,
onRemoveOption,
onClick,
onChange,
value,
label,
...rest
} = option;
searchValue,
autoSizeInputRef,
inputRef,
} = this.props;

const pills = selectedOptions.map((option) => {
const {
value,
label,
...rest
} = option;

return (
<EuiComboBoxPill
option={option}
onClose={onRemoveOption}
key={value}
{...rest}
>
{label}
</EuiComboBoxPill>
)
});

let removeOptionMessage;
let removeOptionMessageId;

if (this.state.hasFocus) {
const removeOptionMessageContent =
`Combo box. Selected. ` +
(searchValue ? `${searchValue}. Selected. ` : '') +
(selectedOptions.length ? `${value}. Unselected. Press Backspace to delete ${selectedOptions[selectedOptions.length - 1].label}. ` : '') +
`You are currently on a combo box. Type text or, to display a list of choices, press Down Arrow. ` +
`To exit the list of choices, press Escape.`;

removeOptionMessageId = makeId();

// aria-live="assertive" will read this message aloud immediately once it enters the DOM.
// We'll render to the DOM when the input gains focus and remove it when the input loses focus.
// We'll use aria-hidden to prevent default aria information from being read by the screen
// reader.
removeOptionMessage = (
<EuiScreenReaderOnly>
<span aria-live="assertive" id={removeOptionMessageId}>
{removeOptionMessageContent}
</span>
</EuiScreenReaderOnly>
);
}

return (
<EuiComboBoxPill
option={option}
onClose={onRemoveOption}
key={value}
{...rest}
<EuiFormControlLayout
icon="arrowDown"
iconSide="right"
>
{label}
</EuiComboBoxPill>
)
});

return (
<EuiFormControlLayout
icon="arrowDown"
iconSide="right"
>
<div
className="euiComboBox__inputWrap"
onClick={onClick}
data-test-subj="comboBoxInput"
>
{pills}

<EuiValidatableControl isInvalid={false}>
<AutosizeInput
role="combobox"
style={{ fontSize: 14 }}
className="euiComboBox__input"
onFocus={onFocus}
onChange={(e) => onChange(e.target.value)}
value={value}
ref={autoSizeInputRef}
inputRef={inputRef}
/>
</EuiValidatableControl>
</div>
</EuiFormControlLayout>
);
};
<div
className="euiComboBox__inputWrap"
onClick={onClick}
data-test-subj="comboBoxInput"
>
{pills}

<EuiValidatableControl isInvalid={false}>
<AutosizeInput
aria-hidden
style={{ fontSize: 14 }}
className="euiComboBox__input"
onFocus={this.onFocus}
onBlur={this.onBlur}
onChange={(e) => onChange(e.target.value)}
value={searchValue}
ref={autoSizeInputRef}
inputRef={inputRef}
/>
</EuiValidatableControl>
{removeOptionMessage}
</div>
</EuiFormControlLayout>
);
}
}

EuiComboBoxInput.propTypes = {
selectedOptions: PropTypes.array,
Expand All @@ -70,6 +127,7 @@ EuiComboBoxInput.propTypes = {
onFocus: PropTypes.func,
onChange: PropTypes.func,
value: PropTypes.string,
searchValue: PropTypes.string,
autoSizeInputRef: PropTypes.func,
inputRef: PropTypes.func,
};
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class EuiComboBoxOption extends Component {

return (
<button
role="option"
className={classes}
onClick={this.onClick}
onKeyDown={this.onKeyDown}
Expand Down

0 comments on commit dbab330

Please sign in to comment.