Fix room list v2 context menus to be aria menus

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2020-07-05 18:23:57 +01:00
parent 83cfdd9c07
commit 069cdf3ce0
3 changed files with 67 additions and 19 deletions

View File

@ -23,6 +23,8 @@ import classNames from 'classnames';
import {Key} from "../../Keyboard"; import {Key} from "../../Keyboard";
import * as sdk from "../../index"; import * as sdk from "../../index";
import AccessibleButton from "../views/elements/AccessibleButton"; import AccessibleButton from "../views/elements/AccessibleButton";
import StyledCheckbox from "../views/elements/StyledCheckbox";
import StyledRadioButton from "../views/elements/StyledRadioButton";
// Shamelessly ripped off Modal.js. There's probably a better way // Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and // of doing reusable widgets like dialog boxes & menus where we go and
@ -421,6 +423,23 @@ MenuItemCheckbox.propTypes = {
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
}; };
// Semantic component for representing a styled role=menuitemcheckbox
export const StyledMenuItemCheckbox = ({children, label, active=false, disabled=false, ...props}) => {
return (
<StyledCheckbox {...props} role="menuitemcheckbox" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
{ children }
</StyledCheckbox>
);
};
StyledMenuItemCheckbox.propTypes = {
...AccessibleButton.propTypes,
label: PropTypes.string, // optional
active: PropTypes.bool.isRequired,
disabled: PropTypes.bool, // optional
className: PropTypes.string, // optional
onClick: PropTypes.func.isRequired,
};
// Semantic component for representing a role=menuitemradio // Semantic component for representing a role=menuitemradio
export const MenuItemRadio = ({children, label, active=false, disabled=false, ...props}) => { export const MenuItemRadio = ({children, label, active=false, disabled=false, ...props}) => {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -439,6 +458,23 @@ MenuItemRadio.propTypes = {
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
}; };
// Semantic component for representing a styled role=menuitemradio
export const StyledMenuItemRadio = ({children, label, active=false, disabled=false, ...props}) => {
return (
<StyledRadioButton {...props} role="menuitemradio" aria-checked={active} aria-disabled={disabled} tabIndex={-1} aria-label={label}>
{ children }
</StyledRadioButton>
);
};
StyledMenuItemRadio.propTypes = {
...StyledMenuItemRadio.propTypes,
label: PropTypes.string, // optional
active: PropTypes.bool.isRequired,
disabled: PropTypes.bool, // optional
className: PropTypes.string, // optional
onClick: PropTypes.func.isRequired,
};
// Placement method for <ContextMenu /> to position context menu to right of elementRect with chevronOffset // Placement method for <ContextMenu /> to position context menu to right of elementRect with chevronOffset
export const toRightOf = (elementRect, chevronOffset=12) => { export const toRightOf = (elementRect, chevronOffset=12) => {
const left = elementRect.right + window.pageXOffset + 3; const left = elementRect.right + window.pageXOffset + 3;

View File

@ -26,16 +26,18 @@ import AccessibleButton from "../../views/elements/AccessibleButton";
import RoomTile2 from "./RoomTile2"; import RoomTile2 from "./RoomTile2";
import { ResizableBox, ResizeCallbackData } from "react-resizable"; import { ResizableBox, ResizeCallbackData } from "react-resizable";
import { ListLayout } from "../../../stores/room-list/ListLayout"; import { ListLayout } from "../../../stores/room-list/ListLayout";
import { ContextMenu, ContextMenuButton } from "../../structures/ContextMenu"; import {
import StyledCheckbox from "../elements/StyledCheckbox"; ContextMenu,
import StyledRadioButton from "../elements/StyledRadioButton"; ContextMenuButton,
StyledMenuItemCheckbox,
StyledMenuItemRadio,
} from "../../structures/ContextMenu";
import RoomListStore from "../../../stores/room-list/RoomListStore2"; import RoomListStore from "../../../stores/room-list/RoomListStore2";
import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models"; import { ListAlgorithm, SortAlgorithm } from "../../../stores/room-list/algorithms/models";
import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
import { ListNotificationState } from "../../../stores/notifications/ListNotificationState"; import { ListNotificationState } from "../../../stores/notifications/ListNotificationState";
import Tooltip from "../elements/Tooltip";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { Key } from "../../../Keyboard"; import { Key } from "../../../Keyboard";
@ -329,40 +331,40 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
<div className="mx_RoomSublist2_contextMenu"> <div className="mx_RoomSublist2_contextMenu">
<div> <div>
<div className='mx_RoomSublist2_contextMenu_title'>{_t("Sort by")}</div> <div className='mx_RoomSublist2_contextMenu_title'>{_t("Sort by")}</div>
<StyledRadioButton <StyledMenuItemRadio
onChange={() => this.onTagSortChanged(SortAlgorithm.Recent)} onChange={() => this.onTagSortChanged(SortAlgorithm.Recent)}
checked={!isAlphabetical} checked={!isAlphabetical}
name={`mx_${this.props.tagId}_sortBy`} name={`mx_${this.props.tagId}_sortBy`}
> >
{_t("Activity")} {_t("Activity")}
</StyledRadioButton> </StyledMenuItemRadio>
<StyledRadioButton <StyledMenuItemRadio
onChange={() => this.onTagSortChanged(SortAlgorithm.Alphabetic)} onChange={() => this.onTagSortChanged(SortAlgorithm.Alphabetic)}
checked={isAlphabetical} checked={isAlphabetical}
name={`mx_${this.props.tagId}_sortBy`} name={`mx_${this.props.tagId}_sortBy`}
> >
{_t("A-Z")} {_t("A-Z")}
</StyledRadioButton> </StyledMenuItemRadio>
</div> </div>
<hr /> <hr />
<div> <div>
<div className='mx_RoomSublist2_contextMenu_title'>{_t("Unread rooms")}</div> <div className='mx_RoomSublist2_contextMenu_title'>{_t("Unread rooms")}</div>
<StyledCheckbox <StyledMenuItemCheckbox
onChange={this.onUnreadFirstChanged} onChange={this.onUnreadFirstChanged}
checked={isUnreadFirst} checked={isUnreadFirst}
> >
{_t("Always show first")} {_t("Always show first")}
</StyledCheckbox> </StyledMenuItemCheckbox>
</div> </div>
<hr /> <hr />
<div> <div>
<div className='mx_RoomSublist2_contextMenu_title'>{_t("Show")}</div> <div className='mx_RoomSublist2_contextMenu_title'>{_t("Show")}</div>
<StyledCheckbox <StyledMenuItemCheckbox
onChange={this.onMessagePreviewChanged} onChange={this.onMessagePreviewChanged}
checked={this.props.layout.showPreviews} checked={this.props.layout.showPreviews}
> >
{_t("Message preview")} {_t("Message preview")}
</StyledCheckbox> </StyledMenuItemCheckbox>
</div> </div>
</div> </div>
</ContextMenu> </ContextMenu>

View File

@ -26,7 +26,13 @@ import dis from '../../../dispatcher/dispatcher';
import { Key } from "../../../Keyboard"; import { Key } from "../../../Keyboard";
import ActiveRoomObserver from "../../../ActiveRoomObserver"; import ActiveRoomObserver from "../../../ActiveRoomObserver";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { ContextMenu, ContextMenuButton, MenuItemRadio } from "../../structures/ContextMenu"; import {
ContextMenu,
ContextMenuButton,
MenuItemRadio,
MenuItemCheckbox,
MenuItem,
} from "../../structures/ContextMenu";
import { DefaultTagID, TagID } from "../../../stores/room-list/models"; import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore"; import { MessagePreviewStore } from "../../../stores/room-list/MessagePreviewStore";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
@ -328,20 +334,24 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
<ContextMenu {...contextMenuBelow(this.state.generalMenuPosition)} onFinished={this.onCloseGeneralMenu}> <ContextMenu {...contextMenuBelow(this.state.generalMenuPosition)} onFinished={this.onCloseGeneralMenu}>
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu"> <div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu">
<div className="mx_IconizedContextMenu_optionList"> <div className="mx_IconizedContextMenu_optionList">
<AccessibleButton onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}> <MenuItemCheckbox
onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}
active={false} // TODO: https://github.com/vector-im/riot-web/issues/14283
label={_t("Favourite")}
>
<span className="mx_IconizedContextMenu_icon mx_RoomTile2_iconStar" /> <span className="mx_IconizedContextMenu_icon mx_RoomTile2_iconStar" />
<span className="mx_IconizedContextMenu_label">{_t("Favourite")}</span> <span className="mx_IconizedContextMenu_label">{_t("Favourite")}</span>
</AccessibleButton> </MenuItemCheckbox>
<AccessibleButton onClick={this.onOpenRoomSettings}> <MenuItem onClick={this.onOpenRoomSettings} label={_t("Settings")}>
<span className="mx_IconizedContextMenu_icon mx_RoomTile2_iconSettings" /> <span className="mx_IconizedContextMenu_icon mx_RoomTile2_iconSettings" />
<span className="mx_IconizedContextMenu_label">{_t("Settings")}</span> <span className="mx_IconizedContextMenu_label">{_t("Settings")}</span>
</AccessibleButton> </MenuItem>
</div> </div>
<div className="mx_IconizedContextMenu_optionList mx_RoomTile2_contextMenu_redRow"> <div className="mx_IconizedContextMenu_optionList mx_RoomTile2_contextMenu_redRow">
<AccessibleButton onClick={this.onLeaveRoomClick}> <MenuItem onClick={this.onLeaveRoomClick} label={_t("Leave Room")}>
<span className="mx_IconizedContextMenu_icon mx_RoomTile2_iconSignOut" /> <span className="mx_IconizedContextMenu_icon mx_RoomTile2_iconSignOut" />
<span className="mx_IconizedContextMenu_label">{_t("Leave Room")}</span> <span className="mx_IconizedContextMenu_label">{_t("Leave Room")}</span>
</AccessibleButton> </MenuItem>
</div> </div>
</div> </div>
</ContextMenu> </ContextMenu>