Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Update TopLeftMenu for accessibility: Keyboard shortcut, reduced screen reader noise #2994

Merged
merged 8 commits into from
May 21, 2019
Merged
8 changes: 8 additions & 0 deletions src/components/structures/LoggedInView.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,14 @@ const LoggedInView = React.createClass({
handled = true;
}
break;
case KeyCode.KEY_I:
turt2live marked this conversation as resolved.
Show resolved Hide resolved
if (ctrlCmdOnly) {
dis.dispatch({
action: 'toggle_top_left_menu',
});
handled = true;
}
break;
}

if (handled) {
Expand Down
36 changes: 31 additions & 5 deletions src/components/structures/TopLeftMenuButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import BaseAvatar from '../views/avatars/BaseAvatar';
import MatrixClientPeg from '../../MatrixClientPeg';
import Avatar from '../../Avatar';
import { _t } from '../../languageHandler';
import dis from "../../dispatcher";

const AVATAR_SIZE = 28;

Expand All @@ -37,6 +38,7 @@ export default class TopLeftMenuButton extends React.Component {
super();
this.state = {
menuDisplayed: false,
menuFunctions: null, // should be { close: fn }
profileInfo: null,
};

Expand All @@ -59,6 +61,8 @@ export default class TopLeftMenuButton extends React.Component {
}

async componentDidMount() {
this._dispatcherRef = dis.register(this.onAction);

try {
const profileInfo = await this._getProfileInfo();
this.setState({profileInfo});
Expand All @@ -68,6 +72,17 @@ export default class TopLeftMenuButton extends React.Component {
}
}

componentWillUnmount() {
dis.unregister(this._dispatcherRef);
}

onAction = (payload) => {
// For accessibility
if (payload.action === "toggle_top_left_menu") {
if (this._buttonRef) this._buttonRef.click();
}
};

_getDisplayName() {
if (MatrixClientPeg.get().isGuest()) {
return _t("Guest");
Expand All @@ -88,7 +103,13 @@ export default class TopLeftMenuButton extends React.Component {
}

return (
<AccessibleButton className="mx_TopLeftMenuButton" onClick={this.onToggleMenu}>
<AccessibleButton
className="mx_TopLeftMenuButton"
role="button"
onClick={this.onToggleMenu}
inputRef={(r) => this._buttonRef = r}
aria-label={_t("Your profile")}
>
<BaseAvatar
idName={MatrixClientPeg.get().getUserId()}
name={name}
Expand All @@ -98,7 +119,7 @@ export default class TopLeftMenuButton extends React.Component {
resizeMethod="crop"
/>
{ nameElement }
<span className="mx_TopLeftMenuButton_chevron"></span>
<span className="mx_TopLeftMenuButton_chevron" />
</AccessibleButton>
);
}
Expand All @@ -107,20 +128,25 @@ export default class TopLeftMenuButton extends React.Component {
e.preventDefault();
e.stopPropagation();

if (this.state.menuDisplayed && this.state.menuFunctions) {
this.state.menuFunctions.close();
return;
}

const elementRect = e.currentTarget.getBoundingClientRect();
const x = elementRect.left;
const y = elementRect.top + elementRect.height;

ContextualMenu.createMenu(TopLeftMenu, {
const menuFunctions = ContextualMenu.createMenu(TopLeftMenu, {
chevronFace: "none",
left: x,
top: y,
userId: MatrixClientPeg.get().getUserId(),
displayName: this._getDisplayName(),
onFinished: () => {
this.setState({ menuDisplayed: false });
this.setState({ menuDisplayed: false, menuFunctions: null });
},
});
this.setState({ menuDisplayed: true });
this.setState({ menuDisplayed: true, menuFunctions });
}
}
5 changes: 5 additions & 0 deletions src/components/views/elements/AccessibleButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export default function AccessibleButton(props) {
};
}

// Pass through the ref - used for keyboard shortcut access to some buttons
restProps.ref = restProps.inputRef;
delete restProps.inputRef;

restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
restProps.className = (restProps.className ? restProps.className + " " : "") +
Expand All @@ -89,6 +93,7 @@ export default function AccessibleButton(props) {
*/
AccessibleButton.propTypes = {
children: PropTypes.node,
inputRef: PropTypes.func,
element: PropTypes.string,
onClick: PropTypes.func.isRequired,

Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,7 @@
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.",
"Failed to load timeline position": "Failed to load timeline position",
"Guest": "Guest",
"Your profile": "Your profile",
"Uploading %(filename)s and %(count)s others|other": "Uploading %(filename)s and %(count)s others",
"Uploading %(filename)s and %(count)s others|zero": "Uploading %(filename)s",
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
Expand Down