Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Tooltip: Improve the accessibility of the composer and the rich text editor #12459

Merged
merged 13 commits into from
May 15, 2024
5 changes: 0 additions & 5 deletions res/css/views/rooms/_MessageComposerFormatBar.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,4 @@ limitations under the License.
font-weight: var(--cpd-font-weight-semibold);
min-width: 54px;
text-align: center;

.mx_MessageComposerFormatBar_tooltipShortcut {
font-size: $font-9px;
opacity: 0.7;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,3 @@ limitations under the License.
color: $tertiary-content;
}
}

.mx_FormattingButtons_Tooltip {
padding: 0 2px 0 2px;

.mx_FormattingButtons_Tooltip_KeyboardShortcut {
color: $tertiary-content;

kbd {
margin-top: 2px;
text-align: center;
display: inline-block;
text-transform: capitalize;
font-size: 12px;
font-family: Inter, sans-serif;
}
}
}
5 changes: 2 additions & 3 deletions src/components/views/rooms/MessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { makeRoomPermalink, RoomPermalinkCreator } from "../../../utils/permalin
import E2EIcon from "./E2EIcon";
import SettingsStore from "../../../settings/SettingsStore";
import { aboveLeftOf, MenuProps } from "../../structures/ContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ReplyPreview from "./ReplyPreview";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
Expand All @@ -52,7 +51,7 @@ import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
import RoomContext from "../../../contexts/RoomContext";
import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
import MessageComposerButtons from "./MessageComposerButtons";
import { ButtonEvent } from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
import { Features } from "../../../settings/Settings";
Expand All @@ -75,7 +74,7 @@ interface ISendButtonProps {

function SendButton(props: ISendButtonProps): JSX.Element {
return (
<AccessibleTooltipButton
<AccessibleButton
className="mx_MessageComposer_sendMessage"
onClick={props.onClick}
title={props.title ?? _t("composer|send_button_title")}
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/rooms/MessageComposerButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { IEventRelation, Room, MatrixClient, THREAD_RELATION_TYPE, M_POLL_START
import React, { createContext, ReactElement, ReactNode, useContext, useRef } from "react";

import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { CollapsibleButton } from "./CollapsibleButton";
import { MenuProps } from "../../structures/ContextMenu";
import dis from "../../../dispatcher/dispatcher";
Expand All @@ -37,7 +36,7 @@ import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_m
import { EmojiButton } from "./EmojiButton";
import { filterBoolean } from "../../../utils/arrays";
import { useSettingValue } from "../../../hooks/useSettings";
import { ButtonEvent } from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";

interface IProps {
addEmoji: (emoji: string) => boolean;
Expand Down Expand Up @@ -128,10 +127,11 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
<UploadButtonContextProvider roomId={room.roomId} relation={props.relation}>
{mainButtons}
{moreButtons.length > 0 && (
<AccessibleTooltipButton
<AccessibleButton
className={moreOptionsClasses}
onClick={props.toggleButtonMenu}
title={_t("quick_settings|sidebar_settings")}
placement="right"
florianduros marked this conversation as resolved.
Show resolved Hide resolved
/>
)}
{props.isMenuOpen && (
Expand Down
Copy link
Member

@t3chguy t3chguy Apr 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes here feel like a visual regression, can design sign off on losing the keyboard key styling here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually my bad, there's no visual regression, only an a11y one. The kbd elements are lost and thus also are the semantics

Before:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, will need to update the tooltip compound to be able this use case.

Original file line number Diff line number Diff line change
Expand Up @@ -30,57 +30,49 @@ import { Icon as NumberedListIcon } from "../../../../../../res/img/element-icon
import { Icon as CodeBlockIcon } from "../../../../../../res/img/element-icons/room/composer/code_block.svg";
import { Icon as IndentIcon } from "../../../../../../res/img/element-icons/room/composer/indent_increase.svg";
import { Icon as UnIndentIcon } from "../../../../../../res/img/element-icons/room/composer/indent_decrease.svg";
import AccessibleTooltipButton from "../../../elements/AccessibleTooltipButton";
import { Alignment } from "../../../elements/Tooltip";
import { KeyboardShortcut } from "../../../settings/KeyboardShortcut";
import { KeyCombo } from "../../../../../KeyBindingsManager";
import { _t } from "../../../../../languageHandler";
import { ButtonEvent } from "../../../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
import { openLinkModal } from "./LinkModal";
import { useComposerContext } from "../ComposerContext";
import { IS_MAC, Key } from "../../../../../Keyboard";
import { ALTERNATE_KEY_NAME } from "../../../../../accessibility/KeyboardShortcuts";

interface TooltipProps {
label: string;
keyCombo?: KeyCombo;
}

function Tooltip({ label, keyCombo }: TooltipProps): JSX.Element {
return (
<div className="mx_FormattingButtons_Tooltip">
{label}
{keyCombo && (
<KeyboardShortcut value={keyCombo} className="mx_FormattingButtons_Tooltip_KeyboardShortcut" />
)}
</div>
);
}

interface ButtonProps extends TooltipProps {
interface ButtonProps {
icon: ReactNode;
actionState: ActionState;
onClick: MouseEventHandler<HTMLButtonElement>;
label: string;
shortcut?: string;
}

function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): JSX.Element {
function Button({ label, shortcut, onClick, actionState, icon }: ButtonProps): JSX.Element {
return (
<AccessibleTooltipButton
<AccessibleButton
element="button"
onClick={onClick as (e: ButtonEvent) => void}
title={label}
aria-label={label}
className={classNames("mx_FormattingButtons_Button", {
mx_FormattingButtons_active: actionState === "reversed",
mx_FormattingButtons_Button_hover: actionState === "enabled",
mx_FormattingButtons_disabled: actionState === "disabled",
})}
tooltip={keyCombo && <Tooltip label={label} keyCombo={keyCombo} />}
forceHide={actionState === "disabled"}
alignment={Alignment.Top}
title={actionState === "disabled" ? undefined : label}
caption={shortcut}
placement="top"
>
{icon}
</AccessibleTooltipButton>
</AccessibleButton>
);
}

/**
* Get the shortcut string for a given key.
* @param key
*/
function getShortcutFromKey(key: string): string {
return (IS_MAC ? "⌘" : _t(ALTERNATE_KEY_NAME[Key.CONTROL])) + "+" + key;
}

interface FormattingButtonsProps {
composer: FormattingFunctions;
actionStates: AllActionStates;
Expand All @@ -94,21 +86,21 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
<Button
actionState={actionStates.bold}
label={_t("composer|format_bold")}
keyCombo={{ ctrlOrCmdKey: true, key: "b" }}
shortcut={getShortcutFromKey("B")}
onClick={() => composer.bold()}
icon={<BoldIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
actionState={actionStates.italic}
label={_t("composer|format_italic")}
keyCombo={{ ctrlOrCmdKey: true, key: "i" }}
shortcut={getShortcutFromKey("I")}
onClick={() => composer.italic()}
icon={<ItalicIcon className="mx_FormattingButtons_Icon" />}
/>
<Button
actionState={actionStates.underline}
label={_t("composer|format_underline")}
keyCombo={{ ctrlOrCmdKey: true, key: "u" }}
shortcut={getShortcutFromKey("U")}
onClick={() => composer.underline()}
icon={<UnderlineIcon className="mx_FormattingButtons_Icon" />}
/>
Expand Down Expand Up @@ -155,7 +147,7 @@ export function FormattingButtons({ composer, actionStates }: FormattingButtonsP
<Button
actionState={actionStates.inlineCode}
label={_t("composer|format_inline_code")}
keyCombo={{ ctrlOrCmdKey: true, key: "e" }}
shortcut={getShortcutFromKey("E")}
onClick={() => composer.inlineCode()}
icon={<InlineCodeIcon className="mx_FormattingButtons_Icon" />}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
<div
aria-label="More options"
class="mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu"
data-state="closed"
role="button"
tabindex="0"
/>
Expand Down Expand Up @@ -735,6 +736,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
<div
aria-label="More options"
class="mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu"
data-state="closed"
role="button"
tabindex="0"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

import React from "react";
import { cleanup, render, screen } from "@testing-library/react";
import { cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ActionState, ActionTypes, AllActionStates, FormattingFunctions } from "@matrix-org/matrix-wysiwyg";

Expand Down Expand Up @@ -135,7 +135,7 @@ describe("FormattingButtons", () => {
const { label } = testCase;

await userEvent.hover(screen.getByLabelText(label));
expect(screen.getByText(label)).toBeInTheDocument();
await waitFor(() => expect(screen.getByText(label)).toBeInTheDocument());
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Fett"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -18,6 +19,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Kursiv"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -28,6 +30,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Unterstrichen"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -38,6 +41,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Durchgestrichen"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -48,6 +52,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Ungeordnete Liste"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -58,6 +63,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Nummerierte Liste"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -68,6 +74,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Zitieren"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -78,6 +85,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Code"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -88,6 +96,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Quelltextblock"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand All @@ -98,6 +107,7 @@ exports[`FormattingButtons renders in german 1`] = `
<button
aria-label="Link"
class="mx_AccessibleButton mx_FormattingButtons_Button mx_FormattingButtons_Button_hover"
data-state="closed"
role="button"
tabindex="0"
>
Expand Down
Loading