A11y: repurpose more divs into AccessibleButtons.

With this more of the
controls that look like buttons can be operated via the keyboard and
navigated to by screen reader users. This includes editor buttons such
as File upload, Audio / Video call, Right pannel hide button, Jump to
the bottom timeline button, and some more buttons found in the user
settings.
Also I have added alt texts to some images that in turn label buttons
which these happen to be packed in and removed some untranslated alt
texts from decorative non-actionable images that might add more
verbosity when talking about screen reader user experience.
This commit is contained in:
Peter Vágner 2018-10-02 13:55:24 +02:00
parent 862d67c0f7
commit ded35e43a0
No known key found for this signature in database
GPG key ID: 72D60A4B389C75AE
14 changed files with 57 additions and 51 deletions

View file

@ -6,7 +6,7 @@ const AppWarning = (props) => {
return (
<div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarningImage'>
<img src='img/warning.svg' alt={_t('Warning!')} />
<img src='img/warning.svg' alt='' />
</div>
<div className='mx_AppPermissionWarningText'>
<span className='mx_AppPermissionWarningTextLabel'>{ props.errorMsg }</span>

View file

@ -51,7 +51,7 @@ export default class CookieBar extends React.Component {
const toolbarClasses = "mx_MatrixToolbar";
return (
<div className={toolbarClasses}>
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning" />
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="" />
<div className="mx_MatrixToolbar_content">
{ this.props.policyUrl ? _t(
"Please help improve Riot.im by sending <UsageDataLink>anonymous usage data</UsageDataLink>. " +
@ -95,7 +95,7 @@ export default class CookieBar extends React.Component {
{ _t("Yes, I want to help!") }
</AccessibleButton>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.onReject}>
<img src="img/cancel.svg" width="18" height="18" />
<img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/>
</AccessibleButton>
</div>
);

View file

@ -35,11 +35,11 @@ module.exports = React.createClass({
render: function() {
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning"/>
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" />
<div className="mx_MatrixToolbar_content">
{ _t('You are not receiving desktop notifications') } <a className="mx_MatrixToolbar_link" onClick={ this.onClick }> { _t('Enable them now') }</a>
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" /></AccessibleButton>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/></AccessibleButton>
</div>
);
},

View file

@ -96,7 +96,7 @@ export default React.createClass({
}
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="Warning"/>
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" />
<div className="mx_MatrixToolbar_content">
{_t("A new version of Riot is available.")}
</div>

View file

@ -34,7 +34,7 @@ export default React.createClass({
src="img/warning.svg"
width="24"
height="23"
alt="Warning"
alt=""
/>
<div className="mx_MatrixToolbar_content">
{ _t(

View file

@ -71,9 +71,9 @@ export default React.createClass({
let image;
if (doneStatuses.includes(this.props.status)) {
image = <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt={warning}/>;
image = <img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="" />;
} else {
image = <img className="mx_MatrixToolbar_warning" src="img/spinner.gif" width="24" height="23" alt={message}/>;
image = <img className="mx_MatrixToolbar_warning" src="img/spinner.gif" width="24" height="23" alt="" />;
}
return (
@ -83,7 +83,7 @@ export default React.createClass({
{message}
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={this.hideToolbar}>
<img src="img/cancel.svg" width="18" height="18" />
<img src="img/cancel.svg" width="18" height="18" alt={_t('Close')}/>
</AccessibleButton>
</div>
);

View file

@ -30,6 +30,7 @@ import ScalarMessaging from '../../../ScalarMessaging';
import { _t } from '../../../languageHandler';
import WidgetUtils from '../../../utils/WidgetUtils';
import WidgetEchoStore from "../../../stores/WidgetEchoStore";
import AccessibleButton from '../elements/AccessibleButton';
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
@ -193,17 +194,15 @@ module.exports = React.createClass({
if (this.props.showApps &&
this._canUserModify()
) {
addWidget = <div
addWidget = <AccessibleButton
onClick={this.onClickAddWidget}
role='button'
tabIndex='0'
className={this.state.apps.length<2 ?
'mx_AddWidget_button mx_AddWidget_button_full_width' :
'mx_AddWidget_button'
}
title={_t('Add a widget')}>
[+] { _t('Add a widget') }
</div>;
</AccessibleButton>;
}
let spinner;

View file

@ -935,7 +935,7 @@ module.exports = withMatrixClient(React.createClass({
<div className="mx_MemberInfo">
<GeminiScrollbarWrapper autoshow={true}>
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}>
<img src="img/cancel.svg" width="18" height="18" className="mx_filterFlipColor" />
<img src="img/cancel.svg" width="18" height="18" className="mx_filterFlipColor" alt={_t('Close')} />
</AccessibleButton>
<div className="mx_MemberInfo_avatar">
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />

View file

@ -292,21 +292,22 @@ export default class MessageComposer extends React.Component {
let videoCallButton;
let hangupButton;
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
// Call buttons
if (this.props.callState && this.props.callState !== 'ended') {
hangupButton =
<div key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
<AccessibleButton key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
<img src="img/hangup.svg" alt={_t('Hangup')} title={_t('Hangup')} width="25" height="26" />
</div>;
</AccessibleButton>;
} else {
callButton =
<div key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title={_t('Voice call')}>
<AccessibleButton key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title={_t('Voice call')}>
<TintableSvg src="img/icon-call.svg" width="35" height="35" />
</div>;
</AccessibleButton>;
videoCallButton =
<div key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title={_t('Video call')}>
<AccessibleButton key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title={_t('Video call')}>
<TintableSvg src="img/icons-video.svg" width="35" height="35" />
</div>;
</AccessibleButton>;
}
const canSendMessages = !this.state.tombstone &&
@ -317,18 +318,19 @@ export default class MessageComposer extends React.Component {
// check separately for whether we can call, but this is slightly
// complex because of conference calls.
const uploadButton = (
<div key="controls_upload" className="mx_MessageComposer_upload"
<AccessibleButton key="controls_upload" className="mx_MessageComposer_upload"
onClick={this.onUploadClick} title={_t('Upload file')}>
<TintableSvg src="img/icons-upload.svg" width="35" height="35" />
<input ref="uploadInput" type="file"
style={uploadInputStyle}
multiple
onChange={this.onUploadFileSelected} />
</div>
</AccessibleButton>
);
const formattingButton = this.state.inputState.isRichTextEnabled ? (
<img className="mx_MessageComposer_formatting"
<AccessibleButton element="img" className="mx_MessageComposer_formatting"
alt={_t("Show Text Formatting Toolbar")}
title={_t("Show Text Formatting Toolbar")}
src="img/button-text-formatting.svg"
onClick={this.onToggleFormattingClicked}
@ -372,7 +374,6 @@ export default class MessageComposer extends React.Component {
} else if (this.state.tombstone) {
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
controls.push(<div className="mx_MessageComposer_replaced_wrapper">
<div className="mx_MessageComposer_replaced_valign">
<img className="mx_MessageComposer_roomReplaced_icon" src="img/room_replaced.svg" />
@ -423,7 +424,7 @@ export default class MessageComposer extends React.Component {
onMouseDown={this.onToggleMarkdownClicked}
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
src={`img/button-md-${!this.state.inputState.isRichTextEnabled}.png`} />
<img title={_t("Hide Text Formatting Toolbar")}
<AccessibleButton element="img" title={_t("Hide Text Formatting Toolbar")}
onClick={this.onToggleFormattingClicked}
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
src="img/icon-text-cancel.svg" />

View file

@ -164,6 +164,7 @@ export default class DevicesPanel extends React.Component {
render() {
const Spinner = sdk.getComponent("elements.Spinner");
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
if (this.state.deviceLoadError !== undefined) {
const classes = classNames(this.props.className, "error");
@ -185,9 +186,9 @@ export default class DevicesPanel extends React.Component {
const deleteButton = this.state.deleting ?
<Spinner w={22} h={22} /> :
<div className="mx_textButton" onClick={this._onDeleteClick}>
<AccessibleButton className="mx_textButton" onClick={this._onDeleteClick}>
{ _t("Delete %(count)s devices", {count: this.state.selectedDevices.length}) }
</div>;
</AccessibleButton>;
const classes = classNames(this.props.className, "mx_DevicesPanel");
return (

View file

@ -125,14 +125,15 @@ module.exports = React.createClass({
render: function() {
const VideoView = sdk.getComponent('voip.VideoView');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let voice;
if (this.state.call && this.state.call.type === "voice" && this.props.showVoice) {
const callRoom = MatrixClientPeg.get().getRoom(this.state.call.roomId);
voice = (
<div className="mx_CallView_voice" onClick={this.props.onClick}>
<AccessibleButton className="mx_CallView_voice" onClick={this.props.onClick}>
{ _t("Active call (%(roomName)s)", {roomName: callRoom.name}) }
</div>
</AccessibleButton>
);
}