Improve keyboard accessibility using :focus-visible CSS polyfill

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
pull/21833/head
Michael Telatynski 2019-09-27 09:00:54 +01:00
parent 3674b87415
commit 8d1d3090f3
6 changed files with 40 additions and 17 deletions

View File

@ -75,6 +75,7 @@
"filesize": "3.5.6", "filesize": "3.5.6",
"flux": "2.1.1", "flux": "2.1.1",
"focus-trap-react": "^3.0.5", "focus-trap-react": "^3.0.5",
"focus-visible": "^5.0.2",
"fuse.js": "^2.2.0", "fuse.js": "^2.2.0",
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566", "gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
"gfm.css": "^1.1.1", "gfm.css": "^1.1.1",

View File

@ -18,7 +18,7 @@ limitations under the License.
cursor: pointer; cursor: pointer;
} }
.mx_AccessibleButton:focus { .mx_AccessibleButton:focus:not(.focus-visible) {
outline: 0; outline: 0;
} }

View File

@ -41,7 +41,9 @@ import * as Rooms from '../../Rooms';
import linkifyMatrix from "../../linkify-matrix"; import linkifyMatrix from "../../linkify-matrix";
import * as Lifecycle from '../../Lifecycle'; import * as Lifecycle from '../../Lifecycle';
// LifecycleStore is not used but does listen to and dispatch actions // LifecycleStore is not used but does listen to and dispatch actions
require('../../stores/LifecycleStore'); import '../../stores/LifecycleStore';
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
import 'focus-visible';
import PageTypes from '../../PageTypes'; import PageTypes from '../../PageTypes';
import { getHomePageUrl } from '../../utils/pages'; import { getHomePageUrl } from '../../utils/pages';

View File

@ -24,6 +24,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 sdk from "../../../index";
export class TopLeftMenu extends React.Component { export class TopLeftMenu extends React.Component {
static propTypes = { static propTypes = {
@ -57,6 +58,8 @@ export class TopLeftMenu extends React.Component {
} }
render() { render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const isGuest = MatrixClientPeg.get().isGuest(); const isGuest = MatrixClientPeg.get().isGuest();
const hostingSignupLink = getHostingLink('user-context-menu'); const hostingSignupLink = getHostingLink('user-context-menu');
@ -77,25 +80,33 @@ export class TopLeftMenu extends React.Component {
let homePageItem = null; let homePageItem = null;
if (this.hasHomePage()) { if (this.hasHomePage()) {
homePageItem = <li className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage} tabIndex={0}> homePageItem = (
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>
{_t("Home")} {_t("Home")}
</li>; </AccessibleButton>
);
} }
let signInOutItem; let signInOutItem;
if (isGuest) { if (isGuest) {
signInOutItem = <li className="mx_TopLeftMenu_icon_signin" onClick={this.signIn} tabIndex={0}> signInOutItem = (
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>
{_t("Sign in")} {_t("Sign in")}
</li>; </AccessibleButton>
);
} else { } else {
signInOutItem = <li className="mx_TopLeftMenu_icon_signout" onClick={this.signOut} tabIndex={0}> signInOutItem = (
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>
{_t("Sign out")} {_t("Sign out")}
</li>; </AccessibleButton>
);
} }
const settingsItem = <li className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings} tabIndex={0}> const settingsItem = (
<AccessibleButton element="li" className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
{_t("Settings")} {_t("Settings")}
</li>; </AccessibleButton>
);
return <div className="mx_TopLeftMenu mx_HiddenFocusable" tabIndex={0} ref={this.props.containerRef}> return <div className="mx_TopLeftMenu mx_HiddenFocusable" tabIndex={0} ref={this.props.containerRef}>
<div className="mx_TopLeftMenu_section_noIcon" aria-readonly={true}> <div className="mx_TopLeftMenu_section_noIcon" aria-readonly={true}>

View File

@ -140,6 +140,8 @@ export default class MessageActionBar extends React.PureComponent {
} }
render() { render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let reactButton; let reactButton;
let replyButton; let replyButton;
let editButton; let editButton;
@ -149,14 +151,16 @@ export default class MessageActionBar extends React.PureComponent {
reactButton = this.renderReactButton(); reactButton = this.renderReactButton();
} }
if (this.context.room.canReply) { if (this.context.room.canReply) {
replyButton = <span className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton" replyButton = <AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_replyButton"
title={_t("Reply")} title={_t("Reply")}
onClick={this.onReplyClick} onClick={this.onReplyClick}
/>; />;
} }
} }
if (canEditContent(this.props.mxEvent)) { if (canEditContent(this.props.mxEvent)) {
editButton = <span className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton" editButton = <AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_editButton"
title={_t("Edit")} title={_t("Edit")}
onClick={this.onEditClick} onClick={this.onEditClick}
/>; />;
@ -166,7 +170,7 @@ export default class MessageActionBar extends React.PureComponent {
{reactButton} {reactButton}
{replyButton} {replyButton}
{editButton} {editButton}
<span <AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton" className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
title={_t("Options")} title={_t("Options")}
onClick={this.onOptionsClick} onClick={this.onOptionsClick}

View File

@ -3367,6 +3367,11 @@ focus-trap@^2.0.1:
dependencies: dependencies:
tabbable "^1.0.3" tabbable "^1.0.3"
focus-visible@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/focus-visible/-/focus-visible-5.0.2.tgz#4fae9cf40458b73c10701c9774c462e3ccd53caf"
integrity sha512-zT2fj/bmOgEBjqGbURGlowTmCwsIs3bRDMr/sFZz8Ly7VkEiwuCn9swNTL3pPuf8Oua2de7CLuKdnuNajWdDsQ==
follow-redirects@^1.0.0: follow-redirects@^1.0.0:
version "1.7.0" version "1.7.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"