From c37e27f03d2ed066c7bfea8c50ead8835657e57d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 26 Sep 2019 14:52:20 +0100 Subject: [PATCH] Improve a11y: + Close context menu on escape + Use AccessibleButtons for more things (Context Menus and TabbedView) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/ContextualMenu.js | 13 ++- src/components/structures/TabbedView.js | 25 ++---- .../structures/TopLeftMenuButton.js | 3 +- .../GroupInviteTileContextMenu.js | 6 +- .../views/context_menus/MessageContextMenu.js | 87 ++++++++----------- .../context_menus/RoomTileContextMenu.js | 46 ++++++---- 6 files changed, 86 insertions(+), 94 deletions(-) diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index 0f4d0b38d4..04314e5a4e 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -21,6 +21,7 @@ import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import {focusCapturedRef} from "../../utils/Accessibility"; +import {KeyCode} from "../../Keyboard"; // Shamelessly ripped off Modal.js. There's probably a better way // of doing reusable widgets like dialog boxes & menus where we go and @@ -67,7 +68,7 @@ export default class ContextualMenu extends React.Component { // on resize callback windowResize: PropTypes.func, // method to close menu - closeMenu: PropTypes.func, + closeMenu: PropTypes.func.isRequired, }; constructor() { @@ -114,6 +115,14 @@ export default class ContextualMenu extends React.Component { } } + _onKeyDown = (ev) => { + if (ev.keyCode === KeyCode.ESCAPE) { + ev.stopPropagation(); + ev.preventDefault(); + this.props.closeMenu(); + } + }; + render() { const position = {}; let chevronFace = null; @@ -210,7 +219,7 @@ export default class ContextualMenu extends React.Component { // FIXME: If a menu uses getDefaultProps it clobbers the onFinished // property set here so you can't close the menu from a button click! - return
+ return
{ chevron } diff --git a/src/components/structures/TabbedView.js b/src/components/structures/TabbedView.js index 0e76143f92..e6d61a2125 100644 --- a/src/components/structures/TabbedView.js +++ b/src/components/structures/TabbedView.js @@ -17,9 +17,9 @@ limitations under the License. */ import * as React from "react"; -import {_t} from '../../languageHandler'; -import {KeyCode} from "../../Keyboard"; +import {_t} from '../../languageHandler';`` import PropTypes from "prop-types"; +import sdk from "../../index"; /** * Represents a tab for the TabbedView. @@ -72,6 +72,8 @@ export class TabbedView extends React.Component { } _renderTabLabel(tab) { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + let classes = "mx_TabbedView_tabLabel "; const idx = this.props.tabs.indexOf(tab); @@ -83,30 +85,15 @@ export class TabbedView extends React.Component { } const onClickHandler = () => this._setActiveTab(tab); - const onKeyDownHandler = (e) => { - if (e.keyCode === KeyCode.ENTER || e.keyCode === KeyCode.SPACE) { - e.stopPropagation(); - e.preventDefault(); - this._setActiveTab(tab); - } - }; const label = _t(tab.label); return ( - + {tabIcon} { label } - + ); } diff --git a/src/components/structures/TopLeftMenuButton.js b/src/components/structures/TopLeftMenuButton.js index cf3dda077c..42b8623e56 100644 --- a/src/components/structures/TopLeftMenuButton.js +++ b/src/components/structures/TopLeftMenuButton.js @@ -109,10 +109,11 @@ export default class TopLeftMenuButton extends React.Component { return ( this._buttonRef = r} aria-label={_t("Your profile")} + aria-haspopup={true} + aria-expanded={this.state.menuDisplayed} > -
+ { _t('Reject') } -
+
; } } diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 1d859da047..a832b2fbb2 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -31,28 +31,11 @@ import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; import { isContentActionable } from '../../../utils/EventUtils'; -import {KeyCode} from "../../../Keyboard"; function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; } -const DropdownButton = ({children, onClick}) => { - const onKeyDown = (e) => { - if (e.keyCode === KeyCode.ENTER || e.keyCode === KeyCode.SPACE) { - e.stopPropagation(); - e.preventDefault(); - onClick(); - } - }; - return ( -
- { children } -
- ); -}; - - module.exports = createReactClass({ displayName: 'MessageContextMenu', @@ -306,6 +289,8 @@ module.exports = createReactClass({ }, render: function() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const cli = MatrixClientPeg.get(); const me = cli.getUserId(); const mxEvent = this.props.mxEvent; @@ -337,89 +322,89 @@ module.exports = createReactClass({ if (!mxEvent.isRedacted()) { if (eventStatus === EventStatus.NOT_SENT) { resendButton = ( - + { _t('Resend') } - + ); } if (editStatus === EventStatus.NOT_SENT) { resendEditButton = ( - + { _t('Resend edit') } - + ); } if (unsentReactionsCount !== 0) { resendReactionsButton = ( - + { _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) } - + ); } } if (redactStatus === EventStatus.NOT_SENT) { resendRedactionButton = ( - + { _t('Resend removal') } - + ); } if (isSent && this.state.canRedact) { redactButton = ( - + { _t('Remove') } - + ); } if (allowCancel) { cancelButton = ( - + { _t('Cancel Sending') } - + ); } if (isContentActionable(mxEvent)) { forwardButton = ( - + { _t('Forward Message') } - + ); if (this.state.canPin) { pinButton = ( - + { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') } - + ); } } const viewSourceButton = ( - + { _t('View Source') } - + ); if (mxEvent.getType() !== mxEvent.getWireType()) { viewClearSourceButton = ( - + { _t('View Decrypted Source') } - + ); } if (this.props.eventTileOps) { if (this.props.eventTileOps.isWidgetHidden()) { unhidePreviewButton = ( - + { _t('Unhide Preview') } - + ); } } @@ -430,19 +415,19 @@ module.exports = createReactClass({ } // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID) const permalinkButton = ( - + { mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message' ? _t('Share Permalink') : _t('Share Message') } - + ); if (this.props.eventTileOps && this.props.eventTileOps.getInnerText) { quoteButton = ( - + { _t('Quote') } - + ); } @@ -452,7 +437,7 @@ module.exports = createReactClass({ isUrlPermitted(mxEvent.event.content.external_url) ) { externalURLButton = ( - + { _t('Source URL') } - + ); } if (this.props.collapseReplyThread) { collapseReplyThread = ( - + { _t('Collapse Reply Thread') } - + ); } let e2eInfo; if (this.props.e2eInfoCallback) { e2eInfo = ( - + { _t('End-to-end encryption information') } - + ); } let reportEventButton; if (mxEvent.getSender() !== me) { reportEventButton = ( - + { _t('Report Content') } - + ); } diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js index eba8254c03..9bb573026f 100644 --- a/src/components/views/context_menus/RoomTileContextMenu.js +++ b/src/components/views/context_menus/RoomTileContextMenu.js @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd 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"); you may not use this file except in compliance with the License. @@ -227,6 +228,8 @@ module.exports = createReactClass({ }, _renderNotifMenu: function() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const alertMeClasses = classNames({ 'mx_RoomTileContextMenu_notif_field': true, 'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES_LOUD, @@ -249,29 +252,29 @@ module.exports = createReactClass({ return (
-
+
-
+ { _t('All messages (noisy)') } -
-
+ + { _t('All messages') } -
-
+ + { _t('Mentions only') } -
-
+ + { _t('Mute') } -
+
); }, @@ -287,12 +290,13 @@ module.exports = createReactClass({ }, _renderSettingsMenu: function() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); return (
-
+ { _t('Settings') } -
+
); }, @@ -302,6 +306,8 @@ module.exports = createReactClass({ return null; } + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + let leaveClickHandler = null; let leaveText = null; @@ -323,15 +329,17 @@ module.exports = createReactClass({ return (
-
+ { leaveText } -
+
); }, _renderRoomTagMenu: function() { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + const favouriteClasses = classNames({ 'mx_RoomTileContextMenu_tag_field': true, 'mx_RoomTileContextMenu_tag_fieldSet': this.state.isFavourite, @@ -352,21 +360,21 @@ module.exports = createReactClass({ return (
-
+ { _t('Favourite') } -
-
+ + { _t('Low Priority') } -
-
+ + { _t('Direct Chat') } -
+
); },