diff --git a/apps/web/playwright/e2e/composer/CIDER.spec.ts b/apps/web/playwright/e2e/composer/CIDER.spec.ts index 2b2a241aeb..0a8489be7d 100644 --- a/apps/web/playwright/e2e/composer/CIDER.spec.ts +++ b/apps/web/playwright/e2e/composer/CIDER.spec.ts @@ -77,6 +77,12 @@ test.describe("Composer", () => { await expect(page.locator(".mx_EventTile_body", { hasText: "😇" })).toBeVisible(); }); + test("renders in narrow viewports", { tag: "@screenshot" }, async ({ page, bot, app }) => { + // Shrink the viewport + await page.setViewportSize({ width: 500, height: 1080 }); + await expect(app.getComposer()).toMatchScreenshot("narrow.png"); + }); + test.describe("render emoji picker with larger viewport height", async () => { test.use({ viewport: { width: 1280, height: 720 } }); test("render emoji picker", { tag: "@screenshot" }, async ({ page, app }) => { @@ -201,12 +207,6 @@ test.describe("Composer", () => { }); test("can paste a file", async ({ page, bot, app }) => { - // Set up a private room so we have another user to mention - await app.client.createRoom({ - is_direct: true, - invite: [bot.credentials.userId], - }); - await app.viewRoomByName("Bob"); await app.composerDragAndPasteFile("room", getSampleFilePath("riot.png"), "image/png"); await expect(page.locator(".mx_ImageBody")).toBeVisible(); }); diff --git a/apps/web/playwright/e2e/composer/RTE.spec.ts b/apps/web/playwright/e2e/composer/RTE.spec.ts index 8227c7f9ae..4294ca4b19 100644 --- a/apps/web/playwright/e2e/composer/RTE.spec.ts +++ b/apps/web/playwright/e2e/composer/RTE.spec.ts @@ -216,6 +216,12 @@ test.describe("Composer", () => { await expect(page.locator(".mx_ImageBody")).toBeVisible(); }); + test("renders in narrow viewports", { tag: "@screenshot" }, async ({ page, bot, app }) => { + // Shrink the viewport + await page.setViewportSize({ width: 750, height: 1080 }); + await expect(page.locator(".mx_MessageComposer_wrapper")).toMatchScreenshot("narrow.png"); + }); + test.describe("when Control+Enter is required to send", () => { test.beforeEach(async ({ app }) => { await app.settings.setValue("MessageComposerInput.ctrlEnterToSend", null, SettingLevel.ACCOUNT, true); diff --git a/apps/web/playwright/e2e/threads/threads.spec.ts b/apps/web/playwright/e2e/threads/threads.spec.ts index c74ca570ea..5241e654d2 100644 --- a/apps/web/playwright/e2e/threads/threads.spec.ts +++ b/apps/web/playwright/e2e/threads/threads.spec.ts @@ -327,19 +327,19 @@ test.describe("Threads", () => { test.describe("with larger viewport", async () => { // Increase viewport size so that voice messages fit - test.use({ viewport: { width: 1280, height: 720 } }); + test.use({ viewport: { width: 1440, height: 720 } }); test.beforeEach(async ({ page }) => { // Increase right-panel size, so that voice messages fit await page.addInitScript(() => { - window.localStorage.setItem("mx_rhs_size", "600"); + window.localStorage.setItem("mx_rhs_size", "700"); }); }); test("can send voice messages", { tag: ["@no-firefox", "@no-webkit"] }, async ({ page, app, user }) => { // Increase right-panel size, so that voice messages fit await page.evaluate(() => { - window.localStorage.setItem("mx_rhs_size", "600"); + window.localStorage.setItem("mx_rhs_size", "700"); }); const roomId = await app.client.createRoom({}); diff --git a/apps/web/playwright/snapshots/composer/CIDER.spec.ts/narrow-linux.png b/apps/web/playwright/snapshots/composer/CIDER.spec.ts/narrow-linux.png new file mode 100644 index 0000000000..366271b095 Binary files /dev/null and b/apps/web/playwright/snapshots/composer/CIDER.spec.ts/narrow-linux.png differ diff --git a/apps/web/playwright/snapshots/composer/RTE.spec.ts/narrow-linux.png b/apps/web/playwright/snapshots/composer/RTE.spec.ts/narrow-linux.png new file mode 100644 index 0000000000..c0f8d16a16 Binary files /dev/null and b/apps/web/playwright/snapshots/composer/RTE.spec.ts/narrow-linux.png differ diff --git a/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png b/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png index b974688d7b..c9d871ac2f 100644 Binary files a/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png and b/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png b/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png index 3af5214e43..91d3bb15dc 100644 Binary files a/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png and b/apps/web/playwright/snapshots/polls/polls.spec.ts/ThreadView-with-a-poll-on-group-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png b/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png index 3c030baa04..de0ba2d3dd 100644 Binary files a/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png and b/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png b/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png index 77c96f7312..8c0272dc12 100644 Binary files a/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png and b/apps/web/playwright/snapshots/threads/threads.spec.ts/Initial-ThreadView-on-group-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png b/apps/web/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png index fd902e5075..0bb39e8701 100644 Binary files a/apps/web/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png and b/apps/web/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png differ diff --git a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png index 4ae55b69f0..1a11e02973 100644 Binary files a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png and b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png index 09448c389d..625495ed6c 100644 Binary files a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png and b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-reaction-and-a-hidden-event-on-group-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png index d17cf99326..d3dcf27986 100644 Binary files a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png and b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-bubble-layout-linux.png differ diff --git a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png index 5e8d75aeb5..cc3bf002de 100644 Binary files a/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png and b/apps/web/playwright/snapshots/threads/threads.spec.ts/ThreadView-with-redacted-messages-on-group-layout-linux.png differ diff --git a/apps/web/src/components/structures/ThreadView.tsx b/apps/web/src/components/structures/ThreadView.tsx index 87303d3f8c..6c0a04bb4b 100644 --- a/apps/web/src/components/structures/ThreadView.tsx +++ b/apps/web/src/components/structures/ThreadView.tsx @@ -435,7 +435,7 @@ export default class ThreadView extends React.Component { PosthogTrackers.trackInteraction("WebThreadViewBackButton", ev); }} > - +
{timeline}
{ContentMessages.sharedInstance().getCurrentUploads(threadRelation).length > 0 && ( diff --git a/apps/web/src/components/views/elements/Measured.tsx b/apps/web/src/components/views/elements/Measured.tsx index b0f084c0ed..fb1465cfb9 100644 --- a/apps/web/src/components/views/elements/Measured.tsx +++ b/apps/web/src/components/views/elements/Measured.tsx @@ -1,4 +1,5 @@ /* +Copyright (C) 2026 Element Creations Ltd Copyright 2024 New Vector Ltd. Copyright 2022 The Matrix.org Foundation C.I.C. @@ -6,57 +7,65 @@ 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 RefObject } from "react"; +import { useEffect, useRef, type RefObject } from "react"; -import UIStore, { UI_EVENTS } from "../../../stores/UIStore"; +import UIStore from "../../../stores/UIStore"; +import { useEventEmitterState } from "../../../hooks/useEventEmitter"; interface IProps { + /** + * Element to watch for resize changes on. + */ sensor: RefObject; - breakpoint: number; - onMeasurement(narrow: boolean): void; + /** + * Minimum width of element to be considered full-size. + * Defaults to `500px` + */ + breakpoint?: number; + /** + * Callback for when the narrowness property changes. + * @param narrow + * @returns + */ + onMeasurement: (narrow: boolean) => void; } -export default class Measured extends React.PureComponent { - private static instanceCount = 0; - private readonly instanceId: number; +let instanceCount = 0; - public static defaultProps = { - breakpoint: 500, - }; +/** + * This component can watch a single element for width changes, and will fire + * a callback if the width changes to be lower or higher than the `breakpoint`. + */ +export default function Measured({ sensor, breakpoint = 500, onMeasurement }: IProps): null { + const instanceIdRef = useRef(instanceCount++); + const instanceId = instanceIdRef.current; - public constructor(props: IProps) { - super(props); - - this.instanceId = Measured.instanceCount++; - } - - public componentDidMount(): void { - UIStore.instance.on(`Measured${this.instanceId}`, this.onResize); - } - - public componentDidUpdate(prevProps: Readonly): void { - const previous = prevProps.sensor.current; - const current = this.props.sensor.current; - if (previous === current) return; - if (previous) { - UIStore.instance.stopTrackingElementDimensions(`Measured${this.instanceId}`); + useEffect(() => { + if (sensor.current) { + UIStore.instance.trackElementDimensions(`Measured${instanceId}`, sensor.current); } - if (current) { - UIStore.instance.trackElementDimensions(`Measured${this.instanceId}`, current); - } - } - public componentWillUnmount(): void { - UIStore.instance.off(`Measured${this.instanceId}`, this.onResize); - UIStore.instance.stopTrackingElementDimensions(`Measured${this.instanceId}`); - } + return () => { + UIStore.instance.stopTrackingElementDimensions(`Measured${instanceId}`); + }; + }, [sensor, instanceId]); - private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry): void => { - if (type !== UI_EVENTS.Resize) return; - this.props.onMeasurement(entry.contentRect.width <= this.props.breakpoint); - }; + const narrow = useEventEmitterState( + UIStore.instance, + `Measured${instanceId}`, + (_type: unknown, entry: ResizeObserverEntry) => { + if (!entry) { + return false; + } + // N.B there is only one `_type` of resize event. + return entry.contentRect.width <= breakpoint; + }, + ); - public render(): React.ReactNode { - return null; - } + // Only fire when the state changes. + useEffect(() => { + onMeasurement(narrow); + }, [onMeasurement, narrow]); + + return null; }