Tooltip: Use `AccessibleButton` in reusable elements (#12461)
* Update reusable elements * Update tests * Make right as default tooltip placement * Add testspull/28217/head
							parent
							
								
									d5bf1022e9
								
							
						
					
					
						commit
						44e2a6d070
					
				| 
						 | 
				
			
			@ -138,7 +138,7 @@ const AccessibleButton = forwardRef(function <T extends keyof JSX.IntrinsicEleme
 | 
			
		|||
        triggerOnMouseDown,
 | 
			
		||||
        title,
 | 
			
		||||
        caption,
 | 
			
		||||
        placement,
 | 
			
		||||
        placement = "right",
 | 
			
		||||
        onTooltipOpenChange,
 | 
			
		||||
        ...restProps
 | 
			
		||||
    }: Props<T>,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
            />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
                />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
    />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
      />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
                    aria-label="Copy"
 | 
			
		||||
                    class="mx_AccessibleButton mx_CopyableText_copyButton"
 | 
			
		||||
                    data-state="closed"
 | 
			
		||||
                    role="button"
 | 
			
		||||
                    tabindex="0"
 | 
			
		||||
                  />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
                    );
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue