mirror of
https://github.com/element-hq/element-web
synced 2024-11-27 11:47:23 +03:00
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:
parent
862d67c0f7
commit
ded35e43a0
14 changed files with 57 additions and 51 deletions
|
@ -51,6 +51,7 @@ class HeaderButton extends React.Component {
|
|||
|
||||
return <AccessibleButton
|
||||
aria-label={this.props.title}
|
||||
aria-expanded={this.props.isHighlighted}
|
||||
title={this.props.title}
|
||||
className="mx_RightPanel_headerButton"
|
||||
onClick={this.onClick} >
|
||||
|
@ -345,11 +346,11 @@ module.exports = React.createClass({
|
|||
// being put in the RoomHeader or GroupView header, so only show the minimise
|
||||
// button on these 2 screens or you won't be able to re-expand the panel.
|
||||
headerButtons.push(
|
||||
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" key="_minimizeButton"
|
||||
<AccessibleButton className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" key="_minimizeButton"
|
||||
title={_t("Hide panel")} aria-label={_t("Hide panel")} onClick={this.onCollapseClick}
|
||||
>
|
||||
<TintableSvg src="img/minimise.svg" width="10" height="16" />
|
||||
</div>,
|
||||
<TintableSvg src="img/minimise.svg" width="10" height="16" alt="" />
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -223,14 +223,15 @@ module.exports = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||
if (!this.props.atEndOfLiveTimeline) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_scrollDownIndicator"
|
||||
<AccessibleButton className="mx_RoomStatusBar_scrollDownIndicator"
|
||||
onClick={this.props.onScrollToBottomClick}>
|
||||
<img src="img/scrolldown.svg" width="24" height="24"
|
||||
alt={_t("Scroll to bottom of page")}
|
||||
title={_t("Scroll to bottom of page")} />
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -385,7 +386,7 @@ module.exports = React.createClass({
|
|||
}
|
||||
|
||||
return <div className="mx_RoomStatusBar_connectionLostBar">
|
||||
<img src="img/warning.svg" width="24" height="23" title={_t("Warning")} alt={_t("Warning")} />
|
||||
<img src="img/warning.svg" width="24" height="23" title={_t("Warning")} alt="" />
|
||||
<div>
|
||||
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||
{ title }
|
||||
|
@ -485,7 +486,9 @@ module.exports = React.createClass({
|
|||
<div className="mx_RoomStatusBar_indicator">
|
||||
{ indicator }
|
||||
</div>
|
||||
{ content }
|
||||
<div role="alert">
|
||||
{ content }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -831,9 +831,9 @@ module.exports = React.createClass({
|
|||
<br />
|
||||
{ _t('Privacy is important to us, so we don\'t collect any personal'
|
||||
+ ' or identifiable data for our analytics.') }
|
||||
<div className="mx_UserSettings_advanced_spoiler" onClick={Analytics.showDetailsModal}>
|
||||
<AccessibleButton className="mx_UserSettings_advanced_spoiler" onClick={Analytics.showDetailsModal}>
|
||||
{ _t('Learn more about how we use analytics.') }
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
{ ANALYTICS_SETTINGS.map( this._renderDeviceSetting ) }
|
||||
</div>
|
||||
</div>;
|
||||
|
@ -1065,9 +1065,9 @@ module.exports = React.createClass({
|
|||
_renderWebRtcDeviceSettings: function() {
|
||||
if (this.state.mediaDevices === false) {
|
||||
return (
|
||||
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
|
||||
<AccessibleButton element="p" className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
|
||||
{ _t('Missing Media Permissions, click here to request.') }
|
||||
</p>
|
||||
</AccessibleButton>
|
||||
);
|
||||
} else if (!this.state.mediaDevices) return;
|
||||
|
||||
|
@ -1233,10 +1233,10 @@ module.exports = React.createClass({
|
|||
value={this.presentableTextForThreepid(val)} disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<AccessibleButton className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<img src="img/cancel-small.svg" width="14" height="14" alt={_t("Remove")}
|
||||
onClick={onRemoveClick} />
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1258,9 +1258,9 @@ module.exports = React.createClass({
|
|||
blurToCancel={false}
|
||||
onValueChanged={this._onAddEmailEditFinished} />
|
||||
</div>
|
||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<AccessibleButton className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||
<img src="img/plus.svg" width="14" height="14" alt={_t("Add")} onClick={this._addEmail} />
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1328,25 +1328,25 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
|
||||
<div className="mx_UserSettings_avatarPicker">
|
||||
<div className="mx_UserSettings_avatarPicker_remove" onClick={this.onAvatarRemoveClick}>
|
||||
<AccessibleButton className="mx_UserSettings_avatarPicker_remove" onClick={this.onAvatarRemoveClick}>
|
||||
<img src="img/cancel.svg"
|
||||
width="15" height="15"
|
||||
className="mx_filterFlipColor"
|
||||
alt={_t("Remove avatar")}
|
||||
title={_t("Remove avatar")} />
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
<div onClick={this.onAvatarPickerClick} className="mx_UserSettings_avatarPicker_imgContainer">
|
||||
<ChangeAvatar ref="changeAvatar" initialAvatarUrl={avatarUrl}
|
||||
showUploadSection={false} className="mx_UserSettings_avatarPicker_img" />
|
||||
</div>
|
||||
<div className="mx_UserSettings_avatarPicker_edit">
|
||||
<AccessibleButton className="mx_UserSettings_avatarPicker_edit">
|
||||
<label htmlFor="avatarInput" ref="file_label">
|
||||
<img src="img/camera.svg" className="mx_filterFlipColor"
|
||||
alt={_t("Upload avatar")} title={_t("Upload avatar")}
|
||||
width="17" height="15" />
|
||||
</label>
|
||||
<input id="avatarInput" type="file" onChange={this.onAvatarSelected} />
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1395,11 +1395,11 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
{ _t('Access Token:') + ' ' }
|
||||
<span className="mx_UserSettings_advanced_spoiler"
|
||||
<AccessibleButton element="span" className="mx_UserSettings_advanced_spoiler"
|
||||
onClick={this._showSpoiler}
|
||||
data-spoiler={MatrixClientPeg.get().getAccessToken()}>
|
||||
<{ _t("click to reveal") }>
|
||||
</span>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div className="mx_UserSettings_advanced">
|
||||
{ _t("Homeserver is") } { MatrixClientPeg.get().getHomeserverUrl() }
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue