Redesign link previews (#33061)

* Commit design update

* Add figma links

* Check in other changes

* revert accidental change

* Iterative update

* linting n test fiddles

* linting

* Cleanup

* update snaps

* Move URL previews to new home

* Fix paths

* compress img

* Add back all the stories

* Improved rendering

* Fixup

* Update previews again

* lint

* update stories

* Update snaps again

* More screenshots

* Also these

* Update snaps

* include site name

* Update snaps again

* Use a scale so the images don't go blur

* update snaps again

* Update snaps

* remove mistaken playwright cfg

* update pw snaps

* update snap

* update previews

* Update with new designs

* Update screenshots
This commit is contained in:
Will Hunt
2026-04-22 14:23:24 +01:00
committed by GitHub
parent 2d16498fe6
commit 9df7182c0c
53 changed files with 1427 additions and 943 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 201 KiB

@@ -1,85 +0,0 @@
/*
* 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.
*/
.thumbnail {
/* Thumbnails are always limited to a maximum of 100px */
max-width: 100px;
max-height: 100px;
/* Ensure we don't stretch the image */
object-fit: cover;
}
.link {
color: var(--cpd-color-text-link-external);
text-decoration-line: none;
}
.container {
display: inline flex;
column-gap: var(--cpd-space-1x);
border-inline-start: 2px solid var(--cpd-color-bg-subtle-primary);
border-radius: 2px;
color: var(--cpd-color-gray-900);
.wrapImageCaption {
display: inline-flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: var(--cpd-space-2x);
flex: 1;
}
.image,
.caption {
display: inline-flex;
flex-direction: column;
margin-inline-start: var(--cpd-space-4x);
min-width: 0; /* Prevent blowout */
}
.image {
/* Clear default <button> styles */
padding: 0;
border: none;
background: none;
/* Consistent image size */
flex: 0 0 100px;
}
.caption {
flex: 1;
overflow: hidden; /* cause it to wrap rather than clip */
}
.title,
.description {
display: inline-block;
-webkit-box-orient: vertical;
overflow: hidden;
white-space: normal;
}
.title {
display: inline-block;
line-clamp: 2;
-webkit-line-clamp: 2;
margin: var(--cpd-space-1x) 0;
> a {
font-weight: var(--cpd-font-weight-semibold);
text-decoration: none;
}
}
.description {
margin: var(--cpd-space-1x) 0;
word-wrap: break-word;
line-clamp: 3;
-webkit-line-clamp: 3;
}
}
@@ -1,75 +0,0 @@
/*
* 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 from "react";
import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite";
import imageFile from "../../../../static/element.png";
import { LinkPreview } from "./LinkPreview";
import { LinkedTextContext } from "../../../core/utils/LinkedText";
export default {
title: "Event/UrlPreviewGroupView/LinkPreview",
component: LinkPreview,
tags: ["autodocs"],
args: {
onImageClick: fn(),
},
} satisfies Meta<typeof LinkPreview>;
const Template: StoryFn<typeof LinkPreview> = (args) => (
<LinkedTextContext.Provider value={{}}>
<LinkPreview {...args} />
</LinkedTextContext.Provider>
);
export const Default = Template.bind({});
Default.args = {
title: "A simple title",
description: "A simple description",
link: "https://matrix.org",
siteName: "Site name",
image: {
imageThumb: imageFile,
imageFull: imageFile,
},
};
export const Title = Template.bind({});
Title.args = {
title: "A simple title",
link: "https://matrix.org",
};
export const TitleAndDescription = Template.bind({});
TitleAndDescription.args = {
title: "A simple title",
description: "A simple description with a link to https://matrix.org",
link: "https://matrix.org",
};
export const WithTooltip = Template.bind({});
WithTooltip.args = {
title: "A simple title",
description: "A simple description",
showTooltipOnLink: true,
link: "https://matrix.org",
};
export const WithVeryLongText = Template.bind({});
WithVeryLongText.args = {
title: "GitHub - element-hq/not-a-real-repo: A very very long PR title that should be rendered nicely",
description:
"This PR doesn't actually exist and neither does the repository. It might exist one day if we go into the business of making paradoxical repository names.",
link: "https://matrix.org",
siteName: "GitHub",
image: {
imageThumb: imageFile,
imageFull: imageFile,
},
};
@@ -1,89 +0,0 @@
/*
* 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 MouseEventHandler, type JSX, useCallback, useMemo } from "react";
import { Tooltip, Text } from "@vector-im/compound-web";
import classNames from "classnames";
import { useI18n } from "../../../core/i18n/i18nContext";
import styles from "./LinkPreview.module.css";
import type { UrlPreview } from "../types";
import { LinkedText } from "../../../core/utils/LinkedText";
export interface LinkPreviewActions {
onImageClick: () => void;
}
export type LinkPreviewProps = UrlPreview & LinkPreviewActions;
/**
* LinkPreview renders a single preview component for a single link on an event. It is usually rendered as part of
* a `UrlPreviewGroupView`.
*/
export function LinkPreview({ onImageClick, ...preview }: LinkPreviewProps): JSX.Element {
const { translate: _t } = useI18n();
const tooltipCaption = useMemo(() => {
if (preview.showTooltipOnLink) {
return new URL(preview.link, window.location.href).toString();
}
return null;
}, [preview.link, preview.showTooltipOnLink]);
const onImageClickHandler = useCallback<MouseEventHandler>(
(ev) => {
if (ev.button != 0 || ev.metaKey) return;
ev.preventDefault();
if (!preview.image?.imageFull) {
return;
}
onImageClick();
},
[preview.image?.imageFull, onImageClick],
);
let img: JSX.Element | undefined;
// Don't render a button to show the image, just hide it outright
if (preview.image?.imageThumb) {
img = (
<button
aria-label={_t("timeline|url_preview|view_image")}
className={styles.image}
onClick={onImageClickHandler}
>
<img className={styles.thumbnail} src={preview.image.imageThumb} alt="" />
</button>
);
}
const anchor = (
<a className={styles.link} href={preview.link} target="_blank" rel="noreferrer noopener">
{preview.title}
</a>
);
return (
<div className={classNames(styles.container)}>
<div className={styles.wrapImageCaption}>
{img}
<div className={styles.caption}>
<Text type="body" size="md" className={styles.title}>
{tooltipCaption ? <Tooltip label={tooltipCaption}>{anchor}</Tooltip> : anchor}
{preview.siteName && (
<Text as="span" size="md" weight="regular">
{" - " + preview.siteName}
</Text>
)}
</Text>
{preview.description && (
<LinkedText className={styles.description}>{preview.description}</LinkedText>
)}
</div>
</div>
</div>
);
}
@@ -1,121 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`LinkPreview > renders a preview 1`] = `
<div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/element.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
<span
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50"
>
- Site name
</span>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description
</p>
</div>
</div>
</div>
</div>
`;
exports[`LinkPreview > renders a preview with just a title 1`] = `
<div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
</p>
</div>
</div>
</div>
</div>
`;
exports[`LinkPreview > renders a preview with just a title and description 1`] = `
<div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description with a link to
<a
data-linkified="true"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
https://matrix.org
</a>
</p>
</div>
</div>
</div>
</div>
`;
@@ -1,492 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`UrlPreviewGroupView > renders a single preview 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup"
>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/element.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description
</p>
</div>
</div>
</div>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="primary"
role="button"
style="--cpd-icon-button-size: 20px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
exports[`UrlPreviewGroupView > renders multiple previews 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup"
>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/element.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
One
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A regular square image.
</p>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/tallImage.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Two
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
This one has a taller image which should crop nicely.
</p>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/element.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Three
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
One more description
</p>
</div>
</div>
</div>
<button
class="_button_13vu4_8 UrlPreviewGroupView-module_toggleButton"
data-kind="tertiary"
data-size="sm"
role="button"
tabindex="0"
>
Collapse
</button>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="primary"
role="button"
style="--cpd-icon-button-size: 20px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
exports[`UrlPreviewGroupView > renders multiple previews which are hidden 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup"
>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/element.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description
</p>
</div>
</div>
</div>
<button
class="_button_13vu4_8 UrlPreviewGroupView-module_toggleButton"
data-kind="tertiary"
data-size="sm"
role="button"
tabindex="0"
>
Show 9 other previews
</button>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="primary"
role="button"
style="--cpd-icon-button-size: 20px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
exports[`UrlPreviewGroupView > renders with a compact view 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup UrlPreviewGroupView-module_compactLayout"
>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/element.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
One
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A regular square image.
</p>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/tallImage.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Two
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
This one has a taller image which should crop nicely.
</p>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_wrapImageCaption"
>
<button
aria-label="View image"
class="LinkPreview-module_image"
>
<img
alt=""
class="LinkPreview-module_thumbnail"
src="/static/element.png"
/>
</button>
<div
class="LinkPreview-module_caption"
>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkPreview-module_title"
>
<a
class="LinkPreview-module_link"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Three
</a>
</p>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
One more description
</p>
</div>
</div>
</div>
<button
class="_button_13vu4_8 UrlPreviewGroupView-module_toggleButton"
data-kind="tertiary"
data-size="sm"
role="button"
tabindex="0"
>
Collapse
</button>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="primary"
role="button"
style="--cpd-icon-button-size: 20px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
@@ -224,6 +224,7 @@
"message_timestamp_sent_at": "Sent at: %(dateTime)s",
"url_preview": {
"close": "Close preview",
"open_link": "Open link",
"show_n_more": {
"one": "Show %(count)s other preview",
"other": "Show %(count)s other previews"
+1 -1
View File
@@ -12,7 +12,6 @@ export * from "./audio/SeekBar";
export * from "./core/AvatarWithDetails";
export * from "./room/composer/Banner";
export * from "./crypto/SasEmoji";
export * from "./event-tiles/UrlPreviewGroupView";
export * from "./room/timeline/ReadMarker";
export * from "./room/timeline/event-tile/body/EventContentBodyView";
export * from "./room/timeline/event-tile/body/RedactedBodyView";
@@ -42,6 +41,7 @@ export * from "./room/timeline/event-tile/reactions/ReactionsRow";
export * from "./room/timeline/event-tile/reactions/ReactionsRowButton";
export * from "./room/timeline/event-tile/reactions/ReactionsRowButtonTooltip";
export * from "./room/timeline/event-tile/timestamp/MessageTimestampView";
export * from "./room/timeline/event-tile/UrlPreviewGroupView";
export * from "./core/rich-list/RichItem";
export * from "./core/rich-list/RichList";
export * from "./room-list/RoomListHeaderView";
@@ -0,0 +1,126 @@
/*
* 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.
*/
button.preview {
background: #fff;
}
.preview {
display: flex;
position: relative;
width: 100%;
height: 200px;
background-size: cover;
background-position: center;
border: none;
padding: 0;
> img {
width: 100%;
object-fit: cover;
}
.playButton[data-kind="primary"] {
padding: 0;
width: 50px;
height: 50px;
margin: auto;
background: var(--cpd-color-text-on-solid-primary);
> svg {
margin: auto;
border-radius: 50px;
color: var(--cpd-color-icon-primary);
}
}
}
.container {
max-width: 478px;
display: flex;
border: 1px solid var(--cpd-color-bg-subtle-secondary);
border-radius: 12px; /* Get radius from cpd */
flex-direction: column;
color: var(--cpd-color-gray-900);
overflow: clip;
background: var(--cpd-color-bg-subtle-secondary);
&.inline {
flex-direction: row;
gap: var(--cpd-space-4x);
padding: var(--cpd-space-3x) var(--cpd-space-4x);
.title {
margin: 0;
}
.siteAvatar {
margin: auto 0;
}
.siteName {
margin: 0;
}
}
.textContent {
padding: var(--cpd-space-3x) var(--cpd-space-4x);
&.inline {
padding: 0;
}
display: flex;
flex-direction: column;
gap: var(--cpd-space-1x);
}
.caption {
display: inline-flex;
flex-direction: column;
min-width: 0; /* Prevent blowout */
}
.caption {
flex: 1;
overflow: hidden; /* cause it to wrap rather than clip */
}
.title,
.description {
display: inline-block;
-webkit-box-orient: vertical;
overflow: hidden;
white-space: normal;
}
.title {
display: inline-block;
line-clamp: 2;
-webkit-line-clamp: 2;
margin: var(--cpd-space-1x) 0;
font-weight: var(--cpd-font-weight-semibold);
color: var(--cpd-color-text-primary);
text-decoration-line: none;
}
.description {
font-size: var(--cpd-font-size-body-lg);
margin: 0;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
.siteName {
margin-top: var(--cpd-space-1x);
vertical-align: middle;
display: flex;
gap: var(--cpd-space-1-5x);
> * {
/* Center everything */
margin: auto 0;
}
}
}
@@ -0,0 +1,169 @@
/*
* 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 from "react";
import { fn } from "storybook/test";
import type { Meta, StoryFn } from "@storybook/react-vite";
import { LinkPreview } from "./LinkPreview";
import { LinkedTextContext } from "../../../../../core/utils/LinkedText";
import imageFile from "../../../../../../static/element.png";
import imageFileWide from "../../../../../../static/wideImage.png";
export default {
title: "Event/UrlPreviewGroupView/LinkPreview",
component: LinkPreview,
tags: ["autodocs"],
args: {
onImageClick: fn(),
},
argTypes: {
siteName: {
control: "text",
},
author: {
control: "text",
},
siteIcon: { control: { type: "file", accept: ".png" } },
image: {},
},
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/design/sI9A2kV2K4xeiyqJsL7Ey3/Link-Previews?node-id=87-7920",
},
},
} satisfies Meta<typeof LinkPreview>;
const Template: StoryFn<typeof LinkPreview> = (args) => (
<LinkedTextContext.Provider value={{}}>
<LinkPreview {...args} />
</LinkedTextContext.Provider>
);
export const Default = Template.bind({});
Default.args = {
title: "A simple title",
description: "A simple description",
link: "https://matrix.org",
siteName: "Site name",
image: {
imageThumb: imageFile,
imageFull: imageFile,
alt: "Element logo",
playable: false,
},
};
export const Title = Template.bind({});
Title.args = {
title: "A simple title",
link: "https://matrix.org",
siteName: "matrix.org",
};
export const TitleAndDescription = Template.bind({});
TitleAndDescription.args = {
title: "A simple title",
description: "A simple description with a link to https://matrix.org",
link: "https://matrix.org",
siteName: "matrix.org",
};
export const WithSiteIcon = Template.bind({});
WithSiteIcon.args = {
title: "A simple title",
link: "https://matrix.org",
siteName: "matrix.org",
siteIcon: imageFile,
};
export const WithSiteIconAndDescription = Template.bind({});
WithSiteIconAndDescription.args = {
title: "A simple title",
description: "A simple description with a link to https://matrix.org",
link: "https://matrix.org",
siteName: "matrix.org",
siteIcon: imageFile,
};
export const WithTooltip = Template.bind({});
WithTooltip.args = {
title: "A simple title",
description: "A simple description",
showTooltipOnLink: true,
link: "https://matrix.org",
siteName: "matrix.org",
};
export const Article = Template.bind({});
Article.args = {
title: "A linked article",
description:
"This is a basic description returned from the linked source, usually with a word or two about what the link contains.",
link: "https://matrix.org",
siteName: "blog.example.org",
image: {
imageThumb: imageFileWide,
imageFull: imageFileWide,
alt: "A dog",
playable: false,
},
};
export const Video = Template.bind({});
Video.args = {
title: "A linked video",
description:
"This is a link to a video. You cannot play the video inline yet, but you can click the play button to open the link",
link: "https://matrix.org",
siteName: "blog.example.org",
image: {
imageThumb: imageFileWide,
imageFull: imageFileWide,
alt: "A dog",
playable: true,
},
};
export const Social = Template.bind({});
Social.args = {
description: "Sending a small message",
link: "https://matrix.org",
siteName: "socialsite.example.org",
title: "Test user (@test)",
author: "@test",
};
export const SocialWithImage = Template.bind({});
SocialWithImage.args = {
description: "Sending a message with an attached image.",
title: "Test user (@test)",
link: "https://matrix.org",
siteName: "socialsite.example.org",
author: "@test",
image: {
imageThumb: imageFileWide,
imageFull: imageFileWide,
alt: "A dog",
playable: false,
},
};
export const WithVeryLongText = Template.bind({});
WithVeryLongText.args = {
title: "GitHub - element-hq/not-a-real-repo: A very very long PR title that should be rendered nicely",
description:
"This PR doesn't actually exist and neither does the repository. It might exist one day if we go into the business of making paradoxical repository names.",
link: "https://matrix.org",
siteName: "GitHub",
image: {
imageThumb: imageFile,
imageFull: imageFile,
alt: "Element logo",
playable: false,
},
};
@@ -13,7 +13,7 @@ import userEvent from "@testing-library/user-event";
import * as stories from "./LinkPreview.stories.tsx";
const { Default, WithTooltip, Title, TitleAndDescription } = composeStories(stories);
const { Default, WithTooltip, Title, TitleAndDescription, Video } = composeStories(stories);
describe("LinkPreview", () => {
it("renders a preview", () => {
@@ -36,4 +36,10 @@ describe("LinkPreview", () => {
// Tooltip has the URL
expect(await screen.findByText("https://matrix.org/")).toBeVisible();
});
it("renders a playable preview that can be opened with a click", () => {
const { container } = render(<Video />);
expect(container).toMatchSnapshot();
const button = screen.getByLabelText("Open link");
expect(button).toHaveAttribute("href", "https://matrix.org");
});
});
@@ -0,0 +1,165 @@
/*
* 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 MouseEventHandler, type JSX, useCallback } from "react";
import { Tooltip, Text, Avatar, Button } from "@vector-im/compound-web";
import PlaySolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/play-solid";
import classNames from "classnames";
import { useI18n } from "../../../../../core/i18n/i18nContext";
import type { UrlPreview } from "../types";
import { LinkedText } from "../../../../../core/utils/LinkedText";
import styles from "./LinkPreview.module.css";
export interface LinkPreviewActions {
onImageClick: () => void;
}
export type LinkPreviewProps = UrlPreview & LinkPreviewActions;
function LinkTitle({
title,
showTooltipOnLink,
link,
}: Pick<LinkPreviewProps, "title" | "showTooltipOnLink" | "link">): JSX.Element {
const caption = new URL(link).toString();
const anchor = (
<Text
as="a"
type="body"
weight="semibold"
size="lg"
className={styles.title}
href={link}
target="_blank"
rel="noreferrer noopener"
>
{title}
</Text>
);
return showTooltipOnLink ? <Tooltip label={caption}>{anchor}</Tooltip> : anchor;
}
function LinkSiteName({ siteIcon, siteName }: { siteIcon?: string; siteName: string }): JSX.Element {
return (
<div className={styles.siteName}>
{siteIcon && <Avatar size="16px" name={siteName} id={siteName} src={siteIcon} />}
<Text as="span" size="sm" weight="regular">
{siteName}
</Text>
</div>
);
}
/**
* A condensed link preview that only contains the site icon, the title of the link and the site name.
*/
function LinkPreviewInline({
title,
showTooltipOnLink,
siteIcon,
siteName,
link,
}: Omit<LinkPreviewProps, "image" | "description" | "author" | "onImageClick">): JSX.Element {
return (
<div className={classNames(styles.container, styles.inline)}>
{siteIcon && (
<div className={styles.siteAvatar}>
<Avatar type="square" size="48px" name={title} id={title} src={siteIcon} />
</div>
)}
<div className={classNames(styles.textContent, styles.inline)}>
<LinkTitle title={title} showTooltipOnLink={showTooltipOnLink} link={link} />
{siteName && <LinkSiteName siteName={siteName} />}
</div>
</div>
);
}
/**
* LinkPreview renders a single preview component for a single link on an event. It is usually rendered as part of
* a `UrlPreviewGroupView`.
*/
export function LinkPreview({ onImageClick, ...preview }: LinkPreviewProps): JSX.Element {
const { translate: _t } = useI18n();
const onImageClickHandler = useCallback<MouseEventHandler>(
(ev) => {
if (ev.button != 0 || ev.metaKey) return;
ev.preventDefault();
if (!preview.image?.imageFull) {
return;
}
onImageClick();
},
[preview.image?.imageFull, onImageClick],
);
if (!preview.image && !preview.author && !preview.description) {
return <LinkPreviewInline {...preview} />;
}
let img: JSX.Element | undefined;
if (preview.image) {
if (preview.image.playable) {
// Playable media do not have clickable images so we don't
// overlay buttons atop buttons, instead we render a
// button for them to open the media.
img = (
<div
style={{
backgroundImage: `url('${preview.image.imageThumb}')`,
}}
className={styles.preview}
>
<Button
as="a"
href={preview.link}
aria-label={_t("timeline|url_preview|open_link")}
className={styles.playButton}
target="_blank"
rel="noreferrer noopener"
kind="primary"
>
<PlaySolidIcon width="24px" height="24px" />
</Button>
</div>
);
} else {
// Otherwise, the preview can be clicked on.
img = (
<button
className={styles.preview}
onClick={onImageClickHandler}
aria-label={_t("timeline|url_preview|view_image")}
>
<img src={preview.image.imageThumb} alt={preview.image.alt} title={preview.image.alt} />
</button>
);
}
}
return (
<div className={styles.container}>
{img}
<div className={styles.textContent}>
{preview.author && (
<Text as="span" size="md" weight="semibold">
{preview.author}
</Text>
)}
<LinkTitle title={preview.title} showTooltipOnLink={preview.showTooltipOnLink} link={preview.link} />
<LinkedText type="body" size="md" className={styles.description}>
{preview.description}
</LinkedText>
{preview.siteName && <LinkSiteName siteName={preview.siteName} siteIcon={preview.siteIcon} />}
</div>
</div>
);
}
@@ -0,0 +1,182 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`LinkPreview > renders a playable preview that can be opened with a click 1`] = `
<div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_preview"
style="background-image: url("/static/wideImage.png");"
>
<a
aria-label="Open link"
class="_button_13vu4_8 LinkPreview-module_playButton"
data-kind="primary"
data-size="lg"
href="https://matrix.org"
rel="noreferrer noopener"
role="link"
tabindex="0"
target="_blank"
>
<svg
fill="currentColor"
height="24px"
viewBox="0 0 24 24"
width="24px"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m8.98 4.677 9.921 5.58c1.36.764 1.36 2.722 0 3.486l-9.92 5.58C7.647 20.073 6 19.11 6 17.58V6.42c0-1.53 1.647-2.493 2.98-1.743"
/>
</svg>
</a>
</div>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A linked video
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
This is a link to a video. You cannot play the video inline yet, but you can click the play button to open the link
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
blog.example.org
</span>
</div>
</div>
</div>
</div>
`;
exports[`LinkPreview > renders a preview 1`] = `
<div>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="Element logo"
src="/static/element.png"
title="Element logo"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
Site name
</span>
</div>
</div>
</div>
</div>
`;
exports[`LinkPreview > renders a preview with just a title 1`] = `
<div>
<div
class="LinkPreview-module_container LinkPreview-module_inline"
>
<div
class="LinkPreview-module_textContent LinkPreview-module_inline"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
</div>
`;
exports[`LinkPreview > renders a preview with just a title and description 1`] = `
<div>
<div
class="LinkPreview-module_container"
>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description with a link to
<a
data-linkified="true"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
https://matrix.org
</a>
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
</div>
`;
@@ -8,17 +8,17 @@
import React, { type JSX } from "react";
import { fn } from "storybook/test";
import imageFile from "../../../static/element.png";
import tallImageFile from "../../../static/tallImage.png";
import imageFile from "../../../../../static/element.png";
import tallImageFile from "../../../../../static/tallImage.png";
import type { Meta, StoryFn } from "@storybook/react-vite";
import {
UrlPreviewGroupView,
type UrlPreviewGroupViewActions,
type UrlPreviewGroupViewSnapshot,
} from "./UrlPreviewGroupView";
import { useMockedViewModel } from "../../core/viewmodel";
import { LinkedTextContext } from "../../core/utils/LinkedText";
import { withViewDocs } from "../../../.storybook/withViewDocs";
import { useMockedViewModel } from "../../../../core/viewmodel";
import { LinkedTextContext } from "../../../../core/utils/LinkedText";
import { withViewDocs } from "../../../../../.storybook/withViewDocs";
type UrlPreviewGroupViewProps = UrlPreviewGroupViewSnapshot & UrlPreviewGroupViewActions;
@@ -51,6 +51,12 @@ export default {
onImageClick: fn(),
onTogglePreviewLimit: fn(),
},
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/design/sI9A2kV2K4xeiyqJsL7Ey3/Link-Previews?node-id=87-7920",
},
},
} satisfies Meta<typeof UrlPreviewGroupViewWrapper>;
const Template: StoryFn<typeof UrlPreviewGroupViewWrapper> = (args) => <UrlPreviewGroupViewWrapper {...args} />;
@@ -62,9 +68,13 @@ Default.args = {
title: "A simple title",
description: "A simple description",
link: "https://matrix.org",
showTooltipOnLink: false,
siteName: "matrix.org",
image: {
imageThumb: imageFile,
imageFull: imageFile,
alt: "The element logo",
playable: false,
},
},
],
@@ -72,17 +82,7 @@ Default.args = {
export const MultiplePreviewsHidden = Template.bind({});
MultiplePreviewsHidden.args = {
previews: [
{
title: "A simple title",
description: "A simple description",
link: "https://matrix.org",
image: {
imageThumb: imageFile,
imageFull: imageFile,
},
},
],
previews: Default.args.previews,
overPreviewLimit: true,
previewsLimited: true,
totalPreviewCount: 10,
@@ -95,9 +95,13 @@ MultiplePreviewsVisible.args = {
title: "One",
description: "A regular square image.",
link: "https://matrix.org",
siteName: "matrix.org",
showTooltipOnLink: false,
image: {
imageThumb: imageFile,
imageFull: imageFile,
alt: "The element logo",
playable: false,
},
},
// These images should appear the same size despite having different dimensions.
@@ -105,18 +109,26 @@ MultiplePreviewsVisible.args = {
title: "Two",
description: "This one has a taller image which should crop nicely.",
link: "https://matrix.org",
siteName: "matrix.org",
showTooltipOnLink: false,
image: {
imageThumb: tallImageFile,
imageFull: tallImageFile,
alt: "A dog",
playable: false,
},
},
{
title: "Three",
description: "One more description",
link: "https://matrix.org",
siteName: "matrix.org",
showTooltipOnLink: false,
image: {
imageThumb: imageFile,
imageFull: imageFile,
alt: "The element logo",
playable: false,
},
},
],
@@ -10,8 +10,8 @@ import { Button, IconButton } from "@vector-im/compound-web";
import CloseIcon from "@vector-im/compound-design-tokens/assets/web/icons/close";
import classNames from "classnames";
import { useViewModel, type ViewModel } from "../../core/viewmodel";
import { useI18n } from "../../core/i18n/i18nContext";
import { useViewModel, type ViewModel } from "../../../../core/viewmodel";
import { useI18n } from "../../../../core/i18n/i18nContext";
import type { UrlPreview } from "./types";
import { LinkPreview } from "./LinkPreview";
import styles from "./UrlPreviewGroupView.module.css";
@@ -68,7 +68,12 @@ export function UrlPreviewGroupView({ vm }: UrlPreviewGroupViewProps): JSX.Eleme
))}
{toggleButton}
</div>
<IconButton size="20px" onClick={vm.onHideClick} aria-label={_t("timeline|url_preview|close")}>
<IconButton
kind="secondary"
size="28px"
onClick={vm.onHideClick}
aria-label={_t("timeline|url_preview|close")}
>
<CloseIcon />
</IconButton>
</div>
@@ -0,0 +1,500 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`UrlPreviewGroupView > renders a single preview 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup"
>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="The element logo"
src="/static/element.png"
title="The element logo"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="secondary"
role="button"
style="--cpd-icon-button-size: 28px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
exports[`UrlPreviewGroupView > renders multiple previews 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup"
>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="The element logo"
src="/static/element.png"
title="The element logo"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
One
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A regular square image.
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="A dog"
src="/static/tallImage.png"
title="A dog"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Two
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
This one has a taller image which should crop nicely.
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="The element logo"
src="/static/element.png"
title="The element logo"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Three
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
One more description
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
<button
class="_button_13vu4_8 UrlPreviewGroupView-module_toggleButton"
data-kind="tertiary"
data-size="sm"
role="button"
tabindex="0"
>
Collapse
</button>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="secondary"
role="button"
style="--cpd-icon-button-size: 28px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
exports[`UrlPreviewGroupView > renders multiple previews which are hidden 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup"
>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="The element logo"
src="/static/element.png"
title="The element logo"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
A simple title
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A simple description
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
<button
class="_button_13vu4_8 UrlPreviewGroupView-module_toggleButton"
data-kind="tertiary"
data-size="sm"
role="button"
tabindex="0"
>
Show 9 other previews
</button>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="secondary"
role="button"
style="--cpd-icon-button-size: 28px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
exports[`UrlPreviewGroupView > renders with a compact view 1`] = `
<div>
<div
class="UrlPreviewGroupView-module_wrapper"
>
<div
class="UrlPreviewGroupView-module_previewGroup UrlPreviewGroupView-module_compactLayout"
>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="The element logo"
src="/static/element.png"
title="The element logo"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
One
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
A regular square image.
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="A dog"
src="/static/tallImage.png"
title="A dog"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Two
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
This one has a taller image which should crop nicely.
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
<div
class="LinkPreview-module_container"
>
<button
aria-label="View image"
class="LinkPreview-module_preview"
>
<img
alt="The element logo"
src="/static/element.png"
title="The element logo"
/>
</button>
<div
class="LinkPreview-module_textContent"
>
<a
class="_typography_6v6n8_153 _font-body-lg-semibold_6v6n8_74 LinkPreview-module_title"
href="https://matrix.org"
rel="noreferrer noopener"
target="_blank"
>
Three
</a>
<p
class="_typography_6v6n8_153 _font-body-md-regular_6v6n8_50 LinkedText-module_container LinkPreview-module_description"
>
One more description
</p>
<div
class="LinkPreview-module_siteName"
>
<span
class="_typography_6v6n8_153 _font-body-sm-regular_6v6n8_31"
>
matrix.org
</span>
</div>
</div>
</div>
<button
class="_button_13vu4_8 UrlPreviewGroupView-module_toggleButton"
data-kind="tertiary"
data-size="sm"
role="button"
tabindex="0"
>
Collapse
</button>
</div>
<button
aria-label="Close preview"
class="_icon-button_1215g_8"
data-kind="secondary"
role="button"
style="--cpd-icon-button-size: 28px;"
tabindex="0"
>
<div
class="_indicator-icon_147l5_17"
style="--cpd-icon-button-size: 100%;"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</button>
</div>
</div>
`;
@@ -5,7 +5,6 @@
* Please see LICENSE files in the repository root for full details.
*/
/** Represents a URL preview. */
export interface UrlPreview {
/**
* The URL for the preview.
@@ -14,7 +13,7 @@ export interface UrlPreview {
/**
* Should the link have a tooltip. Should be `true` if the platform does not provide a tooltip.
*/
showTooltipOnLink?: boolean;
showTooltipOnLink: boolean;
/**
* The title of the page being previewed.
*/
@@ -22,7 +21,11 @@ export interface UrlPreview {
/**
* The site name to be displayed alongside the title.
*/
siteName?: string;
siteName: string;
/**
* The HTTP URI of the the sites icon.
*/
siteIcon?: string;
/**
* Description of the site. May contain links.
*/
@@ -44,12 +47,26 @@ export interface UrlPreview {
*/
fileSize?: number;
/**
* The width of the thumbnail. Must not exceed 100px.
* The width of the thumbnail.
*/
width?: number;
/**
* The height of the thumbnail. Must not exceed 100px.
* The height of the thumbnail.
*/
height?: number;
/**
* Alt text for the image
*/
alt?: string;
/**
* Is the media playable.
*/
playable: boolean;
};
/**
* Author of the content, if specified.
*/
author?: string;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB