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:
parent
d5bf1022e9
commit
44e2a6d070
14 changed files with 160 additions and 30 deletions
|
@ -138,7 +138,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
|
||||||
triggerOnMouseDown,
|
triggerOnMouseDown,
|
||||||
title,
|
title,
|
||||||
caption,
|
caption,
|
||||||
placement,
|
placement = "right",
|
||||||
onTooltipOpenChange,
|
onTooltipOpenChange,
|
||||||
...restProps
|
...restProps
|
||||||
}: Props<T>,
|
}: Props<T>,
|
||||||
|
|
|
@ -20,8 +20,7 @@ import classNames from "classnames";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { copyPlaintext } from "../../../utils/strings";
|
import { copyPlaintext } from "../../../utils/strings";
|
||||||
import { ButtonEvent } from "./AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
|
||||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
@ -53,11 +52,13 @@ const CopyableText: React.FC<IProps> = ({ children, getTextToCopy, border = true
|
||||||
return (
|
return (
|
||||||
<div className={combinedClassName}>
|
<div className={combinedClassName}>
|
||||||
{children}
|
{children}
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
title={tooltip ?? _t("action|copy")}
|
title={tooltip ?? _t("action|copy")}
|
||||||
onClick={onCopyClickInternal}
|
onClick={onCopyClickInternal}
|
||||||
className="mx_CopyableText_copyButton"
|
className="mx_CopyableText_copyButton"
|
||||||
onHideTooltip={onHideTooltip}
|
onTooltipOpenChange={(open) => {
|
||||||
|
if (!open) onHideTooltip();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,7 +21,6 @@ import FocusLock from "react-focus-lock";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
|
||||||
import MemberAvatar from "../avatars/MemberAvatar";
|
import MemberAvatar from "../avatars/MemberAvatar";
|
||||||
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||||
import MessageContextMenu from "../context_menus/MessageContextMenu";
|
import MessageContextMenu from "../context_menus/MessageContextMenu";
|
||||||
|
@ -38,6 +37,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
import { presentableTextForFile } from "../../../utils/FileUtils";
|
import { presentableTextForFile } from "../../../utils/FileUtils";
|
||||||
|
import AccessibleButton from "./AccessibleButton";
|
||||||
|
|
||||||
// Max scale to keep gaps around the image
|
// Max scale to keep gaps around the image
|
||||||
const MAX_SCALE = 0.95;
|
const MAX_SCALE = 0.95;
|
||||||
|
@ -513,14 +513,14 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomOutButton = (
|
const zoomOutButton = (
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_zoomOut"
|
className="mx_ImageView_button mx_ImageView_button_zoomOut"
|
||||||
title={_t("action|zoom_out")}
|
title={_t("action|zoom_out")}
|
||||||
onClick={this.onZoomOutClick}
|
onClick={this.onZoomOutClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const zoomInButton = (
|
const zoomInButton = (
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_zoomIn"
|
className="mx_ImageView_button mx_ImageView_button_zoomIn"
|
||||||
title={_t("action|zoom_in")}
|
title={_t("action|zoom_in")}
|
||||||
onClick={this.onZoomInClick}
|
onClick={this.onZoomInClick}
|
||||||
|
@ -553,23 +553,23 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
<div className="mx_ImageView_toolbar">
|
<div className="mx_ImageView_toolbar">
|
||||||
{zoomOutButton}
|
{zoomOutButton}
|
||||||
{zoomInButton}
|
{zoomInButton}
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
|
className="mx_ImageView_button mx_ImageView_button_rotateCCW"
|
||||||
title={_t("lightbox|rotate_left")}
|
title={_t("lightbox|rotate_left")}
|
||||||
onClick={this.onRotateCounterClockwiseClick}
|
onClick={this.onRotateCounterClockwiseClick}
|
||||||
/>
|
/>
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_rotateCW"
|
className="mx_ImageView_button mx_ImageView_button_rotateCW"
|
||||||
title={_t("lightbox|rotate_right")}
|
title={_t("lightbox|rotate_right")}
|
||||||
onClick={this.onRotateClockwiseClick}
|
onClick={this.onRotateClockwiseClick}
|
||||||
/>
|
/>
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_download"
|
className="mx_ImageView_button mx_ImageView_button_download"
|
||||||
title={_t("action|download")}
|
title={_t("action|download")}
|
||||||
onClick={this.onDownloadClick}
|
onClick={this.onDownloadClick}
|
||||||
/>
|
/>
|
||||||
{contextMenuButton}
|
{contextMenuButton}
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
className="mx_ImageView_button mx_ImageView_button_close"
|
className="mx_ImageView_button mx_ImageView_button_close"
|
||||||
title={_t("action|close")}
|
title={_t("action|close")}
|
||||||
onClick={this.props.onFinished}
|
onClick={this.props.onFinished}
|
||||||
|
|
|
@ -30,7 +30,6 @@ import { Signup } from "@matrix-org/analytics-events/types/typescript/Signup";
|
||||||
import PlatformPeg from "../../../PlatformPeg";
|
import PlatformPeg from "../../../PlatformPeg";
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
import { PosthogAnalytics } from "../../../PosthogAnalytics";
|
import { PosthogAnalytics } from "../../../PosthogAnalytics";
|
||||||
|
|
||||||
|
@ -131,9 +130,9 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
|
||||||
if (mini) {
|
if (mini) {
|
||||||
// TODO fallback icon
|
// TODO fallback icon
|
||||||
return (
|
return (
|
||||||
<AccessibleTooltipButton {...props} title={label} className={classes} onClick={onClick}>
|
<AccessibleButton {...props} title={label} className={classes} onClick={onClick}>
|
||||||
{icon}
|
{icon}
|
||||||
</AccessibleTooltipButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// Whether or not this toggle is in the 'on' position.
|
// 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
|
// 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 => {
|
const _onClick = (): void => {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
onChange(!checked);
|
onChange(!checked);
|
||||||
|
@ -54,15 +54,17 @@ export default ({ checked, disabled = false, onChange, ...props }: IProps): JSX.
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleTooltipButton
|
<AccessibleButton
|
||||||
{...props}
|
{...props}
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={_onClick}
|
onClick={_onClick}
|
||||||
role="switch"
|
role="switch"
|
||||||
|
aria-label={title}
|
||||||
aria-checked={checked}
|
aria-checked={checked}
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
|
title={tooltip}
|
||||||
>
|
>
|
||||||
<div className="mx_ToggleSwitch_ball" />
|
<div className="mx_ToggleSwitch_ball" />
|
||||||
</AccessibleTooltipButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,6 +48,7 @@ exports[`<BeaconListItem /> when a beacon is live and has locations renders beac
|
||||||
<div
|
<div
|
||||||
aria-label="Copy"
|
aria-label="Copy"
|
||||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||||
|
data-state="closed"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -82,6 +82,7 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-label="Copy"
|
aria-label="Copy"
|
||||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||||
|
data-state="closed"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,6 +19,7 @@ exports[`<ShareLatestLocation /> renders share buttons when there is a location
|
||||||
<div
|
<div
|
||||||
aria-label="Copy"
|
aria-label="Copy"
|
||||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||||
|
data-state="closed"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -41,6 +41,7 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-label="Copy"
|
aria-label="Copy"
|
||||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||||
|
data-state="closed"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
|
|
29
test/components/views/elements/ImageView-test.tsx
Normal file
29
test/components/views/elements/ImageView-test.tsx
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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>
|
||||||
|
`;
|
|
@ -45,6 +45,7 @@ exports[`AdvancedRoomSettingsTab should render as expected 1`] = `
|
||||||
<div
|
<div
|
||||||
aria-label="Copy"
|
aria-label="Copy"
|
||||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||||
|
data-state="closed"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,7 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
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 LabsUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab";
|
||||||
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
import SettingsStore from "../../../../../../src/settings/SettingsStore";
|
||||||
|
@ -113,12 +114,14 @@ describe("<LabsUserSettingsTab />", () => {
|
||||||
expect(toggle.getAttribute("aria-checked")).toEqual("true");
|
expect(toggle.getAttribute("aria-checked")).toEqual("true");
|
||||||
|
|
||||||
// Hover over the toggle to make it show the tooltip
|
// Hover over the toggle to make it show the tooltip
|
||||||
fireEvent.mouseOver(toggle);
|
await userEvent.hover(toggle);
|
||||||
|
|
||||||
const tooltip = rendered.getByRole("tooltip");
|
await waitFor(() => {
|
||||||
expect(tooltip).toHaveTextContent(
|
const tooltip = screen.getByRole("tooltip");
|
||||||
"Once enabled, Rust cryptography can only be disabled by logging out and in again",
|
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");
|
expect(toggle.getAttribute("aria-checked")).toEqual("true");
|
||||||
|
|
||||||
// Hover over the toggle to make it show the tooltip
|
// Hover over the toggle to make it show the tooltip
|
||||||
fireEvent.mouseOver(toggle);
|
await userEvent.hover(toggle);
|
||||||
|
|
||||||
const tooltip = rendered.getByRole("tooltip");
|
await waitFor(() => {
|
||||||
expect(tooltip).toHaveTextContent(
|
const tooltip = rendered.getByRole("tooltip");
|
||||||
"Rust cryptography cannot be disabled on this deployment of BrandedClient",
|
expect(tooltip).toHaveTextContent(
|
||||||
);
|
"Rust cryptography cannot be disabled on this deployment of BrandedClient",
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -289,6 +289,7 @@ exports[`PreferencesUserSettingsTab should render 1`] = `
|
||||||
aria-disabled="true"
|
aria-disabled="true"
|
||||||
aria-label="Send read receipts"
|
aria-label="Send read receipts"
|
||||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
|
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on"
|
||||||
|
data-state="closed"
|
||||||
id="mx_SettingsFlag_GQvdMWe954DV"
|
id="mx_SettingsFlag_GQvdMWe954DV"
|
||||||
role="switch"
|
role="switch"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|
Loading…
Reference in a new issue