Move E2ePadlock to shared components (#33538)
* Move E2ePadlock to shared components * Add storybook images * Move translation to shared components * Fix E2ePadlock story accessibility * Update E2eMessageSharedIcon test for padlock role
This commit is contained in:
@@ -51,6 +51,8 @@ import { CircleIcon, CheckCircleIcon, ThreadsIcon } from "@vector-im/compound-de
|
|||||||
import {
|
import {
|
||||||
useCreateAutoDisposedViewModel,
|
useCreateAutoDisposedViewModel,
|
||||||
ActionBarView,
|
ActionBarView,
|
||||||
|
E2ePadlock,
|
||||||
|
E2ePadlockIcon,
|
||||||
MessageTimestampView,
|
MessageTimestampView,
|
||||||
PinnedMessageBadge,
|
PinnedMessageBadge,
|
||||||
ReactionsRowButtonView,
|
ReactionsRowButtonView,
|
||||||
@@ -100,7 +102,6 @@ import { Icon as LateIcon } from "../../../../res/img/sensor.svg";
|
|||||||
import PinningUtils from "../../../utils/PinningUtils";
|
import PinningUtils from "../../../utils/PinningUtils";
|
||||||
import { EventPreview } from "./EventPreview";
|
import { EventPreview } from "./EventPreview";
|
||||||
import { E2eMessageSharedIcon } from "./EventTile/E2eMessageSharedIcon.tsx";
|
import { E2eMessageSharedIcon } from "./EventTile/E2eMessageSharedIcon.tsx";
|
||||||
import { E2ePadlock, E2ePadlockIcon } from "./EventTile/E2ePadlock.tsx";
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { CardContext } from "../right_panel/context";
|
import { CardContext } from "../right_panel/context";
|
||||||
import { EventTileViewModel } from "../../../viewmodels/room/timeline/event-tile/EventTileViewModel";
|
import { EventTileViewModel } from "../../../viewmodels/room/timeline/event-tile/EventTileViewModel";
|
||||||
@@ -849,10 +850,28 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.shieldColour === EventShieldColour.GREY) {
|
if (this.state.shieldColour === EventShieldColour.GREY) {
|
||||||
return <E2ePadlock icon={E2ePadlockIcon.Normal} title={shieldReasonMessage} />;
|
return (
|
||||||
|
<E2ePadlock
|
||||||
|
className={
|
||||||
|
// Timeline PCSS uses this app class as a layout hook for positioning and layout variants.
|
||||||
|
"mx_EventTile_e2eIcon"
|
||||||
|
}
|
||||||
|
icon={E2ePadlockIcon.Normal}
|
||||||
|
title={shieldReasonMessage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// red, by elimination
|
// red, by elimination
|
||||||
return <E2ePadlock icon={E2ePadlockIcon.Warning} title={shieldReasonMessage} />;
|
return (
|
||||||
|
<E2ePadlock
|
||||||
|
className={
|
||||||
|
// Timeline PCSS uses this app class as a layout hook for positioning and layout variants.
|
||||||
|
"mx_EventTile_e2eIcon"
|
||||||
|
}
|
||||||
|
icon={E2ePadlockIcon.Warning}
|
||||||
|
title={shieldReasonMessage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1607,11 +1626,29 @@ const SafeEventTile = (props: EventTileProps): JSX.Element => {
|
|||||||
export default SafeEventTile;
|
export default SafeEventTile;
|
||||||
|
|
||||||
function E2ePadlockUnencrypted(): JSX.Element {
|
function E2ePadlockUnencrypted(): JSX.Element {
|
||||||
return <E2ePadlock title={_t("common|unencrypted")} icon={E2ePadlockIcon.Warning} />;
|
return (
|
||||||
|
<E2ePadlock
|
||||||
|
className={
|
||||||
|
// Timeline PCSS uses this app class as a layout hook for positioning and layout variants.
|
||||||
|
"mx_EventTile_e2eIcon"
|
||||||
|
}
|
||||||
|
title={_t("common|unencrypted")}
|
||||||
|
icon={E2ePadlockIcon.Warning}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function E2ePadlockDecryptionFailure(): JSX.Element {
|
function E2ePadlockDecryptionFailure(): JSX.Element {
|
||||||
return <E2ePadlock title={_t("timeline|undecryptable_tooltip")} icon={E2ePadlockIcon.DecryptionFailure} />;
|
return (
|
||||||
|
<E2ePadlock
|
||||||
|
className={
|
||||||
|
// Timeline PCSS uses this app class as a layout hook for positioning and layout variants.
|
||||||
|
"mx_EventTile_e2eIcon"
|
||||||
|
}
|
||||||
|
title={_t("timeline|undecryptable_tooltip")}
|
||||||
|
icon={E2ePadlockIcon.DecryptionFailure}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISentReceiptProps {
|
interface ISentReceiptProps {
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
|
|
||||||
import React, { type JSX } from "react";
|
import React, { type JSX } from "react";
|
||||||
import { EventTimeline } from "matrix-js-sdk/src/matrix";
|
import { EventTimeline } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { E2ePadlock, E2ePadlockIcon } from "@element-hq/web-shared-components";
|
||||||
|
|
||||||
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
|
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext.tsx";
|
||||||
import { _t } from "../../../../languageHandler.tsx";
|
import { _t } from "../../../../languageHandler.tsx";
|
||||||
import { E2ePadlock, E2ePadlockIcon } from "./E2ePadlock.tsx";
|
|
||||||
|
|
||||||
/** The React properties of an {@link E2eMessageSharedIcon}. */
|
/** The React properties of an {@link E2eMessageSharedIcon}. */
|
||||||
interface E2eMessageSharedIconParams {
|
interface E2eMessageSharedIconParams {
|
||||||
@@ -41,5 +41,14 @@ export function E2eMessageSharedIcon(props: E2eMessageSharedIconParams): JSX.Ele
|
|||||||
userId: keyForwardingUserId,
|
userId: keyForwardingUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return <E2ePadlock icon={E2ePadlockIcon.Normal} title={tooltip} />;
|
return (
|
||||||
|
<E2ePadlock
|
||||||
|
className={
|
||||||
|
// Timeline PCSS uses this app class as a layout hook for positioning and layout variants.
|
||||||
|
"mx_EventTile_e2eIcon"
|
||||||
|
}
|
||||||
|
icon={E2ePadlockIcon.Normal}
|
||||||
|
title={tooltip}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3361,7 +3361,6 @@
|
|||||||
"download_action_downloading": "Downloading",
|
"download_action_downloading": "Downloading",
|
||||||
"download_failed": "Download failed",
|
"download_failed": "Download failed",
|
||||||
"download_failed_description": "An error occurred while downloading this file",
|
"download_failed_description": "An error occurred while downloading this file",
|
||||||
"e2e_state": "State of the end-to-end encryption",
|
|
||||||
"edits": {
|
"edits": {
|
||||||
"tooltip_label": "Edited at %(date)s. Click to view edits.",
|
"tooltip_label": "Edited at %(date)s. Click to view edits.",
|
||||||
"tooltip_sub": "Click to view edits",
|
"tooltip_sub": "Click to view edits",
|
||||||
|
|||||||
+13
-9
@@ -5,7 +5,7 @@ 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.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { render } from "jest-matrix-react";
|
import { render, waitFor } from "jest-matrix-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { type RoomMember, type RoomState } from "matrix-js-sdk/src/matrix";
|
import { type RoomMember, type RoomState } from "matrix-js-sdk/src/matrix";
|
||||||
@@ -14,7 +14,7 @@ import { E2eMessageSharedIcon } from "../../../../../../src/components/views/roo
|
|||||||
import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../../test-utils";
|
import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../../test-utils";
|
||||||
|
|
||||||
describe("E2eMessageSharedIcon", () => {
|
describe("E2eMessageSharedIcon", () => {
|
||||||
it("renders correctly for a known user", () => {
|
it("renders correctly for a known user", async () => {
|
||||||
const mockClient = createTestClient();
|
const mockClient = createTestClient();
|
||||||
const mockMember = { rawDisplayName: "Bob" } as RoomMember;
|
const mockMember = { rawDisplayName: "Bob" } as RoomMember;
|
||||||
const mockState = {
|
const mockState = {
|
||||||
@@ -34,22 +34,26 @@ describe("E2eMessageSharedIcon", () => {
|
|||||||
withClientContextRenderOptions(mockClient),
|
withClientContextRenderOptions(mockClient),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.container).toMatchSnapshot();
|
await waitFor(() =>
|
||||||
expect(result.container.firstChild).toHaveAccessibleName(
|
expect(result.container.firstChild).toHaveAccessibleName(
|
||||||
"Bob (@bob:example.com) shared this message since you were not in the room when it was sent.",
|
"Bob (@bob:example.com) shared this message since you were not in the room when it was sent.",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
expect(result.container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders correctly for an unknown user", () => {
|
it("renders correctly for an unknown user", async () => {
|
||||||
const mockClient = createTestClient();
|
const mockClient = createTestClient();
|
||||||
const result = render(
|
const result = render(
|
||||||
<E2eMessageSharedIcon keyForwardingUserId="@bob:example.com" roomId="!roomId" />,
|
<E2eMessageSharedIcon keyForwardingUserId="@bob:example.com" roomId="!roomId" />,
|
||||||
withClientContextRenderOptions(mockClient),
|
withClientContextRenderOptions(mockClient),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.container).toMatchSnapshot();
|
await waitFor(() =>
|
||||||
expect(result.container.firstChild).toHaveAccessibleName(
|
expect(result.container.firstChild).toHaveAccessibleName(
|
||||||
"@bob:example.com (@bob:example.com) shared this message since you were not in the room when it was sent.",
|
"@bob:example.com (@bob:example.com) shared this message since you were not in the room when it was sent.",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
expect(result.container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,28 +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 { render } from "jest-matrix-react";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { E2ePadlock, E2ePadlockIcon } from "../../../../../../src/components/views/rooms/EventTile/E2ePadlock.tsx";
|
|
||||||
|
|
||||||
describe("E2ePadlock", () => {
|
|
||||||
it("renders a 'Normal' icon", () => {
|
|
||||||
const result = render(<E2ePadlock icon={E2ePadlockIcon.Normal} title="Test tooltip" />);
|
|
||||||
expect(result.asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders a 'Warning' icon", () => {
|
|
||||||
const result = render(<E2ePadlock icon={E2ePadlockIcon.Warning} title="Bad" />);
|
|
||||||
expect(result.asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renders a 'DecryptionFailure' icon", () => {
|
|
||||||
const result = render(<E2ePadlock icon={E2ePadlockIcon.DecryptionFailure} title="UTD" />);
|
|
||||||
expect(result.asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
+4
-2
@@ -5,8 +5,9 @@ exports[`E2eMessageSharedIcon renders correctly for a known user 1`] = `
|
|||||||
<div
|
<div
|
||||||
aria-label="State of the end-to-end encryption"
|
aria-label="State of the end-to-end encryption"
|
||||||
aria-labelledby="_r_0_"
|
aria-labelledby="_r_0_"
|
||||||
class="mx_EventTile_e2eIcon"
|
class="_e2ePadlock_ohpuo_8 mx_EventTile_e2eIcon"
|
||||||
data-testid="e2e-padlock"
|
data-testid="e2e-padlock"
|
||||||
|
role="img"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -35,8 +36,9 @@ exports[`E2eMessageSharedIcon renders correctly for an unknown user 1`] = `
|
|||||||
<div
|
<div
|
||||||
aria-label="State of the end-to-end encryption"
|
aria-label="State of the end-to-end encryption"
|
||||||
aria-labelledby="_r_6_"
|
aria-labelledby="_r_6_"
|
||||||
class="mx_EventTile_e2eIcon"
|
class="_e2ePadlock_ohpuo_8 mx_EventTile_e2eIcon"
|
||||||
data-testid="e2e-padlock"
|
data-testid="e2e-padlock"
|
||||||
|
role="img"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -221,6 +221,7 @@
|
|||||||
},
|
},
|
||||||
"download_action_decrypting": "Decrypting",
|
"download_action_decrypting": "Decrypting",
|
||||||
"download_action_downloading": "Downloading",
|
"download_action_downloading": "Downloading",
|
||||||
|
"e2e_state": "State of the end-to-end encryption",
|
||||||
"m.audio": {
|
"m.audio": {
|
||||||
"audio_player": "Audio player",
|
"audio_player": "Audio player",
|
||||||
"error_downloading_audio": "Error downloading audio",
|
"error_downloading_audio": "Error downloading audio",
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export * from "./room/timeline/DateSeparatorView";
|
|||||||
export * from "./room/timeline/TimelineSeparator";
|
export * from "./room/timeline/TimelineSeparator";
|
||||||
export * from "./room/timeline/event-tile/actions/ActionBarView";
|
export * from "./room/timeline/event-tile/actions/ActionBarView";
|
||||||
export * from "./room/timeline/event-tile/EventTileView/DisambiguatedProfile";
|
export * from "./room/timeline/event-tile/EventTileView/DisambiguatedProfile";
|
||||||
|
export * from "./room/timeline/event-tile/EventTileView/E2ePadlock";
|
||||||
export * from "./room/timeline/event-tile/EventTileView/EncryptionEventView";
|
export * from "./room/timeline/event-tile/EventTileView/EncryptionEventView";
|
||||||
export * from "./room/timeline/event-tile/call";
|
export * from "./room/timeline/event-tile/call";
|
||||||
export * from "./room/timeline/event-tile/EventTileView/EventTileBubble";
|
export * from "./room/timeline/event-tile/EventTileView/EventTileBubble";
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.e2ePadlock {
|
||||||
|
position: relative;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: inherit;
|
||||||
|
width: inherit;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import { E2ePadlock, E2ePadlockIcon } from "./E2ePadlock";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Timeline/Timeline Event/E2ePadlock",
|
||||||
|
component: E2ePadlock,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {
|
||||||
|
icon: {
|
||||||
|
options: Object.values(E2ePadlockIcon),
|
||||||
|
control: { type: "select" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
icon: E2ePadlockIcon.Normal,
|
||||||
|
title: "The authenticity of this message could not be guaranteed",
|
||||||
|
className: "",
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof E2ePadlock>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Normal: Story = {};
|
||||||
|
|
||||||
|
export const Warning: Story = {
|
||||||
|
args: {
|
||||||
|
icon: E2ePadlockIcon.Warning,
|
||||||
|
title: "This message was sent unencrypted",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DecryptionFailure: Story = {
|
||||||
|
args: {
|
||||||
|
icon: E2ePadlockIcon.DecryptionFailure,
|
||||||
|
title: "Unable to decrypt message",
|
||||||
|
},
|
||||||
|
};
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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 { composeStories } from "@storybook/react-vite";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { render, screen } from "@test-utils";
|
||||||
|
|
||||||
|
import { E2ePadlock, E2ePadlockIcon } from "./E2ePadlock";
|
||||||
|
import * as stories from "./E2ePadlock.stories";
|
||||||
|
|
||||||
|
const { Normal, Warning, DecryptionFailure } = composeStories(stories);
|
||||||
|
|
||||||
|
describe("E2ePadlock", () => {
|
||||||
|
it("renders a 'Normal' icon", () => {
|
||||||
|
const { container } = render(<Normal />);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders a 'Warning' icon", () => {
|
||||||
|
const { container } = render(<Warning />);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders a 'DecryptionFailure' icon", () => {
|
||||||
|
const { container } = render(<DecryptionFailure />);
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies a custom className to the icon container", () => {
|
||||||
|
render(<E2ePadlock icon={E2ePadlockIcon.Normal} title="Test tooltip" className="custom-e2e-padlock" />);
|
||||||
|
|
||||||
|
expect(screen.getByTestId("e2e-padlock")).toHaveClass("custom-e2e-padlock");
|
||||||
|
});
|
||||||
|
});
|
||||||
+27
-18
@@ -1,18 +1,21 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2025 Vector Creations Ltd.
|
* Copyright 2026 Element Creations Ltd.
|
||||||
Copyright 2024 New Vector Ltd.
|
* Copyright 2025 Vector Creations Ltd.
|
||||||
Copyright 2015-2023 The Matrix.org Foundation C.I.C.
|
* Copyright 2024 New Vector Ltd.
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
* Copyright 2015-2023 The Matrix.org Foundation C.I.C.
|
||||||
|
* Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
import React, { type JSX } from "react";
|
||||||
Please see LICENSE files in the repository root for full details.
|
import classNames from "classnames";
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { type ReactNode } from "react";
|
|
||||||
import { ErrorSolidIcon, InfoIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
import { ErrorSolidIcon, InfoIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
|
||||||
import { Tooltip } from "@vector-im/compound-web";
|
import { Tooltip } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler.tsx";
|
import { _t } from "../../../../../core/i18n/i18n";
|
||||||
|
import styles from "./E2ePadlock.module.css";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The icon to display in an {@link E2ePadlock}.
|
* The icon to display in an {@link E2ePadlock}.
|
||||||
@@ -28,39 +31,45 @@ export enum E2ePadlockIcon {
|
|||||||
DecryptionFailure = "decryption_failure",
|
DecryptionFailure = "decryption_failure",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IE2ePadlockProps {
|
export interface E2ePadlockProps {
|
||||||
/** The icon to display. */
|
/** The icon to display. */
|
||||||
icon: E2ePadlockIcon;
|
icon: E2ePadlockIcon;
|
||||||
|
|
||||||
/** The tooltip for the icon, displayed on hover. */
|
/** The tooltip for the icon, displayed on hover. */
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
|
/** Optional CSS class name applied to the icon container. */
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const icons = {
|
const icons: Record<E2ePadlockIcon, JSX.Element> = {
|
||||||
[E2ePadlockIcon.Normal]: <InfoIcon color="var(--cpd-color-icon-tertiary)" />,
|
[E2ePadlockIcon.Normal]: <InfoIcon color="var(--cpd-color-icon-tertiary)" />,
|
||||||
[E2ePadlockIcon.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
|
[E2ePadlockIcon.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
|
||||||
[E2ePadlockIcon.DecryptionFailure]: <ErrorSolidIcon color="var(--cpd-color-icon-tertiary)" />,
|
[E2ePadlockIcon.DecryptionFailure]: <ErrorSolidIcon color="var(--cpd-color-icon-tertiary)" />,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A small icon with tooltip, used in the left margin of an {@link EventTile}, which indicates a problem
|
* A small icon with tooltip, used in the left margin of an event tile to
|
||||||
* with an encrypted event.
|
* indicate a problem with an encrypted event.
|
||||||
*
|
*
|
||||||
* The icon is rendered with `data-testid="e2e-padlock"`.
|
* The icon is rendered with `data-testid="e2e-padlock"`.
|
||||||
*/
|
*/
|
||||||
export function E2ePadlock(props: IE2ePadlockProps): ReactNode {
|
export function E2ePadlock({ icon, title, className }: Readonly<E2ePadlockProps>): JSX.Element {
|
||||||
// We specify isTriggerInteractive=true and make the div interactive manually as a workaround for
|
// We specify isTriggerInteractive=true and make the div interactive manually as a workaround for
|
||||||
// https://github.com/element-hq/compound/issues/294
|
// https://github.com/element-hq/compound/issues/294
|
||||||
|
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
|
||||||
return (
|
return (
|
||||||
<Tooltip label={props.title} isTriggerInteractive={true}>
|
<Tooltip label={title} isTriggerInteractive={true}>
|
||||||
<div
|
<div
|
||||||
data-testid="e2e-padlock"
|
data-testid="e2e-padlock"
|
||||||
className="mx_EventTile_e2eIcon"
|
className={classNames(styles.e2ePadlock, className)}
|
||||||
|
role="img"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label={_t("timeline|e2e_state")}
|
aria-label={_t("timeline|e2e_state")}
|
||||||
>
|
>
|
||||||
{icons[props.icon]}
|
{icons[icon]}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
/* eslint-enable jsx-a11y/no-noninteractive-tabindex */
|
||||||
}
|
}
|
||||||
+16
-13
@@ -1,12 +1,13 @@
|
|||||||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`E2ePadlock renders a 'DecryptionFailure' icon 1`] = `
|
exports[`E2ePadlock > renders a 'DecryptionFailure' icon 1`] = `
|
||||||
<DocumentFragment>
|
<div>
|
||||||
<div
|
<div
|
||||||
aria-label="State of the end-to-end encryption"
|
aria-label="State of the end-to-end encryption"
|
||||||
aria-labelledby="_r_c_"
|
aria-labelledby="_r_c_"
|
||||||
class="mx_EventTile_e2eIcon"
|
class="E2ePadlock-module_e2ePadlock"
|
||||||
data-testid="e2e-padlock"
|
data-testid="e2e-padlock"
|
||||||
|
role="img"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -22,16 +23,17 @@ exports[`E2ePadlock renders a 'DecryptionFailure' icon 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</DocumentFragment>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`E2ePadlock renders a 'Normal' icon 1`] = `
|
exports[`E2ePadlock > renders a 'Normal' icon 1`] = `
|
||||||
<DocumentFragment>
|
<div>
|
||||||
<div
|
<div
|
||||||
aria-label="State of the end-to-end encryption"
|
aria-label="State of the end-to-end encryption"
|
||||||
aria-labelledby="_r_0_"
|
aria-labelledby="_r_0_"
|
||||||
class="mx_EventTile_e2eIcon"
|
class="E2ePadlock-module_e2ePadlock"
|
||||||
data-testid="e2e-padlock"
|
data-testid="e2e-padlock"
|
||||||
|
role="img"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -52,16 +54,17 @@ exports[`E2ePadlock renders a 'Normal' icon 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</DocumentFragment>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`E2ePadlock renders a 'Warning' icon 1`] = `
|
exports[`E2ePadlock > renders a 'Warning' icon 1`] = `
|
||||||
<DocumentFragment>
|
<div>
|
||||||
<div
|
<div
|
||||||
aria-label="State of the end-to-end encryption"
|
aria-label="State of the end-to-end encryption"
|
||||||
aria-labelledby="_r_6_"
|
aria-labelledby="_r_6_"
|
||||||
class="mx_EventTile_e2eIcon"
|
class="E2ePadlock-module_e2ePadlock"
|
||||||
data-testid="e2e-padlock"
|
data-testid="e2e-padlock"
|
||||||
|
role="img"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -77,5 +80,5 @@ exports[`E2ePadlock renders a 'Warning' icon 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</DocumentFragment>
|
</div>
|
||||||
`;
|
`;
|
||||||
+8
@@ -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 { E2ePadlock, E2ePadlockIcon, type E2ePadlockProps } from "./E2ePadlock";
|
||||||
Reference in New Issue
Block a user