Ensure tooltip contents is linked via aria to the target element (#10729)

* Ensure tooltip contents is linked via aria to the target element

* Iterate

* Fix tests

* Fix tests

* Update snapshot

* Fix missing aria labels for more tooltips

* Iterate

* Update snapshots
This commit is contained in:
Michael Telatynski 2023-05-05 09:26:11 +01:00 committed by GitHub
parent 8e962f6897
commit 99ac9e5029
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 133 additions and 43 deletions

View file

@ -22,6 +22,7 @@ import { _t, _td } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import Tooltip, { Alignment } from "../elements/Tooltip";
import { E2EStatus } from "../../../utils/ShieldUtils";
import { XOR } from "../../../@types/common";
export enum E2EState {
Verified = "verified",
@ -42,9 +43,7 @@ const crossSigningRoomTitles: { [key in E2EState]?: string } = {
[E2EState.Verified]: _td("Everyone in this room is verified"),
};
interface IProps {
isUser?: boolean;
status?: E2EState | E2EStatus;
interface Props {
className?: string;
size?: number;
onClick?: () => void;
@ -53,7 +52,17 @@ interface IProps {
bordered?: boolean;
}
const E2EIcon: React.FC<IProps> = ({
interface UserProps extends Props {
isUser: true;
status: E2EState | E2EStatus;
}
interface RoomProps extends Props {
isUser?: false;
status: E2EStatus;
}
const E2EIcon: React.FC<XOR<UserProps, RoomProps>> = ({
isUser,
status,
className,
@ -77,12 +86,10 @@ const E2EIcon: React.FC<IProps> = ({
);
let e2eTitle: string | undefined;
if (status) {
if (isUser) {
e2eTitle = crossSigningUserTitles[status];
} else {
e2eTitle = crossSigningRoomTitles[status];
}
if (isUser) {
e2eTitle = crossSigningUserTitles[status];
} else {
e2eTitle = crossSigningRoomTitles[status];
}
let style: CSSProperties | undefined;
@ -93,9 +100,11 @@ const E2EIcon: React.FC<IProps> = ({
const onMouseOver = (): void => setHover(true);
const onMouseLeave = (): void => setHover(false);
const label = e2eTitle ? _t(e2eTitle) : "";
let tip: JSX.Element | undefined;
if (hover && !hideTooltip) {
tip = <Tooltip label={e2eTitle ? _t(e2eTitle) : ""} alignment={tooltipAlignment} />;
if (hover && !hideTooltip && label) {
tip = <Tooltip label={label} alignment={tooltipAlignment} />;
}
if (onClick) {
@ -106,6 +115,7 @@ const E2EIcon: React.FC<IProps> = ({
onMouseLeave={onMouseLeave}
className={classes}
style={style}
aria-label={label}
>
{tip}
</AccessibleButton>
@ -113,7 +123,7 @@ const E2EIcon: React.FC<IProps> = ({
}
return (
<div onMouseOver={onMouseOver} onMouseLeave={onMouseLeave} className={classes} style={style}>
<div onMouseOver={onMouseOver} onMouseLeave={onMouseLeave} className={classes} style={style} aria-label={label}>
{tip}
</div>
);

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { createRef, forwardRef, MouseEvent, ReactNode, RefObject } from "react";
import React, { createRef, forwardRef, MouseEvent, ReactNode, RefObject, useRef } from "react";
import classNames from "classnames";
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
@ -1513,7 +1513,12 @@ class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
return (
<div className={classes} onMouseEnter={this.onHoverStart} onMouseLeave={this.onHoverEnd}>
<div
className={classes}
onMouseEnter={this.onHoverStart}
onMouseLeave={this.onHoverEnd}
aria-label={this.props.title}
>
{tooltip}
</div>
);
@ -1525,6 +1530,7 @@ interface ISentReceiptProps {
}
function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
const tooltipId = useRef(`mx_SentReceipt_${Math.random()}`).current;
const isSent = !messageState || messageState === "sent";
const isFailed = messageState === "not_sent";
const receiptClasses = classNames({
@ -1546,6 +1552,7 @@ function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
label = _t("Failed to send");
}
const [{ showTooltip, hideTooltip }, tooltip] = useTooltip({
id: tooltipId,
label: label,
alignment: Alignment.TopRight,
});
@ -1559,6 +1566,7 @@ function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
onMouseLeave={hideTooltip}
onFocus={showTooltip}
onBlur={hideTooltip}
aria-describedby={tooltipId}
>
<span className="mx_ReadReceiptGroup_container">
<span className={receiptClasses}>{nonCssBadge}</span>

View file

@ -107,6 +107,7 @@ interface IState {
}
export class MessageComposer extends React.Component<IProps, IState> {
private tooltipId = `mx_MessageComposer_${Math.random()}`;
private dispatcherRef?: string;
private messageComposerInput = createRef<SendMessageComposerClass>();
private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
@ -470,7 +471,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
public render(): React.ReactNode {
const hasE2EIcon = Boolean(!this.state.isWysiwygLabEnabled && this.props.e2eStatus);
const e2eIcon = hasE2EIcon && (
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" />
<E2EIcon key="e2eIcon" status={this.props.e2eStatus!} className="mx_MessageComposer_e2eIcon" />
);
const controls: ReactNode[] = [];
@ -561,11 +562,15 @@ export class MessageComposer extends React.Component<IProps, IState> {
);
}
let recordingTooltip;
let recordingTooltip: JSX.Element | undefined;
if (this.state.recordingTimeLeftSeconds) {
const secondsLeft = Math.round(this.state.recordingTimeLeftSeconds);
recordingTooltip = (
<Tooltip label={_t("%(seconds)ss left", { seconds: secondsLeft })} alignment={Alignment.Top} />
<Tooltip
id={this.tooltipId}
label={_t("%(seconds)ss left", { seconds: secondsLeft })}
alignment={Alignment.Top}
/>
);
}
@ -593,7 +598,11 @@ export class MessageComposer extends React.Component<IProps, IState> {
});
return (
<div className={classes} ref={this.ref}>
<div
className={classes}
ref={this.ref}
aria-describedby={this.state.recordingTimeLeftSeconds ? this.tooltipId : undefined}
>
{recordingTooltip}
<div className="mx_MessageComposer_wrapper">
<ReplyPreview