New room list: add notification decoration (#29552)

* chore: update @compound-web

* feat(notification decoration): add NotificationDecoration component

* feat(room list item): get notification state in view model

* feat(room list item): use notification decoration in RoomListItemView

* test(notification decoration): add tests

* test(room list item view model): add a11yLabel tests

* test(room list item): update tests

* test(e2e): add decoration tests
This commit is contained in:
Florian Duros
2025-03-26 14:32:02 +01:00
committed by GitHub
parent f3f05874fa
commit bbd798ef36
17 changed files with 563 additions and 66 deletions

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React, { type HTMLProps, type JSX } from "react";
import MentionIcon from "@vector-im/compound-design-tokens/assets/web/icons/mention";
import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error";
import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid";
import { UnreadCounter, Unread } from "@vector-im/compound-web";
import { Flex } from "../../utils/Flex";
import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState";
interface NotificationDecorationProps extends HTMLProps<HTMLDivElement> {
/**
* The notification state of the room or thread.
*/
notificationState: RoomNotificationState;
}
/**
* Displays the notification decoration for a room or a thread.
*/
export function NotificationDecoration({
notificationState,
...props
}: NotificationDecorationProps): JSX.Element | null {
const {
hasAnyNotificationOrActivity,
isUnsetMessage,
invited,
isMention,
isActivityNotification,
isNotification,
count,
muted,
} = notificationState;
if (!hasAnyNotificationOrActivity && !muted) return null;
return (
<Flex
align="center"
justify="center"
gap="var(--cpd-space-1x)"
{...props}
data-testid="notification-decoration"
>
{isUnsetMessage && <ErrorIcon width="20px" height="20px" fill="var(--cpd-color-icon-critical-primary)" />}
{invited && <UnreadCounter count={1} />}
{isMention && <MentionIcon width="20px" height="20px" fill="var(--cpd-color-icon-accent-primary)" />}
{(isMention || isNotification) && <UnreadCounter count={count || null} />}
{isActivityNotification && <Unread />}
{muted && <NotificationOffIcon width="20px" height="20px" fill="var(--cpd-color-icon-tertiary)" />}
</Flex>
);
}

View File

@@ -12,8 +12,8 @@ import classNames from "classnames";
import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel";
import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
import { Flex } from "../../../utils/Flex";
import { _t } from "../../../../languageHandler";
import { RoomListItemMenuView } from "./RoomListItemMenuView";
import { NotificationDecoration } from "../NotificationDecoration";
interface RoomListItemViewPropsProps extends React.HTMLAttributes<HTMLButtonElement> {
/**
@@ -46,7 +46,7 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie
})}
type="button"
aria-selected={isSelected}
aria-label={_t("room_list|room|open_room", { roomName: room.name })}
aria-label={vm.a11yLabel}
onClick={() => vm.openRoom()}
onMouseOver={() => setIsHover(true)}
onMouseOut={() => setIsHover(false)}
@@ -65,7 +65,7 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie
>
{/* We truncate the room name when too long. Title here is to show the full name on hover */}
<span title={room.name}>{room.name}</span>
{showHoverDecoration && (
{showHoverDecoration ? (
<RoomListItemMenuView
room={room}
setMenuOpen={(isOpen) => {
@@ -74,6 +74,11 @@ export function RoomListItemView({ room, isSelected, ...props }: RoomListItemVie
else setTimeout(() => setIsMenuOpen(isOpen), 0);
}}
/>
) : (
<>
{/* aria-hidden because we summarise the unread count/notification status in a11yLabel variable */}
<NotificationDecoration notificationState={vm.notificationState} aria-hidden={true} />
</>
)}
</Flex>
</Flex>