Restructure TopLeftMenu for accessibility and autofocus it
We use a trick with refs to automatically focus the element, also making use of mx_HiddenFocusable to hide the unnecessary outline. The menu itself has been restructured to hide some elements from screen readers (reduce noise) and to have a single unordered list. Screen readers mention when the user "enters" a list, and each item was previously saying "enter list <action>" when it should have just been "<action>". By focusing automatically, the keyboard can be used to go up/down the menu as may be expected by keyboard users.
This commit is contained in:
parent
c5757d8303
commit
2a187810fd
2 changed files with 43 additions and 19 deletions
|
@ -23,6 +23,7 @@ import Modal from "../../../Modal";
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import { getHostingLink } from '../../../utils/HostingLink';
|
import { getHostingLink } from '../../../utils/HostingLink';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import {focusCapturedRef} from "../../../utils/Accessibility";
|
||||||
|
|
||||||
export class TopLeftMenu extends React.Component {
|
export class TopLeftMenu extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -61,44 +62,48 @@ export class TopLeftMenu extends React.Component {
|
||||||
{_t(
|
{_t(
|
||||||
"<a>Upgrade</a> to your own domain", {},
|
"<a>Upgrade</a> to your own domain", {},
|
||||||
{
|
{
|
||||||
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener">{sub}</a>,
|
a: sub => <a href={hostingSignupLink} target="_blank" rel="noopener" tabIndex="0">{sub}</a>,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
<a href={hostingSignupLink} target="_blank" rel="noopener">
|
<a href={hostingSignupLink} target="_blank" rel="noopener" aria-hidden={true}>
|
||||||
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
|
<img src={require("../../../../res/img/external-link.svg")} width="11" height="10" alt='' />
|
||||||
</a>
|
</a>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let homePageSection = null;
|
let homePageItem = null;
|
||||||
if (this.hasHomePage()) {
|
if (this.hasHomePage()) {
|
||||||
homePageSection = <ul className="mx_TopLeftMenu_section_withIcon">
|
homePageItem = <li className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage} tabIndex={0}>
|
||||||
<li className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>{_t("Home")}</li>
|
{_t("Home")}
|
||||||
</ul>;
|
</li>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let signInOutSection;
|
let signInOutItem;
|
||||||
if (isGuest) {
|
if (isGuest) {
|
||||||
signInOutSection = <ul className="mx_TopLeftMenu_section_withIcon">
|
signInOutItem = <li className="mx_TopLeftMenu_icon_signin" onClick={this.signIn} tabIndex={0}>
|
||||||
<li className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>{_t("Sign in")}</li>
|
{_t("Sign in")}
|
||||||
</ul>;
|
</li>;
|
||||||
} else {
|
} else {
|
||||||
signInOutSection = <ul className="mx_TopLeftMenu_section_withIcon">
|
signInOutItem = <li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut} tabIndex={0}>
|
||||||
<li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>{_t("Sign out")}</li>
|
{_t("Sign out")}
|
||||||
</ul>;
|
</li>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="mx_TopLeftMenu">
|
const settingsItem = <li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings} tabIndex={0}>
|
||||||
<div className="mx_TopLeftMenu_section_noIcon">
|
{_t("Settings")}
|
||||||
|
</li>;
|
||||||
|
|
||||||
|
return <div className="mx_TopLeftMenu mx_HiddenFocusable" tabIndex={0} ref={focusCapturedRef}>
|
||||||
|
<div className="mx_TopLeftMenu_section_noIcon" aria-readonly={true}>
|
||||||
<div>{this.props.displayName}</div>
|
<div>{this.props.displayName}</div>
|
||||||
<div className="mx_TopLeftMenu_greyedText">{this.props.userId}</div>
|
<div className="mx_TopLeftMenu_greyedText" aria-hidden={true}>{this.props.userId}</div>
|
||||||
{hostingSignup}
|
{hostingSignup}
|
||||||
</div>
|
</div>
|
||||||
{homePageSection}
|
|
||||||
<ul className="mx_TopLeftMenu_section_withIcon">
|
<ul className="mx_TopLeftMenu_section_withIcon">
|
||||||
<li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>{_t("Settings")}</li>
|
{homePageItem}
|
||||||
|
{settingsItem}
|
||||||
|
{signInOutItem}
|
||||||
</ul>
|
</ul>
|
||||||
{signInOutSection}
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
src/utils/Accessibility.js
Normal file
19
src/utils/Accessibility.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function focusCapturedRef(ref) {
|
||||||
|
if (ref) ref.focus();
|
||||||
|
}
|
Loading…
Reference in a new issue