Fix composer button visibility in contrast colour mode (#31255)

* Fix composer button visibility in contrast colour mode

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>

* Iterate

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

* Iterate

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

* Update test

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

* Simplify

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

* Update snapshots

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

* Update screenshots

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

* Update screenshots

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

* Update screenshot

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

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2025-12-09 13:59:01 +00:00
committed by GitHub
parent d30e6f25d3
commit 4fda167c11
46 changed files with 428 additions and 214 deletions
@@ -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<ButtonProps<"div">, "title" | "onClick" | "disabled" | "element" | "ref"> & {
type Props = HTMLAttributes<HTMLButtonElement> & {
// Playback instance to manipulate. Cannot change during the component lifecycle.
playback: Playback;
@@ -27,8 +25,7 @@ type Props = Omit<ButtonProps<"div">, "title" | "onClick" | "disabled" | "elemen
*/
export default class PlayPauseButton extends React.PureComponent<Props> {
private onClick = (): void => {
// noinspection JSIgnoredPromiseFromCall
this.toggleState();
void this.toggleState();
};
public async toggleState(): Promise<void> {
@@ -37,21 +34,14 @@ export default class PlayPauseButton extends React.PureComponent<Props> {
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 (
<AccessibleButton
<SharedPlayPauseButton
data-testid="play-pause-button"
className={classes}
title={isPlaying ? _t("action|pause") : _t("action|play")}
onClick={this.onClick}
disabled={isDisabled}
className="mx_PlayPauseButton"
togglePlay={this.onClick}
playing={playback.isPlaying}
disabled={playbackPhase === PlaybackState.Decoding}
{...restProps}
/>
);
@@ -32,6 +32,7 @@ interface IOptionListProps {
}
interface IOptionProps extends React.ComponentProps<typeof MenuItem> {
icon?: ReactNode;
iconClassName?: string;
isDestructive?: boolean;
}
@@ -114,6 +115,7 @@ export const IconizedContextMenuOption: React.FC<IOptionProps> = ({
label,
className,
iconClassName,
icon,
children,
isDestructive,
...props
@@ -129,6 +131,7 @@ export const IconizedContextMenuOption: React.FC<IOptionProps> = ({
label={label}
>
{iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />}
{icon}
<span className="mx_IconizedContextMenu_label">{label}</span>
{children}
</MenuItem>
@@ -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<IProps> = ({ roomId, sender, menuPosition, relati
return (
<React.Fragment>
<CollapsibleButton
className={className}
iconClassName="mx_MessageComposer_location"
onClick={openMenu}
title={_t("common|location")}
inputRef={button}
/>
<CollapsibleButton className={className} onClick={openMenu} title={_t("common|location")} inputRef={button}>
<LocationPinSolidIcon />
</CollapsibleButton>
{contextMenu}
</React.Fragment>
@@ -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<ButtonProps<"div">, "element"> {
inputRef?: RefObject<HTMLElement | null>;
title: string;
iconClassName: string;
}
export const CollapsibleButton: React.FC<Props> = ({
title,
children,
className,
iconClassName,
inputRef,
...props
}) => {
export const CollapsibleButton: React.FC<Props> = ({ title, children, className, inputRef, ...props }) => {
const inOverflowMenu = !!useContext(OverflowMenuContext);
if (inOverflowMenu) {
return <IconizedContextMenuOption {...props} iconClassName={iconClassName} label={title} inputRef={inputRef} />;
return <IconizedContextMenuOption {...props} icon={children} label={title} inputRef={inputRef} />;
}
return (
<AccessibleButton {...props} title={title} className={classNames(className, iconClassName)} ref={inputRef}>
<AccessibleButton {...props} title={title} className={className} ref={inputRef}>
{children}
</AccessibleButton>
);
+4 -2
View File
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import classNames from "classnames";
import React, { type JSX, useContext } from "react";
import { ReactionIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import ContextMenu, { aboveLeftOf, type MenuProps, useContextMenu } from "../../structures/ContextMenu";
@@ -50,11 +51,12 @@ export function EmojiButton({ addEmoji, menuPosition, className }: IEmojiButtonP
<>
<CollapsibleButton
className={computedClassName}
iconClassName="mx_EmojiButton_icon"
onClick={openMenu}
title={_t("common|emoji")}
inputRef={button}
/>
>
<ReactionIcon />
</CollapsibleButton>
{contextMenu}
</>
@@ -18,7 +18,7 @@ import {
} from "matrix-js-sdk/src/matrix";
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";
@@ -72,7 +72,9 @@ function SendButton(props: ISendButtonProps): JSX.Element {
onClick={props.onClick}
title={props.title ?? _t("composer|send_button_title")}
data-testid="sendmessagebtn"
/>
>
<SendSolidIcon />
</AccessibleButton>
);
}
@@ -15,6 +15,13 @@ import {
M_POLL_START,
} from "matrix-js-sdk/src/matrix";
import React, { type JSX, createContext, type ReactElement, type ReactNode, useContext, useRef } from "react";
import {
AttachmentIcon,
MicOnSolidIcon,
OverflowHorizontalIcon,
PollsIcon,
TextFormattingIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import { CollapsibleButton } from "./CollapsibleButton";
@@ -35,6 +42,7 @@ 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 StickersIcon } from "../../../../res/img/element-icons/room/composer/sticker.svg";
interface IProps {
addEmoji: (emoji: string) => boolean;
@@ -125,7 +133,9 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
className={moreOptionsClasses}
onClick={props.toggleButtonMenu}
title={_t("quick_settings|sidebar_settings")}
/>
>
<OverflowHorizontalIcon />
</AccessibleButton>
)}
{props.isMenuOpen && (
<IconizedContextMenu
@@ -235,12 +245,9 @@ const UploadButton: React.FC = () => {
};
return (
<CollapsibleButton
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_upload"
onClick={onClick}
title={_t("common|attachment")}
/>
<CollapsibleButton className="mx_MessageComposer_button" onClick={onClick} title={_t("common|attachment")}>
<AttachmentIcon />
</CollapsibleButton>
);
};
@@ -250,10 +257,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")}
/>
>
<StickersIcon />
</CollapsibleButton>
) : null;
}
@@ -263,10 +271,11 @@ function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement | nu
<CollapsibleButton
key="voice_message_send"
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_voiceMessage"
onClick={props.onRecordStartEndClick}
title={_t("composer|voice_message_button")}
/>
>
<MicOnSolidIcon />
</CollapsibleButton>
);
}
@@ -318,10 +327,11 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
return (
<CollapsibleButton
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_poll"
onClick={this.onCreateClick}
title={_t("composer|poll_button")}
/>
>
<PollsIcon />
</CollapsibleButton>
);
}
}
@@ -349,15 +359,9 @@ function ComposerModeButton({ isRichTextEnabled, onClick }: WysiwygToggleButtonP
const title = isRichTextEnabled ? _t("composer|mode_plain") : _t("composer|mode_rich_text");
return (
<CollapsibleButton
className="mx_MessageComposer_button"
iconClassName={classNames({
mx_MessageComposer_plain_text: !isRichTextEnabled,
mx_MessageComposer_rich_text: isRichTextEnabled,
})}
onClick={onClick}
title={title}
/>
<CollapsibleButton className="mx_MessageComposer_button" onClick={onClick} title={title}>
<TextFormattingIcon />
</CollapsibleButton>
);
}
@@ -9,6 +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 { _t } from "../../../languageHandler";
import { RecordingState } from "../../../audio/VoiceRecording";
@@ -274,7 +275,9 @@ export default class VoiceRecordComposerTile extends React.PureComponent<IProps,
className="mx_VoiceRecordComposerTile_delete"
title={_t("action|delete")}
onClick={this.onCancel}
/>
>
<DeleteIcon />
</AccessibleButton>
);
}