Tooltip: improve accessibility in room (#12493)

* Migrate to `AccessibleButton`

* Update snapshots

* Update snapshots
This commit is contained in:
Florian Duros 2024-05-07 12:20:46 +02:00 committed by GitHub
parent 18ef97161a
commit caef3c1921
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 38 additions and 69 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -49,8 +49,3 @@ limitations under the License.
height: 32px; height: 32px;
} }
} }
.mx_RoomBreadcrumbs_Tooltip {
margin-left: -42px;
margin-top: -42px;
}

View file

@ -18,7 +18,6 @@ import React, { ComponentProps, useContext } from "react";
import classNames from "classnames"; import classNames from "classnames";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { OverflowMenuContext } from "./MessageComposerButtons"; import { OverflowMenuContext } from "./MessageComposerButtons";
import { IconizedContextMenuOption } from "../context_menus/IconizedContextMenu"; import { IconizedContextMenuOption } from "../context_menus/IconizedContextMenu";
import { Ref } from "../../../accessibility/roving/types"; import { Ref } from "../../../accessibility/roving/types";
@ -43,13 +42,8 @@ export const CollapsibleButton: React.FC<Props> = ({
} }
return ( return (
<AccessibleTooltipButton <AccessibleButton {...props} title={title} className={classNames(className, iconClassName)} ref={inputRef}>
{...props}
title={title}
className={classNames(className, iconClassName)}
ref={inputRef}
>
{children} {children}
</AccessibleTooltipButton> </AccessibleButton>
); );
}; };

View file

@ -33,7 +33,6 @@ import RoomHeaderButtons from "../right_panel/LegacyRoomHeaderButtons";
import E2EIcon from "./E2EIcon"; import E2EIcon from "./E2EIcon";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import RoomTopic from "../elements/RoomTopic"; import RoomTopic from "../elements/RoomTopic";
import RoomName from "../elements/RoomName"; import RoomName from "../elements/RoomName";
import { E2EStatus } from "../../../utils/ShieldUtils"; import { E2EStatus } from "../../../utils/ShieldUtils";
@ -68,7 +67,6 @@ import IconizedContextMenu, {
} from "../context_menus/IconizedContextMenu"; } from "../context_menus/IconizedContextMenu";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { SessionDuration } from "../voip/CallDuration"; import { SessionDuration } from "../voip/CallDuration";
import { Alignment } from "../elements/Tooltip";
import RoomCallBanner from "../beacon/RoomCallBanner"; import RoomCallBanner from "../beacon/RoomCallBanner";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { UIComponent } from "../../../settings/UIFeature"; import { UIComponent } from "../../../settings/UIFeature";
@ -111,12 +109,12 @@ const VoiceCallButton: FC<VoiceCallButtonProps> = ({ room, busy, setBusy, behavi
}, [behavior, room, setBusy]); }, [behavior, room, setBusy]);
return ( return (
<AccessibleTooltipButton <AccessibleButton
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_voiceCallButton" className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_voiceCallButton"
onClick={onClick} onClick={onClick}
title={_t("voip|voice_call")} aria-label={_t("voip|voice_call")}
tooltip={tooltip ?? _t("voip|voice_call")} title={tooltip ?? _t("voip|voice_call")}
alignment={Alignment.Bottom} placement="bottom"
disabled={disabled || busy} disabled={disabled || busy}
/> />
); );
@ -237,13 +235,13 @@ const VideoCallButton: FC<VideoCallButtonProps> = ({ room, busy, setBusy, behavi
return ( return (
<> <>
<AccessibleTooltipButton <AccessibleButton
ref={buttonRef} ref={buttonRef}
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_videoCallButton" className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_videoCallButton"
onClick={onClick} onClick={onClick}
title={_t("voip|video_call")} aria-label={_t("voip|video_call")}
tooltip={tooltip ?? _t("voip|video_call")} title={tooltip ?? _t("voip|video_call")}
alignment={Alignment.Bottom} placement="bottom"
disabled={disabled || busy} disabled={disabled || busy}
/> />
{menu} {menu}
@ -442,7 +440,7 @@ const CallLayoutSelector: FC<CallLayoutSelectorProps> = ({ call }) => {
return ( return (
<> <>
<AccessibleTooltipButton <AccessibleButton
ref={buttonRef} ref={buttonRef}
className={classNames("mx_LegacyRoomHeader_button", { className={classNames("mx_LegacyRoomHeader_button", {
"mx_LegacyRoomHeader_layoutButton--freedom": layout === Layout.Tile, "mx_LegacyRoomHeader_layoutButton--freedom": layout === Layout.Tile,
@ -450,7 +448,7 @@ const CallLayoutSelector: FC<CallLayoutSelectorProps> = ({ call }) => {
})} })}
onClick={onClick} onClick={onClick}
title={_t("room|header|video_call_ec_change_layout")} title={_t("room|header|video_call_ec_change_layout")}
alignment={Alignment.Bottom} placement="bottom"
key="layout" key="layout"
/> />
{menu} {menu}
@ -600,11 +598,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
if (!this.props.viewingCall && this.props.onForgetClick) { if (!this.props.viewingCall && this.props.onForgetClick) {
startButtons.push( startButtons.push(
<AccessibleTooltipButton <AccessibleButton
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_forgetButton" className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_forgetButton"
onClick={this.props.onForgetClick} onClick={this.props.onForgetClick}
title={_t("room|header|forget_room_button")} title={_t("room|header|forget_room_button")}
alignment={Alignment.Bottom} placement="bottom"
key="forget" key="forget"
/>, />,
); );
@ -612,7 +610,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
if (!this.props.viewingCall && this.props.onAppsClick) { if (!this.props.viewingCall && this.props.onAppsClick) {
startButtons.push( startButtons.push(
<AccessibleTooltipButton <AccessibleButton
className={classNames("mx_LegacyRoomHeader_button mx_LegacyRoomHeader_appsButton", { className={classNames("mx_LegacyRoomHeader_button mx_LegacyRoomHeader_appsButton", {
mx_LegacyRoomHeader_appsButton_highlight: this.props.appsShown, mx_LegacyRoomHeader_appsButton_highlight: this.props.appsShown,
})} })}
@ -623,7 +621,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
: _t("room|header|show_widgets_button") : _t("room|header|show_widgets_button")
} }
aria-checked={this.props.appsShown} aria-checked={this.props.appsShown}
alignment={Alignment.Bottom} placement="bottom"
key="apps" key="apps"
/>, />,
); );
@ -631,11 +629,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
if (!this.props.viewingCall && this.props.onSearchClick && this.props.inRoom) { if (!this.props.viewingCall && this.props.onSearchClick && this.props.inRoom) {
startButtons.push( startButtons.push(
<AccessibleTooltipButton <AccessibleButton
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_searchButton" className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_searchButton"
onClick={this.props.onSearchClick} onClick={this.props.onSearchClick}
title={_t("action|search")} title={_t("action|search")}
alignment={Alignment.Bottom} placement="bottom"
key="search" key="search"
/>, />,
); );
@ -643,11 +641,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
if (this.props.onInviteClick && (!this.props.viewingCall || isVideoRoom) && this.props.inRoom) { if (this.props.onInviteClick && (!this.props.viewingCall || isVideoRoom) && this.props.inRoom) {
startButtons.push( startButtons.push(
<AccessibleTooltipButton <AccessibleButton
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_inviteButton" className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_inviteButton"
onClick={this.props.onInviteClick} onClick={this.props.onInviteClick}
title={_t("action|invite")} title={_t("action|invite")}
alignment={Alignment.Bottom} placement="bottom"
key="invite" key="invite"
/>, />,
); );
@ -667,11 +665,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
); );
} else { } else {
endButtons.push( endButtons.push(
<AccessibleTooltipButton <AccessibleButton
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_minimiseButton" className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_minimiseButton"
onClick={this.onHideCallClick} onClick={this.onHideCallClick}
title={_t("room|header|video_room_view_chat_button")} title={_t("room|header|video_room_view_chat_button")}
alignment={Alignment.Bottom} placement="bottom"
key="minimise" key="minimise"
/>, />,
); );
@ -754,7 +752,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
onClick={this.onContextMenuOpenClick} onClick={this.onContextMenuOpenClick}
isExpanded={!!this.state.contextMenuPosition} isExpanded={!!this.state.contextMenuPosition}
title={_t("room|context_menu|title")} title={_t("room|context_menu|title")}
alignment={Alignment.Bottom} placement="bottom"
> >
{roomName} {roomName}
{this.props.room && <div className="mx_LegacyRoomHeader_chevron" />} {this.props.room && <div className="mx_LegacyRoomHeader_chevron" />}

View file

@ -27,7 +27,6 @@ import { _t } from "../../../languageHandler";
import { formatDate } from "../../../DateUtils"; import { formatDate } from "../../../DateUtils";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { getUserNameColorClass } from "../../../utils/FormattingUtils"; import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
@ -76,7 +75,7 @@ export default class PinnedEventTile extends React.Component<IProps> {
let unpinButton: JSX.Element | undefined; let unpinButton: JSX.Element | undefined;
if (this.props.onUnpinClicked) { if (this.props.onUnpinClicked) {
unpinButton = ( unpinButton = (
<AccessibleTooltipButton <AccessibleButton
onClick={this.props.onUnpinClicked} onClick={this.props.onUnpinClicked}
className="mx_PinnedEventTile_unpinButton" className="mx_PinnedEventTile_unpinButton"
title={_t("action|unpin")} title={_t("action|unpin")}

View file

@ -26,9 +26,8 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex"; import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
import Toolbar from "../../../accessibility/Toolbar"; import Toolbar from "../../../accessibility/Toolbar";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
interface IProps {} interface IProps {}
@ -47,15 +46,15 @@ const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => v
const [onFocus, isActive, ref] = useRovingTabIndex(); const [onFocus, isActive, ref] = useRovingTabIndex();
return ( return (
<AccessibleTooltipButton <AccessibleButton
className="mx_RoomBreadcrumbs_crumb" className="mx_RoomBreadcrumbs_crumb"
onClick={onClick} onClick={onClick}
aria-label={_t("a11y|room_name", { name: room.name })} aria-label={_t("a11y|room_name", { name: room.name })}
title={room.name} title={room.name}
tooltipClassName="mx_RoomBreadcrumbs_Tooltip"
onFocus={onFocus} onFocus={onFocus}
ref={ref} ref={ref}
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
placement="right"
> >
<DecoratedRoomAvatar <DecoratedRoomAvatar
room={room} room={room}
@ -64,7 +63,7 @@ const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => v
hideIfDot={true} hideIfDot={true}
tooltipProps={{ tabIndex: isActive ? 0 : -1 }} tooltipProps={{ tabIndex: isActive ? 0 : -1 }}
/> />
</AccessibleTooltipButton> </AccessibleButton>
); );
}; };

View file

@ -57,10 +57,10 @@ import IconizedContextMenu, {
IconizedContextMenuOption, IconizedContextMenuOption,
IconizedContextMenuOptionList, IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu"; } from "../context_menus/IconizedContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ExtraTile from "./ExtraTile"; import ExtraTile from "./ExtraTile";
import RoomSublist, { IAuxButtonProps } from "./RoomSublist"; import RoomSublist, { IAuxButtonProps } from "./RoomSublist";
import { SdkContextClass } from "../../../contexts/SDKContext"; import { SdkContextClass } from "../../../contexts/SDKContext";
import AccessibleButton from "../elements/AccessibleButton";
interface IProps { interface IProps {
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
@ -185,7 +185,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
); );
} else if (!activeSpace && showCreateRooms) { } else if (!activeSpace && showCreateRooms) {
return ( return (
<AccessibleTooltipButton <AccessibleButton
tabIndex={tabIndex} tabIndex={tabIndex}
onClick={(e) => { onClick={(e) => {
dispatcher.dispatch({ action: "view_create_chat" }); dispatcher.dispatch({ action: "view_create_chat" });

View file

@ -49,7 +49,6 @@ import ContextMenu, {
StyledMenuItemRadio, StyledMenuItemRadio,
} from "../../structures/ContextMenu"; } from "../../structures/ContextMenu";
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ExtraTile from "./ExtraTile"; import ExtraTile from "./ExtraTile";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { SlidingSyncManager } from "../../../SlidingSyncManager"; import { SlidingSyncManager } from "../../../SlidingSyncManager";
@ -684,11 +683,6 @@ export default class RoomSublist extends React.Component<IProps, IState> {
const badgeContainer = <div className="mx_RoomSublist_badgeContainer">{badge}</div>; const badgeContainer = <div className="mx_RoomSublist_badgeContainer">{badge}</div>;
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
if (this.props.isMinimized) {
Button = AccessibleTooltipButton;
}
// Note: the addRoomButton conditionally gets moved around // Note: the addRoomButton conditionally gets moved around
// the DOM depending on whether or not the list is minimized. // the DOM depending on whether or not the list is minimized.
// If we're minimized, we want it below the header so it // If we're minimized, we want it below the header so it
@ -707,7 +701,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
> >
<div className="mx_RoomSublist_stickableContainer"> <div className="mx_RoomSublist_stickableContainer">
<div className="mx_RoomSublist_stickable"> <div className="mx_RoomSublist_stickable">
<Button <AccessibleButton
onFocus={onFocus} onFocus={onFocus}
ref={ref} ref={ref}
tabIndex={tabIndex} tabIndex={tabIndex}
@ -719,7 +713,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
> >
<span className={collapseClasses} /> <span className={collapseClasses} />
<span id={getLabelId(this.props.tagId)}>{this.props.label}</span> <span id={getLabelId(this.props.tagId)}>{this.props.label}</span>
</Button> </AccessibleButton>
{this.renderMenu()} {this.renderMenu()}
{this.props.isMinimized ? null : badgeContainer} {this.props.isMinimized ? null : badgeContainer}
{this.props.isMinimized ? null : addRoomButton} {this.props.isMinimized ? null : addRoomButton}

View file

@ -37,7 +37,6 @@ import NotificationBadge from "./NotificationBadge";
import { ActionPayload } from "../../../dispatcher/payloads"; import { ActionPayload } from "../../../dispatcher/payloads";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState"; import { NotificationState, NotificationStateEvents } from "../../../stores/notifications/NotificationState";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { EchoChamber } from "../../../stores/local-echo/EchoChamber"; import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber"; import { CachedRoomKey, RoomEchoChamber } from "../../../stores/local-echo/RoomEchoChamber";
import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber"; import { PROPERTY_UPDATED } from "../../../stores/local-echo/GenericEchoChamber";
@ -464,21 +463,11 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
ariaDescribedBy = messagePreviewId(this.props.room.roomId); ariaDescribedBy = messagePreviewId(this.props.room.roomId);
} }
const props: Partial<React.ComponentProps<typeof AccessibleTooltipButton>> = {};
let Button: React.ComponentType<React.ComponentProps<typeof AccessibleButton>> = AccessibleButton;
if (this.props.isMinimized) {
Button = AccessibleTooltipButton;
props.title = name;
// force the tooltip to hide whilst we are showing the context menu
props.forceHide = !!this.state.generalMenuPosition;
}
return ( return (
<React.Fragment> <React.Fragment>
<RovingTabIndexWrapper inputRef={this.roomTileRef}> <RovingTabIndexWrapper inputRef={this.roomTileRef}>
{({ onFocus, isActive, ref }) => ( {({ onFocus, isActive, ref }) => (
<Button <AccessibleButton
{...props}
onFocus={onFocus} onFocus={onFocus}
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
ref={ref} ref={ref}
@ -489,6 +478,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
aria-label={ariaLabel} aria-label={ariaLabel}
aria-selected={this.state.selected} aria-selected={this.state.selected}
aria-describedby={ariaDescribedBy} aria-describedby={ariaDescribedBy}
title={this.props.isMinimized && !this.state.generalMenuPosition ? name : undefined}
> >
<DecoratedRoomAvatar <DecoratedRoomAvatar
room={this.props.room} room={this.props.room}
@ -500,7 +490,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
{badge} {badge}
{this.renderGeneralMenu()} {this.renderGeneralMenu()}
{this.renderNotificationsMenu(isActive)} {this.renderNotificationsMenu(isActive)}
</Button> </AccessibleButton>
)} )}
</RovingTabIndexWrapper> </RovingTabIndexWrapper>
</React.Fragment> </React.Fragment>

View file

@ -19,7 +19,6 @@ import { Room, IEventRelation, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk"; import { Optional } from "matrix-events-sdk";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import { RecordingState } from "../../../audio/VoiceRecording"; import { RecordingState } from "../../../audio/VoiceRecording";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
@ -44,6 +43,7 @@ import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import { IUpload, VoiceMessageRecording } from "../../../audio/VoiceMessageRecording"; import { IUpload, VoiceMessageRecording } from "../../../audio/VoiceMessageRecording";
import { createVoiceMessageContent } from "../../../utils/createVoiceMessageContent"; import { createVoiceMessageContent } from "../../../utils/createVoiceMessageContent";
import AccessibleButton from "../elements/AccessibleButton";
interface IProps { interface IProps {
room: Room; room: Room;
@ -271,7 +271,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
} }
stopBtn = ( stopBtn = (
<AccessibleTooltipButton <AccessibleButton
className="mx_VoiceRecordComposerTile_stop" className="mx_VoiceRecordComposerTile_stop"
onClick={this.onRecordStartEndClick} onClick={this.onRecordStartEndClick}
title={tooltip} title={tooltip}
@ -284,7 +284,7 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
if (this.state.recorder && this.state.recordingPhase !== RecordingState.Uploading) { if (this.state.recorder && this.state.recordingPhase !== RecordingState.Uploading) {
deleteButton = ( deleteButton = (
<AccessibleTooltipButton <AccessibleButton
className="mx_VoiceRecordComposerTile_delete" className="mx_VoiceRecordComposerTile_delete"
title={_t("action|delete")} title={_t("action|delete")}
onClick={this.onCancel} onClick={this.onCancel}