Switch to rendering svg icons rather than masking them (#31557)

* Switch to rendering svg icons rather than masking them in SpacePanel

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix badly rendered icon in JoinRuleDropdown

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix badly rendered icon in RoomPreviewCard

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix badly rendered icon in Space menus

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix badly rendered icon in ThreadPanel

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update snapshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove unused icon underfill

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update screenshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add test

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add missing snapshot

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove unused AccessibleButton kinds

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in EventTileBubble

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in ReactionsRow

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in MFileBody

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in BaseCard

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in RedactedBody

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in E2ePadlock

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tidy up E2EIcon

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in DialPad

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in AccessSecretStorageDialog

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch to rendering svg icons rather than masking them in HiddenBody

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix overflow in base card size

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add missing screenshots

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Add tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2026-01-05 17:14:51 +00:00
committed by GitHub
parent be7be39d0f
commit 914c959e31
63 changed files with 594 additions and 500 deletions
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type RefObject } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import type ResizeNotifier from "../../utils/ResizeNotifier";
import ErrorBoundary from "../views/elements/ErrorBoundary";
@@ -43,6 +44,7 @@ export const WaitingForThirdPartyRoomView: React.FC<Props> = ({ roomView, resize
<div className="mx_RoomView_timeline">
<ScrollPanel className="mx_RoomView_messagePanel">
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("room|waiting_for_join_title", { brand })}
subtitle={_t("room|waiting_for_join_subtitle", { brand })}
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useCallback, useEffect } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LinkIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { LinkIcon, OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { type ButtonEvent } from "../elements/AccessibleButton";
import dis from "../../../dispatcher/dispatcher";
@@ -90,7 +90,9 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
isExpanded={menuDisplayed}
ref={button}
data-testid="threadlist-dropdown-button"
/>
>
<OverflowHorizontalIcon />
</ContextMenuTooltipButton>
{menuDisplayed && (
<IconizedContextMenu
onFinished={closeThreadOptions}
@@ -10,9 +10,10 @@ import { Button, PasswordInput } from "@vector-im/compound-web";
import LockSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/lock-solid";
import { debounce } from "lodash";
import classNames from "classnames";
import React, { type ChangeEvent, type FormEvent } from "react";
import React, { type ChangeEvent, type FormEvent, type ReactNode } from "react";
import { type SecretStorage } from "matrix-js-sdk/src/matrix";
import { Flex } from "@element-hq/web-shared-components";
import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../../languageHandler";
import { EncryptionCard } from "../../settings/encryption/EncryptionCard";
@@ -142,27 +143,29 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
};
private getRecoveryKeyFeedback(): React.ReactNode | null {
let validationText: string;
let content: ReactNode;
let classes: string | undefined;
if (this.state.recoveryKeyCorrect) {
// The recovery key is good. Empty feedback.
validationText = "\xA0"; // &nbsp;
content = "\xA0"; // &nbsp;
} else if (this.state.recoveryKeyCorrect === null) {
// The input element is empty. Tell the user they can also use a passphrase.
validationText = _t("encryption|access_secret_storage_dialog|alternatives");
content = _t("encryption|access_secret_storage_dialog|alternatives");
} else {
// The entered key is not (yet) correct. Tell them so.
validationText = _t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key");
classes = classNames({
"mx_AccessSecretStorageDialog_recoveryKeyFeedback": true,
"mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid": true,
});
content = (
<>
<ErrorSolidIcon />
{_t("encryption|access_secret_storage_dialog|key_validation_text|wrong_security_key")}
</>
);
classes = "mx_AccessSecretStorageDialog_recoveryKeyFeedback--invalid";
}
return (
<Flex align="center" className={classes}>
{validationText}
<Flex align="center" className={classNames("mx_AccessSecretStorageDialog_recoveryKeyFeedback", classes)}>
{content}
</Flex>
);
}
@@ -30,7 +30,6 @@ export type AccessibleButtonKind =
| "primary_outline"
| "primary_sm"
| "secondary"
| "secondary_content"
| "content_inline"
| "danger"
| "danger_outline"
@@ -39,8 +38,6 @@ export type AccessibleButtonKind =
| "link"
| "link_inline"
| "link_sm"
| "confirm_sm"
| "cancel_sm"
| "icon"
| "icon_primary"
| "icon_primary_outline";
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type Ref, type ReactNode } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { ErrorSolidIcon, LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import type { RoomEncryptionEventContent } from "matrix-js-sdk/src/types";
import { _t } from "../../../languageHandler";
@@ -58,6 +59,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
return (
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={stateEncrypted ? _t("common|state_encryption_enabled") : _t("common|encryption_enabled")}
subtitle={subtitle}
@@ -69,6 +71,7 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
if (isRoomEncrypted) {
return (
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={_t("common|encryption_enabled")}
subtitle={_t("timeline|m.room.encryption|disable_attempt")}
@@ -79,7 +82,8 @@ const EncryptionEvent = ({ mxEvent, timestamp, ref }: IProps): ReactNode => {
return (
<EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon mx_cryptoEvent_icon_warning"
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
className="mx_cryptoEvent"
title={_t("timeline|m.room.encryption|disabled")}
subtitle={_t("timeline|m.room.encryption|unsupported")}
ref={ref}
@@ -11,6 +11,7 @@ import classNames from "classnames";
interface IProps {
className: string;
icon: JSX.Element;
title: string;
timestamp?: JSX.Element;
subtitle?: ReactNode;
@@ -18,9 +19,10 @@ interface IProps {
ref?: Ref<HTMLDivElement>;
}
const EventTileBubble = ({ className, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
const EventTileBubble = ({ className, icon, title, timestamp, subtitle, children, ref }: IProps): JSX.Element => {
return (
<div className={classNames("mx_EventTileBubble", className)} ref={ref}>
{icon}
<div className="mx_EventTileBubble_title">{title}</div>
{subtitle && <div className="mx_EventTileBubble_subtitle">{subtitle}</div>}
{children}
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import { type IBodyProps } from "./IBodyProps";
@@ -34,6 +35,7 @@ const HiddenBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
return (
<span className="mx_HiddenBody" ref={ref}>
<VisibilityOffIcon />
{text}
</span>
);
+16 -2
View File
@@ -9,8 +9,14 @@ Please see LICENSE files in the repository root for full details.
import React, { type AllHTMLAttributes, createRef } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { type MediaEventContent } from "matrix-js-sdk/src/types";
import { MsgType } from "matrix-js-sdk/src/matrix";
import { Button } from "@vector-im/compound-web";
import { DownloadIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import {
AttachmentIcon,
DownloadIcon,
VideoCallSolidIcon,
VolumeOnSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
@@ -190,9 +196,17 @@ export default class MFileBody extends React.Component<IProps, IState> {
let placeholder: React.ReactNode = null;
if (showGenericPlaceholder) {
let icon = <AttachmentIcon />;
// MFileBody is not generally used for Audio/Video but can be as part of ReplyTile
if (this.content.msgtype === MsgType.Audio) {
icon = <VolumeOnSolidIcon />;
} else if (this.content.msgtype === MsgType.Video) {
icon = <VideoCallSolidIcon />;
}
placeholder = (
<AccessibleButton className="mx_MediaBody mx_MFileBody_info" onClick={this.onPlaceholderClick}>
<span className="mx_MFileBody_info_icon" />
<span className="mx_MFileBody_info_icon">{icon}</span>
<TextWithTooltip tooltip={presentableTextForFile(this.content, _t("common|attachment"), true)}>
<span className="mx_MFileBody_info_filename">
{presentableTextForFile(this.content, _t("common|attachment"), true, true)}
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { VideoCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import WidgetStore from "../../../stores/WidgetStore";
@@ -41,6 +42,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
// removed
return (
<EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_ended", { senderName })}
timestamp={this.props.timestamp}
@@ -50,6 +52,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
// modified
return (
<EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_updated", { senderName })}
subtitle={joinCopy}
@@ -60,6 +63,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
// assume added
return (
<EventTileBubble
icon={<VideoCallSolidIcon />}
className="mx_MJitsiWidgetEvent"
title={_t("timeline|m.widget|jitsi_started", { senderName })}
subtitle={joinCopy}
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LockSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import { getNameForEventRoom, userLabelForEventRoom } from "../../../utils/KeyVerificationStateObserver";
@@ -73,6 +74,7 @@ const MKeyVerificationRequest: React.FC<Props> = ({ mxEvent, timestamp }) => {
return (
<EventTileBubble
icon={<LockSolidIcon />}
className="mx_cryptoEvent mx_cryptoEvent_icon"
title={title}
subtitle={subtitle}
@@ -11,6 +11,7 @@ import classNames from "classnames";
import { type MatrixEvent, MatrixEventEvent, type Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
import { uniqBy } from "lodash";
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
import { ReactionAddIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import { isContentActionable } from "../../../utils/EventUtils";
@@ -54,7 +55,9 @@ const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
}}
isExpanded={menuDisplayed}
ref={button}
/>
>
<ReactionAddIcon />
</ContextMenuTooltipButton>
{contextMenu}
</React.Fragment>
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useContext, type JSX } from "react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { DeleteIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
@@ -34,6 +35,7 @@ const RedactedBody = ({ mxEvent, ref }: IBodyProps): JSX.Element => {
return (
<span className="mx_RedactedBody" ref={ref} title={titleText}>
<DeleteIcon />
{text}
</span>
);
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useCallback } from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { type MatrixEvent, type Room, type RoomState } from "matrix-js-sdk/src/matrix";
import { ChatSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
@@ -89,6 +90,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
return (
<EventTileBubble
icon={<ChatSolidIcon />}
className="mx_CreateEvent"
title={_t("timeline|m.room.create|continuation")}
timestamp={timestamp}
@@ -128,6 +130,7 @@ export const RoomPredecessorTile: React.FC<IProps> = ({ mxEvent, timestamp }) =>
return (
<EventTileBubble
icon={<ChatSolidIcon />}
className="mx_CreateEvent"
title={_t("timeline|m.room.create|continuation")}
subtitle={link}
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useContext, useEffect } from "react";
import { type Room } from "matrix-js-sdk/src/matrix";
import { OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import BaseCard from "./BaseCard";
@@ -73,7 +74,9 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
onClick={openMenu}
isExpanded={menuDisplayed}
label={_t("common|options")}
/>
>
<OverflowHorizontalIcon />
</ContextMenuButton>
{contextMenu}
</div>
);
+3 -3
View File
@@ -38,9 +38,9 @@ interface Props {
}
const icons: Record<E2EStatus, JSX.Element> = {
[E2EStatus.Warning]: <ErrorSolidIcon className="mx_E2EIcon_warning" />,
[E2EStatus.Normal]: <LockSolidIcon className="mx_E2EIcon_normal" />,
[E2EStatus.Verified]: <ShieldIcon className="mx_E2EIcon_verified" />,
[E2EStatus.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
[E2EStatus.Normal]: <LockSolidIcon color="var(--cpd-color-icon-tertiary)" />,
[E2EStatus.Verified]: <ShieldIcon color="var(--cpd-color-icon-success-primary)" />,
};
const E2EIcon: React.FC<Props> = ({ isUser, status, className, size, onClick, hideTooltip, tooltipPlacement }) => {
+13 -5
View File
@@ -35,6 +35,7 @@ import {
} from "matrix-js-sdk/src/crypto-api";
import { Tooltip } from "@vector-im/compound-web";
import { uniqueId } from "lodash";
import { ErrorSolidIcon, InfoIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import ReplyChain from "../elements/ReplyChain";
import { _t } from "../../../languageHandler";
@@ -1518,13 +1519,13 @@ function E2ePadlockDecryptionFailure(props: Omit<IE2ePadlockProps, "title" | "ic
}
enum E2ePadlockIcon {
/** grey shield */
/** Compound Info icon in grey */
Normal = "normal",
/** red shield with (!) */
/** Compound ErrorSolid icon in red */
Warning = "warning",
/** key in grey circle */
/** Compound ErrorSolid icon in grey */
DecryptionFailure = "decryption_failure",
}
@@ -1534,6 +1535,12 @@ interface IE2ePadlockProps {
}
class E2ePadlock extends React.Component<IE2ePadlockProps> {
private static icons: Record<E2ePadlockIcon, JSX.Element> = {
[E2ePadlockIcon.Normal]: <InfoIcon color="var(--cpd-color-icon-tertiary)" />,
[E2ePadlockIcon.Warning]: <ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />,
[E2ePadlockIcon.DecryptionFailure]: <ErrorSolidIcon color="var(--cpd-color-icon-tertiary)" />,
};
public constructor(props: IE2ePadlockProps) {
super(props);
@@ -1543,12 +1550,13 @@ class E2ePadlock extends React.Component<IE2ePadlockProps> {
}
public render(): ReactNode {
const classes = `mx_EventTile_e2eIcon mx_EventTile_e2eIcon_${this.props.icon}`;
// We specify isTriggerInteractive=true and make the div interactive manually as a workaround for
// https://github.com/element-hq/compound/issues/294
return (
<Tooltip label={this.props.title} isTriggerInteractive={true}>
<div className={classes} tabIndex={0} aria-label={_t("timeline|e2e_state")} />
<div className="mx_EventTile_e2eIcon" tabIndex={0} aria-label={_t("timeline|e2e_state")}>
{E2ePadlock.icons[this.props.icon]}
</div>
</Tooltip>
);
}
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { EventTimeline } from "matrix-js-sdk/src/matrix";
import { VisibilityOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import EventTileBubble from "../messages/EventTileBubble";
import { _t } from "../../../languageHandler";
@@ -28,6 +29,7 @@ const HistoryTile: React.FC = () => {
return (
<EventTileBubble
icon={<VisibilityOffIcon />}
className="mx_HistoryTile"
title={_t("timeline|historical_messages_unavailable")}
subtitle={subtitle}
+3 -1
View File
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useContext } from "react";
import { EventType, type Room, type User, type MatrixClient } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { ErrorSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DMRoomMap from "../../../utils/DMRoomMap";
@@ -291,7 +292,8 @@ const NewRoomIntro: React.FC = () => {
<li className="mx_NewRoomIntro">
{!hasExpectedEncryptionSettings(cli, room) && (
<EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
icon={<ErrorSolidIcon color="var(--cpd-color-icon-critical-primary)" />}
className="mx_cryptoEvent"
title={_t("room|intro|unencrypted_warning")}
subtitle={subtitle}
/>
-2
View File
@@ -112,8 +112,6 @@ export default class ReplyTile extends React.PureComponent<IProps> {
const classes = classNames("mx_ReplyTile", {
mx_ReplyTile_inline: msgType === MsgType.Emote,
mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(),
mx_ReplyTile_audio: msgType === MsgType.Audio,
mx_ReplyTile_video: msgType === MsgType.Video,
});
let permalink = "#";
+4 -1
View File
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React, { type JSX } from "react";
import { VoiceCallSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton";
import { _t } from "../../../languageHandler";
@@ -59,7 +60,9 @@ class DialPadButton extends React.PureComponent<DigitButtonProps | DialButtonPro
className="mx_DialPad_button mx_DialPad_dialButton"
onClick={this.onClick}
aria-label={_t("voip|dial")}
/>
>
<VoiceCallSolidIcon />
</AccessibleButton>
);
}
}