Update the way we render icons for accessibility (#31731)

* Switch to Compound icons to replace old icons

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

* Apply same treatment to missed icons

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

* Iterate

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

* Update snapshots

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

* Remove duplicated icon

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

* Update icon

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

* Update snapshots

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

* Switch from css masks to rendering svg in ImageView

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

* Switch from css masks to rendering svg in ExtensionsCard

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

* Switch from css masks to rendering svg in LegacyRoomListHeader

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

* Switch from css masks to rendering svg in ImageSizePanel

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

* Switch from css masks to rendering svg in LegacyRoomList

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

* Remove icon from CreateSecretStorageDialog title

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

* Switch from css masks to rendering svg in LiveContentSummary

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

* Switch from css masks to rendering svg in RoomCallBanner

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

* Switch from css masks to rendering svg in NonUrgentEchoFailureToast

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

* Switch from css masks to rendering svg in LegacyCallViewHeader

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

* Switch from css masks to rendering svg in CallEvent

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

* Delint

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

* Iterate

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

* Update screenshots

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

* Replace dark-light-mode.svg with Compound

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

* Draw stop icon using svg rather than square mask

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

* Replace masks in RoomSublist with SVG icons

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

* Replace masks with SVG icons in LegacyCall views

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

* Replace masks with SVG icons in EventTile

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

* Replace masks with SVG icons in ForwardDialog

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

* Remove redundant css style

The `::before` has no content so is never rendered

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

* delint

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

* Update tests

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

* Update playwright tests & screenshots

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

* Iterate

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

* Update snapshot

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

* Remove snapshot as it causes issues

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

* Delint

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

* More 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-16 11:21:57 +00:00
committed by GitHub
parent 28464b4d12
commit 466f60ead5
31 changed files with 347 additions and 281 deletions

View File

@@ -16,6 +16,7 @@ import {
SettingsSolidIcon,
LeaveIcon,
NotificationsSolidIcon,
ThemeIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { MatrixClientPeg } from "../../MatrixClientPeg";
@@ -51,7 +52,6 @@ import PosthogTrackers from "../../PosthogTrackers";
import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
import { SDKContext } from "../../contexts/SDKContext";
import { shouldShowFeedback } from "../../utils/Feedback";
import { Icon as DarkLightModeSvg } from "../../../res/img/element-icons/roomlist/dark-light-mode.svg";
interface IProps {
isPanelCollapsed: boolean;
@@ -407,7 +407,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
: _t("user_menu|switch_theme_dark")
}
>
<DarkLightModeSvg width="16px" height="16px" />
<ThemeIcon width="16px" height="16px" />
</RovingAccessibleButton>
</div>
{topSection}

View File

@@ -23,6 +23,7 @@ import {
type TimelineEvents,
} from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types";
import { CheckCircleIcon, CircleIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
@@ -127,12 +128,12 @@ const Entry: React.FC<IEntryProps<any>> = ({ room, type, content, matrixClient:
className = "mx_ForwardList_sending";
disabled = true;
title = _t("forward|sending");
icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
icon = <CircleIcon aria-label={title} />;
} else if (sendState === SendState.Sent) {
className = "mx_ForwardList_sent";
disabled = true;
title = _t("forward|sent");
icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
icon = <CheckCircleIcon aria-label={title} />;
} else {
className = "mx_ForwardList_sendFailed";
disabled = true;

View File

@@ -11,6 +11,7 @@ import { type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { CallErrorCode, CallState } from "matrix-js-sdk/src/webrtc/call";
import classNames from "classnames";
import { Clock } from "@element-hq/web-shared-components";
import { VolumeOffSolidIcon, VolumeOnSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import MemberAvatar from "../avatars/MemberAvatar";
@@ -99,18 +100,14 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
}
private renderSilenceIcon(): JSX.Element {
const silenceClass = classNames({
mx_LegacyCallEvent_iconButton: true,
mx_LegacyCallEvent_unSilence: this.state.silenced,
mx_LegacyCallEvent_silence: !this.state.silenced,
});
return (
<AccessibleButton
className={silenceClass}
className="mx_LegacyCallEvent_iconButton"
onClick={this.props.callEventGrouper.toggleSilenced}
title={this.state.silenced ? _t("voip|unsilence") : _t("voip|silence")}
/>
>
{this.state.silenced ? <VolumeOffSolidIcon /> : <VolumeOnSolidIcon />}
</AccessibleButton>
);
}

View File

@@ -35,7 +35,12 @@ 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 {
CircleIcon,
ErrorSolidIcon,
InfoIcon,
CheckCircleIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import ReplyChain from "../elements/ReplyChain";
import { _t } from "../../../languageHandler";
@@ -175,7 +180,7 @@ export interface EventTileProps {
// the status of this event - ie, mxEvent.status. Denormalised to here so
// that we can tell when it changes.
eventSendStatus?: string;
eventSendStatus?: EventStatus;
forExport?: boolean;
@@ -1187,7 +1192,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
let msgOption: JSX.Element | undefined;
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
msgOption = <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
msgOption = <SentReceipt messageState={this.props.eventSendStatus} />;
} else if (this.props.showReadReceipts) {
msgOption = (
<ReadReceiptGroup
@@ -1563,29 +1568,27 @@ class E2ePadlock extends React.Component<IE2ePadlockProps> {
}
interface ISentReceiptProps {
messageState: EventStatus | null;
messageState: EventStatus | undefined;
}
function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
const isSent = !messageState || messageState === "sent";
const isFailed = messageState === "not_sent";
const receiptClasses = classNames({
mx_EventTile_receiptSent: isSent,
mx_EventTile_receiptSending: !isSent && !isFailed,
});
let nonCssBadge: JSX.Element | undefined;
if (isFailed) {
nonCssBadge = <NotificationBadge notification={StaticNotificationState.RED_EXCLAMATION} />;
}
let label = _t("timeline|send_state_sending");
let icon: JSX.Element | undefined;
let label: string | undefined;
if (messageState === "encrypting") {
icon = <CircleIcon />;
label = _t("timeline|send_state_encrypting");
} else if (isSent) {
icon = <CheckCircleIcon />;
label = _t("timeline|send_state_sent");
} else if (isFailed) {
icon = <NotificationBadge notification={StaticNotificationState.RED_EXCLAMATION} />;
label = _t("timeline|send_state_failed");
} else {
icon = <CircleIcon />;
label = _t("timeline|send_state_sending");
}
return (
@@ -1593,9 +1596,7 @@ function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
<div className="mx_ReadReceiptGroup">
<Tooltip label={label} placement="top-end">
<div className="mx_ReadReceiptGroup_button" role="status">
<span className="mx_ReadReceiptGroup_container">
<span className={receiptClasses}>{nonCssBadge}</span>
</span>
<span className="mx_ReadReceiptGroup_container">{icon}</span>
</div>
</Tooltip>
</div>

View File

@@ -16,6 +16,7 @@ import React, { type JSX, type ComponentType, createRef, type ReactComponentElem
import {
ChevronDownIcon,
ChevronRightIcon,
ChevronUpIcon,
OverflowHorizontalIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
@@ -763,9 +764,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
className={showMoreBtnClasses}
aria-label={label}
>
<span className="mx_RoomSublist_showMoreButtonChevron mx_RoomSublist_showNButtonChevron">
{/* set by CSS masking */}
</span>
<ChevronDownIcon className="mx_RoomSublist_showNButtonChevron" />
{showMoreText}
</RovingAccessibleButton>
);
@@ -781,9 +780,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
className={showMoreBtnClasses}
aria-label={label}
>
<span className="mx_RoomSublist_showLessButtonChevron mx_RoomSublist_showNButtonChevron">
{/* set by CSS masking */}
</span>
<ChevronUpIcon className="mx_RoomSublist_showNButtonChevron" />
{showLessText}
</RovingAccessibleButton>
);

View File

@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type ReactNode } from "react";
import { type Room, type IEventRelation, type MatrixEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { DeleteIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { DeleteIcon, StopSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import { RecordingState } from "../../../audio/VoiceRecording";
@@ -262,7 +262,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
className="mx_VoiceRecordComposerTile_stop"
onClick={this.onRecordStartEndClick}
title={tooltip}
/>
>
<StopSolidIcon />
</AccessibleButton>
);
if (this.state.recorder && !this.state.recorder?.isRecording) {
stopBtn = null;

View File

@@ -175,7 +175,7 @@ const AvatarSetting: React.FC<IProps> = ({
* to the menu component, hence the empty onClick.
*/
onClick={() => {}}
className="mx_AvatarSetting_avatarPlaceholder mx_AvatarSetting_avatarDisplay"
className="mx_AvatarSetting_avatarDisplay"
disabled={disabled}
>
<BaseAvatar

View File

@@ -8,9 +8,23 @@ 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.
*/
import React, { createRef, useState, type Ref, type FC } from "react";
import React, { createRef, useState, type Ref, type FC, type JSX } from "react";
import classNames from "classnames";
import { type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import {
ChevronDownIcon,
ChevronUpIcon,
DialPadIcon,
EndCallIcon,
ListViewIcon,
MicOffSolidIcon,
MicOnSolidIcon,
OverflowHorizontalIcon,
ShareScreenSolidIcon,
SpotlightViewIcon,
VideoCallOffSolidIcon,
VideoCallSolidIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import LegacyCallContextMenu from "../../context_menus/LegacyCallContextMenu";
import DialpadContextMenu from "../../context_menus/DialpadContextMenu";
@@ -38,7 +52,9 @@ const CONTROLS_HIDE_DELAY = 2000;
type ButtonProps = Omit<AccessibleButtonProps<"div">, "title" | "element"> & {
state: boolean;
onLabel?: string;
onIcon: JSX.Element;
offLabel?: string;
offIcon: JSX.Element;
forceHide?: boolean;
onHover?: (hovering: boolean) => void;
ref?: Ref<HTMLElement>;
@@ -49,7 +65,9 @@ const LegacyCallViewToggleButton: FC<ButtonProps> = ({
state: isOn,
className,
onLabel,
onIcon,
offLabel,
offIcon,
forceHide,
onHover,
ref,
@@ -71,6 +89,7 @@ const LegacyCallViewToggleButton: FC<ButtonProps> = ({
onTooltipOpenChange={onHover}
{...props}
>
{isOn ? onIcon : offIcon}
{children}
</AccessibleButton>
);
@@ -84,10 +103,6 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
const [menuDisplayed, buttonRef, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
const [hoveringDropdown, setHoveringDropdown] = useState(false);
const classes = classNames("mx_LegacyCallViewButtons_button", "mx_LegacyCallViewButtons_dropdownButton", {
mx_LegacyCallViewButtons_dropdownButton_collapsed: !menuDisplayed,
});
const onClick = (event: ButtonEvent): void => {
event.stopPropagation();
openMenu();
@@ -101,8 +116,10 @@ const LegacyCallViewDropdownButton: React.FC<IDropdownButtonProps> = ({ state, d
{...props}
>
<LegacyCallViewToggleButton
className={classes}
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_dropdownButton"
onClick={onClick}
onIcon={<ChevronUpIcon />}
offIcon={<ChevronDownIcon />}
onHover={(hovering) => setHoveringDropdown(hovering)}
state={state}
/>
@@ -267,19 +284,23 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
{this.props.buttonsVisibility.dialpad && (
<ContextMenuTooltipButton
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_dialpad"
className="mx_LegacyCallViewButtons_button"
ref={this.dialpadButton}
onClick={this.onDialpadClick}
isExpanded={this.state.showDialpad}
title={_t("voip|dialpad")}
placement="top"
/>
>
<DialPadIcon />
</ContextMenuTooltipButton>
)}
<LegacyCallViewDropdownButton
state={!this.props.buttonsState.micMuted}
className="mx_LegacyCallViewButtons_button_mic"
onLabel={_t("voip|disable_microphone")}
onIcon={<MicOnSolidIcon />}
offLabel={_t("voip|enable_microphone")}
offIcon={<MicOffSolidIcon />}
onClick={this.props.handlers.onMicMuteClick}
deviceKinds={[MediaDeviceKindEnum.AudioInput, MediaDeviceKindEnum.AudioOutput]}
/>
@@ -288,7 +309,9 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
state={!this.props.buttonsState.vidMuted}
className="mx_LegacyCallViewButtons_button_vid"
onLabel={_t("voip|disable_camera")}
onIcon={<VideoCallSolidIcon />}
offLabel={_t("voip|enable_camera")}
offIcon={<VideoCallOffSolidIcon />}
onClick={this.props.handlers.onVidMuteClick}
deviceKinds={[MediaDeviceKindEnum.VideoInput]}
/>
@@ -298,7 +321,9 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
state={this.props.buttonsState.screensharing}
className="mx_LegacyCallViewButtons_button_screensharing"
onLabel={_t("voip|stop_screenshare")}
onIcon={<ShareScreenSolidIcon />}
offLabel={_t("voip|start_screenshare")}
offIcon={<ShareScreenSolidIcon />}
onClick={this.props.handlers.onScreenshareClick}
/>
)}
@@ -307,26 +332,32 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
state={this.props.buttonsState.sidebarShown}
className="mx_LegacyCallViewButtons_button_sidebar"
onLabel={_t("voip|hide_sidebar_button")}
onIcon={<ListViewIcon />}
offLabel={_t("voip|show_sidebar_button")}
offIcon={<SpotlightViewIcon />}
onClick={this.props.handlers.onToggleSidebarClick}
/>
)}
{this.props.buttonsVisibility.contextMenu && (
<ContextMenuTooltipButton
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_button_more"
className="mx_LegacyCallViewButtons_button"
onClick={this.onMoreClick}
ref={this.contextMenuButton}
isExpanded={this.state.showMoreMenu}
title={_t("voip|more_button")}
placement="top"
/>
>
<OverflowHorizontalIcon />
</ContextMenuTooltipButton>
)}
<AccessibleButton
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_button_hangup"
onClick={this.props.handlers.onHangupClick}
title={_t("voip|hangup")}
placement="top"
/>
>
<EndCallIcon />
</AccessibleButton>
</div>
);
}

View File

@@ -12,6 +12,7 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { CallType, type MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import classNames from "classnames";
import { VolumeOffSolidIcon, VolumeOnSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../LegacyCallHandler";
import { MatrixClientPeg } from "../MatrixClientPeg";
@@ -98,10 +99,6 @@ export default class IncomingLegacyCallToast extends React.Component<IProps, ISt
mx_IncomingLegacyCallToast_content_voice: isVoice,
mx_IncomingLegacyCallToast_content_video: !isVoice,
});
const silenceClass = classNames("mx_IncomingLegacyCallToast_iconButton", {
mx_IncomingLegacyCallToast_unSilence: this.state.silenced,
mx_IncomingLegacyCallToast_silence: !this.state.silenced,
});
return (
<React.Fragment>
@@ -130,11 +127,13 @@ export default class IncomingLegacyCallToast extends React.Component<IProps, ISt
</div>
</div>
<AccessibleButton
className={silenceClass}
className="mx_IncomingLegacyCallToast_iconButton"
disabled={callForcedSilent}
onClick={this.onSilenceClick}
title={silenceButtonTooltip}
/>
>
{this.state.silenced ? <VolumeOffSolidIcon /> : <VolumeOnSolidIcon />}
</AccessibleButton>
</React.Fragment>
);
}