Tooltip: Use AccessibleButton in reusable elements (#12461)

* Update reusable elements

* Update tests

* Make right as default tooltip placement

* Add tests
This commit is contained in:
Florian Duros 2024-04-29 19:19:05 +02:00 committed by GitHub
parent d5bf1022e9
commit 44e2a6d070
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 160 additions and 30 deletions

View file

@ -138,7 +138,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
triggerOnMouseDown,
title,
caption,
placement,
placement = "right",
onTooltipOpenChange,
...restProps
}: Props<T>,

View file

@ -20,8 +20,7 @@ import classNames from "classnames";
import { _t } from "../../../languageHandler";
import { copyPlaintext } from "../../../utils/strings";
import { ButtonEvent } from "./AccessibleButton";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
interface IProps {
children?: React.ReactNode;
@ -53,11 +52,13 @@ const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true
return (
<div className={combinedClassName}>
{children}
<AccessibleTooltipButton
<AccessibleButton
title={tooltip ?? _t("action|copy")}
onClick={onCopyClickInternal}
className="mx_CopyableText_copyButton"
onHideTooltip={onHideTooltip}
onTooltipOpenChange={(open) => {
if (!open) onHideTooltip();
}}
/>
</div>
);

View file

@ -21,7 +21,6 @@ import FocusLock from "react-focus-lock";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import MemberAvatar from "../avatars/MemberAvatar";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import MessageContextMenu from "../context_menus/MessageContextMenu";
@ -38,6 +37,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { presentableTextForFile } from "../../../utils/FileUtils";
import AccessibleButton from "./AccessibleButton";
// Max scale to keep gaps around the image
const MAX_SCALE = 0.95;
@ -513,14 +513,14 @@ export default class ImageView extends React.Component<IProps, IState> {
}
const zoomOutButton = (
<AccessibleTooltipButton
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_zoomOut"
title={_t("action|zoom_out")}
onClick={this.onZoomOutClick}
/>
);
const zoomInButton = (
<AccessibleTooltipButton
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_zoomIn"
title={_t("action|zoom_in")}
onClick={this.onZoomInClick}
@ -553,23 +553,23 @@ export default class ImageView extends React.Component<IProps, IState> {
<div className="mx_ImageView_toolbar">
{zoomOutButton}
{zoomInButton}
<AccessibleTooltipButton
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
title={_t("lightbox|rotate_left")}
onClick={this.onRotateCounterClockwiseClick}
/>
<AccessibleTooltipButton
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_rotateCW"
title={_t("lightbox|rotate_right")}
onClick={this.onRotateClockwiseClick}
/>
<AccessibleTooltipButton
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_download"
title={_t("action|download")}
onClick={this.onDownloadClick}
/>
{contextMenuButton}
<AccessibleTooltipButton
<AccessibleButton
className="mx_ImageView_button mx_ImageView_button_close"
title={_t("action|close")}
onClick={this.props.onFinished}

View file

@ -30,7 +30,6 @@ import { Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
import PlatformPeg from "../../../PlatformPeg";
import AccessibleButton from "./AccessibleButton";
import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import { mediaFromMxc } from "../../../customisations/Media";
import { PosthogAnalytics } from "../../../PosthogAnalytics";
@ -131,9 +130,9 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
if (mini) {
// TODO fallback icon
return (
<AccessibleTooltipButton {...props} title={label} className={classes} onClick={onClick}>
<AccessibleButton {...props} title={label} className={classes} onClick={onClick}>
{icon}
</AccessibleTooltipButton>
</AccessibleButton>
);
}

View file

@ -18,7 +18,7 @@ limitations under the License.
import React from "react";
import classNames from "classnames";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import AccessibleButton from "./AccessibleButton";
interface IProps {
// Whether or not this toggle is in the 'on' position.
@ -41,7 +41,7 @@ interface IProps {
}
// Controlled Toggle Switch element, written with Accessibility in mind
export default ({ checked, disabled = false, onChange, ...props }: IProps): JSX.Element => {
export default ({ checked, disabled = false, onChange, title, tooltip, ...props }: IProps): JSX.Element => {
const _onClick = (): void => {
if (disabled) return;
onChange(!checked);
@ -54,15 +54,17 @@ export default ({ checked, disabled = false, onChange, ...props }: IProps): JSX.
});
return (
<AccessibleTooltipButton
<AccessibleButton
{...props}
className={classes}
onClick={_onClick}
role="switch"
aria-label={title}
aria-checked={checked}
aria-disabled={disabled}
title={tooltip}
>
<div className="mx_ToggleSwitch_ball" />
</AccessibleTooltipButton>
</AccessibleButton>
);
};

View file

@ -48,6 +48,7 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View file

@ -82,6 +82,7 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View file

@ -19,6 +19,7 @@ exports[`<ShareLatestLocation /> renders share buttons when there is a location
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View file

@ -41,6 +41,7 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View file

@ -0,0 +1,29 @@
/*
*
* Copyright 2024 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
import React from "react";
import { render } from "@testing-library/react";
import ImageView from "../../../../src/components/views/elements/ImageView";
describe("<ImageView />", () => {
it("renders correctly", () => {
const { container } = render(<ImageView src="https://example.com/image.png" onFinished={jest.fn()} />);
expect(container).toMatchSnapshot();
});
});

View file

@ -0,0 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ImageView /> renders correctly 1`] = `
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-label="Image view"
class="mx_ImageView"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_ImageView_panel"
>
<div />
<div
class="mx_ImageView_toolbar"
>
<div
aria-describedby="floating-ui-2"
aria-label="Zoom out"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_zoomOut"
data-state="open"
role="button"
tabindex="0"
/>
<div
aria-label="Zoom in"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_zoomIn"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Rotate Left"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_rotateCCW"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Rotate Right"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_rotateCW"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Download"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_download"
data-state="closed"
role="button"
tabindex="0"
/>
<div
aria-label="Close"
class="mx_AccessibleButton mx_ImageView_button mx_ImageView_button_close"
data-state="closed"
role="button"
tabindex="0"
/>
</div>
</div>
<div
class="mx_ImageView_image_wrapper"
>
<img
class="mx_ImageView_image "
draggable="true"
src="https://example.com/image.png"
style="transform: translateX(0px)
translateY(0px)
scale(0)
rotate(0deg); cursor: zoom-out;"
/>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;

View file

@ -45,6 +45,7 @@ exports[`AdvancedRoomSettingsTab should render as expected 1`] = `
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
data-state="closed"
role="button"
tabindex="0"
/>

View file

@ -15,7 +15,8 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import LabsUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab";
import SettingsStore from "../../../../../../src/settings/SettingsStore";
@ -113,12 +114,14 @@ describe("<LabsUserSettingsTab />", () => {
expect(toggle.getAttribute("aria-checked")).toEqual("true");
// Hover over the toggle to make it show the tooltip
fireEvent.mouseOver(toggle);
await userEvent.hover(toggle);
const tooltip = rendered.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Once enabled, Rust cryptography can only be disabled by logging out and in again",
);
await waitFor(() => {
const tooltip = screen.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Once enabled, Rust cryptography can only be disabled by logging out and in again",
);
});
});
});
@ -150,12 +153,14 @@ describe("<LabsUserSettingsTab />", () => {
expect(toggle.getAttribute("aria-checked")).toEqual("true");
// Hover over the toggle to make it show the tooltip
fireEvent.mouseOver(toggle);
await userEvent.hover(toggle);
const tooltip = rendered.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Rust cryptography cannot be disabled on this deployment of BrandedClient",
);
await waitFor(() => {
const tooltip = rendered.getByRole("tooltip");
expect(tooltip).toHaveTextContent(
"Rust cryptography cannot be disabled on this deployment of BrandedClient",
);
});
});
});
});

View file

@ -289,6 +289,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
aria-disabled="true"
aria-label="Send read receipts"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
data-state="closed"
id="mx_SettingsFlag_GQvdMWe954DV"
role="switch"
tabindex="0"