Fix accessibility regressions (#7336)
* Fix room list roving treeview New TooltipTarget & TextWithTooltip were not roving-accessible * Fix programmatic focus management in roving tab index not triggering onFocus handler * Fix toolbar no longer handling left & right arrows * Fix roving tab index focus tracking on interactive element like context menu trigger * Fix thread list context menu roving * add comment * fix comment * Fix handling vertical arrows in the wrong direction * iterate PR * delint * tidy up
This commit is contained in:
parent
60286f6170
commit
a667677c57
8 changed files with 103 additions and 58 deletions
|
@ -131,6 +131,8 @@ export const reducer = (state: IState, action: IAction) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
case Type.SetFocus: {
|
case Type.SetFocus: {
|
||||||
|
// if the ref doesn't change just return the same object reference to skip a re-render
|
||||||
|
if (state.activeRef === action.payload.ref) return state;
|
||||||
// update active ref
|
// update active ref
|
||||||
state.activeRef = action.payload.ref;
|
state.activeRef = action.payload.ref;
|
||||||
return { ...state };
|
return { ...state };
|
||||||
|
@ -194,6 +196,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
let handled = false;
|
let handled = false;
|
||||||
|
let focusRef: RefObject<HTMLElement>;
|
||||||
// Don't interfere with input default keydown behaviour
|
// Don't interfere with input default keydown behaviour
|
||||||
if (ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") {
|
if (ev.target.tagName !== "INPUT" && ev.target.tagName !== "TEXTAREA") {
|
||||||
// check if we actually have any items
|
// check if we actually have any items
|
||||||
|
@ -202,7 +205,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
|
||||||
if (handleHomeEnd) {
|
if (handleHomeEnd) {
|
||||||
handled = true;
|
handled = true;
|
||||||
// move focus to first (visible) item
|
// move focus to first (visible) item
|
||||||
findSiblingElement(context.state.refs, 0)?.current?.focus();
|
focusRef = findSiblingElement(context.state.refs, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -210,28 +213,30 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
|
||||||
if (handleHomeEnd) {
|
if (handleHomeEnd) {
|
||||||
handled = true;
|
handled = true;
|
||||||
// move focus to last (visible) item
|
// move focus to last (visible) item
|
||||||
findSiblingElement(context.state.refs, context.state.refs.length - 1, true)?.current?.focus();
|
focusRef = findSiblingElement(context.state.refs, context.state.refs.length - 1, true);
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Key.ARROW_UP:
|
|
||||||
case Key.ARROW_RIGHT:
|
|
||||||
if ((ev.key === Key.ARROW_UP && handleUpDown) || (ev.key === Key.ARROW_RIGHT && handleLeftRight)) {
|
|
||||||
handled = true;
|
|
||||||
if (context.state.refs.length > 0) {
|
|
||||||
const idx = context.state.refs.indexOf(context.state.activeRef);
|
|
||||||
findSiblingElement(context.state.refs, idx - 1)?.current?.focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Key.ARROW_DOWN:
|
case Key.ARROW_DOWN:
|
||||||
case Key.ARROW_LEFT:
|
case Key.ARROW_RIGHT:
|
||||||
if ((ev.key === Key.ARROW_DOWN && handleUpDown) || (ev.key === Key.ARROW_LEFT && handleLeftRight)) {
|
if ((ev.key === Key.ARROW_DOWN && handleUpDown) ||
|
||||||
|
(ev.key === Key.ARROW_RIGHT && handleLeftRight)
|
||||||
|
) {
|
||||||
handled = true;
|
handled = true;
|
||||||
if (context.state.refs.length > 0) {
|
if (context.state.refs.length > 0) {
|
||||||
const idx = context.state.refs.indexOf(context.state.activeRef);
|
const idx = context.state.refs.indexOf(context.state.activeRef);
|
||||||
findSiblingElement(context.state.refs, idx + 1, true)?.current?.focus();
|
focusRef = findSiblingElement(context.state.refs, idx + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.ARROW_UP:
|
||||||
|
case Key.ARROW_LEFT:
|
||||||
|
if ((ev.key === Key.ARROW_UP && handleUpDown) || (ev.key === Key.ARROW_LEFT && handleLeftRight)) {
|
||||||
|
handled = true;
|
||||||
|
if (context.state.refs.length > 0) {
|
||||||
|
const idx = context.state.refs.indexOf(context.state.activeRef);
|
||||||
|
focusRef = findSiblingElement(context.state.refs, idx - 1, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -242,7 +247,18 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}, [context.state, onKeyDown, handleHomeEnd, handleUpDown, handleLeftRight]);
|
|
||||||
|
if (focusRef) {
|
||||||
|
focusRef.current?.focus();
|
||||||
|
// programmatic focus doesn't fire the onFocus handler, so we must do the do ourselves
|
||||||
|
dispatch({
|
||||||
|
type: Type.SetFocus,
|
||||||
|
payload: {
|
||||||
|
ref: focusRef,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [context, onKeyDown, handleHomeEnd, handleUpDown, handleLeftRight]);
|
||||||
|
|
||||||
return <RovingTabIndexContext.Provider value={context}>
|
return <RovingTabIndexContext.Provider value={context}>
|
||||||
{ children({ onKeyDownHandler }) }
|
{ children({ onKeyDownHandler }) }
|
||||||
|
@ -283,7 +299,7 @@ export const useRovingTabIndex = (inputRef?: Ref): [FocusHandler, boolean, Ref]
|
||||||
type: Type.SetFocus,
|
type: Type.SetFocus,
|
||||||
payload: { ref },
|
payload: { ref },
|
||||||
});
|
});
|
||||||
}, [ref, context]);
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const isActive = context.state.activeRef === ref;
|
const isActive = context.state.activeRef === ref;
|
||||||
return [onFocus, isActive, ref];
|
return [onFocus, isActive, ref];
|
||||||
|
|
|
@ -52,7 +52,7 @@ const Toolbar: React.FC<IProps> = ({ children, ...props }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return <RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}>
|
return <RovingTabIndexProvider handleHomeEnd handleLeftRight onKeyDown={onKeyDown}>
|
||||||
{ ({ onKeyDownHandler }) => <div {...props} onKeyDown={onKeyDownHandler} role="toolbar">
|
{ ({ onKeyDownHandler }) => <div {...props} onKeyDown={onKeyDownHandler} role="toolbar">
|
||||||
{ children }
|
{ children }
|
||||||
</div> }
|
</div> }
|
||||||
|
|
|
@ -31,6 +31,7 @@ import TextWithTooltip from "../elements/TextWithTooltip";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||||
|
import TooltipTarget from "../elements/TooltipTarget";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -39,6 +40,7 @@ interface IProps {
|
||||||
forceCount?: boolean;
|
forceCount?: boolean;
|
||||||
oobData?: IOOBData;
|
oobData?: IOOBData;
|
||||||
viewAvatarOnClick?: boolean;
|
viewAvatarOnClick?: boolean;
|
||||||
|
tooltipProps?: Omit<React.ComponentProps<typeof TooltipTarget>, "label" | "tooltipClassName" | "className">;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -189,6 +191,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
||||||
if (this.state.icon !== Icon.None) {
|
if (this.state.icon !== Icon.None) {
|
||||||
icon = <TextWithTooltip
|
icon = <TextWithTooltip
|
||||||
tooltip={tooltipText(this.state.icon)}
|
tooltip={tooltipText(this.state.icon)}
|
||||||
|
tooltipProps={this.props.tooltipProps}
|
||||||
class={`mx_DecoratedRoomAvatar_icon mx_DecoratedRoomAvatar_icon_${this.state.icon.toLowerCase()}`}
|
class={`mx_DecoratedRoomAvatar_icon mx_DecoratedRoomAvatar_icon_${this.state.icon.toLowerCase()}`}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { RefObject, useCallback, useEffect } from "react";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src";
|
import { MatrixEvent } from "matrix-js-sdk/src";
|
||||||
|
|
||||||
import { ButtonEvent } from "../elements/AccessibleButton";
|
import { ButtonEvent } from "../elements/AccessibleButton";
|
||||||
|
@ -22,11 +22,12 @@ import dis from '../../../dispatcher/dispatcher';
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
import { copyPlaintext } from "../../../utils/strings";
|
import { copyPlaintext } from "../../../utils/strings";
|
||||||
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
|
||||||
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
mxEvent: MatrixEvent;
|
mxEvent: MatrixEvent;
|
||||||
|
@ -34,6 +35,13 @@ interface IProps {
|
||||||
onMenuToggle?: (open: boolean) => void;
|
onMenuToggle?: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IExtendedProps extends IProps {
|
||||||
|
// Props for making the button into a roving one
|
||||||
|
tabIndex?: number;
|
||||||
|
inputRef?: RefObject<HTMLElement>;
|
||||||
|
onFocus?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
const contextMenuBelow = (elementRect: DOMRect) => {
|
const contextMenuBelow = (elementRect: DOMRect) => {
|
||||||
// align the context menu's icons with the icon which opened the context menu
|
// align the context menu's icons with the icon which opened the context menu
|
||||||
const left = elementRect.left + window.pageXOffset + elementRect.width;
|
const left = elementRect.left + window.pageXOffset + elementRect.width;
|
||||||
|
@ -42,11 +50,27 @@ const contextMenuBelow = (elementRect: DOMRect) => {
|
||||||
return { left, top, chevronFace };
|
return { left, top, chevronFace };
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, onMenuToggle }) => {
|
export const RovingThreadListContextMenu: React.FC<IProps> = (props) => {
|
||||||
const [optionsPosition, setOptionsPosition] = useState(null);
|
const [onFocus, isActive, ref] = useRovingTabIndex();
|
||||||
const closeThreadOptions = useCallback(() => {
|
|
||||||
setOptionsPosition(null);
|
return <ThreadListContextMenu
|
||||||
}, []);
|
{...props}
|
||||||
|
onFocus={onFocus}
|
||||||
|
tabIndex={isActive ? 0 : -1}
|
||||||
|
inputRef={ref}
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThreadListContextMenu: React.FC<IExtendedProps> = ({
|
||||||
|
mxEvent,
|
||||||
|
permalinkCreator,
|
||||||
|
onMenuToggle,
|
||||||
|
onFocus,
|
||||||
|
inputRef,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [menuDisplayed, _ref, openMenu, closeThreadOptions] = useContextMenu();
|
||||||
|
const button = inputRef ?? _ref; // prefer the ref we receive via props in case we are being controlled
|
||||||
|
|
||||||
const viewInRoom = useCallback((evt: ButtonEvent): void => {
|
const viewInRoom = useCallback((evt: ButtonEvent): void => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
@ -68,37 +92,31 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
|
||||||
closeThreadOptions();
|
closeThreadOptions();
|
||||||
}, [mxEvent, closeThreadOptions, permalinkCreator]);
|
}, [mxEvent, closeThreadOptions, permalinkCreator]);
|
||||||
|
|
||||||
const toggleOptionsMenu = useCallback((ev: ButtonEvent): void => {
|
|
||||||
if (!!optionsPosition) {
|
|
||||||
closeThreadOptions();
|
|
||||||
} else {
|
|
||||||
const position = ev.currentTarget.getBoundingClientRect();
|
|
||||||
setOptionsPosition(position);
|
|
||||||
}
|
|
||||||
}, [closeThreadOptions, optionsPosition]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onMenuToggle) {
|
if (onMenuToggle) {
|
||||||
onMenuToggle(!!optionsPosition);
|
onMenuToggle(menuDisplayed);
|
||||||
}
|
}
|
||||||
}, [optionsPosition, onMenuToggle]);
|
onFocus?.();
|
||||||
|
}, [menuDisplayed, onMenuToggle, onFocus]);
|
||||||
|
|
||||||
const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget(
|
const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget(
|
||||||
MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
|
MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
|
||||||
);
|
);
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
|
{...props}
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||||
onClick={toggleOptionsMenu}
|
onClick={openMenu}
|
||||||
title={_t("Thread options")}
|
title={_t("Thread options")}
|
||||||
isExpanded={!!optionsPosition}
|
isExpanded={menuDisplayed}
|
||||||
|
inputRef={button}
|
||||||
/>
|
/>
|
||||||
{ !!optionsPosition && (<IconizedContextMenu
|
{ menuDisplayed && (<IconizedContextMenu
|
||||||
onFinished={closeThreadOptions}
|
onFinished={closeThreadOptions}
|
||||||
className="mx_RoomTile_contextMenu"
|
className="mx_RoomTile_contextMenu"
|
||||||
compact
|
compact
|
||||||
rightAligned
|
rightAligned
|
||||||
{...contextMenuBelow(optionsPosition)}
|
{...contextMenuBelow(button.current.getBoundingClientRect())}
|
||||||
>
|
>
|
||||||
<IconizedContextMenuOptionList>
|
<IconizedContextMenuOptionList>
|
||||||
{ isMainSplitTimelineShown &&
|
{ isMainSplitTimelineShown &&
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface IProps {
|
||||||
class?: string;
|
class?: string;
|
||||||
tooltipClass?: string;
|
tooltipClass?: string;
|
||||||
tooltip: React.ReactNode;
|
tooltip: React.ReactNode;
|
||||||
tooltipProps?: {};
|
tooltipProps?: Omit<React.ComponentProps<typeof TooltipTarget>, "label" | "tooltipClassName" | "className">;
|
||||||
onClick?: (ev?: React.MouseEvent) => void;
|
onClick?: (ev?: React.MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,13 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||||
title={_t("Options")}
|
title={_t("Options")}
|
||||||
onClick={openMenu}
|
onClick={() => {
|
||||||
|
openMenu();
|
||||||
|
// when the context menu is opened directly, e.g. via mouse click, the onFocus handler which tracks
|
||||||
|
// the element that is currently focused is skipped. So we want to call onFocus manually to keep the
|
||||||
|
// position in the page even when someone is clicking around.
|
||||||
|
onFocus();
|
||||||
|
}}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
|
@ -127,7 +133,13 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
||||||
title={_t("React")}
|
title={_t("React")}
|
||||||
onClick={openMenu}
|
onClick={() => {
|
||||||
|
openMenu();
|
||||||
|
// when the context menu is opened directly, e.g. via mouse click, the onFocus handler which tracks
|
||||||
|
// the element that is currently focused is skipped. So we want to call onFocus manually to keep the
|
||||||
|
// position in the page even when someone is clicking around.
|
||||||
|
onFocus();
|
||||||
|
}}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
|
@ -196,10 +208,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
|
||||||
};
|
};
|
||||||
|
|
||||||
private onFocusChange = (focused: boolean): void => {
|
private onFocusChange = (focused: boolean): void => {
|
||||||
if (!this.props.onFocusChange) {
|
this.props.onFocusChange?.(focused);
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.props.onFocusChange(focused);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onReplyClick = (ev: React.MouseEvent): void => {
|
private onReplyClick = (ev: React.MouseEvent): void => {
|
||||||
|
|
|
@ -67,7 +67,7 @@ import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||||
import Toolbar from '../../../accessibility/Toolbar';
|
import Toolbar from '../../../accessibility/Toolbar';
|
||||||
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
|
import { POLL_START_EVENT_TYPE } from '../../../polls/consts';
|
||||||
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
|
import { RovingAccessibleTooltipButton } from '../../../accessibility/roving/RovingAccessibleTooltipButton';
|
||||||
import ThreadListContextMenu from '../context_menus/ThreadListContextMenu';
|
import { RovingThreadListContextMenu } from '../context_menus/ThreadListContextMenu';
|
||||||
import { ThreadNotificationState } from '../../../stores/notifications/ThreadNotificationState';
|
import { ThreadNotificationState } from '../../../stores/notifications/ThreadNotificationState';
|
||||||
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
|
import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNotificationStateStore';
|
||||||
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
||||||
|
@ -1432,7 +1432,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
|
onClick={() => dispatchShowThreadEvent(this.props.mxEvent)}
|
||||||
key="thread"
|
key="thread"
|
||||||
/>
|
/>
|
||||||
<ThreadListContextMenu
|
<RovingThreadListContextMenu
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
onMenuToggle={this.onActionBarFocusChange}
|
onMenuToggle={this.onActionBarFocusChange}
|
||||||
|
|
|
@ -566,13 +566,6 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
if (typeof name !== 'string') name = '';
|
if (typeof name !== 'string') name = '';
|
||||||
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
|
||||||
|
|
||||||
const roomAvatar = <DecoratedRoomAvatar
|
|
||||||
room={this.props.room}
|
|
||||||
avatarSize={32}
|
|
||||||
displayBadge={this.props.isMinimized}
|
|
||||||
oobData={({ avatarUrl: roomProfile.avatarMxc })}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
let badge: React.ReactNode;
|
let badge: React.ReactNode;
|
||||||
if (!this.props.isMinimized && this.notificationState) {
|
if (!this.props.isMinimized && this.notificationState) {
|
||||||
// aria-hidden because we summarise the unread count/highlight status in a manual aria-label below
|
// aria-hidden because we summarise the unread count/highlight status in a manual aria-label below
|
||||||
|
@ -663,7 +656,13 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
||||||
aria-selected={this.state.selected}
|
aria-selected={this.state.selected}
|
||||||
aria-describedby={ariaDescribedBy}
|
aria-describedby={ariaDescribedBy}
|
||||||
>
|
>
|
||||||
{ roomAvatar }
|
<DecoratedRoomAvatar
|
||||||
|
room={this.props.room}
|
||||||
|
avatarSize={32}
|
||||||
|
displayBadge={this.props.isMinimized}
|
||||||
|
oobData={({ avatarUrl: roomProfile.avatarMxc })}
|
||||||
|
tooltipProps={{ tabIndex: isActive ? 0 : -1 }}
|
||||||
|
/>
|
||||||
{ nameContainer }
|
{ nameContainer }
|
||||||
{ badge }
|
{ badge }
|
||||||
{ this.renderGeneralMenu() }
|
{ this.renderGeneralMenu() }
|
||||||
|
|
Loading…
Reference in a new issue