Skip to content

Commit

Permalink
microsoft#1499 Fix screen reader handling of name, activity, & timestamp
Browse files Browse the repository at this point in the history
  • Loading branch information
Corina committed Dec 21, 2018
1 parent 7b745f6 commit 71f6758
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 25 deletions.
2 changes: 2 additions & 0 deletions packages/component/src/Activity/Avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const connectAvatar = (...selectors) => connectToWebChat(
// We have 2 different upstreamers <CarouselFilmStrip> and <StackedLayout>

const Avatar = ({
'aria-hidden': ariaHidden,
avatarImage,
avatarInitials,
className,
Expand All @@ -33,6 +34,7 @@ const Avatar = ({
}) =>
!!(avatarImage || avatarInitials) &&
<div
aria-hidden={ ariaHidden }
className={ classNames(
styleSet.avatar + '',
{ 'from-user': fromUser },
Expand Down
2 changes: 2 additions & 0 deletions packages/component/src/Activity/Bubble.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ export default connectToWebChat(
({ styleSet }) => ({ styleSet })
)(
({
'aria-label': ariaLabel,
children,
className,
fromUser,
styleSet
}) =>
<div
aria-label={ ariaLabel }
className={ classNames(
styleSet.bubble + '',
{ 'from-user': fromUser },
Expand Down
48 changes: 40 additions & 8 deletions packages/component/src/Activity/CarouselFilmStrip.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ 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 SendStatus from './SendStatus';
import textFormatToContentType from '../Utils/textFormatToContentType';
import Timestamp from './Timestamp';

import connectToWebChat from '../connectToWebChat';
import textFormatToContentType from '../Utils/textFormatToContentType';

const { ActivityClientState: { SENDING, SEND_FAILED } } = Constants;

Expand Down Expand Up @@ -62,22 +63,44 @@ const ROOT_CSS = css({
});

const connectCarouselFilmStrip = (...selectors) => connectToWebChat(
({ language }) => ({ language }),
({
language,
styleSet: {
options: {
botAvatarInitials,
userAvatarInitials
}
}
}, { activity }) => ({
avatarInitials: activity.from && activity.from.role === 'user' ? userAvatarInitials : botAvatarInitials,
language
}),
...selectors
)

const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
({ styleSet }) => ({ styleSet })
({
avatarInitials,
language,
styleSet
}) => ({
avatarInitials,
language,
styleSet
})
)(
({
activity,
avatarInitials,
children,
language,
className,
filmContext,
showTimestamp,
styleSet,
}) => {
const fromUser = activity.from.role === 'user';
const ariaLabel = localize('Bot said something', language, avatarInitials, activity.text, activity.timestamp)

return (
<div
Expand All @@ -88,12 +111,17 @@ const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
) }
ref={ filmContext._setFilmStripRef }
>
<Avatar className="avatar" fromUser={ fromUser } />
<Avatar
aria-hidden={ true }
className="avatar"
fromUser={ fromUser }
/>
<div className="content">
{
!!activity.text &&
<div className="message">
<Bubble
aria-label={ ariaLabel }
className="bubble"
fromUser={ fromUser }
>
Expand Down Expand Up @@ -122,8 +150,11 @@ const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
)
}
</ul>
{
(
<div
aria-hidden={ true }
className="row"
>
{(
activity.channelData
&& (
activity.channelData.state === SENDING
Expand All @@ -133,7 +164,8 @@ const ConnectedCarouselFilmStrip = connectCarouselFilmStrip(
<SendStatus activity={ activity } />
: showTimestamp &&
<Timestamp activity={ activity } />
}
}
</div>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/component/src/Activity/SendStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default connectSendStatus(
retrySend,
styleSet
}) =>
<span className={ styleSet.sendStatus }>
<span aria-live="polite" className={ styleSet.sendStatus }>
{
state === SENDING ?
<Localize text="Sending" />
Expand Down
43 changes: 33 additions & 10 deletions packages/component/src/Activity/StackedLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import React from 'react';

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

import { localize } from '../Localization/Localize';
import Avatar from './Avatar';
import Bubble from './Bubble';
import SendStatus from './SendStatus';
import Timestamp from './Timestamp';

import connectToWebChat from '../connectToWebChat';
import SendStatus from './SendStatus';
import textFormatToContentType from '../Utils/textFormatToContentType';
import Timestamp from './Timestamp';

const { ActivityClientState: { SENDING, SEND_FAILED } } = Constants;

Expand Down Expand Up @@ -55,29 +55,47 @@ const ROOT_CSS = css({

const connectStackedLayout = (...selectors) => connectToWebChat(
({
botAvatarInitials,
language,
userAvatarInitials
}) => ({
botAvatarInitials,
styleSet: {
options: {
botAvatarInitials,
userAvatarInitials
}
}
}, { activity }) => ({
avatarInitials: activity.from && activity.from.role === 'user' ? userAvatarInitials : botAvatarInitials,
language,

// TODO: [P4] We want to deprecate botAvatarInitials/userAvatarInitials because they are not as helpful as avatarInitials
botAvatarInitials,
userAvatarInitials
}),
...selectors
);

export default connectStackedLayout(
({ styleSet }) => ({ styleSet })
({
avatarInitials,
language,
styleSet
}) => ({
avatarInitials,
language,
styleSet
})
)(
({
activity,
avatarInitials,
children,
language,
showTimestamp,
styleSet
styleSet,
}) => {
const fromUser = activity.from.role === 'user';
const { state } = activity.channelData || {};
const showSendStatus = state === SENDING || state === SEND_FAILED;
const ariaLabel = localize(fromUser ? 'User said something' : 'Bot said something', language, avatarInitials, activity.text, activity.timestamp);

return (
<div
Expand All @@ -88,6 +106,7 @@ export default connectStackedLayout(
) }
>
<Avatar
aria-hidden={ true }
className="avatar"
fromUser={ fromUser }
/>
Expand All @@ -106,6 +125,7 @@ export default connectStackedLayout(
: !!activity.text &&
<div className="row message">
<Bubble
aria-label={ ariaLabel }
className="bubble"
fromUser={ fromUser }
>
Expand Down Expand Up @@ -137,7 +157,10 @@ export default connectStackedLayout(
}
{
(showSendStatus || showTimestamp) &&
<div className="row">
<div
aria-hidden={ true }
className="row"
>
{ showSendStatus ?
<SendStatus activity={ activity } className="timestamp" />
:
Expand Down
6 changes: 3 additions & 3 deletions packages/component/src/BasicTranscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ const BasicTranscript = ({
speechSynthesis={ speechSynthesis }
speechSynthesisUtterance={ SpeechSynthesisUtterance }
>
<ul
<ul
aria-live="polite"
className={ classNames(LIST_CSS + '', styleSet.activities + '') }
className={ classNames(LIST_CSS + '', styleSet.activities + '') }
role="list"
>
{
Expand All @@ -106,7 +106,7 @@ const BasicTranscript = ({
return (
<li
className={ styleSet.activity }
key={ activity.id || (activity.channelData && activity.channelData.clientActivityID) || index }
key={ (activity.channelData && activity.channelData.clientActivityID) || activity.id || index }
role="listitem"
>
{ activityRenderer({ activity, showTimestamp })(({ attachment }) => attachmentRenderer({ activity, attachment })) }
Expand Down
6 changes: 3 additions & 3 deletions packages/component/src/Localization/Localize.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,19 @@ function getStrings(language) {
}
}

function localize(text, language, args) {
function localize(text, language, ...args) {
const string = (getStrings(language) || {})[text] || enUS[text];

if (typeof string === 'function') {
return string(args);
return string(...args);
} else {
return string || text;
}
}

export default connectToWebChat(
({ language }) => ({ language })
)(({ args, language, text }) => localize(text, language, args))
)(({ args, language, text }) => localize(text, language, ...(args || [])))

export { localize }

Expand Down
10 changes: 10 additions & 0 deletions packages/component/src/Localization/en-US.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
function botSaidSomething(avatarInitials, text, timestamp) {
return `Bot ${ avatarInitials } said, ${ text }, ${ xMinutesAgo(timestamp) }`;
}

function userSaidSomething(avatarInitials, text, timestamp) {
return `User ${ avatarInitials } said, ${ text }, ${ xMinutesAgo(timestamp) }`;
}

function xMinutesAgo(date) {
const now = Date.now();
const deltaInMs = now - new Date(date).getTime();
Expand All @@ -24,6 +32,8 @@ function xMinutesAgo(date) {
}

export default {
'Bot said something': botSaidSomething,
'User said something': userSaidSomething,
'X minutes ago': xMinutesAgo,
...[
// '[File of type '%1']",
Expand Down

0 comments on commit 71f6758

Please sign in to comment.