diff --git a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png index 9fc79671a1..281f1cebe5 100644 Binary files a/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png and b/playwright/snapshots/threads/threads.spec.ts/Reply-to-the-location-on-ThreadView-linux.png differ diff --git a/res/css/components/views/elements/_AppPermission.pcss b/res/css/components/views/elements/_AppPermission.pcss index 4bbe0ac07a..3b770c7879 100644 --- a/res/css/components/views/elements/_AppPermission.pcss +++ b/res/css/components/views/elements/_AppPermission.pcss @@ -44,24 +44,3 @@ limitations under the License. } } } - -.mx_Tooltip.mx_Tooltip--appPermission { - box-shadow: none; - background-color: $tooltip-timeline-bg-color; - color: $tooltip-timeline-fg-color; - border: none; - border-radius: 3px; - padding: 6px 8px; - - &.mx_Tooltip--appPermission--dark { - .mx_Tooltip_chevron::after { - border-right-color: $tooltip-timeline-bg-color; - } - } - - ul { - list-style-position: inside; - padding-left: 2px; - margin-left: 0; - } -} diff --git a/src/components/views/elements/AppPermission.tsx b/src/components/views/elements/AppPermission.tsx index 362863fc3a..fd788290c0 100644 --- a/src/components/views/elements/AppPermission.tsx +++ b/src/components/views/elements/AppPermission.tsx @@ -18,6 +18,7 @@ limitations under the License. import React from "react"; import { RoomMember } from "matrix-js-sdk/src/matrix"; +import { Tooltip } from "@vector-im/compound-web"; import { _t } from "../../../languageHandler"; import SdkConfig from "../../../SdkConfig"; @@ -29,7 +30,6 @@ import Heading from "../typography/Heading"; import AccessibleButton from "./AccessibleButton"; import { parseUrl } from "../../../utils/UrlUtils"; import { Icon as HelpIcon } from "../../../../res/img/feather-customised/help-circle.svg"; -import TooltipTarget from "./TooltipTarget"; interface IProps { url: string; @@ -99,31 +99,27 @@ export default class AppPermission extends React.Component { ); - const warningTooltipText = ( -
- {_t("analytics|shared_data_heading")} -
    -
  • {_t("widget|shared_data_name")}
  • -
  • {_t("widget|shared_data_avatar")}
  • -
  • {_t("widget|shared_data_mxid")}
  • -
  • {_t("widget|shared_data_device_id")}
  • -
  • {_t("widget|shared_data_theme")}
  • -
  • {_t("widget|shared_data_lang")}
  • -
  • {_t("widget|shared_data_url", { brand })}
  • -
  • {_t("widget|shared_data_room_id")}
  • -
  • {_t("widget|shared_data_widget_id")}
  • -
-
- ); const warningTooltip = ( - +
  • {_t("widget|shared_data_name")}
  • +
  • {_t("widget|shared_data_avatar")}
  • +
  • {_t("widget|shared_data_mxid")}
  • +
  • {_t("widget|shared_data_device_id")}
  • +
  • {_t("widget|shared_data_theme")}
  • +
  • {_t("widget|shared_data_lang")}
  • +
  • {_t("widget|shared_data_url", { brand })}
  • +
  • {_t("widget|shared_data_room_id")}
  • +
  • {_t("widget|shared_data_widget_id")}
  • + + } > - -
    +
    + +
    + ); // Due to i18n limitations, we can't dedupe the code for variables in these two messages. diff --git a/src/components/views/elements/RoomTopic.tsx b/src/components/views/elements/RoomTopic.tsx index f926ef5cf4..9647188304 100644 --- a/src/components/views/elements/RoomTopic.tsx +++ b/src/components/views/elements/RoomTopic.tsx @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useContext, useRef } from "react"; +import React, { useCallback, useContext, useState } from "react"; import { Room, EventType } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; +import { Tooltip } from "@vector-im/compound-web"; import { useTopic } from "../../../hooks/room/useTopic"; -import { Alignment } from "./Tooltip"; import { _t } from "../../../languageHandler"; import dis from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; @@ -28,7 +28,6 @@ import InfoDialog from "../dialogs/InfoDialog"; import { useDispatcher } from "../../../hooks/useDispatcher"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import AccessibleButton from "./AccessibleButton"; -import TooltipTarget from "./TooltipTarget"; import { Linkify, topicToHtml } from "../../../HtmlUtils"; import { tryTransformPermalinkToLocalHref } from "../../../utils/permalinks/Permalinks"; @@ -49,10 +48,10 @@ export function onRoomTopicLinkClick(e: React.MouseEvent): void { export default function RoomTopic({ room, className, ...props }: IProps): JSX.Element { const client = useContext(MatrixClientContext); - const ref = useRef(null); + const [disableTooltip, setDisableTooltip] = useState(false); const topic = useTopic(room); - const body = topicToHtml(topic?.text, topic?.html, ref); + const body = topicToHtml(topic?.text, topic?.html); const onClick = useCallback( (e: React.MouseEvent) => { @@ -70,14 +69,14 @@ export default function RoomTopic({ room, className, ...props }: IProps): JSX.El [props], ); - const ignoreHover = (ev: React.MouseEvent): boolean => { - return (ev.target as HTMLElement).tagName.toUpperCase() === "A"; + const onHover = (ev: React.MouseEvent | React.FocusEvent): void => { + setDisableTooltip((ev.target as HTMLElement).tagName.toUpperCase() === "A"); }; useDispatcher(dis, (payload) => { if (payload.action === Action.ShowRoomTopic) { const canSetTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, client.getSafeUserId()); - const body = topicToHtml(topic?.text, topic?.html, ref, true); + const body = topicToHtml(topic?.text, topic?.html, undefined, true); const modal = Modal.createDialog(InfoDialog, { title: room.name, @@ -115,18 +114,24 @@ export default function RoomTopic({ room, className, ...props }: IProps): JSX.El } }); + // Do not render the tooltip if the topic is empty + // We still need to have a div for the header buttons to be displayed correctly + if (!body) return
    ; + return ( - - {body} - + +
    + {body} +
    +
    ); } diff --git a/src/components/views/elements/TooltipTarget.tsx b/src/components/views/elements/TooltipTarget.tsx deleted file mode 100644 index 89de915b45..0000000000 --- a/src/components/views/elements/TooltipTarget.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright 2021 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, { forwardRef, HTMLAttributes, useRef } from "react"; -import { randomString } from "matrix-js-sdk/src/randomstring"; - -import useFocus from "../../../hooks/useFocus"; -import useHover from "../../../hooks/useHover"; -import Tooltip, { ITooltipProps } from "./Tooltip"; - -interface IProps - extends HTMLAttributes, - Omit { - tooltipTargetClassName?: string; - ignoreHover?: (ev: React.MouseEvent) => boolean; -} - -/** - * Generic tooltip target element that handles tooltip visibility state - * and displays children - */ -const TooltipTarget = forwardRef( - ( - { - children, - tooltipTargetClassName, - // tooltip pass through props - className, - id, - label, - alignment, - tooltipClassName, - maxParentWidth, - ignoreHover, - ...rest - }, - ref, - ) => { - const idRef = useRef("mx_TooltipTarget_" + randomString(8)); - // Use generated ID if one is not passed - if (id === undefined) { - id = idRef.current; - } - - const [isFocused, focusProps] = useFocus(); - const [isHovering, hoverProps] = useHover(ignoreHover || (() => false)); - - // No need to fill up the DOM with hidden tooltip elements. Only add the - // tooltip when we're hovering over the item (performance) - const tooltip = (isFocused || isHovering) && ( - - ); - - return ( -
    - {children} - {tooltip} -
    - ); - }, -); - -export default TooltipTarget; diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx index 29c1c97e1a..eedf5a6046 100644 --- a/src/components/views/messages/MLocationBody.tsx +++ b/src/components/views/messages/MLocationBody.tsx @@ -17,6 +17,7 @@ limitations under the License. import React from "react"; import { MatrixEvent, ClientEvent, ClientEventHandlerMap } from "matrix-js-sdk/src/matrix"; import { randomString } from "matrix-js-sdk/src/randomstring"; +import { Tooltip } from "@vector-im/compound-web"; import { _t } from "../../../languageHandler"; import Modal from "../../../Modal"; @@ -27,8 +28,6 @@ import { isSelfLocation, } from "../../../utils/location"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import TooltipTarget from "../elements/TooltipTarget"; -import { Alignment } from "../elements/Tooltip"; import { SmartMarker, Map, LocationViewDialog } from "../location"; import { IBodyProps } from "./IBodyProps"; import { createReconnectedListener } from "../../../utils/connection"; @@ -126,7 +125,7 @@ export const LocationBodyFallbackContent: React.FC<{ event: MatrixEvent; error: interface LocationBodyContentProps { mxEvent: MatrixEvent; mapId: string; - tooltip?: string; + tooltip: string; onError: (error: Error) => void; onClick?: () => void; } @@ -156,13 +155,9 @@ export const LocationBodyContent: React.FC = ({ return (
    - {tooltip ? ( - - {mapElement} - - ) : ( - mapElement - )} + +
    {mapElement}
    +
    ); }; diff --git a/test/components/structures/__snapshots__/RoomView-test.tsx.snap b/test/components/structures/__snapshots__/RoomView-test.tsx.snap index 91bfd33e83..5134769908 100644 --- a/test/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/test/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -43,10 +43,7 @@ exports[`RoomView for a local room in state CREATING should match the snapshot 1
    @@ -121,10 +118,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
    @@ -284,10 +278,7 @@ exports[`RoomView for a local room in state NEW should match the snapshot 1`] =
    @@ -531,10 +522,7 @@ exports[`RoomView for a local room in state NEW that is encrypted should match t
    diff --git a/test/components/views/elements/RoomTopic-test.tsx b/test/components/views/elements/RoomTopic-test.tsx index dc05779794..8e62bd641f 100644 --- a/test/components/views/elements/RoomTopic-test.tsx +++ b/test/components/views/elements/RoomTopic-test.tsx @@ -16,7 +16,8 @@ limitations under the License. import React from "react"; import { Room } from "matrix-js-sdk/src/matrix"; -import { fireEvent, render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { mkEvent, stubClient } from "../../../test-utils"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; @@ -33,9 +34,12 @@ describe("", () => { window.location.href = originalHref; }); - function runClickTest(topic: string, clickText: string) { + /** + * Create a room with the given topic + * @param topic + */ + function createRoom(topic: string) { stubClient(); - const room = new Room("!pMBteVpcoJRdCJxDmn:matrix.org", MatrixClientPeg.safeGet(), "@alice:example.org"); const topicEvent = mkEvent({ type: "m.room.topic", @@ -45,11 +49,27 @@ describe("", () => { ts: 123, event: true, }); - room.addLiveEvents([topicEvent]); - render(); + return room; + } + /** + * Create a room and render it + * @param topic + */ + const renderRoom = (topic: string) => { + const room = createRoom(topic); + render(); + }; + + /** + * Create a room and click on the given text + * @param topic + * @param clickText + */ + function runClickTest(topic: string, clickText: string) { + renderRoom(topic); fireEvent.click(screen.getByText(clickText)); } @@ -78,4 +98,18 @@ describe("", () => { expect(window.location.href).toEqual(expectedHref); expect(dis.fire).toHaveBeenCalledWith(Action.ShowRoomTopic); }); + + it("should open the tooltip when hovering a text", async () => { + const topic = "room topic"; + renderRoom(topic); + await userEvent.hover(screen.getByText(topic)); + await waitFor(() => expect(screen.getByRole("tooltip", { name: "Click to read topic" })).toBeInTheDocument()); + }); + + it("should not open the tooltip when hovering a link", async () => { + const topic = "https://matrix.org"; + renderRoom(topic); + await userEvent.hover(screen.getByText(topic)); + await waitFor(() => expect(screen.queryByRole("tooltip", { name: "Click to read topic" })).toBeNull()); + }); }); diff --git a/test/components/views/elements/TooltipTarget-test.tsx b/test/components/views/elements/TooltipTarget-test.tsx deleted file mode 100644 index 0823229a90..0000000000 --- a/test/components/views/elements/TooltipTarget-test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2022 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 { fireEvent, render } from "@testing-library/react"; - -import { Alignment } from "../../../../src/components/views/elements/Tooltip"; -import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget"; - -describe("", () => { - const defaultProps = { - "tooltipTargetClassName": "test tooltipTargetClassName", - "className": "test className", - "tooltipClassName": "test tooltipClassName", - "label": "test label", - "alignment": Alignment.Left, - "id": "test id", - "data-testid": "test", - }; - - const getComponent = (props = {}) => { - const wrapper = render( - // wrap in element so renderIntoDocument can render functional component - - - child - - , - ); - return wrapper.getByTestId("test"); - }; - - const getVisibleTooltip = () => document.querySelector(".mx_Tooltip.mx_Tooltip_visible"); - - it("renders container", () => { - const component = getComponent(); - expect(component).toMatchSnapshot(); - expect(getVisibleTooltip()).toBeFalsy(); - }); - - const alignmentKeys = Object.keys(Alignment).filter((o: any) => isNaN(o)); - it.each(alignmentKeys)("displays %s aligned tooltip on mouseover", async (alignment: any) => { - const wrapper = getComponent({ alignment: Alignment[alignment] })!; - fireEvent.mouseOver(wrapper); - expect(getVisibleTooltip()).toMatchSnapshot(); - }); - - it("hides tooltip on mouseleave", () => { - const wrapper = getComponent()!; - fireEvent.mouseOver(wrapper); - expect(getVisibleTooltip()).toBeTruthy(); - fireEvent.mouseLeave(wrapper); - expect(getVisibleTooltip()).toBeFalsy(); - }); - - it("displays tooltip on focus", () => { - const wrapper = getComponent()!; - fireEvent.focus(wrapper); - expect(getVisibleTooltip()).toBeTruthy(); - }); - - it("hides tooltip on blur", async () => { - const wrapper = getComponent()!; - fireEvent.focus(wrapper); - expect(getVisibleTooltip()).toBeTruthy(); - fireEvent.blur(wrapper); - expect(getVisibleTooltip()).toBeFalsy(); - }); -}); diff --git a/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap b/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap index 8f36256547..b344e3cd58 100644 --- a/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap +++ b/test/components/views/elements/__snapshots__/AppTile-test.tsx.snap @@ -288,9 +288,7 @@ exports[`AppTile for a pinned widget should render permission request 1`] = ` Using this widget may share data
    displays Bottom aligned tooltip on mouseover 1`] = ` -