From 9df1a616a123fe2cd5d04a76c8e12eab172499c3 Mon Sep 17 00:00:00 2001 From: Zack Date: Wed, 3 Jun 2026 10:08:21 +0200 Subject: [PATCH] Phase 1: Add shared MImageReplyBody view (#33517) * Add shared image reply body view * Added storybook screens * Fix Prettier * Add image reply body view coverage * Show image reply banner in visual story * fix annotation issue with stories * stop blurhush from reanimating to pass CI image diff --- .../shared-components/.storybook/preview.css | 5 + .../animated-preview-auto.png | Bin 0 -> 18429 bytes .../default-auto.png | Bin 0 -> 17815 bytes .../loading-with-blurhash-auto.png | Bin 0 -> 20049 bytes .../loading-with-spinner-auto.png | Bin 0 -> 17420 bytes .../with-banner-auto.png | Bin 0 -> 5285 bytes packages/shared-components/src/index.ts | 1 + .../ImageReplyBodyView.module.css | 98 ++++++++ .../ImageReplyBodyView.stories.tsx | 85 +++++++ .../ImageReplyBodyView.test.tsx | 164 +++++++++++++ .../ImageReplyBodyView.tsx | 229 ++++++++++++++++++ .../ImageReplyBodyView.test.tsx.snap | 185 ++++++++++++++ .../body/MImageReplyBodyView/index.tsx | 8 + 13 files changed, 775 insertions(+) create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/animated-preview-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/default-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/loading-with-blurhash-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/loading-with-spinner-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/with-banner-auto.png create mode 100644 packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.module.css create mode 100644 packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx create mode 100644 packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.test.tsx create mode 100644 packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.tsx create mode 100644 packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/__snapshots__/ImageReplyBodyView.test.tsx.snap create mode 100644 packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/index.tsx diff --git a/packages/shared-components/.storybook/preview.css b/packages/shared-components/.storybook/preview.css index 9f49585937..842351b5e0 100644 --- a/packages/shared-components/.storybook/preview.css +++ b/packages/shared-components/.storybook/preview.css @@ -8,3 +8,8 @@ Please see LICENSE files in the repository root for full details. .docs-story { background: var(--cpd-color-bg-canvas-default); } + +[class*="blurhash"] > canvas { + animation: none !important; + opacity: 1 !important; +} diff --git a/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/animated-preview-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/animated-preview-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..ac9f34b7b1a3cd4b8ada633fa3687767c007a291 GIT binary patch literal 18429 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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/loading-with-blurhash-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx/loading-with-blurhash-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..16ea6246ae4265d01c7361b7a74e04ac77742e03 GIT binary patch literal 20049 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 canvas { + animation: blurhashPulse 1.75s infinite cubic-bezier(0.4, 0, 0.6, 1); +} + +.mediaContent { + max-width: 100%; + max-height: 100%; +} + +.image { + display: block; + height: 100%; + width: 100%; +} + +.banner { + position: absolute; + bottom: 4px; + left: 4px; + padding: 4px; + border-radius: var(--MBody-border-radius); + font-size: 0.9375rem; + user-select: none; + pointer-events: none; + max-width: min(100%, 350px); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + background-color: rgb(0, 0, 0, 0.6); + color: #ffffff; +} + +.gifLabel { + position: absolute; + display: block; + top: 0px; + left: 14px; + padding: 5px; + border-radius: 5px; + background: rgba(0, 0, 0, 0.7); + border: 2px solid rgba(0, 0, 0, 0.2); + color: #ffffff; + pointer-events: none; +} + +@keyframes blurhashPulse { + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.65; + } +} diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx new file mode 100644 index 0000000000..be54397237 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.stories.tsx @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 ReactNode } from "react"; +import { expect, userEvent, within } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { withViewDocs } from "../../../../../../.storybook/withViewDocs"; +import { ImageReplyBodyView, ImageReplyBodyViewPlaceholder, type ImageReplyBodyViewProps } from "./ImageReplyBodyView"; +import imageSrc from "../../../../../../static/image-body/install-spinner.png"; + +const demoBlurhash = "LEHV6nWB2yk8pyo0adR*.7kCMdnj"; + +type ImageReplyBodyViewWrapperProps = Omit & { + aspectRatio?: string; +}; + +const ImageReplyBodyViewWrapperImpl = (props: ImageReplyBodyViewWrapperProps): ReactNode => ( + +); + +const ImageReplyBodyViewWrapper = withViewDocs(ImageReplyBodyViewWrapperImpl, ImageReplyBodyView); + +const meta = { + title: "Timeline/Timeline Body/ImageReplyBodyView", + component: ImageReplyBodyViewWrapper, + tags: ["autodocs"], + args: { + className: "", + src: imageSrc, + thumbnailSrc: imageSrc, + alt: "Reply preview", + maxWidth: 57, + maxHeight: 44, + aspectRatio: "57 / 43.95", + placeholder: ImageReplyBodyViewPlaceholder.NONE, + blurhash: demoBlurhash, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithBanner: Story = { + args: { + bannerLabel: "image.png", + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.hover(canvas.getByRole("img", { name: "Reply preview" })); + await expect(canvas.findByText("image.png")).resolves.toBeInTheDocument(); + }, +}; + +export const LoadingWithSpinner: Story = { + args: { + placeholder: ImageReplyBodyViewPlaceholder.SPINNER, + }, +}; + +export const LoadingWithBlurhash: Story = { + args: { + placeholder: ImageReplyBodyViewPlaceholder.BLURHASH, + }, +}; + +export const AnimatedPreview: Story = { + args: { + showAnimatedContentOnHover: true, + gifLabel: "GIF", + }, +}; diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.test.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.test.tsx new file mode 100644 index 0000000000..74c3b5de0f --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.test.tsx @@ -0,0 +1,164 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 { composeStories } from "@storybook/react-vite"; +import { fireEvent, render, screen } from "@test-utils"; +import React from "react"; +import { describe, expect, it, vi } from "vitest"; + +import { ImageReplyBodyView, ImageReplyBodyViewPlaceholder } from "./ImageReplyBodyView"; +import * as stories from "./ImageReplyBodyView.stories"; + +const { Default, WithBanner, LoadingWithSpinner, LoadingWithBlurhash, AnimatedPreview } = composeStories(stories); + +describe("ImageReplyBodyView", () => { + it.each([ + ["default", Default], + ["with-banner", WithBanner], + ["loading-with-spinner", LoadingWithSpinner], + ["loading-with-blurhash", LoadingWithBlurhash], + ["animated-preview", AnimatedPreview], + ])("matches snapshot for %s story", (_name, Story) => { + const { container } = render(); + + expect(container).toMatchSnapshot(); + }); + + it("applies a custom className to the root element", () => { + render( + , + ); + + expect(screen.getByRole("img", { name: "Reply preview" }).closest(".custom-reply-body")).not.toBeNull(); + }); + + it("renders a hidden measuring image when dimensions are not known yet", () => { + const onImageLoad = vi.fn(); + + const { container } = render( + , + ); + + const image = container.querySelector("img"); + expect(image).toHaveAttribute("alt", "Reply preview"); + expect(image).toHaveStyle({ display: "none" }); + + fireEvent.load(image!); + expect(onImageLoad).toHaveBeenCalledTimes(1); + }); + + it("renders an empty root when no image source is available", () => { + const { container } = render(); + + expect(container.querySelector(".empty-reply-body")).not.toBeNull(); + expect(screen.queryByRole("img")).not.toBeInTheDocument(); + }); + + it("does not render the measuring image when dimensions and visible media are unavailable", () => { + const { container } = render( + , + ); + + expect(container.querySelector("img")).toBeNull(); + }); + + it("swaps animated preview content and overlays on hover", () => { + render( + , + ); + + const image = screen.getByRole("img", { name: "Animated reply preview" }); + expect(image).toHaveAttribute("src", "https://example.org/thumb.png"); + expect(screen.getByText("GIF")).toBeInTheDocument(); + expect(screen.queryByText("animated.gif")).not.toBeInTheDocument(); + + fireEvent.mouseEnter(image); + expect(image).toHaveAttribute("src", "https://example.org/full.gif"); + expect(screen.queryByText("GIF")).not.toBeInTheDocument(); + expect(screen.getByText("animated.gif")).toBeInTheDocument(); + + fireEvent.mouseLeave(image); + expect(image).toHaveAttribute("src", "https://example.org/thumb.png"); + expect(screen.getByText("GIF")).toBeInTheDocument(); + expect(screen.queryByText("animated.gif")).not.toBeInTheDocument(); + }); + + it("invokes visible image load and error handlers", () => { + const onImageLoad = vi.fn(); + const onImageError = vi.fn(); + + render( + , + ); + + const image = screen.getByRole("img", { name: "Reply preview" }); + fireEvent.load(image); + fireEvent.error(image); + + expect(onImageLoad).toHaveBeenCalledTimes(1); + expect(onImageError).toHaveBeenCalledTimes(1); + }); + + it("falls back to a spinner when the blurhash placeholder has no hash", () => { + render( + , + ); + + expect(screen.getByRole("progressbar")).toBeInTheDocument(); + }); + + it("uses explicit width sizing for SVG images", () => { + render( + , + ); + + expect(screen.getByRole("img", { name: "SVG reply preview" }).parentElement).toHaveStyle({ + width: "58px", + maxWidth: "58px", + maxHeight: "44px", + }); + }); +}); diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.tsx new file mode 100644 index 0000000000..0ff18f2e48 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/ImageReplyBodyView.tsx @@ -0,0 +1,229 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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 classNames from "classnames"; +import React, { type CSSProperties, type JSX, type ReactEventHandler, type Ref, useState } from "react"; +import { Blurhash } from "react-blurhash"; +import { InlineSpinner } from "@vector-im/compound-web"; + +import { useI18n } from "../../../../../core/i18n/i18nContext"; +import styles from "./ImageReplyBodyView.module.css"; + +export const enum ImageReplyBodyViewPlaceholder { + NONE = "NONE", + SPINNER = "SPINNER", + BLURHASH = "BLURHASH", +} + +export interface ImageReplyBodyViewProps { + /** + * CSS class names applied to the root element. + */ + className?: string; + /** + * Ref to the rendered image element. + */ + imageRef?: Ref; + /** + * Full-resolution image source. + */ + src?: string; + /** + * Thumbnail/static preview image source. + */ + thumbnailSrc?: string; + /** + * Image alt text. + */ + alt?: string; + /** + * Maximum rendered width for the media frame. + */ + maxWidth?: number; + /** + * Maximum rendered height for the media frame. + */ + maxHeight?: number; + /** + * Aspect ratio reserved for the media frame. + */ + aspectRatio?: CSSProperties["aspectRatio"]; + /** + * Whether the displayed image is an SVG and should therefore use explicit width sizing. + */ + isSvg?: boolean; + /** + * Which placeholder to render over the image frame. + */ + placeholder?: ImageReplyBodyViewPlaceholder; + /** + * Blurhash string used when `placeholder` is `BLURHASH`. + */ + blurhash?: string; + /** + * Whether hovering the preview should swap to the full-resolution image. + */ + showAnimatedContentOnHover?: boolean; + /** + * Whether the image element should be rendered inside the reserved media frame. + */ + showImage?: boolean; + /** + * Optional badge shown for animated images when not hovered. + */ + gifLabel?: string; + /** + * Optional overlay banner shown while hovered. + */ + bannerLabel?: string; + /** + * Invoked when the rendered image loads. + */ + onImageLoad?: ReactEventHandler; + /** + * Invoked when the rendered image fails to load. + */ + onImageError?: ReactEventHandler; +} + +function renderPlaceholder({ + placeholder, + blurhash, + maxWidth, + maxHeight, + loadingLabel, +}: Pick & { + loadingLabel: string; +}): JSX.Element | null { + switch (placeholder) { + case ImageReplyBodyViewPlaceholder.BLURHASH: + if (!blurhash) { + return ( +
+ +
+ ); + } + + return ( + + ); + + case ImageReplyBodyViewPlaceholder.SPINNER: + return ( +
+ +
+ ); + + case ImageReplyBodyViewPlaceholder.NONE: + default: + return null; + } +} + +/** + * Presentational wrapper for the compact image preview used inside reply tiles. + */ +export function ImageReplyBodyView({ + className, + imageRef, + src, + thumbnailSrc, + alt, + maxWidth, + maxHeight, + aspectRatio, + isSvg, + placeholder = ImageReplyBodyViewPlaceholder.NONE, + blurhash, + showAnimatedContentOnHover, + showImage = true, + gifLabel, + bannerLabel, + onImageLoad, + onImageError, +}: Readonly): JSX.Element { + const { translate: _t } = useI18n(); + const [hover, setHover] = useState(false); + const resolvedThumbnailSrc = thumbnailSrc ?? src; + const resolvedImageSrc = hover && showAnimatedContentOnHover && src ? src : resolvedThumbnailSrc; + + if (!resolvedImageSrc) { + return
; + } + + if (maxWidth === undefined || maxHeight === undefined || aspectRatio === undefined) { + if (!showImage) { + return
; + } + + return ( +
+ {alt} +
+ ); + } + + const containerStyle: CSSProperties = { + maxWidth, + maxHeight, + aspectRatio, + }; + const mediaStyle: CSSProperties = isSvg + ? { + width: maxWidth, + maxWidth, + maxHeight, + } + : { + width: "100%", + height: "100%", + maxWidth, + maxHeight, + }; + + const placeholderNode = renderPlaceholder({ + placeholder, + blurhash, + maxWidth, + maxHeight, + loadingLabel: _t("common|loading"), + }); + + return ( +
+
+ {placeholderNode &&
{placeholderNode}
} + +
+ {showImage ? ( + {alt} setHover(true)} + onMouseLeave={(): void => setHover(false)} + /> + ) : null} + {gifLabel && !hover ?

{gifLabel}

: null} + {bannerLabel && hover ? {bannerLabel} : null} +
+
+
+ ); +} diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/__snapshots__/ImageReplyBodyView.test.tsx.snap b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/__snapshots__/ImageReplyBodyView.test.tsx.snap new file mode 100644 index 0000000000..858f34ad2e --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/__snapshots__/ImageReplyBodyView.test.tsx.snap @@ -0,0 +1,185 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`ImageReplyBodyView > matches snapshot for animated-preview story 1`] = ` +
+
+
+
+
+ Reply preview +

+ GIF +

+
+
+
+
+
+`; + +exports[`ImageReplyBodyView > matches snapshot for default story 1`] = ` +
+
+
+
+
+ Reply preview +
+
+
+
+
+`; + +exports[`ImageReplyBodyView > matches snapshot for loading-with-blurhash story 1`] = ` +
+
+
+
+
+
+ +
+
+
+ Reply preview +
+
+
+
+
+`; + +exports[`ImageReplyBodyView > matches snapshot for loading-with-spinner story 1`] = ` +
+
+
+
+
+
+ + + +
+
+
+ Reply preview +
+
+
+
+
+`; + +exports[`ImageReplyBodyView > matches snapshot for with-banner story 1`] = ` +
+
+
+
+
+ Reply preview +
+
+
+
+
+`; diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/index.tsx b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/index.tsx new file mode 100644 index 0000000000..e76209c905 --- /dev/null +++ b/packages/shared-components/src/room/timeline/event-tile/body/MImageReplyBodyView/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright 2026 Element Creations Ltd. + * + * 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. + */ + +export { ImageReplyBodyView, ImageReplyBodyViewPlaceholder, type ImageReplyBodyViewProps } from "./ImageReplyBodyView";