Sprinkle in some better ARIA props

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2020-07-05 01:07:46 +01:00
parent 4c7014167d
commit 1620feb55e
5 changed files with 55 additions and 19 deletions

View file

@ -235,7 +235,12 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
private renderSearchExplore(): React.ReactNode { private renderSearchExplore(): React.ReactNode {
return ( return (
<div className="mx_LeftPanel2_filterContainer" onFocus={this.onFocus} onBlur={this.onBlur}> <div
className="mx_LeftPanel2_filterContainer"
onFocus={this.onFocus}
onBlur={this.onBlur}
onKeyDown={this.onKeyDown}
>
<RoomSearch <RoomSearch
onQueryUpdate={this.onSearch} onQueryUpdate={this.onSearch}
isMinimized={this.props.isMinimized} isMinimized={this.props.isMinimized}
@ -245,7 +250,7 @@ export default class LeftPanel2 extends React.Component<IProps, IState> {
// TODO fix the accessibility of this: https://github.com/vector-im/riot-web/issues/14180 // TODO fix the accessibility of this: https://github.com/vector-im/riot-web/issues/14180
className="mx_LeftPanel2_exploreButton" className="mx_LeftPanel2_exploreButton"
onClick={this.onExplore} onClick={this.onExplore}
alt={_t("Explore rooms")} title={_t("Explore rooms")}
/> />
</div> </div>
); );

View file

@ -149,7 +149,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
let clearButton = ( let clearButton = (
<AccessibleButton <AccessibleButton
tabIndex={-1} tabIndex={-1}
className='mx_RoomSearch_clearButton' title={_t("Clear filter")}
className="mx_RoomSearch_clearButton"
onClick={this.clearInput} onClick={this.clearInput}
/> />
); );
@ -157,8 +158,8 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
if (this.props.isMinimized) { if (this.props.isMinimized) {
icon = ( icon = (
<AccessibleButton <AccessibleButton
tabIndex={-1} title={_t("Search rooms")}
className='mx_RoomSearch_icon' className="mx_RoomSearch_icon"
onClick={this.openSearch} onClick={this.openSearch}
/> />
); );

View file

@ -276,9 +276,6 @@ export default class RoomList2 extends React.Component<IProps, IState> {
className="mx_RoomList2" className="mx_RoomList2"
role="tree" role="tree"
aria-label={_t("Rooms")} aria-label={_t("Rooms")}
// Firefox sometimes makes this element focusable due to
// overflow:scroll;, so force it out of tab order.
tabIndex={-1}
>{sublists}</div> >{sublists}</div>
)} )}
</RovingTabIndexProvider> </RovingTabIndexProvider>

View file

@ -63,7 +63,7 @@ interface IProps {
onAddRoom?: () => void; onAddRoom?: () => void;
addRoomLabel: string; addRoomLabel: string;
isInvite: boolean; isInvite: boolean;
layout: ListLayout; layout?: ListLayout;
isMinimized: boolean; isMinimized: boolean;
tagId: TagID; tagId: TagID;
@ -203,6 +203,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: room.roomId, room_id: room.roomId,
show_room_tile: true, // to make sure the room gets scrolled into view
}); });
} }
}; };
@ -383,16 +384,22 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
private renderHeader(): React.ReactElement { private renderHeader(): React.ReactElement {
return ( return (
<RovingTabIndexWrapper> <RovingTabIndexWrapper inputRef={this.headerButton}>
{({onFocus, isActive, ref}) => { {({onFocus, isActive, ref}) => {
const tabIndex = isActive ? 0 : -1; const tabIndex = isActive ? 0 : -1;
let ariaLabel = _t("Jump to first unread room.");
if (this.props.tagId === DefaultTagID.Invite) {
ariaLabel = _t("Jump to first invite.");
}
const badge = ( const badge = (
<NotificationBadge <NotificationBadge
forceCount={true} forceCount={true}
notification={this.state.notificationState} notification={this.state.notificationState}
onClick={this.onBadgeClick} onClick={this.onBadgeClick}
tabIndex={tabIndex} tabIndex={tabIndex}
aria-label={ariaLabel}
/> />
); );
@ -433,7 +440,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
// doesn't become sticky. // doesn't become sticky.
// The same applies to the notification badge. // The same applies to the notification badge.
return ( return (
<div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus}> <div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus} aria-label={this.props.label}>
<div className="mx_RoomSublist2_stickable"> <div className="mx_RoomSublist2_stickable">
<AccessibleButton <AccessibleButton
onFocus={onFocus} onFocus={onFocus}
@ -441,6 +448,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
tabIndex={tabIndex} tabIndex={tabIndex}
className="mx_RoomSublist2_headerText" className="mx_RoomSublist2_headerText"
role="treeitem" role="treeitem"
aria-expanded={!this.props.layout || !this.props.layout.isCollapsed}
aria-level={1} aria-level={1}
onClick={this.onHeaderClick} onClick={this.onHeaderClick}
onContextMenu={this.onContextMenu} onContextMenu={this.onContextMenu}
@ -496,12 +504,12 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
); );
if (this.props.isMinimized) showMoreText = null; if (this.props.isMinimized) showMoreText = null;
showNButton = ( showNButton = (
<div onClick={this.onShowAllClick} className={showMoreBtnClasses}> <AccessibleButton onClick={this.onShowAllClick} className={showMoreBtnClasses} tabIndex={-1}>
<span className='mx_RoomSublist2_showMoreButtonChevron mx_RoomSublist2_showNButtonChevron'> <span className='mx_RoomSublist2_showMoreButtonChevron mx_RoomSublist2_showNButtonChevron'>
{/* set by CSS masking */} {/* set by CSS masking */}
</span> </span>
{showMoreText} {showMoreText}
</div> </AccessibleButton>
); );
} else if (this.numTiles <= visibleTiles.length && this.numTiles > this.props.layout.defaultVisibleTiles) { } else if (this.numTiles <= visibleTiles.length && this.numTiles > this.props.layout.defaultVisibleTiles) {
// we have all tiles visible - add a button to show less // we have all tiles visible - add a button to show less
@ -512,12 +520,12 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
); );
if (this.props.isMinimized) showLessText = null; if (this.props.isMinimized) showLessText = null;
showNButton = ( showNButton = (
<div onClick={this.onShowLessClick} className={showMoreBtnClasses}> <AccessibleButton onClick={this.onShowLessClick} className={showMoreBtnClasses} tabIndex={-1}>
<span className='mx_RoomSublist2_showLessButtonChevron mx_RoomSublist2_showNButtonChevron'> <span className='mx_RoomSublist2_showLessButtonChevron mx_RoomSublist2_showNButtonChevron'>
{/* set by CSS masking */} {/* set by CSS masking */}
</span> </span>
{showLessText} {showLessText}
</div> </AccessibleButton>
); );
} }

View file

@ -30,9 +30,15 @@ import { ContextMenu, ContextMenuButton, MenuItemRadio } from "../../structures/
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";
import { getRoomNotifsState, ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs"; import {
getRoomNotifsState,
setRoomNotifsState,
ALL_MESSAGES,
ALL_MESSAGES_LOUD,
MENTIONS_ONLY,
MUTE,
} from "../../../RoomNotifs";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { setRoomNotifsState } from "../../../RoomNotifs";
import { TagSpecificNotificationState } from "../../../stores/notifications/TagSpecificNotificationState"; import { TagSpecificNotificationState } from "../../../stores/notifications/TagSpecificNotificationState";
import { INotificationState } from "../../../stores/notifications/INotificationState"; import { INotificationState } from "../../../stores/notifications/INotificationState";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
@ -406,10 +412,11 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
} }
} }
const notificationColor = this.state.notificationState.color;
const nameClasses = classNames({ const nameClasses = classNames({
"mx_RoomTile2_name": true, "mx_RoomTile2_name": true,
"mx_RoomTile2_nameWithPreview": !!messagePreview, "mx_RoomTile2_nameWithPreview": !!messagePreview,
"mx_RoomTile2_nameHasUnreadEvents": this.state.notificationState.color >= NotificationColor.Bold, "mx_RoomTile2_nameHasUnreadEvents": notificationColor >= NotificationColor.Bold,
}); });
let nameContainer = ( let nameContainer = (
@ -422,6 +429,22 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
); );
if (this.props.isMinimized) nameContainer = null; if (this.props.isMinimized) nameContainer = null;
let ariaLabel = name;
// The following labels are written in such a fashion to increase screen reader efficiency (speed).
if (this.props.tag === DefaultTagID.Invite) {
// append nothing
} else if (notificationColor >= NotificationColor.Red) {
ariaLabel += " " + _t("%(count)s unread messages including mentions.", {
count: this.state.notificationState.count,
});
} else if (notificationColor >= NotificationColor.Grey) {
ariaLabel += " " + _t("%(count)s unread messages.", {
count: this.state.notificationState.count,
});
} else if (notificationColor >= NotificationColor.Bold) {
ariaLabel += " " + _t("Unread messages.");
}
return ( return (
<React.Fragment> <React.Fragment>
<RovingTabIndexWrapper> <RovingTabIndexWrapper>
@ -434,8 +457,10 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
onMouseEnter={this.onTileMouseEnter} onMouseEnter={this.onTileMouseEnter}
onMouseLeave={this.onTileMouseLeave} onMouseLeave={this.onTileMouseLeave}
onClick={this.onTileClick} onClick={this.onTileClick}
role="treeitem"
onContextMenu={this.onContextMenu} onContextMenu={this.onContextMenu}
role="treeitem"
aria-label={ariaLabel}
aria-selected={this.state.selected}
> >
{roomAvatar} {roomAvatar}
{nameContainer} {nameContainer}