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

* Use `AccessibleButton` in `RovingAccessibleTooltipButton`

* Update snapshots

* Update @vector-im/compound-web

* Update composer

* Update formating buttons

* Update snapshots

* Remove placement

* Update snapshots

* Use kbd

* Update ``@vector-im/compound-web`
This commit is contained in:
Florian Duros 2024-05-15 10:32:53 +02:00 committed by GitHub
parent 6e31f69118
commit 77a724526e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 36 additions and 63 deletions

View file

@ -76,7 +76,7 @@
"@sentry/browser": "^7.0.0", "@sentry/browser": "^7.0.0",
"@testing-library/react-hooks": "^8.0.1", "@testing-library/react-hooks": "^8.0.1",
"@vector-im/compound-design-tokens": "^1.2.0", "@vector-im/compound-design-tokens": "^1.2.0",
"@vector-im/compound-web": "^4.1.2", "@vector-im/compound-web": "^4.2.0",
"@zxcvbn-ts/core": "^3.0.4", "@zxcvbn-ts/core": "^3.0.4",
"@zxcvbn-ts/language-common": "^3.0.4", "@zxcvbn-ts/language-common": "^3.0.4",
"@zxcvbn-ts/language-en": "^3.0.2", "@zxcvbn-ts/language-en": "^3.0.2",

View file

@ -102,9 +102,4 @@ limitations under the License.
font-weight: var(--cpd-font-weight-semibold); font-weight: var(--cpd-font-weight-semibold);
min-width: 54px; min-width: 54px;
text-align: center; text-align: center;
.mx_MessageComposerFormatBar_tooltipShortcut {
font-size: $font-9px;
opacity: 0.7;
}
} }

View file

@ -64,19 +64,11 @@ limitations under the License.
} }
} }
.mx_FormattingButtons_Tooltip { .mx_FormattingButtons_Tooltip_KeyboardShortcut {
padding: 0 2px 0 2px;
.mx_FormattingButtons_Tooltip_KeyboardShortcut {
color: $tertiary-content;
kbd { kbd {
margin-top: 2px;
text-align: center; text-align: center;
display: inline-block; display: inline-block;
text-transform: capitalize; text-transform: capitalize;
font-size: 12px;
font-family: Inter, sans-serif; font-family: Inter, sans-serif;
} }
}
} }

View file

@ -92,12 +92,12 @@ type Props<T extends keyof JSX.IntrinsicElements> = DynamicHtmlElementProps<T> &
/** /**
* The tooltip to show on hover or focus. * The tooltip to show on hover or focus.
*/ */
title?: string; title?: TooltipProps["label"];
/** /**
* The caption is a secondary text displayed under the `title` of the tooltip. * The caption is a secondary text displayed under the `title` of the tooltip.
* Only valid when used in conjunction with `title`. * Only valid when used in conjunction with `title`.
*/ */
caption?: string; caption?: TooltipProps["caption"];
/** /**
* The placement of the tooltip. * The placement of the tooltip.
*/ */

View file

@ -35,7 +35,6 @@ import { makeRoomPermalink, RoomPermalinkCreator } from "../../../utils/permalin
import E2EIcon from "./E2EIcon"; import E2EIcon from "./E2EIcon";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { aboveLeftOf, MenuProps } from "../../structures/ContextMenu"; import { aboveLeftOf, MenuProps } from "../../structures/ContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ReplyPreview from "./ReplyPreview"; import ReplyPreview from "./ReplyPreview";
import { UPDATE_EVENT } from "../../../stores/AsyncStore"; import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import VoiceRecordComposerTile from "./VoiceRecordComposerTile"; import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
@ -52,7 +51,7 @@ import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload"; import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
import MessageComposerButtons from "./MessageComposerButtons"; import MessageComposerButtons from "./MessageComposerButtons";
import { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom"; import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom";
import { Features } from "../../../settings/Settings"; import { Features } from "../../../settings/Settings";
@ -75,7 +74,7 @@ interface ISendButtonProps {
function SendButton(props: ISendButtonProps): JSX.Element { function SendButton(props: ISendButtonProps): JSX.Element {
return ( return (
<AccessibleTooltipButton <AccessibleButton
className="mx_MessageComposer_sendMessage" className="mx_MessageComposer_sendMessage"
onClick={props.onClick} onClick={props.onClick}
title={props.title ?? _t("composer|send_button_title")} title={props.title ?? _t("composer|send_button_title")}

View file

@ -19,7 +19,6 @@ import { IEventRelation, Room, MatrixClient, THREAD_RELATION_TYPE, M_POLL_START
import React, { createContext, ReactElement, ReactNode, useContext, useRef } from "react"; import React, { createContext, ReactElement, ReactNode, useContext, useRef } from "react";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { CollapsibleButton } from "./CollapsibleButton"; import { CollapsibleButton } from "./CollapsibleButton";
import { MenuProps } from "../../structures/ContextMenu"; import { MenuProps } from "../../structures/ContextMenu";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@ -37,7 +36,7 @@ import IconizedContextMenu, { IconizedContextMenuOptionList } from "../context_m
import { EmojiButton } from "./EmojiButton"; import { EmojiButton } from "./EmojiButton";
import { filterBoolean } from "../../../utils/arrays"; import { filterBoolean } from "../../../utils/arrays";
import { useSettingValue } from "../../../hooks/useSettings"; import { useSettingValue } from "../../../hooks/useSettings";
import { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
interface IProps { interface IProps {
addEmoji: (emoji: string) => boolean; addEmoji: (emoji: string) => boolean;
@ -128,7 +127,7 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
<UploadButtonContextProvider roomId={room.roomId} relation={props.relation}> <UploadButtonContextProvider roomId={room.roomId} relation={props.relation}>
{mainButtons} {mainButtons}
{moreButtons.length > 0 && ( {moreButtons.length > 0 && (
<AccessibleTooltipButton <AccessibleButton
className={moreOptionsClasses} className={moreOptionsClasses}
onClick={props.toggleButtonMenu} onClick={props.toggleButtonMenu}
title={_t("quick_settings|sidebar_settings")} title={_t("quick_settings|sidebar_settings")}

View file

@ -30,54 +30,42 @@ 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 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 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 { 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 { _t } from "../../../../../languageHandler";
import { ButtonEvent } from "../../../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
import { openLinkModal } from "./LinkModal"; import { openLinkModal } from "./LinkModal";
import { useComposerContext } from "../ComposerContext"; import { useComposerContext } from "../ComposerContext";
import { KeyboardShortcut } from "../../../settings/KeyboardShortcut";
import { KeyCombo } from "../../../../../KeyBindingsManager";
interface TooltipProps { interface ButtonProps {
icon: ReactNode;
actionState: ActionState;
onClick: MouseEventHandler<HTMLButtonElement>;
label: string; label: string;
keyCombo?: KeyCombo; 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 {
icon: ReactNode;
actionState: ActionState;
onClick: MouseEventHandler<HTMLButtonElement>;
}
function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): JSX.Element { function Button({ label, keyCombo, onClick, actionState, icon }: ButtonProps): JSX.Element {
return ( return (
<AccessibleTooltipButton <AccessibleButton
element="button" element="button"
onClick={onClick as (e: ButtonEvent) => void} onClick={onClick as (e: ButtonEvent) => void}
title={label} aria-label={label}
className={classNames("mx_FormattingButtons_Button", { className={classNames("mx_FormattingButtons_Button", {
mx_FormattingButtons_active: actionState === "reversed", mx_FormattingButtons_active: actionState === "reversed",
mx_FormattingButtons_Button_hover: actionState === "enabled", mx_FormattingButtons_Button_hover: actionState === "enabled",
mx_FormattingButtons_disabled: actionState === "disabled", mx_FormattingButtons_disabled: actionState === "disabled",
})} })}
tooltip={keyCombo && <Tooltip label={label} keyCombo={keyCombo} />} title={actionState === "disabled" ? undefined : label}
forceHide={actionState === "disabled"} caption={
alignment={Alignment.Top} keyCombo && (
<KeyboardShortcut value={keyCombo} className="mx_FormattingButtons_Tooltip_KeyboardShortcut" />
)
}
placement="top"
> >
{icon} {icon}
</AccessibleTooltipButton> </AccessibleButton>
); );
} }

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import React from "react"; 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 userEvent from "@testing-library/user-event";
import { ActionState, ActionTypes, AllActionStates, FormattingFunctions } from "@matrix-org/matrix-wysiwyg"; import { ActionState, ActionTypes, AllActionStates, FormattingFunctions } from "@matrix-org/matrix-wysiwyg";
@ -135,7 +135,7 @@ describe("FormattingButtons", () => {
const { label } = testCase; const { label } = testCase;
await userEvent.hover(screen.getByLabelText(label)); await userEvent.hover(screen.getByLabelText(label));
expect(screen.getByText(label)).toBeInTheDocument(); await waitFor(() => expect(screen.getByText(label)).toBeInTheDocument());
} }
}); });

View file

@ -3097,10 +3097,10 @@
dependencies: dependencies:
svg2vectordrawable "^2.9.1" svg2vectordrawable "^2.9.1"
"@vector-im/compound-web@^4.1.2": "@vector-im/compound-web@^4.2.0":
version "4.1.2" version "4.2.0"
resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-4.1.2.tgz#d8f9ba523700660942722a800c64406216bbbfea" resolved "https://registry.yarnpkg.com/@vector-im/compound-web/-/compound-web-4.2.0.tgz#16915a5e64c405360fc049ddfa39b5185725f950"
integrity sha512-u/jj8HF8qpX1NU+sh6f/S1B7HUMGcoAGYLH0wc5lVbf6x6elBsYKD0LSa+/8NDPuQqVWMztu76chUsM5slC49w== integrity sha512-VSZxIFToDesjiiCGLOj+DrrKv1I0rtpzJbdylarJXY7REnHzVdgaBBtGm403iJ8KkZ2Rn16Mxe+P1/+VS4yiAA==
dependencies: dependencies:
"@floating-ui/react" "^0.26.9" "@floating-ui/react" "^0.26.9"
"@floating-ui/react-dom" "^2.0.8" "@floating-ui/react-dom" "^2.0.8"