Upgrade to latest compound-web package (#84)

* Upgrade to latest compound-web package

* Use a custom render function for jest tests

This way we don't need to manually wrap our components with
<TooltipProvider>

* Pin wrap-ansi to fix broken yarn install

* Add playwright helper to find tooltip from element

and use it in the failing test

* Exclude floating-ui divs/spans from axe testing

This is rendered outside .MatrixChat by compound and contains all the
tooltips.

* Wrap outermost components with TooltipProvider

* Remove onChange and use onSelect for toggle

* Fix jest tests and update snapshots

* Use vector-im/matrix-wysiwig

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
R Midhun Suresh
2024-10-14 21:41:58 +05:30
committed by GitHub
parent 3bc0439fd2
commit 91e84f7951
389 changed files with 1261 additions and 1084 deletions

View File

@@ -12,7 +12,7 @@ import ReactDOM from "react-dom";
import classNames from "classnames";
import { IDeferred, defer, sleep } from "matrix-js-sdk/src/utils";
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
import { Glass } from "@vector-im/compound-web";
import { Glass, TooltipProvider } from "@vector-im/compound-web";
import dis, { defaultDispatcher } from "./dispatcher/dispatcher";
import AsyncWrapper from "./AsyncWrapper";
@@ -416,16 +416,18 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
const classes = classNames("mx_Dialog_wrapper mx_Dialog_staticWrapper", this.staticModal.className);
const staticDialog = (
<div className={classes}>
<Glass className="mx_Dialog_border">
<div className="mx_Dialog">{this.staticModal.elem}</div>
</Glass>
<div
data-testid="dialog-background"
className="mx_Dialog_background mx_Dialog_staticBackground"
onClick={this.onBackgroundClick}
/>
</div>
<TooltipProvider>
<div className={classes}>
<Glass className="mx_Dialog_border">
<div className="mx_Dialog">{this.staticModal.elem}</div>
</Glass>
<div
data-testid="dialog-background"
className="mx_Dialog_background mx_Dialog_staticBackground"
onClick={this.onBackgroundClick}
/>
</div>
</TooltipProvider>
);
ReactDOM.render(staticDialog, ModalManager.getOrCreateStaticContainer());
@@ -441,16 +443,18 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
});
const dialog = (
<div className={classes}>
<Glass className="mx_Dialog_border">
<div className="mx_Dialog">{modal.elem}</div>
</Glass>
<div
data-testid="dialog-background"
className="mx_Dialog_background"
onClick={this.onBackgroundClick}
/>
</div>
<TooltipProvider>
<div className={classes}>
<Glass className="mx_Dialog_border">
<div className="mx_Dialog">{modal.elem}</div>
</Glass>
<div
data-testid="dialog-background"
className="mx_Dialog_background"
onClick={this.onBackgroundClick}
/>
</div>
</TooltipProvider>
);
setTimeout(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()), 0);

View File

@@ -12,6 +12,7 @@ import React, { CSSProperties, RefObject, SyntheticEvent, useRef, useState } fro
import ReactDOM from "react-dom";
import classNames from "classnames";
import FocusLock from "react-focus-lock";
import { TooltipProvider } from "@vector-im/compound-web";
import { Writeable } from "../../@types/common";
import UIStore from "../../stores/UIStore";
@@ -621,15 +622,17 @@ export function createMenu(
};
const menu = (
<ContextMenu
{...props}
mountAsChild={true}
hasBackground={false}
onFinished={onFinished} // eslint-disable-line react/jsx-no-bind
windowResize={onFinished} // eslint-disable-line react/jsx-no-bind
>
<ElementClass {...props} onFinished={onFinished} />
</ContextMenu>
<TooltipProvider>
<ContextMenu
{...props}
mountAsChild={true}
hasBackground={false}
onFinished={onFinished} // eslint-disable-line react/jsx-no-bind
windowResize={onFinished} // eslint-disable-line react/jsx-no-bind
>
<ElementClass {...props} onFinished={onFinished} />
</ContextMenu>
</TooltipProvider>
);
ReactDOM.render(menu, getOrCreateContainer());

View File

@@ -24,6 +24,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import { throttle } from "lodash";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
import { TooltipProvider } from "@vector-im/compound-web";
// what-input helps improve keyboard accessibility
import "what-input";
@@ -2181,7 +2182,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
return (
<ErrorBoundary>
<SDKContext.Provider value={this.stores}>{view}</SDKContext.Provider>
<SDKContext.Provider value={this.stores}>
<TooltipProvider>{view}</TooltipProvider>
</SDKContext.Provider>
</ErrorBoundary>
);
}

View File

@@ -130,7 +130,7 @@ export const ThreadPanelHeader: React.FC<{
return (
<div className="mx_BaseCard_header_title">
<Tooltip label={_t("threads|mark_all_read")}>
<IconButton onClick={onMarkAllThreadsReadClick} aria-label={_t("threads|mark_all_read")} size="24px">
<IconButton onClick={onMarkAllThreadsReadClick} size="24px">
<MarkAllThreadsReadIcon />
</IconButton>
</Tooltip>

View File

@@ -58,7 +58,7 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
<input onClick={onSubmitForm} type="button" className="mx_Login_submit" value={_t("action|next")} />
<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<Tooltip label={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}

View File

@@ -57,7 +57,7 @@ export const VerifyEmailModal: React.FC<Props> = ({
<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{_t("auth|check_email_resend_prompt")}</span>
<Tooltip label={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<Tooltip description={_t("auth|check_email_resend_tooltip")} placement="top" open={tooltipVisible}>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("action|resend")}

View File

@@ -212,7 +212,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
if (title) {
return (
<Tooltip
label={title}
description={title}
caption={caption}
isTriggerInteractive={true}
placement={placement}

View File

@@ -35,7 +35,7 @@ export default class InfoTooltip extends React.PureComponent<TooltipProps> {
// Tooltip are forced on the right for a more natural feel to them on info icons
return (
<Tooltip label={tooltip || title} placement="right">
<Tooltip description={tooltip || title} placement="right">
<div className={classNames("mx_InfoTooltip", className)} tabIndex={this.props.tabIndex ?? 0}>
<span className={classNames("mx_InfoTooltip_icon", iconClassName)} aria-label={title} />
{children}

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { MutableRefObject, ReactNode } from "react";
import ReactDOM from "react-dom";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { TooltipProvider } from "@vector-im/compound-web";
import dis from "../../../dispatcher/dispatcher";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -167,9 +168,11 @@ export default class PersistedElement extends React.Component<IProps> {
private renderApp(): void {
const content = (
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
<div ref={this.collectChild} style={this.props.style}>
{this.props.children}
</div>
<TooltipProvider>
<div ref={this.collectChild} style={this.props.style}>
{this.props.children}
</div>
</TooltipProvider>
</MatrixClientContext.Provider>
);

View File

@@ -141,7 +141,7 @@ export const Pill: React.FC<PillProps> = ({ type: propType, url, inMessage, room
<bdi>
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
<Tooltip
label={resourceId ?? ""}
description={resourceId ?? ""}
open={resourceId ? undefined : false}
placement="right"
isTriggerInteractive={isAnchor}

View File

@@ -111,7 +111,7 @@ export default function RoomTopic({ room, className, ...props }: IProps): JSX.El
if (!body) return <div className={classNames(className, "mx_RoomTopic")} />;
return (
<Tooltip label={_t("room|read_topic")} disabled={disableTooltip}>
<Tooltip description={_t("room|read_topic")} disabled={disableTooltip}>
<div
{...props}
tabIndex={0}

View File

@@ -61,7 +61,7 @@ export default class MStickerBody extends MImageBody {
return {
placement: "right",
label: content.body,
description: content.body,
};
}

View File

@@ -53,7 +53,7 @@ export default class MessageTimestamp extends React.Component<IProps> {
}
return (
<Tooltip label={label} caption={caption}>
<Tooltip description={label} caption={caption}>
<span className="mx_MessageTimestamp" aria-hidden={true} aria-live="off">
{icon}
{timestamp}

View File

@@ -51,7 +51,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<Props
const caption = shortName ? _t("timeline|reactions|tooltip_caption", { shortName }) : undefined;
return (
<Tooltip label={formattedSenders} caption={caption} placement="right">
<Tooltip description={formattedSenders} caption={caption} placement="right">
{children}
</Tooltip>
);

View File

@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { createRef, SyntheticEvent, MouseEvent } from "react";
import ReactDOM from "react-dom";
import { MsgType } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";
import * as HtmlUtils from "../../../HtmlUtils";
import { formatDate } from "../../../DateUtils";
@@ -335,7 +336,11 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const reason = node.getAttribute("data-mx-spoiler") ?? undefined;
node.removeAttribute("data-mx-spoiler"); // we don't want to recurse
const spoiler = <Spoiler reason={reason} contentHtml={node.outerHTML} />;
const spoiler = (
<TooltipProvider>
<Spoiler reason={reason} contentHtml={node.outerHTML} />
</TooltipProvider>
);
ReactDOM.render(spoiler, spoilerContainer);
node.parentNode?.replaceChild(spoilerContainer, node);

View File

@@ -373,9 +373,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
Icon={FavouriteIcon}
label={_t("room|context_menu|favourite")}
checked={isFavorite}
onChange={() => tagRoom(room, DefaultTagID.Favourite)}
// XXX: https://github.com/element-hq/compound/issues/288
onSelect={() => {}}
onSelect={() => tagRoom(room, DefaultTagID.Favourite)}
/>
<MenuItem
Icon={UserAddIcon}

View File

@@ -1555,7 +1555,7 @@ function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
<div className="mx_EventTile_msgOption">
<div className="mx_ReadReceiptGroup">
<Tooltip label={label} placement="top-end">
<div className="mx_ReadReceiptGroup_button">
<div className="mx_ReadReceiptGroup_button" role="status">
<span className="mx_ReadReceiptGroup_container">
<span className={receiptClasses}>{nonCssBadge}</span>
</span>

View File

@@ -666,7 +666,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
});
return (
<Tooltip open={isTooltipOpen} label={formatTimeLeft(secondsLeft)} placement="top">
<Tooltip open={isTooltipOpen} description={formatTimeLeft(secondsLeft)} placement="bottom">
<div className={classes} ref={this.ref} role="region" aria-label={_t("a11y|message_composer")}>
{recordingTooltip}
<div className="mx_MessageComposer_wrapper">

View File

@@ -211,7 +211,7 @@ export function ReadReceiptPerson({
onAfterClick,
}: ReadReceiptPersonProps): JSX.Element {
return (
<Tooltip label={roomMember?.rawDisplayName ?? userId} caption={userId} placement="top">
<Tooltip description={roomMember?.rawDisplayName ?? userId} caption={userId} placement="top">
<div>
<MenuItem
className="mx_ReadReceiptGroup_person"

View File

@@ -205,7 +205,7 @@ export default function RoomHeader({
);
const closeLobbyButton = (
<Tooltip label={_t("voip|close_lobby")}>
<IconButton onClick={toggleCall} aria-label={_t("voip|close_lobby")}>
<IconButton onClick={toggleCall}>
<CloseCallIcon />
</IconButton>
</Tooltip>

View File

@@ -80,7 +80,7 @@ export const CallGuestLinkButton: React.FC<{ room: Room }> = ({ room }) => {
<>
{canInviteGuests && (
<Tooltip label={_t("voip|get_call_link")}>
<IconButton onClick={shareClick} aria-label={_t("voip|get_call_link")}>
<IconButton onClick={shareClick}>
<ExternalLinkIcon />
</IconButton>
</Tooltip>

View File

@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useCallback, useEffect, useState } from "react";
import { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
import { Button, Tooltip } from "@vector-im/compound-web";
import { Button, Tooltip, TooltipProvider } from "@vector-im/compound-web";
import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid";
import { _t } from "../languageHandler";
@@ -47,7 +47,7 @@ function JoinCallButtonWithCall({ onClick, call, disabledTooltip }: JoinCallButt
disTooltip = disabledTooltip ?? disabledBecauseFullTooltip ?? undefined;
return (
<Tooltip label={disTooltip ?? _t("voip|video_call")}>
<Tooltip description={disTooltip ?? _t("voip|video_call")}>
<Button
className="mx_IncomingCallToast_joinButton"
onClick={onClick}
@@ -163,38 +163,40 @@ export function IncomingCallToast({ notifyEvent }: Props): JSX.Element {
useEventEmitter(call ?? undefined, CallEvent.Participants, onParticipantChange);
return (
<>
<div>
<RoomAvatar room={room ?? undefined} size="24px" />
</div>
<div className="mx_IncomingCallToast_content">
<div className="mx_IncomingCallToast_info">
<span className="mx_IncomingCallToast_room">
{room ? room.name : _t("voip|call_toast_unknown_room")}
</span>
<div className="mx_IncomingCallToast_message">{_t("voip|video_call_started")}</div>
{call ? (
<LiveContentSummaryWithCall call={call} />
) : (
<LiveContentSummary
type={LiveContentType.Video}
text={_t("common|video")}
active={false}
participantCount={0}
/>
)}
<TooltipProvider>
<>
<div>
<RoomAvatar room={room ?? undefined} size="24px" />
</div>
<JoinCallButtonWithCall
onClick={onJoinClick}
call={call}
disabledTooltip={otherCallIsOngoing ? "Ongoing call" : undefined}
<div className="mx_IncomingCallToast_content">
<div className="mx_IncomingCallToast_info">
<span className="mx_IncomingCallToast_room">
{room ? room.name : _t("voip|call_toast_unknown_room")}
</span>
<div className="mx_IncomingCallToast_message">{_t("voip|video_call_started")}</div>
{call ? (
<LiveContentSummaryWithCall call={call} />
) : (
<LiveContentSummary
type={LiveContentType.Video}
text={_t("common|video")}
active={false}
participantCount={0}
/>
)}
</div>
<JoinCallButtonWithCall
onClick={onJoinClick}
call={call}
disabledTooltip={otherCallIsOngoing ? "Ongoing call" : undefined}
/>
</div>
<AccessibleButton
className="mx_IncomingCallToast_closeButton"
onClick={onCloseClick}
title={_t("action|close")}
/>
</div>
<AccessibleButton
className="mx_IncomingCallToast_closeButton"
onClick={onCloseClick}
title={_t("action|close")}
/>
</>
</>
</TooltipProvider>
);
}

View File

@@ -12,6 +12,7 @@ import { Room, MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix"
import { renderToStaticMarkup } from "react-dom/server";
import { logger } from "matrix-js-sdk/src/logger";
import escapeHtml from "escape-html";
import { TooltipProvider } from "@vector-im/compound-web";
import Exporter from "./Exporter";
import { mediaFromMxc } from "../../customisations/Media";
@@ -271,25 +272,27 @@ export default class HTMLExporter extends Exporter {
return (
<div className="mx_Export_EventWrapper" id={mxEv.getId()}>
<MatrixClientContext.Provider value={this.room.client}>
<EventTile
mxEvent={mxEv}
continuation={continuation}
isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()}
forExport={true}
alwaysShowTimestamps={true}
showUrlPreview={false}
checkUnmounting={() => false}
isTwelveHour={false}
last={false}
lastInSection={false}
permalinkCreator={this.permalinkCreator}
lastSuccessful={false}
isSelectedEvent={false}
showReactions={false}
layout={Layout.Group}
showReadReceipts={false}
/>
<TooltipProvider>
<EventTile
mxEvent={mxEv}
continuation={continuation}
isRedacted={mxEv.isRedacted()}
replacingEventId={mxEv.replacingEventId()}
forExport={true}
alwaysShowTimestamps={true}
showUrlPreview={false}
checkUnmounting={() => false}
isTwelveHour={false}
last={false}
lastInSection={false}
permalinkCreator={this.permalinkCreator}
lastSuccessful={false}
isSelectedEvent={false}
showReactions={false}
layout={Layout.Group}
showReadReceipts={false}
/>
</TooltipProvider>
</MatrixClientContext.Provider>
</div>
);

View File

@@ -10,6 +10,7 @@ import React from "react";
import ReactDOM from "react-dom";
import { PushProcessor } from "matrix-js-sdk/src/pushprocessor";
import { MatrixClient, MatrixEvent, RuleId } from "matrix-js-sdk/src/matrix";
import { TooltipProvider } from "@vector-im/compound-web";
import SettingsStore from "../settings/SettingsStore";
import { Pill, pillRoomNotifLen, pillRoomNotifPos, PillType } from "../components/views/elements/Pill";
@@ -75,7 +76,9 @@ export function pillifyLinks(
const pillContainer = document.createElement("span");
const pill = (
<Pill url={href} inMessage={true} room={room} shouldShowPillAvatar={shouldShowPillAvatar} />
<TooltipProvider>
<Pill url={href} inMessage={true} room={room} shouldShowPillAvatar={shouldShowPillAvatar} />
</TooltipProvider>
);
ReactDOM.render(pill, pillContainer);
@@ -130,12 +133,14 @@ export function pillifyLinks(
const pillContainer = document.createElement("span");
const pill = (
<Pill
type={PillType.AtRoomMention}
inMessage={true}
room={room}
shouldShowPillAvatar={shouldShowPillAvatar}
/>
<TooltipProvider>
<Pill
type={PillType.AtRoomMention}
inMessage={true}
room={room}
shouldShowPillAvatar={shouldShowPillAvatar}
/>
</TooltipProvider>
);
ReactDOM.render(pill, pillContainer);

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import ReactDOM from "react-dom";
import { TooltipProvider } from "@vector-im/compound-web";
import PlatformPeg from "../PlatformPeg";
import LinkWithTooltip from "../components/views/elements/LinkWithTooltip";
@@ -52,9 +53,11 @@ export function tooltipifyLinks(rootNodes: ArrayLike<Element>, ignoredNodes: Ele
// wrapping the link with the LinkWithTooltip component, keeping the same children. Ideally we'd do this
// without the superfluous span but this is not something React trivially supports at this time.
const tooltip = (
<LinkWithTooltip tooltip={href}>
<span dangerouslySetInnerHTML={{ __html: node.innerHTML }} />
</LinkWithTooltip>
<TooltipProvider>
<LinkWithTooltip tooltip={href}>
<span dangerouslySetInnerHTML={{ __html: node.innerHTML }} />
</LinkWithTooltip>
</TooltipProvider>
);
ReactDOM.render(tooltip, node);