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

WIP: [Accessibility] Make toast global list is an aria live region #1958

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dabd580
toasts live region
PhilippBaranovskiy May 16, 2019
d69cc46
Merge branch 'master' into 1947
PhilippBaranovskiy May 23, 2019
ba2bed2
update snapshost
PhilippBaranovskiy May 23, 2019
663b4fb
WIP: new notification logger; excludes 'dismiss toast' voice announce…
PhilippBaranovskiy May 28, 2019
0c0e96e
WIP: new notification logger; excludes 'dismiss toast' voice announce…
PhilippBaranovskiy May 28, 2019
f27f8af
update snapshots
PhilippBaranovskiy May 28, 2019
13eb817
WIP: clearing notification stack after an idle time
PhilippBaranovskiy May 29, 2019
2de11ef
Merge branch 'master' into 1947
PhilippBaranovskiy May 29, 2019
daff943
Merge branch 'master' into 1947
PhilippBaranovskiy May 30, 2019
5c3381a
updated CHANGEDLOG
PhilippBaranovskiy May 30, 2019
852c54a
adjusted time period
PhilippBaranovskiy May 30, 2019
dfa834c
review comments
PhilippBaranovskiy May 31, 2019
23a3342
Merge branch 'master' into 1947
PhilippBaranovskiy May 31, 2019
aa07a5c
Merge branch 'master' into 1947
PhilippBaranovskiy Jun 3, 2019
990732d
Merge branch 'master' into 1947
PhilippBaranovskiy Jun 4, 2019
2547260
Merge branch '1947' of https://github.com/rockfield/eui into 1947
PhilippBaranovskiy Jun 4, 2019
2595b38
Annoncing only the last toast notification with the help of <EuiDelay…
PhilippBaranovskiy Jun 4, 2019
6f24324
Merge branch 'master' into 1947
PhilippBaranovskiy Jun 4, 2019
257afef
Add screenReaderOnly way to announce a toast but not show it
PhilippBaranovskiy Jun 5, 2019
6277412
fix eslint review
PhilippBaranovskiy Jun 5, 2019
6adbf30
WIP: need to figure out how to re-render a toast in the logger withou…
PhilippBaranovskiy Jun 13, 2019
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Added ability to update `EuiInMemoryTable` `sorting` prop and remove columns after sorting is applied ([#1972](https://github.com/elastic/eui/pull/1972))
- Added `onToggle` callback to `EuiAccordion` ([#1974](https://github.com/elastic/eui/pull/1974))
- Removed `options` `defaultProps` value from `EuiSuperSelect` ([#1975](https://github.com/elastic/eui/pull/1975))
- Added a logger based on `Aria Live Region` to `EuiGlobalToastList` ([#1958](https://github.com/elastic/eui/pull/1958))

**Bug fixes**

Expand Down
40 changes: 27 additions & 13 deletions src/components/toast/__snapshots__/global_toast_list.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@ exports[`EuiGlobalToastList is rendered 1`] = `
aria-label="aria-label"
class="euiGlobalToastList testClass1 testClass2"
data-test-subj="test subject string"
/>
>
<div
aria-live="polite"
class="euiScreenReaderOnly"
role="region"
/>
</div>
`;

exports[`EuiGlobalToastList props toasts is rendered 1`] = `
<div
class="euiGlobalToastList"
>
<div
aria-live="polite"
class="euiToast euiToast--success euiGlobalToastListItem"
data-test-subj="a"
id="a"
>
<p
class="euiScreenReaderOnly"
>
A new notification appears
</p>
<div
aria-label="Notification"
class="euiToastHeader euiToastHeader--withBody"
Expand Down Expand Up @@ -66,16 +66,10 @@ exports[`EuiGlobalToastList props toasts is rendered 1`] = `
</div>
</div>
<div
aria-live="polite"
class="euiToast euiToast--danger euiGlobalToastListItem"
data-test-subj="b"
id="b"
>
<p
class="euiScreenReaderOnly"
>
A new notification appears
</p>
<div
aria-label="Notification"
class="euiToastHeader euiToastHeader--withBody"
Expand Down Expand Up @@ -118,5 +112,25 @@ exports[`EuiGlobalToastList props toasts is rendered 1`] = `
b
</div>
</div>
<div
aria-live="polite"
class="euiScreenReaderOnly"
role="region"
>
<p>
A new notification appears
</p>
<p>
A
</p>
a
<p>
A new notification appears
</p>
<p>
B
</p>
b
</div>
</div>
`;
84 changes: 0 additions & 84 deletions src/components/toast/__snapshots__/toast.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,8 @@ exports[`EuiToast Props color danger is rendered 1`] = `
color="danger"
>
<div
aria-live="polite"
className="euiToast euiToast--danger"
>
<EuiScreenReaderOnly>
<p
className="euiScreenReaderOnly"
>
<EuiI18n
default="A new notification appears"
token="euiToast.newNotification"
>
A new notification appears
</EuiI18n>
</p>
</EuiScreenReaderOnly>
<EuiI18n
default="Notification"
token="euiToast.notification"
Expand All @@ -43,21 +30,8 @@ exports[`EuiToast Props color primary is rendered 1`] = `
color="primary"
>
<div
aria-live="polite"
className="euiToast euiToast--primary"
>
<EuiScreenReaderOnly>
<p
className="euiScreenReaderOnly"
>
<EuiI18n
default="A new notification appears"
token="euiToast.newNotification"
>
A new notification appears
</EuiI18n>
</p>
</EuiScreenReaderOnly>
<EuiI18n
default="Notification"
token="euiToast.notification"
Expand All @@ -81,21 +55,8 @@ exports[`EuiToast Props color success is rendered 1`] = `
color="success"
>
<div
aria-live="polite"
className="euiToast euiToast--success"
>
<EuiScreenReaderOnly>
<p
className="euiScreenReaderOnly"
>
<EuiI18n
default="A new notification appears"
token="euiToast.newNotification"
>
A new notification appears
</EuiI18n>
</p>
</EuiScreenReaderOnly>
<EuiI18n
default="Notification"
token="euiToast.notification"
Expand All @@ -119,21 +80,8 @@ exports[`EuiToast Props color warning is rendered 1`] = `
color="warning"
>
<div
aria-live="polite"
className="euiToast euiToast--warning"
>
<EuiScreenReaderOnly>
<p
className="euiScreenReaderOnly"
>
<EuiI18n
default="A new notification appears"
token="euiToast.newNotification"
>
A new notification appears
</EuiI18n>
</p>
</EuiScreenReaderOnly>
<EuiI18n
default="Notification"
token="euiToast.notification"
Expand All @@ -157,21 +105,8 @@ exports[`EuiToast Props iconType is rendered 1`] = `
iconType="user"
>
<div
aria-live="polite"
className="euiToast"
>
<EuiScreenReaderOnly>
<p
className="euiScreenReaderOnly"
>
<EuiI18n
default="A new notification appears"
token="euiToast.newNotification"
>
A new notification appears
</EuiI18n>
</p>
</EuiScreenReaderOnly>
<EuiI18n
default="Notification"
token="euiToast.notification"
Expand Down Expand Up @@ -219,21 +154,8 @@ exports[`EuiToast Props title is rendered 1`] = `
title="toast title"
>
<div
aria-live="polite"
className="euiToast"
>
<EuiScreenReaderOnly>
<p
className="euiScreenReaderOnly"
>
<EuiI18n
default="A new notification appears"
token="euiToast.newNotification"
>
A new notification appears
</EuiI18n>
</p>
</EuiScreenReaderOnly>
<EuiI18n
default="Notification"
token="euiToast.notification"
Expand All @@ -257,15 +179,9 @@ exports[`EuiToast Props title is rendered 1`] = `
exports[`EuiToast is rendered 1`] = `
<div
aria-label="aria-label"
aria-live="polite"
class="euiToast testClass1 testClass2"
data-test-subj="test subject string"
>
<p
class="euiScreenReaderOnly"
>
A new notification appears
</p>
<div
aria-label="Notification"
class="euiToastHeader euiToastHeader--withBody"
Expand Down
64 changes: 63 additions & 1 deletion src/components/toast/global_toast_list.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { Component } from 'react';
import React, { Fragment, Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { Timer } from '../../services/time';
import { IconPropType } from '../icon';
import { EuiGlobalToastListItem } from './global_toast_list_item';
import { EuiToast } from './toast';
import { EuiScreenReaderOnly } from '../accessibility';
import { EuiI18n } from '../i18n';

export const TOAST_FADE_OUT_MS = 250;

Expand All @@ -27,6 +29,9 @@ export class EuiGlobalToastList extends Component {
// for information on initial value of 0
this.isScrollingAnimationFrame = 0;
this.startScrollingAnimationFrame = 0;

this.renderedForScreenReaderToasts = [];
this.clearScreenReaderToastStorageID = null;
}

static propTypes = {
Expand Down Expand Up @@ -172,6 +177,56 @@ export class EuiGlobalToastList extends Component {
});
};

clearScreenReaderToastStorage = () => {
this.renderedForScreenReaderToasts = [];
this.forceUpdate();
PhilippBaranovskiy marked this conversation as resolved.
Show resolved Hide resolved
};

getRenderedForScreenReaderToasts = () => {
return this.renderedForScreenReaderToasts.map(toast => toast.reactElement);
};

filterNewOnlyToasts = toasts => {
return toasts.filter(toastFromProp => {
const withTheSameID = this.renderedForScreenReaderToasts.filter(
PhilippBaranovskiy marked this conversation as resolved.
Show resolved Hide resolved
existToast => existToast.id === toastFromProp.id
);
return !withTheSameID.length;
});
};

logSetOfToasts = toasts => {
// map and filter incoming toasts to get only new ones
const newToasts = this.filterNewOnlyToasts(toasts).map(newToast => ({
id: newToast.id,
reactElement: (
<Fragment key={newToast.id}>
<p>
<EuiI18n
token="euiGlobalToastList.newNotification"
default="A new notification appears"
/>
</p>
<p>{newToast.title}</p>
{newToast.text}
</Fragment>
),
})); // returns element, if element is false, then it excludes the one

// add new incoming toasts to the stack
this.renderedForScreenReaderToasts.push(...newToasts);
// skip previous stack clearing
clearTimeout(this.clearScreenReaderToastStorageID);
// Set it to wait 57 seconds after the last notification before clear the stack
// 27s is the time chosen approx. That time is that Screen Reader needs to finish reading
// at least the last one notifications.
// It strictly depends on how long that notifications is.
this.clearScreenReaderToastStorageID = setTimeout(
this.clearScreenReaderToastStorage,
57000
PhilippBaranovskiy marked this conversation as resolved.
Show resolved Hide resolved
);
};

componentDidMount() {
this.listElement.addEventListener('scroll', this.onScroll);
this.listElement.addEventListener('mouseenter', this.onMouseEnter);
Expand Down Expand Up @@ -221,6 +276,8 @@ export class EuiGlobalToastList extends Component {
...rest
} = this.props;

this.logSetOfToasts(toasts);

const renderedToasts = toasts.map(toast => {
const {
text,
Expand Down Expand Up @@ -253,6 +310,11 @@ export class EuiGlobalToastList extends Component {
className={classes}
{...rest}>
{renderedToasts}
<EuiScreenReaderOnly>
<div role="region" aria-live="polite">
{this.getRenderedForScreenReaderToasts()}
</div>
</EuiScreenReaderOnly>
</div>
);
}
Expand Down
12 changes: 1 addition & 11 deletions src/components/toast/toast.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import { EuiScreenReaderOnly } from '../accessibility';
import { EuiI18n } from '../i18n';

import { IconPropType, EuiIcon } from '../icon';
Expand Down Expand Up @@ -75,16 +74,7 @@ export const EuiToast = ({
}

return (
<div className={classes} aria-live="polite" {...rest}>
<EuiScreenReaderOnly>
<p>
<EuiI18n
token="euiToast.newNotification"
default="A new notification appears"
/>
</p>
</EuiScreenReaderOnly>

<div className={classes} {...rest}>
<EuiI18n token="euiToast.notification" default="Notification">
{notification => (
<div
Expand Down