Various ARIA a11y fixes.

Notate RightPanel tabs.
Shorten Screen Reader queues.
Make AccessibleTooltipButton screen reader friendly
Flatten DOM for Sticker button using React Fragments

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2019-10-03 09:35:39 +01:00
parent b94c94db04
commit f1db0cf027
16 changed files with 62 additions and 57 deletions

View file

@ -126,11 +126,12 @@ const FilePanel = createReactClass({
tileShape="file_grid" tileShape="file_grid"
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
empty={_t('There are no visible files in this room')} empty={_t('There are no visible files in this room')}
role="tabpanel"
/> />
); );
} else { } else {
return ( return (
<div className="mx_FilePanel"> <div className="mx_FilePanel" role="tabpanel">
<Loader /> <Loader />
</div> </div>
); );

View file

@ -46,12 +46,13 @@ const NotificationPanel = createReactClass({
showUrlPreview={false} showUrlPreview={false}
tileShape="notif" tileShape="notif"
empty={_t('You have no visible notifications')} empty={_t('You have no visible notifications')}
role="tabpanel"
/> />
); );
} else { } else {
console.error("No notifTimelineSet available!"); console.error("No notifTimelineSet available!");
return ( return (
<div className="mx_NotificationPanel"> <div className="mx_NotificationPanel" role="tabpanel">
<Loader /> <Loader />
</div> </div>
); );

View file

@ -258,7 +258,7 @@ const RoomSubList = createReactClass({
const tabindex = this.props.isFiltered ? "0" : "-1"; const tabindex = this.props.isFiltered ? "0" : "-1";
return ( return (
<div className="mx_RoomSubList_labelContainer" title={ title } ref="header"> <div className="mx_RoomSubList_labelContainer" title={ title } ref="header">
<AccessibleButton onClick={ this.onClick } className="mx_RoomSubList_label" tabIndex={tabindex}> <AccessibleButton onClick={this.onClick} className="mx_RoomSubList_label" tabIndex={tabindex} aria-expanded={!isCollapsed}>
{ chevron } { chevron }
<span>{this.props.label}</span> <span>{this.props.label}</span>
{ incomingCall } { incomingCall }

View file

@ -1,18 +1,19 @@
/* /*
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -55,7 +56,7 @@ export default class AccessibleTooltipButton extends React.PureComponent {
label={title} label={title}
/> : <div />; /> : <div />;
return ( return (
<AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}> <AccessibleButton {...props} onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut} aria-label={title}>
{ tip } { tip }
</AccessibleButton> </AccessibleButton>
); );

View file

@ -183,7 +183,7 @@ module.exports = createReactClass({
const GeminiScrollbarWrapper = sdk.getComponent('elements.GeminiScrollbarWrapper'); const GeminiScrollbarWrapper = sdk.getComponent('elements.GeminiScrollbarWrapper');
return ( return (
<div className="mx_MemberInfo"> <div className="mx_MemberInfo" role="tabpanel">
<GeminiScrollbarWrapper autoshow={true}> <GeminiScrollbarWrapper autoshow={true}>
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}> <AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" /> <img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />

View file

@ -222,7 +222,7 @@ export default createReactClass({
} }
return ( return (
<div className="mx_MemberList"> <div className="mx_MemberList" role="tabpanel">
{ inviteButton } { inviteButton }
<GeminiScrollbarWrapper autoshow={true}> <GeminiScrollbarWrapper autoshow={true}>
{ joined } { joined }

View file

@ -214,7 +214,7 @@ module.exports = createReactClass({
const groupRoomName = this.state.groupRoom.displayname; const groupRoomName = this.state.groupRoom.displayname;
return ( return (
<div className="mx_MemberInfo"> <div className="mx_MemberInfo" role="tabpanel">
<GeminiScrollbarWrapper autoshow={true}> <GeminiScrollbarWrapper autoshow={true}>
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}> <AccessibleButton className="mx_MemberInfo_cancel" onClick={this._onCancel}>
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" /> <img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />

View file

@ -153,7 +153,7 @@ export default createReactClass({
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
const TruncatedList = sdk.getComponent("elements.TruncatedList"); const TruncatedList = sdk.getComponent("elements.TruncatedList");
return ( return (
<div className="mx_GroupRoomList"> <div className="mx_GroupRoomList" role="tabpanel">
{ inviteButton } { inviteButton }
<GeminiScrollbarWrapper autoshow={true} className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper"> <GeminiScrollbarWrapper autoshow={true} className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt} <TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}

View file

@ -3,6 +3,7 @@ Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2017 New Vector Ltd Copyright 2017 New Vector Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -42,8 +43,8 @@ export default class HeaderButton extends React.Component {
}); });
return <AccessibleButton return <AccessibleButton
aria-label={this.props.title} aria-selected={this.props.isHighlighted}
aria-expanded={this.props.isHighlighted} role="tab"
title={this.props.title} title={this.props.title}
className={classes} className={classes}
onClick={this.onClick}> onClick={this.onClick}>

View file

@ -91,7 +91,7 @@ export default class HeaderButtons extends React.Component {
render() { render() {
// inline style as this will be swapped around in future commits // inline style as this will be swapped around in future commits
return <div className="mx_HeaderButtons"> return <div className="mx_HeaderButtons" role="tablist">
{ this.renderButtons() } { this.renderButtons() }
</div>; </div>;
} }

View file

@ -1124,35 +1124,35 @@ module.exports = createReactClass({
} }
return ( return (
<div className="mx_MemberInfo"> <div className="mx_MemberInfo" role="tabpanel">
<div className="mx_MemberInfo_name"> <div className="mx_MemberInfo_name">
{ backButton } { backButton }
{ e2eIconElement } { e2eIconElement }
<h2>{ memberName }</h2> <h2>{ memberName }</h2>
</div>
{ avatarElement }
<div className="mx_MemberInfo_container">
<div className="mx_MemberInfo_profile">
<div className="mx_MemberInfo_profileField">
{ this.props.member.userId }
</div>
{ roomMemberDetails }
</div> </div>
{ avatarElement } </div>
<AutoHideScrollbar className="mx_MemberInfo_scrollContainer">
<div className="mx_MemberInfo_container"> <div className="mx_MemberInfo_container">
{ this._renderUserOptions() }
<div className="mx_MemberInfo_profile"> { adminTools }
<div className="mx_MemberInfo_profileField">
{ this.props.member.userId } { startChat }
</div>
{ roomMemberDetails } { this._renderDevices() }
</div>
{ spinner }
</div> </div>
<AutoHideScrollbar className="mx_MemberInfo_scrollContainer"> </AutoHideScrollbar>
<div className="mx_MemberInfo_container">
{ this._renderUserOptions() }
{ adminTools }
{ startChat }
{ this._renderDevices() }
{ spinner }
</div>
</AutoHideScrollbar>
</div> </div>
); );
}, },

View file

@ -475,7 +475,7 @@ module.exports = createReactClass({
} }
return ( return (
<div className="mx_MemberList"> <div className="mx_MemberList" role="tabpanel">
{ inviteButton } { inviteButton }
<AutoHideScrollbar> <AutoHideScrollbar>
<div className="mx_MemberList_wrapper"> <div className="mx_MemberList_wrapper">

View file

@ -382,14 +382,15 @@ module.exports = createReactClass({
/>; />;
} }
// The following labels are written in such a fashion to increase screen reader efficiency (speed).
if (notifBadges && mentionBadges && !isInvite) { if (notifBadges && mentionBadges && !isInvite) {
ariaLabel += " " + _t("It has %(count)s unread messages including mentions.", { ariaLabel += " " + _t("%(count)s unread messages including mentions.", {
count: notificationCount, count: notificationCount,
}); });
} else if (notifBadges) { } else if (notifBadges) {
ariaLabel += " " + _t("It has %(count)s unread messages.", { count: notificationCount }); ariaLabel += " " + _t("%(count)s unread messages.", { count: notificationCount });
} else if (mentionBadges && !isInvite) { } else if (mentionBadges && !isInvite) {
ariaLabel += " " + _t("It has unread mentions."); ariaLabel += " " + _t("Unread mentions.");
} }
return <AccessibleButton tabIndex="0" return <AccessibleButton tabIndex="0"

View file

@ -409,9 +409,9 @@ export default class Stickerpicker extends React.Component {
> >
</AccessibleButton>; </AccessibleButton>;
} }
return <div> return <React.Fragment>
{stickersButton} {stickersButton}
{this.state.showStickers && stickerPicker} {this.state.showStickers && stickerPicker}
</div>; </React.Fragment>;
} }
} }

View file

@ -121,7 +121,7 @@ export default class ThirdPartyMemberInfo extends React.Component {
// We shamelessly rip off the MemberInfo styles here. // We shamelessly rip off the MemberInfo styles here.
return ( return (
<div className="mx_MemberInfo"> <div className="mx_MemberInfo" role="tabpanel">
<div className="mx_MemberInfo_name"> <div className="mx_MemberInfo_name">
<AccessibleButton className="mx_MemberInfo_cancel" <AccessibleButton className="mx_MemberInfo_cancel"
onClick={this.onCancel} onClick={this.onCancel}

View file

@ -950,9 +950,9 @@
"Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>", "Securely back up your keys to avoid losing them. <a>Learn more.</a>": "Securely back up your keys to avoid losing them. <a>Learn more.</a>",
"Not now": "Not now", "Not now": "Not now",
"Don't ask me again": "Don't ask me again", "Don't ask me again": "Don't ask me again",
"It has %(count)s unread messages including mentions.|other": "It has %(count)s unread messages including mentions.", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
"It has %(count)s unread messages.|other": "It has %(count)s unread messages.", "%(count)s unread messages.|other": "%(count)s unread messages.",
"It has unread mentions.": "It has unread mentions.", "Unread mentions.": "Unread mentions.",
"Add a topic": "Add a topic", "Add a topic": "Add a topic",
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.", "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.",
"This room has already been upgraded.": "This room has already been upgraded.", "This room has already been upgraded.": "This room has already been upgraded.",