Refactor ContextMenu to use RovingTabIndex (more consistent keyboard navigation accessibility) (#7353)

Split off from https://github.com/matrix-org/matrix-react-sdk/pull/7339
This commit is contained in:
Eric Eastwood 2021-12-17 11:08:56 -06:00 committed by GitHub
parent 6761ef9540
commit 9289c0c90f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 224 additions and 160 deletions

View file

@ -18,10 +18,9 @@ limitations under the License.
import React from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
import { RovingAccessibleButton, RovingAccessibleTooltipButton } from "../RovingTabIndex";
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
interface IProps extends React.ComponentProps<typeof RovingAccessibleButton> {
label?: string;
tooltip?: string;
}
@ -31,15 +30,14 @@ export const MenuItem: React.FC<IProps> = ({ children, label, tooltip, ...props
const ariaLabel = props["aria-label"] || label;
if (tooltip) {
return <AccessibleTooltipButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel} title={tooltip}>
return <RovingAccessibleTooltipButton {...props} role="menuitem" aria-label={ariaLabel} title={tooltip}>
{ children }
</AccessibleTooltipButton>;
</RovingAccessibleTooltipButton>;
}
return (
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel}>
<RovingAccessibleButton {...props} role="menuitem" aria-label={ariaLabel}>
{ children }
</AccessibleButton>
</RovingAccessibleButton>
);
};

View file

@ -18,9 +18,9 @@ limitations under the License.
import React from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { RovingAccessibleButton } from "../RovingTabIndex";
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
interface IProps extends React.ComponentProps<typeof RovingAccessibleButton> {
label?: string;
active: boolean;
}
@ -28,16 +28,15 @@ interface IProps extends React.ComponentProps<typeof AccessibleButton> {
// Semantic component for representing a role=menuitemcheckbox
export const MenuItemCheckbox: React.FC<IProps> = ({ children, label, active, disabled, ...props }) => {
return (
<AccessibleButton
<RovingAccessibleButton
{...props}
role="menuitemcheckbox"
aria-checked={active}
aria-disabled={disabled}
disabled={disabled}
tabIndex={-1}
aria-label={label}
>
{ children }
</AccessibleButton>
</RovingAccessibleButton>
);
};

View file

@ -18,9 +18,9 @@ limitations under the License.
import React from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import { RovingAccessibleButton } from "../RovingTabIndex";
interface IProps extends React.ComponentProps<typeof AccessibleButton> {
interface IProps extends React.ComponentProps<typeof RovingAccessibleButton> {
label?: string;
active: boolean;
}
@ -28,16 +28,15 @@ interface IProps extends React.ComponentProps<typeof AccessibleButton> {
// Semantic component for representing a role=menuitemradio
export const MenuItemRadio: React.FC<IProps> = ({ children, label, active, disabled, ...props }) => {
return (
<AccessibleButton
<RovingAccessibleButton
{...props}
role="menuitemradio"
aria-checked={active}
aria-disabled={disabled}
disabled={disabled}
tabIndex={-1}
aria-label={label}
>
{ children }
</AccessibleButton>
</RovingAccessibleButton>
);
};

View file

@ -19,6 +19,7 @@ limitations under the License.
import React from "react";
import { Key } from "../../Keyboard";
import { useRovingTabIndex } from "../RovingTabIndex";
import StyledCheckbox from "../../components/views/elements/StyledCheckbox";
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
@ -29,6 +30,8 @@ interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
// Semantic component for representing a styled role=menuitemcheckbox
export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onChange, onClose, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === Key.ENTER || e.key === Key.SPACE) {
e.stopPropagation();
@ -52,11 +55,13 @@ export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onCh
<StyledCheckbox
{...props}
role="menuitemcheckbox"
tabIndex={-1}
aria-label={label}
onChange={onChange}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
onFocus={onFocus}
inputRef={ref}
tabIndex={isActive ? 0 : -1}
>
{ children }
</StyledCheckbox>

View file

@ -19,6 +19,7 @@ limitations under the License.
import React from "react";
import { Key } from "../../Keyboard";
import { useRovingTabIndex } from "../RovingTabIndex";
import StyledRadioButton from "../../components/views/elements/StyledRadioButton";
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
@ -29,6 +30,8 @@ interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
// Semantic component for representing a styled role=menuitemradio
export const StyledMenuItemRadio: React.FC<IProps> = ({ children, label, onChange, onClose, ...props }) => {
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === Key.ENTER || e.key === Key.SPACE) {
e.stopPropagation();
@ -52,11 +55,13 @@ export const StyledMenuItemRadio: React.FC<IProps> = ({ children, label, onChang
<StyledRadioButton
{...props}
role="menuitemradio"
tabIndex={-1}
aria-label={label}
onChange={onChange}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
onFocus={onFocus}
inputRef={ref}
tabIndex={isActive ? 0 : -1}
>
{ children }
</StyledRadioButton>