diff --git a/res/css/_common.pcss b/res/css/_common.pcss index 908da2bdda6..be42edc4267 100644 --- a/res/css/_common.pcss +++ b/res/css/_common.pcss @@ -896,53 +896,23 @@ legend { @define-mixin composerButtonHighLight { background: var(--cpd-color-bg-subtle-primary); - &::before { - background-color: var(--cpd-color-icon-primary) !important; - } + color: var(--cpd-color-icon-primary) !important; } @define-mixin composerButton $border-radius, $hover-color, $hover-bg { - --size: 26px; position: relative; cursor: pointer; - height: var(--size); - line-height: var(--size); - width: auto; - padding-left: var(--size); border-radius: $border-radius; - &::before { - content: ""; - position: absolute; - top: 3px; - left: 3px; - height: 20px; - width: 20px; - background-color: $icon-button-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - z-index: 2; - } - - &::after { - content: ""; - position: absolute; - left: 0; - top: 0; - z-index: 0; - width: var(--size); - height: var(--size); - border-radius: $border-radius; + svg { + color: $icon-button-color; } &:hover { - &::after { - background: $hover-bg; - } + background-color: $hover-bg; - &::before { - background-color: $hover-color; + svg { + color: $hover-color; } } } diff --git a/res/css/views/audio_messages/_PlayPauseButton.pcss b/res/css/views/audio_messages/_PlayPauseButton.pcss index e932c282e33..96d419281be 100644 --- a/res/css/views/audio_messages/_PlayPauseButton.pcss +++ b/res/css/views/audio_messages/_PlayPauseButton.pcss @@ -14,28 +14,16 @@ Please see LICENSE files in the repository root for full details. min-height: 32px; /* for when the button is used in a flexbox */ border-radius: 32px; background-color: $system; + padding: var(--cpd-space-1-5x); + box-sizing: border-box; - &::before { - content: ""; - position: absolute; /* sizing varies by icon */ - background-color: $secondary-content; - mask-repeat: no-repeat; - mask-size: contain; - top: 6px; /* center */ - left: 6px; /* center */ + svg { + color: $secondary-content; width: 20px; height: 20px; } - &.mx_PlayPauseButton_disabled::before { + &[disabled] svg { opacity: 0.5; } - - &.mx_PlayPauseButton_play::before { - mask-image: url("@vector-im/compound-design-tokens/icons/play-solid.svg"); - } - - &.mx_PlayPauseButton_pause::before { - mask-image: url("@vector-im/compound-design-tokens/icons/pause-solid.svg"); - } } diff --git a/res/css/views/context_menus/_IconizedContextMenu.pcss b/res/css/views/context_menus/_IconizedContextMenu.pcss index 853d97c9354..7292e260ab4 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.pcss +++ b/res/css/views/context_menus/_IconizedContextMenu.pcss @@ -69,11 +69,16 @@ Please see LICENSE files in the repository root for full details. } img, + svg, .mx_IconizedContextMenu_icon { /* icons */ width: 16px; min-width: 16px; max-width: 16px; + + & + .mx_IconizedContextMenu_label { + padding-left: 14px; + } } span.mx_IconizedContextMenu_label { @@ -87,10 +92,6 @@ Please see LICENSE files in the repository root for full details. white-space: nowrap; } - .mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label { - padding-left: 14px; - } - .mx_BetaCard_betaPill { margin-left: 16px; } @@ -99,8 +100,6 @@ Please see LICENSE files in the repository root for full details. .mx_IconizedContextMenu_icon { position: relative; - width: 16px; - height: 16px; &::before { content: ""; @@ -119,6 +118,10 @@ Please see LICENSE files in the repository root for full details. color: $alert !important; } + svg { + color: var(--cpd-color-icon-critical-primary); + } + .mx_IconizedContextMenu_icon::before { background-color: var(--cpd-color-icon-critical-primary); } @@ -127,6 +130,10 @@ Please see LICENSE files in the repository root for full details. .mx_IconizedContextMenu_option_red { color: $alert !important; + svg { + color: $alert; + } + .mx_IconizedContextMenu_icon::before { background-color: $alert; } @@ -138,6 +145,10 @@ Please see LICENSE files in the repository root for full details. color: $accent !important; } + svg { + color: $accent; + } + .mx_IconizedContextMenu_icon::before { background-color: $accent; } diff --git a/res/css/views/rooms/_EmojiButton.pcss b/res/css/views/rooms/_EmojiButton.pcss index 16281046464..5911bf723fe 100644 --- a/res/css/views/rooms/_EmojiButton.pcss +++ b/res/css/views/rooms/_EmojiButton.pcss @@ -14,10 +14,6 @@ Please see LICENSE files in the repository root for full details. @mixin composerButtonHighLight; } -.mx_EmojiButton_icon::before { - mask-image: url("$(res)/img/element-icons/room/composer/emoji.svg"); -} - .mx_MessageComposer_wysiwyg { .mx_EmojiButton { @mixin composerButton 5px, $tertiary-content, $panels; diff --git a/res/css/views/rooms/_MessageComposer.pcss b/res/css/views/rooms/_MessageComposer.pcss index 8b92b682ec1..6dc99205b5e 100644 --- a/res/css/views/rooms/_MessageComposer.pcss +++ b/res/css/views/rooms/_MessageComposer.pcss @@ -183,6 +183,9 @@ Please see LICENSE files in the repository root for full details. .mx_MessageComposer_button { @mixin composerButton 50%, var(--cpd-color-icon-primary), var(--cpd-color-bg-subtle-primary); + padding: 3px; + height: 20px; + width: 20px; &:last-child { margin-right: auto; @@ -202,6 +205,12 @@ Please see LICENSE files in the repository root for full details. &.mx_MessageComposer_hangup:not(.mx_AccessibleButton_disabled)::before { background-color: $alert; } + + svg { + --size: 20px; + width: var(--size); + height: var(--size); + } } .mx_MessageComposer_wysiwyg { .mx_MessageComposer_wrapper { @@ -244,61 +253,31 @@ Please see LICENSE files in the repository root for full details. } } -.mx_MessageComposer_upload::before { - mask-image: url("$(res)/img/element-icons/room/composer/attach.svg"); -} - -.mx_MessageComposer_poll::before { - mask-image: url("$(res)/img/element-icons/room/composer/poll.svg"); -} - -.mx_MessageComposer_voiceMessage::before { - mask-image: url("@vector-im/compound-design-tokens/icons/mic-on-solid.svg"); -} - -.mx_MessageComposer_plain_text::before { - mask-image: url("$(res)/img/element-icons/room/composer/plain_text.svg"); -} - -.mx_MessageComposer_rich_text::before { - mask-image: url("@vector-im/compound-design-tokens/icons/text-formatting.svg"); -} +.mx_MessageComposer_buttonMenu { + width: 24px; + height: 24px; + padding: 1px; -.mx_MessageComposer_location::before { - mask-image: url("@vector-im/compound-design-tokens/icons/location-pin-solid.svg"); -} - -.mx_MessageComposer_stickers::before { - mask-image: url("$(res)/img/element-icons/room/composer/sticker.svg"); -} - -.mx_MessageComposer_buttonMenu::before { - mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); - mask-size: 24px; + svg { + width: 24px; + height: 24px; + } } .mx_MessageComposer_sendMessage { cursor: pointer; position: relative; - width: 32px; - height: 32px; + padding: var(--cpd-space-2x); border-radius: 100%; background-color: var(--cpd-color-icon-accent-tertiary); + height: 16px; + width: 16px; - &::before { - position: absolute; - height: 16px; - width: 16px; - top: 8px; - left: 9px; - - mask-image: url("@vector-im/compound-design-tokens/icons/send-solid.svg"); - mask-repeat: no-repeat; - mask-size: contain; - mask-position: center; - - background-color: var(--cpd-color-icon-on-solid-primary); - content: ""; + svg { + height: inherit; + width: inherit; + + color: var(--cpd-color-icon-on-solid-primary); } } diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.pcss b/res/css/views/rooms/_VoiceRecordComposerTile.pcss index 8dabc06fe58..1715a8efe5d 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.pcss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.pcss @@ -32,10 +32,12 @@ Please see LICENSE files in the repository root for full details. height: 24px; vertical-align: middle; margin-right: 2px; /* distance from left edge of waveform container (container has some margin too) */ - background-color: $voice-record-icon-color; - mask-repeat: no-repeat; - mask-size: contain; - mask-image: url("@vector-im/compound-design-tokens/icons/delete.svg"); + + svg { + width: inherit; + height: inherit; + color: $voice-record-icon-color; + } } .mx_VoiceRecordComposerTile_uploadingState { @@ -115,3 +117,9 @@ Please see LICENSE files in the repository root for full details. opacity: 1; } } + +@media (forced-colors: active) { + .mx_VoiceMessagePrimaryContainer { + outline: 1px solid transparent; + } +} diff --git a/res/img/element-icons/room/composer/attach.svg b/res/img/element-icons/room/composer/attach.svg index 0cac44d29f0..e2081100b7c 100644 --- a/res/img/element-icons/room/composer/attach.svg +++ b/res/img/element-icons/room/composer/attach.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/element-icons/room/composer/emoji.svg b/res/img/element-icons/room/composer/emoji.svg index b02cb693645..231dffbd93e 100644 --- a/res/img/element-icons/room/composer/emoji.svg +++ b/res/img/element-icons/room/composer/emoji.svg @@ -1,7 +1,7 @@ - - + + - - + + diff --git a/src/components/views/audio_messages/PlayPauseButton.tsx b/src/components/views/audio_messages/PlayPauseButton.tsx index a4457b22305..1af6971d2c5 100644 --- a/src/components/views/audio_messages/PlayPauseButton.tsx +++ b/src/components/views/audio_messages/PlayPauseButton.tsx @@ -6,14 +6,12 @@ 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, { type ReactNode } from "react"; -import classNames from "classnames"; +import React, { type HTMLAttributes, type ReactNode } from "react"; +import { PlayPauseButton as SharedPlayPauseButton } from "@element-hq/web-shared-components"; -import { _t } from "../../../languageHandler"; import { type Playback, PlaybackState } from "../../../audio/Playback"; -import AccessibleButton, { type ButtonProps } from "../elements/AccessibleButton"; -type Props = Omit, "title" | "onClick" | "disabled" | "element" | "ref"> & { +type Props = HTMLAttributes & { // Playback instance to manipulate. Cannot change during the component lifecycle. playback: Playback; @@ -27,8 +25,7 @@ type Props = Omit, "title" | "onClick" | "disabled" | "elemen */ export default class PlayPauseButton extends React.PureComponent { private onClick = (): void => { - // noinspection JSIgnoredPromiseFromCall - this.toggleState(); + void this.toggleState(); }; public async toggleState(): Promise { @@ -37,21 +34,14 @@ export default class PlayPauseButton extends React.PureComponent { public render(): ReactNode { const { playback, playbackPhase, ...restProps } = this.props; - const isPlaying = playback.isPlaying; - const isDisabled = playbackPhase === PlaybackState.Decoding; - const classes = classNames("mx_PlayPauseButton", { - mx_PlayPauseButton_play: !isPlaying, - mx_PlayPauseButton_pause: isPlaying, - mx_PlayPauseButton_disabled: isDisabled, - }); return ( - ); diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index b6372a96c66..4ca35f2cc71 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -32,6 +32,7 @@ interface IOptionListProps { } interface IOptionProps extends React.ComponentProps { + icon?: ReactNode; iconClassName?: string; isDestructive?: boolean; } @@ -114,6 +115,7 @@ export const IconizedContextMenuOption: React.FC = ({ label, className, iconClassName, + icon, children, isDestructive, ...props @@ -129,6 +131,7 @@ export const IconizedContextMenuOption: React.FC = ({ label={label} > {iconClassName && } + {icon} {label} {children} diff --git a/src/components/views/location/LocationButton.tsx b/src/components/views/location/LocationButton.tsx index e84ce37ec56..eb56207411e 100644 --- a/src/components/views/location/LocationButton.tsx +++ b/src/components/views/location/LocationButton.tsx @@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details. import React, { type ReactNode, type SyntheticEvent, useContext } from "react"; import classNames from "classnames"; import { type RoomMember, type IEventRelation } from "matrix-js-sdk/src/matrix"; +import { LocationPinSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; import { CollapsibleButton } from "../rooms/CollapsibleButton"; @@ -54,13 +55,9 @@ const LocationButton: React.FC = ({ roomId, sender, menuPosition, relati return ( - + + + {contextMenu} diff --git a/src/components/views/rooms/CollapsibleButton.tsx b/src/components/views/rooms/CollapsibleButton.tsx index 60d82eb27fb..158a4e426bb 100644 --- a/src/components/views/rooms/CollapsibleButton.tsx +++ b/src/components/views/rooms/CollapsibleButton.tsx @@ -7,7 +7,6 @@ Please see LICENSE files in the repository root for full details. */ import React, { type RefObject, useContext } from "react"; -import classNames from "classnames"; import AccessibleButton, { type ButtonProps } from "../elements/AccessibleButton"; import { OverflowMenuContext } from "./MessageComposerButtons"; @@ -16,24 +15,16 @@ import { IconizedContextMenuOption } from "../context_menus/IconizedContextMenu" interface Props extends Omit, "element"> { inputRef?: RefObject; title: string; - iconClassName: string; } -export const CollapsibleButton: React.FC = ({ - title, - children, - className, - iconClassName, - inputRef, - ...props -}) => { +export const CollapsibleButton: React.FC = ({ title, children, className, inputRef, ...props }) => { const inOverflowMenu = !!useContext(OverflowMenuContext); if (inOverflowMenu) { - return ; + return ; } return ( - + {children} ); diff --git a/src/components/views/rooms/EmojiButton.tsx b/src/components/views/rooms/EmojiButton.tsx index 7eff845a9c7..71259161c59 100644 --- a/src/components/views/rooms/EmojiButton.tsx +++ b/src/components/views/rooms/EmojiButton.tsx @@ -14,6 +14,7 @@ import ContextMenu, { aboveLeftOf, type MenuProps, useContextMenu } from "../../ import EmojiPicker from "../emojipicker/EmojiPicker"; import { CollapsibleButton } from "./CollapsibleButton"; import { OverflowMenuContext } from "./MessageComposerButtons"; +import { Icon as EmojiIcon } from "../../../../res/img/element-icons/room/composer/emoji.svg"; interface IEmojiButtonProps { addEmoji: (unicode: string) => boolean; @@ -50,11 +51,12 @@ export function EmojiButton({ addEmoji, menuPosition, className }: IEmojiButtonP <> + > + + {contextMenu} diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index d4e617a1f45..94294a5d1cf 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -19,7 +19,7 @@ import { import { type Optional } from "matrix-events-sdk"; import { Tooltip } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; -import { LockOffIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; +import { LockOffIcon, SendSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -73,7 +73,9 @@ function SendButton(props: ISendButtonProps): JSX.Element { onClick={props.onClick} title={props.title ?? _t("composer|send_button_title")} data-testid="sendmessagebtn" - /> + > + + ); } diff --git a/src/components/views/rooms/MessageComposerButtons.tsx b/src/components/views/rooms/MessageComposerButtons.tsx index d0c9e00ffc2..3212c05bab4 100644 --- a/src/components/views/rooms/MessageComposerButtons.tsx +++ b/src/components/views/rooms/MessageComposerButtons.tsx @@ -15,6 +15,11 @@ import { M_POLL_START, } from "matrix-js-sdk/src/matrix"; import React, { type JSX, createContext, type ReactElement, type ReactNode, useContext, useRef } from "react"; +import { + MicOnSolidIcon, + OverflowHorizontalIcon, + TextFormattingIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; import { CollapsibleButton } from "./CollapsibleButton"; @@ -35,6 +40,10 @@ import { filterBoolean } from "../../../utils/arrays"; import { useSettingValue } from "../../../hooks/useSettings"; import AccessibleButton, { type ButtonEvent } from "../elements/AccessibleButton"; import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx"; +import { Icon as PlainTextIcon } from "../../../../res/img/element-icons/room/composer/plain_text.svg"; +import { Icon as UploadIcon } from "../../../../res/img/element-icons/room/composer/attach.svg"; +import { Icon as StickersIcon } from "../../../../res/img/element-icons/room/composer/sticker.svg"; +import { Icon as PollIcon } from "../../../../res/img/element-icons/room/composer/poll.svg"; interface IProps { addEmoji: (emoji: string) => boolean; @@ -125,7 +134,9 @@ const MessageComposerButtons: React.FC = (props: IProps) => { className={moreOptionsClasses} onClick={props.toggleButtonMenu} title={_t("quick_settings|sidebar_settings")} - /> + > + + )} {props.isMenuOpen && ( { }; return ( - + + + ); }; @@ -250,10 +258,11 @@ function showStickersButton(props: IProps): ReactElement | null { id="stickersButton" key="controls_stickers" className="mx_MessageComposer_button" - iconClassName="mx_MessageComposer_stickers" onClick={() => props.setStickerPickerOpen(!props.isStickerPickerOpen)} title={props.isStickerPickerOpen ? _t("composer|close_sticker_picker") : _t("common|sticker")} - /> + > + + ) : null; } @@ -263,10 +272,11 @@ function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement | nu + > + + ); } @@ -318,10 +328,11 @@ class PollButton extends React.PureComponent { return ( + > + + ); } } @@ -349,15 +360,9 @@ function ComposerModeButton({ isRichTextEnabled, onClick }: WysiwygToggleButtonP const title = isRichTextEnabled ? _t("composer|mode_plain") : _t("composer|mode_rich_text"); return ( - + + {isRichTextEnabled ? : } + ); } diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 9c13f1c8729..27a4154a86e 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -10,6 +10,7 @@ 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 { type Optional } from "matrix-events-sdk"; +import { DeleteIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; import { RecordingState } from "../../../audio/VoiceRecording"; @@ -275,7 +276,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent + > + + ); } diff --git a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap index 15582ebe8aa..3db55940a09 100644 --- a/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -457,22 +457,38 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] = >
+ > +
+
+ > +
+
+ > + + + +
+ > +
+
+ > +
+
+ > + + + +
+ > +
+
+ > +
+
+ > + + + +
+ > +
+
+ > +
+
+ > + + + +
+ > +
+
+ > +
+
+ > + + + +
+ > +
+
+ > +
+
+ > + + + +
", () => { it("disables play button while playback is decoding", async () => { const playback = new Playback(new ArrayBuffer(8)); const component = getComponent({ playback }); - expect(getPlayButton(component)).toHaveAttribute("disabled"); expect(getPlayButton(component)).toHaveAttribute("aria-disabled", "true"); }); @@ -90,7 +89,6 @@ describe("", () => { const playback = new Playback(new ArrayBuffer(8)); const component = getComponent({ playback }); await flushPromises(); - expect(getPlayButton(component)).not.toHaveAttribute("disabled"); expect(getPlayButton(component)).not.toHaveAttribute("aria-disabled", "true"); }); @@ -110,7 +108,6 @@ describe("", () => { await playback.prepare(); const component = getComponent({ playback }); // playback already decoded, button is not disabled - expect(getPlayButton(component)).not.toHaveAttribute("disabled"); expect(getPlayButton(component)).not.toHaveAttribute("aria-disabled", "true"); expect(component.container.querySelector(".text-warning")).toBeFalsy(); });