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:
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.container {
|
||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
||||
padding: var(--cpd-space-3x);
|
||||
border-radius: 8px;
|
||||
/* Reserve space for external timestamps, but also cap the width */
|
||||
/* Legacy variable: --MessageTimestamp-width: 46px; /* 8 + 30 (avatar) + 8 */
|
||||
/* max-width: min(calc(100% - 2 * var(--MessageTimestamp-width)), 600px); */
|
||||
max-width: min(calc(100% - 2 * 46px), 600px);
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: 24px minmax(0, 1fr) min-content min-content;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / 3;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
content: "";
|
||||
inset: 0;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
margin-top: var(--cpd-space-1x);
|
||||
}
|
||||
|
||||
.title,
|
||||
.subtitle {
|
||||
grid-column: 2;
|
||||
overflow-wrap: break-word;
|
||||
min-inline-size: 50px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--cpd-font-weight-semibold);
|
||||
font-size: var(--cpd-font-size-body-md);
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: var(--cpd-font-size-body-sm);
|
||||
grid-row: 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 { LockSolidIcon, ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||
|
||||
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||
import { EventTileBubble } from "./EventTileBubble";
|
||||
|
||||
export default {
|
||||
title: "Event/EventTileBubble",
|
||||
component: EventTileBubble,
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
icon: <ErrorSolidIcon />,
|
||||
title: "Title goes here",
|
||||
subtitle: "Subtitle goes here",
|
||||
className: "custom-class",
|
||||
},
|
||||
} as Meta<typeof EventTileBubble>;
|
||||
|
||||
const Template: StoryFn<typeof EventTileBubble> = (args) => <EventTileBubble {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
export const HasLockSolidIcon = Template.bind({});
|
||||
HasLockSolidIcon.args = {
|
||||
className: undefined,
|
||||
icon: <LockSolidIcon />,
|
||||
children: undefined,
|
||||
};
|
||||
|
||||
export const HasChildren = Template.bind({});
|
||||
HasChildren.args = {
|
||||
className: undefined,
|
||||
children: <div>children</div>,
|
||||
};
|
||||
|
||||
export const IsCryptoEventBubble = Template.bind({});
|
||||
IsCryptoEventBubble.args = {
|
||||
className: undefined,
|
||||
icon: <LockSolidIcon />,
|
||||
title: "Encryption enabled",
|
||||
subtitle: "Messages here are end-to-end encrypted. Verify XYZ in their profile - tap on their profile picture.",
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { render } from "@test-utils";
|
||||
import { composeStories } from "@storybook/react-vite";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import React from "react";
|
||||
|
||||
import * as stories from "./EventTileBubble.stories.tsx";
|
||||
|
||||
const { Default, HasLockSolidIcon, HasChildren, IsCryptoEventBubble } = composeStories(stories);
|
||||
|
||||
describe("EventTileBubble", () => {
|
||||
it("renders the event tile bubble", () => {
|
||||
const { container } = render(<Default />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the event tile bubble with icon", () => {
|
||||
const { container } = render(<HasLockSolidIcon />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the event tile bubble with children", () => {
|
||||
const { container } = render(<HasChildren />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders the event tile bubble as crypto event bubble", () => {
|
||||
const { container } = render(<IsCryptoEventBubble />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 JSX, type ReactNode } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
import styles from "./EventTileBubble.module.css";
|
||||
|
||||
export interface EventTileBubbleProps {
|
||||
/**
|
||||
* Icon rendered at the start of the bubble.
|
||||
*/
|
||||
icon: JSX.Element;
|
||||
/**
|
||||
* Main title text for the bubble.
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Optional subtitle rendered beneath the title.
|
||||
*/
|
||||
subtitle?: ReactNode;
|
||||
/**
|
||||
* Optional extra class name for the container.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Optional children rendered between subtitle and timestamp.
|
||||
*/
|
||||
children?: JSX.Element;
|
||||
/**
|
||||
* Forwarded ref for the container element.
|
||||
*/
|
||||
ref?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
/**
|
||||
* EventTileBubble renders a compact event tile with an icon, title, and optional subtitle/content.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <EventTileBubble icon={<Icon />} title="Room created" />
|
||||
* ```
|
||||
*/
|
||||
export function EventTileBubble({
|
||||
icon,
|
||||
title,
|
||||
subtitle,
|
||||
className,
|
||||
children,
|
||||
ref,
|
||||
}: EventTileBubbleProps): JSX.Element {
|
||||
return (
|
||||
<div className={classNames(styles.container, className)} ref={ref}>
|
||||
{icon}
|
||||
<div className={styles.title}>{title}</div>
|
||||
{subtitle && <div className={styles.subtitle}>{subtitle}</div>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container custom-class"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Title goes here
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Subtitle goes here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble as crypto event bubble 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Encryption enabled
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Messages here are end-to-end encrypted. Verify XYZ in their profile - tap on their profile picture.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble with children 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 17q.424 0 .713-.288A.97.97 0 0 0 13 16a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 15a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 16q0 .424.287.712.288.288.713.288m0-4q.424 0 .713-.287A.97.97 0 0 0 13 12V8a.97.97 0 0 0-.287-.713A.97.97 0 0 0 12 7a.97.97 0 0 0-.713.287A.97.97 0 0 0 11 8v4q0 .424.287.713.288.287.713.287m0 9a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Title goes here
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Subtitle goes here
|
||||
</div>
|
||||
<div>
|
||||
children
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`EventTileBubble > renders the event tile bubble with icon 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="container"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 22q-.824 0-1.412-.587A1.93 1.93 0 0 1 4 20V10q0-.825.588-1.412A1.93 1.93 0 0 1 6 8h1V6q0-2.075 1.463-3.537Q9.926 1 12 1q2.075 0 3.537 1.463Q17 3.925 17 6v2h1q.824 0 1.413.588Q20 9.175 20 10v10q0 .824-.587 1.413A1.93 1.93 0 0 1 18 22zM9 8h6V6q0-1.25-.875-2.125A2.9 2.9 0 0 0 12 3q-1.25 0-2.125.875A2.9 2.9 0 0 0 9 6z"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
class="title"
|
||||
>
|
||||
Title goes here
|
||||
</div>
|
||||
<div
|
||||
class="subtitle"
|
||||
>
|
||||
Subtitle goes here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -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 { EventTileBubble, type EventTileBubbleProps } from "./EventTileBubble";
|
||||
@@ -13,6 +13,7 @@ export * from "./audio/SeekBar";
|
||||
export * from "./avatar/AvatarWithDetails";
|
||||
export * from "./composer/Banner";
|
||||
export * from "./crypto/SasEmoji";
|
||||
export * from "./event-tiles/EventTileBubble";
|
||||
export * from "./event-tiles/TextualEventView";
|
||||
export * from "./message-body/MediaBody";
|
||||
export * from "./message-body/DecryptionFailureBodyView";
|
||||
|
||||
Reference in New Issue
Block a user