diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png index a5403f2d01..c3b5f34feb 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png index 963caeacda..bc2085b205 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png index bd9499c105..b44e61b3eb 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png index 5624092cbf..c182c853b9 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png index 498415e78b..f21e798adf 100644 Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ diff --git a/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss index 06ffe532d7..7ce6280331 100644 --- a/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss @@ -28,6 +28,41 @@ padding-left: var(--cpd-space-3x); font: var(--cpd-font-body-md-regular); + /* Hide the menu by default */ + .mx_RoomListItemView_menu { + display: none; + } + + &:hover, + &:focus-visible, + /* When the context menu is opened */ + &[data-state="open"], + /* When the options and notifications menu are opened */ + &:has(.mx_RoomListItemMenuView > button[data-state="open"]) { + background-color: var(--cpd-color-bg-action-secondary-hovered); + + .mx_RoomListItemView_menu { + display: flex; + } + + &.mx_RoomListItemView_has_menu { + /** + * The figma uses 16px padding (--cpd-space-4x) but due to https://github.com/element-hq/compound-web/issues/331 + * the icon size of the menu is 18px instead of 20px with a different internal padding + * We need to use 18px to align the icon with the others icons + * 18px is not available in compound spacing + */ + .mx_RoomListItemView_content { + padding-right: 18px; + } + + /* When the menu is visible, hide the notification decoration to avoid clutter */ + .mx_RoomListItemView_notificationDecoration { + display: none; + } + } + } + .mx_RoomListItemView_content { height: 100%; flex: 1; @@ -57,20 +92,6 @@ } } -.mx_RoomListItemView_hover { - background-color: var(--cpd-color-bg-action-secondary-hovered); -} - -.mx_RoomListItemView_menu_open .mx_RoomListItemView_content { - /** - * The figma uses 16px padding (--cpd-space-4x) but due to https://github.com/element-hq/compound-web/issues/331 - * the icon size of the menu is 18px instead of 20px with a different internal padding - * We need to use 18px to align the icon with the others icons - * 18px is not available in compound spacing - */ - padding-right: 18px; -} - .mx_RoomListItemView_selected { background-color: var(--cpd-color-bg-action-secondary-pressed); } diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx index 0769d9e40a..f3ba4167e7 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemContextMenuView.tsx @@ -19,10 +19,6 @@ interface RoomListItemContextMenuViewProps { * The room to display the menu for. */ room: Room; - /** - * Set the menu open state. - */ - setMenuOpen: (isOpen: boolean) => void; } /** @@ -30,7 +26,6 @@ interface RoomListItemContextMenuViewProps { */ export function RoomListItemContextMenuView({ room, - setMenuOpen, children, }: PropsWithChildren): JSX.Element { const vm = useRoomListItemMenuViewModel(room); @@ -42,7 +37,6 @@ export function RoomListItemContextMenuView({ // To not mess with the roving tab index of the button hasAccessibleAlternative={true} trigger={children} - onOpenChange={setMenuOpen} > diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx index ad92559f5c..1162d07e9a 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx @@ -5,8 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { type ComponentProps, type JSX, type Ref, useState } from "react"; -import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem, Tooltip } from "@vector-im/compound-web"; +import React, { type JSX, useState } from "react"; +import { IconButton, Menu, MenuItem, Separator, ToggleMenuItem } from "@vector-im/compound-web"; import MarkAsReadIcon from "@vector-im/compound-design-tokens/assets/web/icons/mark-as-read"; import MarkAsUnreadIcon from "@vector-im/compound-design-tokens/assets/web/icons/mark-as-unread"; import FavouriteIcon from "@vector-im/compound-design-tokens/assets/web/icons/favourite"; @@ -20,6 +20,7 @@ import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/ic import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check"; import { type Room } from "matrix-js-sdk/src/matrix"; import { Flex } from "@element-hq/web-shared-components"; +import classNames from "classnames"; import { _t } from "../../../../languageHandler"; import { @@ -29,26 +30,27 @@ import { import { RoomNotifState } from "../../../../RoomNotifs"; interface RoomListItemMenuViewProps { + /** + * Additional class name for the root element. + */ + className?: string; + /** * The room to display the menu for. */ room: Room; - /** - * Set the menu open state. - */ - setMenuOpen: (isOpen: boolean) => void; } /** * A view for the room list item menu. */ -export function RoomListItemMenuView({ room, setMenuOpen }: RoomListItemMenuViewProps): JSX.Element { +export function RoomListItemMenuView({ room, className }: RoomListItemMenuViewProps): JSX.Element { const vm = useRoomListItemMenuViewModel(room); return ( - - {vm.showMoreOptionsMenu && } - {vm.showNotificationMenu && } + + {vm.showMoreOptionsMenu && } + {vm.showNotificationMenu && } ); } @@ -58,30 +60,30 @@ interface MoreOptionsMenuProps { * The view model state for the menu. */ vm: RoomListItemMenuViewState; - /** - * Set the menu open state. - * @param isOpen - */ - setMenuOpen: (isOpen: boolean) => void; } /** * The more options menu for the room list item. */ -function MoreOptionsMenu({ vm, setMenuOpen }: MoreOptionsMenuProps): JSX.Element { +function MoreOptionsMenu({ vm }: MoreOptionsMenuProps): JSX.Element { const [open, setOpen] = useState(false); return ( { - setOpen(isOpen); - setMenuOpen(isOpen); - }} + onOpenChange={setOpen} title={_t("room_list|room|more_options")} showTitle={false} align="start" - trigger={} + trigger={ + + + + } > @@ -164,55 +166,37 @@ export function MoreOptionContent({ vm }: MoreOptionContentProps): JSX.Element { ); } -interface MoreOptionsButtonProps extends ComponentProps { - ref?: Ref; -} - -/** - * A button to trigger the more options menu. - */ -const MoreOptionsButton = function MoreOptionsButton(props: MoreOptionsButtonProps): JSX.Element { - return ( - - - - - - ); -}; - interface NotificationMenuProps { /** * The view model state for the menu. */ vm: RoomListItemMenuViewState; - /** - * Set the menu open state. - * @param isOpen - */ - setMenuOpen: (isOpen: boolean) => void; } -function NotificationMenu({ vm, setMenuOpen }: NotificationMenuProps): JSX.Element { +function NotificationMenu({ vm }: NotificationMenuProps): JSX.Element { const [open, setOpen] = useState(false); - const checkComponent = ; return ( -
e.stopPropagation()} + + {vm.isNotificationMute ? : } + + } > - { - setOpen(isOpen); - setMenuOpen(isOpen); - }} - title={_t("room_list|notification_options")} - showTitle={false} - align="start" - trigger={} +
e.stopPropagation()} > {vm.isNotificationMute && checkComponent} -
-
+ + ); } - -interface NotificationButtonProps extends ComponentProps { - /** - * Whether the room is muted. - */ - isRoomMuted: boolean; - ref?: Ref; -} - -/** - * A button to trigger the notification menu. - */ -const NotificationButton = function MoreOptionsButton({ - isRoomMuted, - ref, - ...props -}: NotificationButtonProps): JSX.Element { - return ( - - - {isRoomMuted ? : } - - - ); -}; diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index 5db97397dc..d87da9c034 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, memo, useCallback, useEffect, useRef, useState } from "react"; +import React, { type JSX, memo, useEffect, useRef } from "react"; import { type Room } from "matrix-js-sdk/src/matrix"; import classNames from "classnames"; import { Flex } from "@element-hq/web-shared-components"; @@ -57,18 +57,6 @@ export const RoomListItemView = memo(function RoomListItemView({ }: RoomListItemViewProps): JSX.Element { const ref = useRef(null); const vm = useRoomListItemViewModel(room); - const [isHover, setHover] = useState(false); - const [isMenuOpen, setIsMenuOpen] = useState(false); - // The compound menu in RoomListItemMenuView needs to be rendered when the hover menu is shown - // Using display: none; and then display:flex when hovered in CSS causes the menu to be misaligned - const showHoverDecoration = isMenuOpen || isFocused || isHover; - const showHoverMenu = showHoverDecoration && vm.showHoverMenu; - - const closeMenu = useCallback(() => { - // To avoid icon blinking when closing the menu, we delay the state update - // Also, let the focus move to the menu trigger before closing the menu - setTimeout(() => setIsMenuOpen(false), 10); - }, []); useEffect(() => { if (isFocused) { @@ -81,8 +69,7 @@ export const RoomListItemView = memo(function RoomListItemView({ as="button" ref={ref} className={classNames("mx_RoomListItemView", { - mx_RoomListItemView_hover: showHoverDecoration, - mx_RoomListItemView_menu_open: showHoverMenu, + mx_RoomListItemView_has_menu: vm.showHoverMenu, mx_RoomListItemView_selected: isSelected, mx_RoomListItemView_bold: vm.isBold, })} @@ -96,8 +83,6 @@ export const RoomListItemView = memo(function RoomListItemView({ aria-label={vm.a11yLabel} onClick={() => vm.openRoom()} onFocus={(e: React.FocusEvent) => onFocus(room, e)} - onMouseOver={() => setHover(true)} - onMouseOut={() => setHover(false)} tabIndex={isFocused ? 0 : -1} {...props} > @@ -119,44 +104,21 @@ export const RoomListItemView = memo(function RoomListItemView({ )} - {showHoverMenu ? ( - (isOpen ? setIsMenuOpen(true) : closeMenu())} + {vm.showHoverMenu && } + + {/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */} + {vm.showNotificationDecoration && ( + - ) : ( - <> - {/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */} - {vm.showNotificationDecoration && ( - - )} - )}
); - // Rendering multiple context menus can causes crashes in radix upstream, - // See https://github.com/radix-ui/primitives/issues/2717. if (!vm.showContextMenu) return content; - - return ( - { - if (isOpen) { - // To avoid icon blinking when the context menu is re-opened - setTimeout(() => setIsMenuOpen(true), 0); - } else { - closeMenu(); - } - }} - > - {content} - - ); + return {content}; }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx index 4da783d10c..58ab0c672b 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx @@ -56,8 +56,8 @@ describe("", () => { room = mkRoom(matrixClient, "room1"); }); - function renderMenu(setMenuOpen = jest.fn()) { - return render(); + function renderMenu() { + return render(); } it("should render the more options menu", () => { @@ -84,18 +84,6 @@ describe("", () => { expect(screen.queryByRole("button", { name: "Notification options" })).toBeNull(); }); - it.each([["More Options"], ["Notification options"]])( - "should call setMenuOpen when the menu is opened for %s menu", - async (label) => { - const user = userEvent.setup(); - const setMenuOpen = jest.fn(); - renderMenu(setMenuOpen); - - await user.click(screen.getByRole("button", { name: label })); - expect(setMenuOpen).toHaveBeenCalledWith(true); - }, - ); - it("should display all the buttons and have the actions linked for the more options menu", async () => { const user = userEvent.setup(); renderMenu(); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx index 499573f8a7..d3a8fdfbeb 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx @@ -102,41 +102,6 @@ describe("", () => { expect(defaultValue.openRoom).toHaveBeenCalled(); }); - test("should hover decoration if hovered", async () => { - mocked(useRoomListItemViewModel).mockReturnValue({ ...defaultValue, showHoverMenu: true }); - - const user = userEvent.setup(); - renderRoomListItem(); - - const listItem = screen.getByRole("option", { name: `Open room ${room.name}` }); - expect(screen.queryByRole("button", { name: "More Options" })).toBeNull(); - - await user.hover(listItem); - await waitFor(() => expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument()); - }); - - test("should hover decoration if focused", async () => { - const { rerender } = renderRoomListItem({ - isFocused: true, - }); - - const listItem = screen.getByRole("option", { name: `Open room ${room.name}` }); - expect(listItem).toHaveClass("_flex_4dswl_9 mx_RoomListItemView mx_RoomListItemView_hover"); - - rerender( - , - ); - - await waitFor(() => expect(listItem).not.toHaveClass("flex mx_RoomListItemView mx_RoomListItemView_hover")); - }); - test("should be selected if isSelected=true", async () => { const { asFragment } = renderRoomListItem({ isSelected: true, diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap index 71bfd45578..4456a46f1c 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap @@ -30,7 +30,7 @@ exports[` should render a room list 1`] = ` aria-posinset="1" aria-selected="false" aria-setsize="10" - class="_flex_4dswl_9 mx_RoomListItemView" + class="_flex_4dswl_9 mx_RoomListItemView mx_RoomListItemView_has_menu" data-state="closed" role="option" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: start; --mx-flex-gap: var(--cpd-space-3x); --mx-flex-wrap: nowrap;" @@ -70,506 +70,1186 @@ exports[` should render a room list 1`] = ` room0 +
- -
-
-
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
+
+ + + +
+ +
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap index a5f199f767..91e687412f 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap @@ -38,43 +38,41 @@ exports[` should render the more options menu 1`] = ` -
- -
+ + + + + `; @@ -117,43 +115,41 @@ exports[` should render the notification options menu 1` -
- -
+ + + + + `; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap index 9e84fc2d40..f46588370f 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap @@ -99,7 +99,7 @@ exports[` should display notification decoration 1`] = `