Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SR-only help text to EuiPopover and dependent components. #6589

Merged
merged 20 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5cb12af
WIP. Updating EuiPopover for focusable content instructions.
1Copenut Dec 19, 2022
4a5f6a4
Merge branch 'main' into feature/screen-reader-dialogue
1Copenut Feb 6, 2023
7232812
Added SR-only help text to EuiPopover and dependent components.
1Copenut Feb 8, 2023
3366a9d
Added CHANGELOG entry.
1Copenut Feb 9, 2023
0b901c3
Fixed the EuiSuggest CodeSandbox demo.
1Copenut Feb 9, 2023
32a303c
Added i18n to EuiRange. Refactored ARIA logic for simpler reasoning.
1Copenut Feb 10, 2023
87dc3f3
Simplified the screen reader message for EuiPopover.
1Copenut Feb 14, 2023
7ea121e
Removing unneeded type isTabbable.
1Copenut Feb 14, 2023
dade2f3
Update upcoming_changelogs/6589.md
1Copenut Mar 1, 2023
3e1adc4
Reducing redundant SR-only help text.
1Copenut Mar 1, 2023
030c02b
Merge branch 'feature/screen-reader-dialogue' of github.com:1Copenut/…
1Copenut Mar 1, 2023
2b7ddd4
Simplified aria-describedby API for EuiSuggest.
1Copenut Mar 2, 2023
df24be2
Renaming EuiSelectable instructions prop.
1Copenut Mar 2, 2023
a913539
Merging main to resolve EuiSuggest snapshot conflict.
1Copenut Mar 2, 2023
2afdb27
Update EuiSuggest instructions to optionally prepend to existing.
1Copenut Mar 6, 2023
faedbef
Added unit test for custom aria-describedby string.
1Copenut Mar 6, 2023
082150d
Refactored EuiSelectable unit test to correctly find SR-only instruct…
1Copenut Mar 6, 2023
eb0d5f3
Updated screen reader selector with ID.
1Copenut Mar 6, 2023
d780a2e
Update src/components/selectable/selectable.tsx
1Copenut Mar 6, 2023
75c8076
Updating unit tests and snapshots for simpler instruction string conc…
1Copenut Mar 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src-docs/src/views/suggest/suggest_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const SuggestExample = {
{
source: [
{
type: GuideSectionTypes.JS,
type: GuideSectionTypes.TSX,
code: suggestSource,
},
],
Expand Down
3 changes: 3 additions & 0 deletions src/components/date_picker/auto_refresh/auto_refresh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ export const EuiAutoRefreshButton: FunctionComponent<EuiAutoRefreshButtonProps>
closePopover={() => {
setIsPopoverOpen(false);
}}
popoverScreenReaderText={
isPaused ? autoRefeshLabelOff : autoRefeshLabelOn
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
}
1Copenut marked this conversation as resolved.
Show resolved Hide resolved
>
<EuiRefreshInterval
onRefreshChange={onRefreshChange}
Expand Down
35 changes: 29 additions & 6 deletions src/components/form/range/dual_range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type { EuiDualRangeProps, _SingleRangeValue } from './types';

import { euiRangeStyles } from './range.styles';
import { euiDualRangeStyles } from './dual_range.styles';
import { EuiI18n } from '../../i18n';

type ValueMember = _SingleRangeValue['value'];

Expand Down Expand Up @@ -566,6 +567,13 @@ export class EuiDualRangeClass extends Component<
}
: rightThumbPosition;

const dualSliderScreenReaderInstructions = (
cee-chen marked this conversation as resolved.
Show resolved Hide resolved
<EuiI18n
token="euiDualRange.sliderScreenReaderInstructions"
default="You are in a custom range slider. Use the Up and Down arrow keys to change the minimum value. Press Tab to interact with the maximum value."
/>
);

const theRange = (
<EuiRangeWrapper
css={cssStyles}
Expand Down Expand Up @@ -647,8 +655,14 @@ export class EuiDualRangeClass extends Component<
onFocus={this.onThumbFocus}
onBlur={this.onThumbBlur}
onKeyDown={this.handleDraggableKeyDown}
aria-describedby={this.props['aria-describedby']}
aria-label={this.props['aria-label']}
aria-describedby={
showInputOnly
? undefined
: this.props['aria-describedby']
}
aria-label={
showInputOnly ? undefined : this.props['aria-label']
}
/>
)}

Expand All @@ -663,8 +677,12 @@ export class EuiDualRangeClass extends Component<
onFocus={this.onThumbFocus}
onBlur={this.onThumbBlur}
style={logicalStyles(leftThumbStyles)}
aria-describedby={this.props['aria-describedby']}
aria-label={this.props['aria-label']}
aria-describedby={
showInputOnly ? undefined : this.props['aria-describedby']
}
aria-label={
showInputOnly ? undefined : this.props['aria-label']
}
/>

<EuiRangeThumb
Expand All @@ -678,8 +696,12 @@ export class EuiDualRangeClass extends Component<
onFocus={this.onThumbFocus}
onBlur={this.onThumbBlur}
style={logicalStyles(rightThumbStyles)}
aria-describedby={this.props['aria-describedby']}
aria-label={this.props['aria-label']}
aria-describedby={
showInputOnly ? undefined : this.props['aria-describedby']
}
aria-label={
showInputOnly ? undefined : this.props['aria-label']
}
/>
</React.Fragment>
)}
Expand Down Expand Up @@ -740,6 +762,7 @@ export class EuiDualRangeClass extends Component<
closePopover={this.closePopover}
disableFocusTrap={true}
onPanelResize={this.onResize}
popoverScreenReaderText={dualSliderScreenReaderInstructions}
>
{theRange}
</EuiInputPopover>
Expand Down
13 changes: 11 additions & 2 deletions src/components/form/range/range.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { EuiRangeWrapper } from './range_wrapper';
import type { EuiRangeProps } from './types';

import { euiRangeStyles } from './range.styles';
import { EuiI18n } from '../../i18n';

export class EuiRangeClass extends Component<
EuiRangeProps & WithEuiThemeProps
Expand Down Expand Up @@ -179,6 +180,13 @@ export class EuiRangeClass extends Component<
const cssStyles = [styles.euiRange, showInput && styles.hasInput];
const thumbColor = levels && getLevelColor(levels, Number(value));

const sliderScreenReaderInstructions = (
<EuiI18n
token="euiRange.sliderScreenReaderInstructions"
default="You are in a custom range slider. Use the Up and Down arrow keys to change the value."
/>
);

const theRange = (
<EuiRangeWrapper
className={classes}
Expand All @@ -203,7 +211,7 @@ export class EuiRangeClass extends Component<
levels={levels}
onChange={this.handleOnChange}
value={value}
aria-hidden={showInput === true}
aria-hidden={!!showInput}
showRange={showRange}
>
{(trackWidth) => (
Expand All @@ -227,7 +235,7 @@ export class EuiRangeClass extends Component<
}
onFocus={showInput === true ? undefined : onFocus}
onBlur={showInputOnly ? this.onInputBlur : onBlur}
aria-hidden={showInput === true ? true : false}
aria-hidden={!!showInput}
thumbColor={thumbColor}
{...rest}
/>
Expand Down Expand Up @@ -291,6 +299,7 @@ export class EuiRangeClass extends Component<
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
disableFocusTrap={true}
popoverScreenReaderText={sliderScreenReaderInstructions}
>
{theRange}
</EuiInputPopover>
Expand Down
1 change: 1 addition & 0 deletions src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ export class EuiPopover extends Component<Props, State> {
let focusTrapScreenReaderText;
if (ownFocus || popoverScreenReaderText) {
ariaDescribedby = this.descriptionId;

focusTrapScreenReaderText = (
<EuiScreenReaderOnly>
<p id={this.descriptionId}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@ exports[`EuiSelectable custom options with data 1`] = `
<div
class="euiSelectable"
>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id_instructions"
>
Filter options
</p>
<div
class="euiSelectableList"
data-test-subj="euiSelectableList"
Expand Down Expand Up @@ -119,12 +113,6 @@ exports[`EuiSelectable errorMessage prop can render an element as the message 1`
<div
class="euiSelectable"
>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id_instructions"
>
Filter options
</p>
<div
class="euiText euiSelectableMessage emotion-euiText-xs-euiTextColor-subdued"
data-test-subj="euiSelectableMessage"
Expand All @@ -141,12 +129,6 @@ exports[`EuiSelectable errorMessage prop does not render the message when not de
<div
class="euiSelectable"
>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id_instructions"
>
Filter options
</p>
<div
class="euiSelectableList"
data-test-subj="euiSelectableList"
Expand Down Expand Up @@ -257,12 +239,6 @@ exports[`EuiSelectable errorMessage prop does renders the message when defined 1
<div
class="euiSelectable"
>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id_instructions"
>
Filter options
</p>
<div
class="euiText euiSelectableMessage emotion-euiText-xs-euiTextColor-subdued"
data-test-subj="euiSelectableMessage"
Expand Down Expand Up @@ -357,12 +333,6 @@ exports[`EuiSelectable search value supports inheriting initialSearchValue from
</p>
</div>
</div>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id_instructions"
>
Filter options
</p>
<div
class="euiText euiSelectableMessage emotion-euiText-xs-euiTextColor-subdued"
data-test-subj="euiSelectableMessage"
Expand All @@ -383,6 +353,7 @@ exports[`EuiSelectable search value supports inheriting initialSearchValue from
>
<input
aria-activedescendant=""
aria-describedby="generated-id_instructions"
aria-haspopup="listbox"
aria-label="Filter options"
autocomplete="off"
Expand Down Expand Up @@ -422,6 +393,12 @@ exports[`EuiSelectable search value supports inheriting initialSearchValue from
</div>
</div>
</div>
<p
class="emotion-euiScreenReaderOnly"
id="generated-id_instructions"
>
Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options.
</p>
</div>
`;

Expand Down
81 changes: 46 additions & 35 deletions src/components/selectable/selectable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ export type EuiSelectableProps<T = {}> = CommonProps &
* Default: false
*/
isPreFiltered?: boolean;
/**
* Screen readers will announce the ariaDescriptiveInstructions after they
* read the `EuiSelectable` label and a brief pause
*/
ariaDescriptiveInstructions?: string;
1Copenut marked this conversation as resolved.
Show resolved Hide resolved
};

export interface EuiSelectableState<T> {
Expand Down Expand Up @@ -503,6 +508,7 @@ export class EuiSelectable<T = {}> extends Component<
noMatchesMessage,
emptyMessage,
errorMessage,
ariaDescriptiveInstructions,
isPreFiltered,
...rest
} = this.props;
Expand Down Expand Up @@ -673,26 +679,44 @@ export class EuiSelectable<T = {}> extends Component<
Object.keys(searchAccessibleName).length
);
const search = searchable ? (
<EuiI18n token="euiSelectable.placeholderName" default="Filter options">
{(placeholderName: string) => (
<EuiSelectableSearch<T>
key="listSearch"
options={options}
value={searchValue}
onChange={this.onSearchChange}
listId={this.optionsListRef.current ? this.listId : undefined} // Only pass the listId if it exists on the page
aria-activedescendant={this.makeOptionId(activeOptionIndex)} // the current faux-focused option
placeholder={placeholderName}
isPreFiltered={isPreFiltered ?? false}
inputRef={(node) => {
this.inputRef = node;
searchProps?.inputRef?.(node);
}}
{...(searchHasAccessibleName
? searchAccessibleName
: { 'aria-label': placeholderName })}
{...cleanedSearchProps}
/>
<EuiI18n
tokens={[
'euiSelectable.screenReaderInstructions',
'euiSelectable.placeholderName',
]}
defaults={[
'Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options.',
1Copenut marked this conversation as resolved.
Show resolved Hide resolved
'Filter options',
]}
>
{([screenReaderInstructions, placeholderName]: string[]) => (
<>
<EuiSelectableSearch<T>
aria-describedby={listAriaDescribedbyId}
1Copenut marked this conversation as resolved.
Show resolved Hide resolved
key="listSearch"
options={options}
value={searchValue}
onChange={this.onSearchChange}
listId={this.optionsListRef.current ? this.listId : undefined} // Only pass the listId if it exists on the page
aria-activedescendant={this.makeOptionId(activeOptionIndex)} // the current faux-focused option
placeholder={placeholderName}
isPreFiltered={isPreFiltered ?? false}
inputRef={(node) => {
this.inputRef = node;
searchProps?.inputRef?.(node);
}}
{...(searchHasAccessibleName
? searchAccessibleName
: { 'aria-label': placeholderName })}
{...cleanedSearchProps}
/>

<EuiScreenReaderOnly>
<p id={listAriaDescribedbyId}>
{ariaDescriptiveInstructions ?? screenReaderInstructions}
</p>
</EuiScreenReaderOnly>
</>
)}
</EuiI18n>
) : undefined;
Expand All @@ -718,17 +742,8 @@ export class EuiSelectable<T = {}> extends Component<
Object.keys(listAccessibleName).length
);
const list = (
<EuiI18n
tokens={[
'euiSelectable.screenReaderInstructions',
'euiSelectable.placeholderName',
]}
defaults={[
'Use up and down arrows to move focus over options. Enter to select. Escape to collapse options.',
'Filter options',
]}
>
{([placeholderName, screenReaderInstructions]: string[]) => (
<EuiI18n token="euiSelectable.placeholderName" default="Filter options">
{(placeholderName: string) => (
<>
{searchable && (
<EuiScreenReaderLive
Expand All @@ -738,10 +753,6 @@ export class EuiSelectable<T = {}> extends Component<
</EuiScreenReaderLive>
)}

<EuiScreenReaderOnly>
<p id={listAriaDescribedbyId}>{screenReaderInstructions}</p>
</EuiScreenReaderOnly>

{messageContent ? (
<EuiSelectableMessage
data-test-subj="euiSelectableMessage"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class EuiSelectableList<T> extends Component<EuiSelectableListProps<T>> {
}

if (typeof ariaDescribedby === 'string') {
ref.setAttribute('aria-labelledby', ariaDescribedby);
ref.setAttribute('aria-describedby', ariaDescribedby);
1Copenut marked this conversation as resolved.
Show resolved Hide resolved
}
}
};
Expand Down
Loading