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

Add ScreenReaderText component #2278

Merged
merged 5 commits into from
Aug 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fix [#2245](https://github.com/microsoft/BotFramework-WebChat/issues/2245). Fix speech synthesis not working on Safari by priming the engine on the first microphone button click, by [@compulim](https://github.com/compulim) in PR [#2246](https://github.com/microsoft/BotFramework-WebChat/pull/2246)
- Fix [#1514](https://github.com/microsoft/BotFramework-WebChat/issues/1514). Added reference grammar ID when using Cognitive Services Speech Services, by [@compulim](https://github.com/compulim) in PR [#2246](https://github.com/microsoft/BotFramework-WebChat/pull/2246)
- Fix [#1515](https://github.com/microsoft/BotFramework-WebChat/issues/1515). Added dynamic phrases when using Cognitive Services Speech Services, by [@compulim](https://github.com/compulim) in PR [#2246](https://github.com/microsoft/BotFramework-WebChat/pull/2246)
- Fix [#2273](https://github.com/microsoft/BotFramework-WebChat/issues/2273). Add `ScreenReaderText` component, by [@corinagum](https://github.com/corinagum) in PR [#2278](https://github.com/microsoft/BotFramework-WebChat/pull/2278)


### Added

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ class AdaptiveCardRenderer extends React.PureComponent {

if (failures.length) {
// TODO: [P3] Since this can be called from `componentDidUpdate` and potentially error, we should fix a better way to propagate the error.
const errors = failures.map(({ errors }) => errors).flat();

const errors = failures.reduce((items, { errors }) => [...items, ...errors], []);
return this.setState(() => ({ error: errors }));
}

Expand Down
10 changes: 8 additions & 2 deletions packages/component/src/Activity/CarouselFilmStrip.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import React from 'react';

import { Constants } from 'botframework-webchat-core';

import { localize } from '../Localization/Localize';
import Avatar from './Avatar';
import Bubble from './Bubble';
import connectToWebChat from '../connectToWebChat';
import ScreenReaderText from '../ScreenReaderText';
import SendStatus from './SendStatus';
import textFormatToContentType from '../Utils/textFormatToContentType';
import Timestamp from './Timestamp';
Expand Down Expand Up @@ -88,6 +90,7 @@ const WebChatCarouselFilmStrip = ({
children,
className,
itemContainerRef,
language,
scrollableRef,
styleSet,
timestampClassName
Expand Down Expand Up @@ -115,7 +118,8 @@ const WebChatCarouselFilmStrip = ({
<div className="content">
{!!activityDisplayText && (
<div className="message">
<Bubble aria-hidden={true} className="bubble" fromUser={fromUser} nub={true}>
<ScreenReaderText text={fromUser ? localize('UserSent', language) : localize('BotSent', language)} />
<Bubble className="bubble" fromUser={fromUser} nub={true}>
{children({
activity,
attachment: {
Expand All @@ -130,7 +134,8 @@ const WebChatCarouselFilmStrip = ({
<ul className={classNames({ webchat__carousel__item_indented: indented })} ref={itemContainerRef}>
{attachments.map((attachment, index) => (
<li key={index}>
<Bubble aria-hidden={true} fromUser={fromUser} key={index} nub={false}>
<ScreenReaderText text={fromUser ? localize('UserSent', language) : localize('BotSent', language)} />
<Bubble fromUser={fromUser} key={index} nub={false}>
{children({ attachment })}
</Bubble>
</li>
Expand Down Expand Up @@ -175,6 +180,7 @@ WebChatCarouselFilmStrip.propTypes = {
children: PropTypes.any,
className: PropTypes.string,
itemContainerRef: PropTypes.any.isRequired,
language: PropTypes.string.isRequired,
scrollableRef: PropTypes.any.isRequired,
styleSet: PropTypes.shape({
carouselFilmStrip: PropTypes.any.isRequired
Expand Down
4 changes: 2 additions & 2 deletions packages/component/src/Activity/SendStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';

import { localize } from '../Localization/Localize';
import connectToWebChat from '../connectToWebChat';
import ScreenReaderText from '../ScreenReaderText';

const {
ActivityClientState: { SEND_FAILED, SENDING }
Expand Down Expand Up @@ -36,8 +37,7 @@ const SendStatus = ({ activity: { channelData: { state } = {} }, language, retry

return (
<React.Fragment>
{/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers in Edge v44 */}
<span aria-label={localizedSendStatus + localizedSending} />
<ScreenReaderText text={localizedSendStatus + localizedSending} />
<span aria-hidden={true} className={styleSet.sendStatus}>
{state === SENDING ? (
localizedSending
Expand Down
7 changes: 3 additions & 4 deletions packages/component/src/Activity/StackedLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { localize } from '../Localization/Localize';
import Avatar from './Avatar';
import Bubble from './Bubble';
import connectToWebChat from '../connectToWebChat';
import ScreenReaderText from '../ScreenReaderText';
import SendStatus from './SendStatus';
import textFormatToContentType from '../Utils/textFormatToContentType';
import Timestamp from './Timestamp';
Expand Down Expand Up @@ -132,8 +133,7 @@ const StackedLayout = ({ activity, avatarInitials, children, language, styleSet,
) : (
!!activityDisplayText && (
<div className="webchat__row message">
{/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers in Edge v44 */}
<span aria-label={ariaLabel} />
<ScreenReaderText text={ariaLabel} />
<Bubble aria-hidden={true} className="bubble" fromUser={fromUser} nub={true}>
{children({
activity,
Expand All @@ -154,8 +154,7 @@ const StackedLayout = ({ activity, avatarInitials, children, language, styleSet,
className={classNames('webchat__row attachment', { webchat__stacked_item_indented: indented })}
key={index}
>
{/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers in Edge v44 */}
<span aria-label={fromUser ? localize('UserSent', language) : localize('BotSent', language)} />
<ScreenReaderText text={fromUser ? localize('UserSent', language) : localize('BotSent', language)} />
<Bubble className="attachment bubble" fromUser={fromUser} key={index} nub={false}>
{children({ attachment })}
</Bubble>
Expand Down
4 changes: 2 additions & 2 deletions packages/component/src/Activity/Timestamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import PropTypes from 'prop-types';
import React from 'react';

import connectToWebChat from '../connectToWebChat';
import TimeAgo from '../Utils/TimeAgo';
import RelativeTime from '../Utils/RelativeTime';

const Timestamp = ({ activity: { timestamp }, className, styleSet }) => (
<span className={classNames(styleSet.timestamp + '', (className || '') + '')}>
<TimeAgo value={timestamp} />
<RelativeTime value={timestamp} />
</span>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import PropTypes from 'prop-types';
import React from 'react';

import connectToWebChat from '../../connectToWebChat';
import ScreenReaderText from '../../ScreenReaderText';

const TypingAnimation = ({ 'aria-label': ariaLabel, styleSet }) => (
<React.Fragment>
<span aria-label={ariaLabel} />
<ScreenReaderText text={ariaLabel} />
<div aria-hidden={true} className={styleSet.typingAnimation} />
</React.Fragment>
);
Expand Down
9 changes: 5 additions & 4 deletions packages/component/src/Attachment/DownloadAttachment.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { format } from 'bytes';
import PropTypes from 'prop-types';
import React from 'react';

import { localize } from '../Localization/Localize';
import connectToWebChat from '../connectToWebChat';
import DownloadIcon from './Assets/DownloadIcon';
import { localize } from '../Localization/Localize';
import ScreenReaderText from '../ScreenReaderText';

const DownloadAttachment = ({
activity: { attachments = [], channelData: { attachmentSizes = [] } = {} } = {},
Expand All @@ -25,11 +26,11 @@ const DownloadAttachment = ({
);
return (
<React.Fragment>
{/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers in Edge v44 */}
<span aria-label={downloadFileWithFileSizeLabel} />
<ScreenReaderText text={downloadFileWithFileSizeLabel} />
<div aria-hidden={true} className={styleSet.downloadAttachment}>
<a href={attachment.contentUrl} rel="noopener noreferrer" target="_blank">
<div className="details">
{/* Although nested, Chrome v75 does not respect the above aria-hidden and makes the below aria-hidden necessary */}
<div aria-hidden={true} className="details">
<div className="name">{attachment.name}</div>
<div className="size">{formattedSize}</div>
</div>
Expand Down
7 changes: 3 additions & 4 deletions packages/component/src/Attachment/TextContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import PropTypes from 'prop-types';
import React from 'react';

import connectToWebChat from '../connectToWebChat';
import ScreenReaderText from '../ScreenReaderText';

const TextContent = ({ contentType, renderMarkdown, styleSet, text }) =>
contentType === 'text/markdown' && renderMarkdown ? (
<React.Fragment>
{/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers in Edge v44 */}
<span aria-label={text} />
<ScreenReaderText text={text} />
<div
aria-hidden={true}
className={classNames('markdown', styleSet.textContent + '')}
Expand All @@ -22,8 +22,7 @@ const TextContent = ({ contentType, renderMarkdown, styleSet, text }) =>
) : (
(text || '').split('\n').map((line, index) => (
<React.Fragment key={index}>
{/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers in Edge v44 */}
<span aria-label={text} />
<ScreenReaderText text={text} />
<p aria-hidden={true} className={classNames('plain', styleSet.textContent + '')}>
{line.trim()}
</p>
Expand Down
6 changes: 3 additions & 3 deletions packages/component/src/Attachment/UploadAttachment.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';

import connectToWebChat from '../connectToWebChat';
import { localize } from '../Localization/Localize';
import connectToWebChat from '../connectToWebChat';
import ScreenReaderText from '../ScreenReaderText';

const ROOT_CSS = css({
display: 'flex',
Expand All @@ -24,8 +25,7 @@ const UploadAttachment = ({
const uploadFileWithFileSizeLabel = localize('UploadFileWithFileSize', language, attachment.name, formattedSize);
return (
<React.Fragment>
{/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers in Edge v44 */}
<span aria-label={uploadFileWithFileSizeLabel} />
<ScreenReaderText text={uploadFileWithFileSizeLabel} />
<div aria-hidden={true} className={classNames(ROOT_CSS + '', styleSet.uploadAttachment + '')}>
<div className="name">{attachment.name}</div>
<div className="size">{formattedSize}</div>
Expand Down
14 changes: 9 additions & 5 deletions packages/component/src/ErrorBox.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import PropTypes from 'prop-types';
import React from 'react';

import connectToWebChat from './connectToWebChat';
import { localize } from './Localization/Localize';
import connectToWebChat from './connectToWebChat';
import ScreenReaderText from './ScreenReaderText';

const ErrorBox = ({ children, language, message, styleSet }) => (
<div aria-label={localize('ErrorMessage', language)} className={styleSet.errorBox}>
<div aria-label={message}>{message}</div>
<div>{children}</div>
</div>
<React.Fragment>
<ScreenReaderText text={localize('ErrorMessage', language)} />
<div className={styleSet.errorBox}>
<div>{message}</div>
<div>{children}</div>
</div>
</React.Fragment>
);

ErrorBox.defaultProps = {
Expand Down
1 change: 1 addition & 0 deletions packages/component/src/Localization/en-US.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function userSaidSomething(avatarInitials, text) {
}

export default {
CONNECTED_NOTIFICATION: 'Connected',
FAILED_CONNECTION_NOTIFICATION: 'Unable to connect.',
INITIAL_CONNECTION_NOTIFICATION: 'Connecting…',
INTERRUPTED_CONNECTION_NOTIFICATION: 'Network interruption occurred. Reconnecting…',
Expand Down
31 changes: 31 additions & 0 deletions packages/component/src/ScreenReaderText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { css } from 'glamor';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';

import connectToWebChat from './connectToWebChat';

const ROOT_CSS = css({
// .sr-only - This component is intended to be invisible to the visual Web Chat user, but read by the AT when using a screen reader
color: 'transparent',
height: 1,
left: -10000,
overflow: 'hidden',
position: 'absolute',
top: 0,
whiteSpace: 'nowrap',
width: 1
});

const ScreenReaderText = ({ text }) => (
/* Because of differences in browser implementations, <span aria-label> is used to make the screen reader perform the same on different browsers. This workaround was made to accommodate Edge v44 */
<span aria-label={text} className={classNames(ROOT_CSS + '')}>
{text}
</span>
);

ScreenReaderText.propTypes = {
text: PropTypes.string.isRequired
};

export default connectToWebChat()(ScreenReaderText);
98 changes: 67 additions & 31 deletions packages/component/src/SendBox/ConnectivityStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React from 'react';
import { localize } from '../Localization/Localize';
import connectToWebChat from '../connectToWebChat';
import ErrorNotificationIcon from '../Attachment/Assets/ErrorNotificationIcon';
import ScreenReaderText from '../ScreenReaderText';
import SpinnerAnimation from '../Attachment/Assets/SpinnerAnimation';
import WarningNotificationIcon from '../Attachment/Assets/WarningNotificationIcon';

Expand Down Expand Up @@ -62,42 +63,77 @@ const connectConnectivityStatus = (...selectors) =>
connectToWebChat(({ connectivityStatus, language }) => ({ connectivityStatus, language }), ...selectors);

const ConnectivityStatus = ({ connectivityStatus, language, styleSet }) => (
<div aria-live="polite" role="status">
<div aria-atomic="false" aria-live="polite" role="status">
<DebouncedConnectivityStatus
interval={
connectivityStatus === 'uninitialized' || connectivityStatus === 'error' ? 0 : CONNECTIVITY_STATUS_DEBOUNCE
}
>
{() =>
connectivityStatus === 'connectingslow' ? (
<div className={styleSet.warningNotification}>
<WarningNotificationIcon />
{localize('SLOW_CONNECTION_NOTIFICATION', language)}
</div>
) : connectivityStatus === 'error' || connectivityStatus === 'notconnected' ? (
<div className={styleSet.errorNotification}>
<ErrorNotificationIcon />
{localize('FAILED_CONNECTION_NOTIFICATION', language)}
</div>
) : connectivityStatus === 'uninitialized' ? (
<div className={styleSet.connectivityNotification}>
<SpinnerAnimation />
{localize('INITIAL_CONNECTION_NOTIFICATION', language)}
</div>
) : connectivityStatus === 'reconnecting' ? (
<div className={styleSet.connectivityNotification}>
<SpinnerAnimation />
{localize('INTERRUPTED_CONNECTION_NOTIFICATION', language)}
</div>
) : connectivityStatus === 'sagaerror' ? (
<div className={styleSet.errorNotification}>
<ErrorNotificationIcon />
{localize('RENDER_ERROR_NOTIFICATION', language)}
</div>
) : (
connectivityStatus === 'reconnected' || (connectivityStatus === 'connected' && false)
)
}
{() => {
if (connectivityStatus === 'connectingslow') {
const localizedText = localize('SLOW_CONNECTION_NOTIFICATION', language);

return (
<React.Fragment>
<ScreenReaderText text={localizedText} />
<div aria-hidden={true} className={styleSet.warningNotification}>
<WarningNotificationIcon />
{localizedText}
</div>
</React.Fragment>
);
} else if (connectivityStatus === 'error' || connectivityStatus === 'notconnected') {
const localizedText = localize('FAILED_CONNECTION_NOTIFICATION', language);

return (
<React.Fragment>
<ScreenReaderText text={localizedText} />
<div aria-hidden={true} className={styleSet.errorNotification}>
<ErrorNotificationIcon />
{localizedText}
</div>
</React.Fragment>
);
} else if (connectivityStatus === 'uninitialized') {
const localizedText = localize('INITIAL_CONNECTION_NOTIFICATION', language);

return (
<React.Fragment>
<ScreenReaderText text={localizedText} />
<div aria-hidden={true} className={styleSet.connectivityNotification}>
<SpinnerAnimation />
{localizedText}
</div>
</React.Fragment>
);
} else if (connectivityStatus === 'reconnecting') {
const localizedText = localize('INTERRUPTED_CONNECTION_NOTIFICATION', language);

return (
<React.Fragment>
<ScreenReaderText text={localizedText} />
<div aria-hidden={true} className={styleSet.connectivityNotification}>
<SpinnerAnimation />
{localizedText}
</div>
</React.Fragment>
);
} else if (connectivityStatus === 'sagaerror') {
const localizedText = localize('RENDER_ERROR_NOTIFICATION', language);

return (
<React.Fragment>
<ScreenReaderText text={localizedText} />
<div className={styleSet.errorNotification}>
<ErrorNotificationIcon />
{localizedText}
</div>
</React.Fragment>
);
}

return <ScreenReaderText text={localize('CONNECTED_NOTIFICATION', language)} />;
}}
</DebouncedConnectivityStatus>
</div>
);
Expand Down
Loading