Improve ThreadPanel ctx menu accessibility (#7217)

This commit is contained in:
Michael Telatynski 2021-11-29 17:42:53 +00:00 committed by GitHub
parent 9727a82a12
commit fe24c8ad2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 35 additions and 48 deletions

View File

@ -27,7 +27,6 @@ limitations under the License.
width: 100%;
height: 100%;
opacity: 1.0;
z-index: 5000;
}
.mx_ContextualMenu {

View File

@ -120,7 +120,7 @@ limitations under the License.
&:hover {
background-color: $event-selected-color;
}
&[aria-selected="true"] {
&[aria-checked="true"] {
:first-child {
margin-left: -20px;
}

View File

@ -103,7 +103,7 @@ interface IState {
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
@replaceableComponent("structures.ContextMenu")
export class ContextMenu extends React.PureComponent<IProps, IState> {
export default class ContextMenu extends React.PureComponent<IProps, IState> {
private readonly initialFocus: HTMLElement;
static defaultProps = {
@ -411,6 +411,7 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
onClick={this.onClick}
onContextMenu={this.onContextMenuPreventBubbling}
>
{ background }
<div
className={menuClasses}
style={menuStyle}
@ -419,7 +420,6 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
>
{ body }
</div>
{ background }
</div>
);
}
@ -530,30 +530,22 @@ export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<
return [isOpen, button, open, close, setIsOpen];
};
@replaceableComponent("structures.LegacyContextMenu")
export default class LegacyContextMenu extends ContextMenu {
render() {
return this.renderMenu(false);
}
}
// XXX: Deprecated, used only for dynamic Tooltips. Avoid using at all costs.
export function createMenu(ElementClass, props) {
const onFinished = function(...args) {
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
if (props && props.onFinished) {
props.onFinished.apply(null, args);
}
props?.onFinished?.apply(null, args);
};
const menu = <LegacyContextMenu
const menu = <ContextMenu
{...props}
mountAsChild={true}
hasBackground={false}
onFinished={onFinished} // eslint-disable-line react/jsx-no-bind
windowResize={onFinished} // eslint-disable-line react/jsx-no-bind
>
<ElementClass {...props} onFinished={onFinished} />
</LegacyContextMenu>;
</ContextMenu>;
ReactDOM.render(menu, getOrCreateContainer());

View File

@ -24,12 +24,11 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
import MatrixClientContext from '../../contexts/MatrixClientContext';
import { _t } from '../../languageHandler';
import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuButton';
import ContextMenu, { ChevronFace, useContextMenu } from './ContextMenu';
import ContextMenu, { ChevronFace, MenuItemRadio, useContextMenu } from './ContextMenu';
import RoomContext, { TimelineRenderingType } from '../../contexts/RoomContext';
import TimelinePanel from './TimelinePanel';
import { Layout } from '../../settings/enums/Layout';
import { useEventEmitter } from '../../hooks/useEventEmitter';
import AccessibleButton from '../views/elements/AccessibleButton';
import { TileShape } from '../views/rooms/EventTile';
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
@ -98,14 +97,14 @@ export const ThreadPanelHeaderFilterOptionItem = ({
onClick: () => void;
isSelected: boolean;
}) => {
return <AccessibleButton
aria-selected={isSelected}
return <MenuItemRadio
active={isSelected}
className="mx_ThreadPanel_Header_FilterOptionItem"
onClick={onClick}
>
<span>{ label }</span>
<span>{ description }</span>
</AccessibleButton>;
</MenuItemRadio>;
};
export const ThreadPanelHeader = ({ filterOption, setFilterOption }: {
@ -141,8 +140,8 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption }: {
top={0}
right={25}
onFinished={closeMenu}
managed={false}
chevronFace={ChevronFace.Top}
mountAsChild={true}
>
{ contextMenuOptions }
</ContextMenu> : null;

View File

@ -21,7 +21,7 @@ import MemberAvatar from '../avatars/MemberAvatar';
import classNames from 'classnames';
import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu";
import SettingsStore from "../../../settings/SettingsStore";
import { ChevronFace, ContextMenu, ContextMenuButton } from "../../structures/ContextMenu";
import ContextMenu, { ChevronFace, ContextMenuButton } from "../../structures/ContextMenu";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";

View File

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import { ContextMenu, IProps as IContextMenuProps, MenuItem } from '../../structures/ContextMenu';
import ContextMenu, { IProps as IContextMenuProps, MenuItem } from '../../structures/ContextMenu';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import CallHandler from '../../../CallHandler';
import InviteDialog, { KIND_CALL_TRANSFER } from '../dialogs/InviteDialog';

View File

@ -17,7 +17,7 @@ limitations under the License.
import * as React from "react";
import { createRef } from "react";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
import ContextMenu, { IProps as IContextMenuProps } from '../../structures/ContextMenu';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import Field from "../elements/Field";
import DialPad from '../voip/DialPad';

View File

@ -17,9 +17,8 @@ limitations under the License.
import React from "react";
import classNames from "classnames";
import {
import ContextMenu, {
ChevronFace,
ContextMenu,
IProps as IContextMenuProps,
MenuItem,
MenuItemCheckbox, MenuItemRadio,

View File

@ -21,9 +21,8 @@ import { IProtocol } from "matrix-js-sdk/src/client";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { instanceForInstanceId } from '../../../utils/DirectoryUtils';
import {
import ContextMenu, {
ChevronFace,
ContextMenu,
ContextMenuButton,
MenuGroup,
MenuItem,

View File

@ -18,7 +18,7 @@ limitations under the License.
import TagTile from './TagTile';
import React from 'react';
import { ContextMenu, toRightOf, useContextMenu } from "../../structures/ContextMenu";
import ContextMenu, { toRightOf, useContextMenu } from "../../structures/ContextMenu";
import * as sdk from '../../../index';
export default function DNDTagTile(props) {

View File

@ -23,7 +23,7 @@ import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import classNames from 'classnames';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { ContextMenu, ContextMenuButton, toRightOf } from "../../structures/ContextMenu";
import ContextMenu, { ContextMenuButton, toRightOf } from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex";
import { replaceableComponent } from "../../../utils/replaceableComponent";

View File

@ -23,7 +23,7 @@ import type { Relations } from 'matrix-js-sdk/src/models/relations';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import { Action } from '../../../dispatcher/actions';
import { aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
import ContextMenu, { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import Toolbar from "../../../accessibility/Toolbar";

View File

@ -24,7 +24,7 @@ import { _t } from '../../../languageHandler';
import { isContentActionable } from '../../../utils/EventUtils';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import { aboveLeftOf, ContextMenu, useContextMenu } from "../../structures/ContextMenu";
import ContextMenu, { aboveLeftOf, useContextMenu } from "../../structures/ContextMenu";
import ReactionPicker from "../emojipicker/ReactionPicker";
import ReactionsRowButton from "./ReactionsRowButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext";

View File

@ -27,9 +27,8 @@ import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalin
import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore";
import {
import ContextMenu, {
aboveLeftOf,
ContextMenu,
useContextMenu,
MenuItem,
AboveLeftOf,

View File

@ -26,9 +26,8 @@ import { _t } from "../../../languageHandler";
import AccessibleButton from "../../views/elements/AccessibleButton";
import RoomTile from "./RoomTile";
import { ListLayout } from "../../../stores/room-list/ListLayout";
import {
import ContextMenu, {
ChevronFace,
ContextMenu,
ContextMenuTooltipButton,
StyledMenuItemCheckbox,
StyledMenuItemRadio,

View File

@ -13,6 +13,7 @@ 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.
*/
import React from 'react';
import { Room } from 'matrix-js-sdk/src/models/room';
import { _t, _td } from '../../../languageHandler';
@ -24,7 +25,7 @@ import WidgetUtils, { IWidgetEvent } from '../../../utils/WidgetUtils';
import PersistedElement from "../elements/PersistedElement";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore";
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
import ContextMenu, { ChevronFace } from "../../structures/ContextMenu";
import { WidgetType } from "../../../widgets/WidgetType";
import { Action } from "../../../dispatcher/actions";
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";

View File

@ -18,7 +18,7 @@ import React, { useMemo } from "react";
import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { alwaysAboveRightOf, ChevronFace, ContextMenu, useContextMenu } from "../../structures/ContextMenu";
import ContextMenu, { alwaysAboveRightOf, ChevronFace, useContextMenu } from "../../structures/ContextMenu";
import AccessibleButton from "../elements/AccessibleButton";
import StyledCheckbox from "../elements/StyledCheckbox";
import { MetaSpace } from "../../../stores/spaces";

View File

@ -22,7 +22,7 @@ import { HistoryVisibility, Preset } from "matrix-js-sdk/src/@types/partials";
import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
import ContextMenu, { ChevronFace } from "../../structures/ContextMenu";
import createRoom, { IOpts as ICreateOpts } from "../../../createRoom";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import SpaceBasicSettings, { SpaceAvatar } from "./SpaceBasicSettings";

View File

@ -70,7 +70,7 @@ describe('ThreadPanel', () => {
wrapper.find(ContextMenuButton).simulate('click');
const found = wrapper.find(ThreadPanelHeaderFilterOptionItem);
expect(found.length).toEqual(2);
const foundButton = found.find('[aria-selected=true]').first();
const foundButton = found.find('[aria-checked=true]').first();
expect(foundButton.text()).toEqual(`${_t("All threads")}${_t('Shows all threads from current room')}`);
expect(foundButton).toMatchSnapshot();
});

View File

@ -46,21 +46,21 @@ exports[`ThreadPanel Header expect that My filter for ThreadPanelHeader properly
exports[`ThreadPanel Header expect that ThreadPanelHeader has the correct option selected in the context menu 1`] = `
<AccessibleButton
aria-selected={true}
aria-checked={true}
className="mx_ThreadPanel_Header_FilterOptionItem"
element="div"
onClick={[Function]}
role="button"
tabIndex={0}
role="menuitemradio"
tabIndex={-1}
>
<div
aria-selected={true}
aria-checked={true}
className="mx_AccessibleButton mx_ThreadPanel_Header_FilterOptionItem"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
role="menuitemradio"
tabIndex={-1}
>
<span>
All threads