Move EventTileBubble to shared components (#31911)

* Move EventTileBubble to shared components as is

* Added documentation and updated stories and unit tests

* Move 'global' element web css to _common.pcss

* Adding playwright snapshots

* Updated comments

* Added legacy mx_MessageTimestamp class and updated snapshots

* Regenerate snapshots with correct hash

* Changes to css and removed timestamp from properties after review.

* Update screenshot for room-list and fix flaky CI playwright test.

* Blur the play button before matching screenshots

* Changed to button focused instead of blur for consistancy

* Stabilize play button appearance in CI (disabled due to decoding)

* Force play button appearance in CI (disabled due to decoding)

* Add comments on playwright test changes.
Change from React.RefObject<any> to Ref<HTMLDivElement> in EncryptionEvent.tsx

* Update playwright/e2e/composer/CIDER.spec.ts

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

* Update playwright/e2e/composer/CIDER.spec.ts

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

* Update playwright/e2e/crypto/toasts.spec.ts

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
rbondesson
2026-02-03 14:37:57 +00:00
committed by GitHub
co-authored by Michael Telatynski
parent eb909f1090
commit a1be203683
76 changed files with 463 additions and 180 deletions
@@ -9,12 +9,12 @@ Please see LICENSE files in the repository root for full details.
import React, { type RefObject } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { EventTileBubble } from "@element-hq/web-shared-components";
import type ResizeNotifier from "../../utils/ResizeNotifier";
import ErrorBoundary from "../views/elements/ErrorBoundary";
import RoomHeader from "../views/rooms/RoomHeader/RoomHeader.tsx";
import ScrollPanel from "./ScrollPanel";
import EventTileBubble from "../views/messages/EventTileBubble";
import NewRoomIntro from "../views/rooms/NewRoomIntro";
import { UnwrappedEventTile } from "../views/rooms/EventTile";
import { _t } from "../../languageHandler";
@@ -45,7 +45,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
<ScrollPanel className="mx_RoomView_messagePanel">
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("room|waiting_for_join_title", { brand })}
subtitle={_t("room|waiting_for_join_subtitle", { brand })}
/>
@@ -6,13 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type Ref, type ReactNode } from "react";
import React, { type JSX, type ReactNode } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { ErrorSolidIcon, LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { EventTileBubble } from "@element-hq/web-shared-components";
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
import { _t } from "../../../languageHandler";
import EventTileBubble from "./EventTileBubble";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap";
import { objectHasDiff } from "../../../utils/objects";
@@ -23,7 +23,7 @@ import { useIsEncrypted } from "../../../hooks/useIsEncrypted.ts";
interface IProps {
mxEvent: MatrixEvent;
timestamp?: JSX.Element;
ref?: Ref<HTMLDivElement>;
ref?: React.RefObject<HTMLDivElement>;
}
const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
@@ -60,11 +60,12 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
return (
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
title={stateEncrypted ? _t("common|state_encryption_enabled") : _t("common|encryption_enabled")}
subtitle={subtitle}
timestamp={timestamp}
/>
>
{timestamp}
</EventTileBubble>
);
}
@@ -72,23 +73,25 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
return (
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("common|encryption_enabled")}
subtitle={_t("timeline|m.room.encryption|disable_attempt")}
timestamp={timestamp}
/>
>
{timestamp}
</EventTileBubble>
);
}
return (
<EventTileBubble
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
className="mx_cryptoEvent"
className="mx_EventTileBubble mx_cryptoEvent"
title={_t("timeline|m.room.encryption|disabled")}
subtitle={_t("timeline|m.room.encryption|unsupported")}
ref={ref}
timestamp={timestamp}
/>
>
{timestamp}
</EventTileBubble>
);
};
@@ -1,34 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2020 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX, type ReactNode, type Ref } from "react";
import classNames from "classnames";
interface IProps {
className: string;
icon: JSX.Element;
title: string;
timestamp?: JSX.Element;
subtitle?: ReactNode;
children?: JSX.Element;
ref?: Ref<HTMLDivElement>;
}
const EventTileBubble = ({ className, icon, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
return (
<div className={classNames("mx_EventTileBubble", className)} ref={ref}>
{icon}
<div className="mx_EventTileBubble_title">{title}</div>
{subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>}
{children}
{timestamp}
</div>
);
};
export default EventTileBubble;
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { VideoCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { EventTileBubble } from "@element-hq/web-shared-components";
import { _t } from "../../../languageHandler";
import WidgetStore from "../../../stores/WidgetStore";
import EventTileBubble from "./EventTileBubble";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
@@ -43,32 +43,35 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
return (
<EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent"
className="mx_EventTileBubble mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_ended", { senderName })}
timestamp={this.props.timestamp}
/>
>
{this.props.timestamp}
</EventTileBubble>
);
} else if (prevUrl) {
// modified
return (
<EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent"
className="mx_EventTileBubble mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_updated", { senderName })}
subtitle={joinCopy}
timestamp={this.props.timestamp}
/>
>
{this.props.timestamp}
</EventTileBubble>
);
} else {
// assume added
return (
<EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent"
className="mx_EventTileBubble mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_started", { senderName })}
subtitle={joinCopy}
timestamp={this.props.timestamp}
/>
>
{this.props.timestamp}
</EventTileBubble>
);
}
}
@@ -9,10 +9,10 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { EventTileBubble } from "@element-hq/web-shared-components";
import { _t } from "../../../languageHandler";
import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
import EventTileBubble from "./EventTileBubble";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
interface Props {
@@ -75,12 +75,11 @@ const MKeyVerificationRequest: React.FC<Props> = ({ mxEvent, timestamp }) => {
return (
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
className="mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon"
title={title}
subtitle={subtitle}
timestamp={timestamp}
>
<></>
{timestamp}
</EventTileBubble>
);
};
@@ -11,13 +11,13 @@ import React, { type JSX, useCallback } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { type MatrixEvent, type Room, type RoomState } from "matrix-js-sdk/src/matrix";
import { ChatSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { EventTileBubble } from "@element-hq/web-shared-components";
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import EventTileBubble from "./EventTileBubble";
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { useRoomState } from "../../../hooks/useRoomState";
import SettingsStore from "../../../settings/SettingsStore";
@@ -91,26 +91,28 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
return (
<EventTileBubble
icon={<ChatSolidIcon />}
className="mx_CreateEvent"
className="mx_EventTileBubble mx_CreateEvent"
title={_t("timeline|m.room.create|continuation")}
timestamp={timestamp}
>
<div className="mx_EventTile_body">
<span className="mx_EventTile_tileError">
{!!guessedLink ? (
<>
{_t("timeline|m.room.create|unknown_predecessor_guess_server", {
<>
<div className="mx_EventTile_body">
<span className="mx_EventTile_tileError">
{!!guessedLink ? (
<>
{_t("timeline|m.room.create|unknown_predecessor_guess_server", {
roomId: predecessor.roomId,
})}
<a href={guessedLink}>{guessedLink}</a>
</>
) : (
_t("timeline|m.room.create|unknown_predecessor", {
roomId: predecessor.roomId,
})}
<a href={guessedLink}>{guessedLink}</a>
</>
) : (
_t("timeline|m.room.create|unknown_predecessor", {
roomId: predecessor.roomId,
})
)}
</span>
</div>
})
)}
</span>
</div>
{timestamp}
</>
</EventTileBubble>
);
}
@@ -131,11 +133,12 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
return (
<EventTileBubble
icon={<ChatSolidIcon />}
className="mx_CreateEvent"
className="mx_EventTileBubble mx_CreateEvent"
title={_t("timeline|m.room.create|continuation")}
subtitle={link}
timestamp={timestamp}
/>
>
{timestamp}
</EventTileBubble>
);
function createLinkWithRoom(room: Room, roomId: string, eventId?: string): string {
+1 -1
View File
@@ -117,7 +117,7 @@ export interface IEventTileOps {
unhideWidget(): void;
}
export interface IEventTileType extends React.Component {
export interface IEventTileType extends React.Component<HTMLDivElement> {
getEventTileOps?(): IEventTileOps;
getMediaHelper(): MediaEventHelper | undefined;
}
+2 -2
View File
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { EventTimeline } from "matrix-js-sdk/src/matrix";
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { EventTileBubble } from "@element-hq/web-shared-components";
import EventTileBubble from "../messages/EventTileBubble";
import { _t } from "../../../languageHandler";
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
@@ -30,7 +30,7 @@ const HistoryTile: React.FC = () => {
return (
<EventTileBubble
icon={<VisibilityOffIcon />}
className="mx_HistoryTile"
className="mx_EventTileBubble mx_HistoryTile"
title={_t("timeline|historical_messages_unavailable")}
subtitle={subtitle}
/>
+2 -2
View File
@@ -10,6 +10,7 @@ import React, { type JSX, useContext } from "react";
import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { ErrorSolidIcon, UserAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { EventTileBubble } from "@element-hq/web-shared-components";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap";
@@ -22,7 +23,6 @@ import { type ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPaylo
import { Action } from "../../../dispatcher/actions";
import SpaceStore from "../../../stores/spaces/SpaceStore";
import { showSpaceInvite } from "../../../utils/space";
import EventTileBubble from "../messages/EventTileBubble";
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
@@ -296,7 +296,7 @@ const NewRoomIntro: React.FC = () => {
{!hasExpectedEncryptionSettings(cli, room) && (
<EventTileBubble
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
className="mx_cryptoEvent"
className="mx_EventTileBubble mx_cryptoEvent"
title={_t("room|intro|unencrypted_warning")}
subtitle={subtitle}
/>
+2 -2
View File
@@ -21,7 +21,7 @@ import { TextualEventView } from "@element-hq/web-shared-components";
import SettingsStore from "../settings/SettingsStore";
import type LegacyCallEventGrouper from "../components/structures/LegacyCallEventGrouper";
import { type EventTileProps } from "../components/views/rooms/EventTile";
import { type IEventTileType, type EventTileProps } from "../components/views/rooms/EventTile";
import { TimelineRenderingType } from "../contexts/RoomContext";
import MessageEvent from "../components/views/messages/MessageEvent";
import LegacyCallEvent from "../components/views/messages/LegacyCallEvent";
@@ -61,7 +61,7 @@ export interface EventTileTypeProps extends Pick<
| "isSeeingThroughMessageHiddenForModeration"
| "inhibitInteraction"
> {
ref?: React.RefObject<any>; // `any` because it's effectively impossible to convince TS of a reasonable type
ref?: React.RefObject<IEventTileType | null>;
maxImageHeight?: number; // pixels
overrideBodyTypes?: Record<string, React.ComponentType<IBodyProps>>;
overrideEventTypes?: Record<string, React.ComponentType<IBodyProps>>;