Skip to content

Commit 40c037c

Browse files
refactor: sidebar badges
1 parent fb7a7b8 commit 40c037c

21 files changed

+592
-159
lines changed

apps/meteor/client/sidebar/RoomList/SideBarItemTemplateWithData.tsx

Lines changed: 9 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { IMessage, IRoom, ISubscription } from '@rocket.chat/core-typings';
22
import { isDirectMessageRoom, isMultipleDirectMessageRoom, isOmnichannelRoom, isVideoConfMessage } from '@rocket.chat/core-typings';
3-
import { Badge, Sidebar, SidebarItemAction, SidebarItemActions, Margins } from '@rocket.chat/fuselage';
3+
import { Sidebar, SidebarItemAction, SidebarItemActions } from '@rocket.chat/fuselage';
44
import { useLayout } from '@rocket.chat/ui-contexts';
55
import DOMPurify from 'dompurify';
66
import type { TFunction } from 'i18next';
@@ -13,8 +13,9 @@ import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
1313
import { isIOsDevice } from '../../lib/utils/isIOsDevice';
1414
import { useOmnichannelPriorities } from '../../views/omnichannel/hooks/useOmnichannelPriorities';
1515
import RoomMenu from '../RoomMenu';
16-
import { OmnichannelBadges } from '../badges/OmnichannelBadges';
16+
import SidebarItemBadges from '../badges/SidebarItemBadges';
1717
import type { useAvatarTemplate } from '../hooks/useAvatarTemplate';
18+
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
1819

1920
const getMessage = (room: IRoom, lastMessage: IMessage | undefined, t: TFunction): string | undefined => {
2021
if (!lastMessage) {
@@ -35,24 +36,6 @@ const getMessage = (room: IRoom, lastMessage: IMessage | undefined, t: TFunction
3536
return `${lastMessage.u.name || lastMessage.u.username}: ${normalizeSidebarMessage(lastMessage, t)}`;
3637
};
3738

38-
const getBadgeTitle = (userMentions: number, threadUnread: number, groupMentions: number, unread: number, t: TFunction) => {
39-
const title = [] as string[];
40-
if (userMentions) {
41-
title.push(t('mentions_counter', { count: userMentions }));
42-
}
43-
if (threadUnread) {
44-
title.push(t('threads_counter', { count: threadUnread }));
45-
}
46-
if (groupMentions) {
47-
title.push(t('group_mentions_counter', { count: groupMentions }));
48-
}
49-
const count = unread - userMentions - groupMentions;
50-
if (count > 0) {
51-
title.push(t('unread_messages_counter', { count }));
52-
}
53-
return title.join(', ');
54-
};
55-
5639
type RoomListRowProps = {
5740
extended: boolean;
5841
t: TFunction;
@@ -110,20 +93,9 @@ function SideBarItemTemplateWithData({
11093
const href = roomCoordinator.getRouteLink(room.t, room) || '';
11194
const title = roomCoordinator.getRoomName(room.t, room) || '';
11295

113-
const {
114-
lastMessage,
115-
hideUnreadStatus,
116-
hideMentionStatus,
117-
unread = 0,
118-
alert,
119-
userMentions,
120-
groupMentions,
121-
tunread = [],
122-
tunreadUser = [],
123-
rid,
124-
t: type,
125-
cl,
126-
} = room;
96+
const { lastMessage, hideUnreadStatus, unread = 0, alert, rid, t: type, cl } = room;
97+
98+
const { unreadCount, unreadTitle, showUnread } = useUnreadDisplay(room);
12799

128100
const highlighted = Boolean(!hideUnreadStatus && (alert || unread));
129101
const icon = (
@@ -152,32 +124,6 @@ function SideBarItemTemplateWithData({
152124
<span className='message-body--unstyled' dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(message) }} />
153125
) : null;
154126

155-
const threadUnread = tunread.length > 0;
156-
const variant =
157-
((userMentions || tunreadUser.length) && 'danger') || (threadUnread && 'primary') || (groupMentions && 'warning') || 'secondary';
158-
159-
const isUnread = unread > 0 || threadUnread;
160-
const showBadge = !hideUnreadStatus || (!hideMentionStatus && (Boolean(userMentions) || tunreadUser.length > 0));
161-
162-
const badgeTitle = getBadgeTitle(userMentions, tunread.length, groupMentions, unread, t);
163-
164-
const badges = (
165-
<Margins inlineStart={8}>
166-
{showBadge && isUnread && (
167-
<Badge
168-
role='status'
169-
{...({ style: { display: 'inline-flex', flexShrink: 0 } } as any)}
170-
variant={variant}
171-
title={badgeTitle}
172-
aria-label={t('__unreadTitle__from__roomTitle__', { unreadTitle: badgeTitle, roomTitle: title })}
173-
>
174-
<span aria-hidden>{unread + tunread?.length}</span>
175-
</Badge>
176-
)}
177-
{isOmnichannelRoom(room) && <OmnichannelBadges room={room} />}
178-
</Margins>
179-
);
180-
181127
return (
182128
<SideBarItemTemplate
183129
is='a'
@@ -190,21 +136,21 @@ function SideBarItemTemplateWithData({
190136
onClick={(): void => {
191137
!selected && sidebar.toggle();
192138
}}
193-
aria-label={showBadge && isUnread ? t('__unreadTitle__from__roomTitle__', { unreadTitle: badgeTitle, roomTitle: title }) : title}
139+
aria-label={showUnread ? t('__unreadTitle__from__roomTitle__', { unreadTitle, roomTitle: title }) : title}
194140
title={title}
195141
time={lastMessage?.ts}
196142
subtitle={subtitle}
197143
icon={icon}
198144
style={style}
199-
badges={badges}
145+
badges={<SidebarItemBadges room={room} roomTitle={title} />}
200146
avatar={AvatarTemplate && <AvatarTemplate {...room} />}
201147
actions={actions}
202148
menu={
203149
!isIOsDevice && !isAnonymous && (!isQueued || (isQueued && isPriorityEnabled))
204150
? (): ReactElement => (
205151
<RoomMenu
206152
alert={alert}
207-
threadUnread={threadUnread}
153+
threadUnread={unreadCount.threads > 0}
208154
rid={rid}
209155
unread={!!unread}
210156
roomOpen={selected}

apps/meteor/client/sidebar/badges/OmnichannelBadges.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import RoomActivityIcon from '../../views/omnichannel/components/RoomActivityIco
55
import { useOmnichannelPriorities } from '../../views/omnichannel/hooks/useOmnichannelPriorities';
66
import { PriorityIcon } from '../../views/omnichannel/priorities/PriorityIcon';
77

8-
export const OmnichannelBadges = ({ room }: { room: ISubscription & IRoom }) => {
8+
const OmnichannelBadges = ({ room }: { room: ISubscription & IRoom }) => {
99
const { enabled: isPriorityEnabled } = useOmnichannelPriorities();
1010

1111
if (!isOmnichannelRoom(room)) {
@@ -19,3 +19,5 @@ export const OmnichannelBadges = ({ room }: { room: ISubscription & IRoom }) =>
1919
</>
2020
);
2121
};
22+
23+
export default OmnichannelBadges;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { IRoom, ISubscription } from '@rocket.chat/core-typings';
2+
import { mockAppRoot } from '@rocket.chat/mock-providers';
3+
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
4+
import { render, screen } from '@testing-library/react';
5+
6+
import SidebarItemBadges from './SidebarItemBadges';
7+
import { createFakeSubscription } from '../../../tests/mocks/data';
8+
9+
jest.mock('./OmnichannelBadges', () => ({
10+
__esModule: true,
11+
default: () => <div data-testid='omnichannel-badges'>OmnichannelBadges</div>,
12+
}));
13+
14+
const createRoomWithSubscription = (overrides: Partial<SubscriptionWithRoom> = {}) => {
15+
return createFakeSubscription(overrides) as unknown as IRoom & ISubscription;
16+
};
17+
18+
describe('SidebarItemBadges', () => {
19+
const appRoot = mockAppRoot()
20+
.withTranslations('en', 'core', {
21+
Message_request: 'Message request',
22+
mentions_counter_one: '{{count}} mention',
23+
mentions_counter_other: '{{count}} mentions',
24+
__unreadTitle__from__roomTitle__: '{{unreadTitle}} from {{roomTitle}}',
25+
})
26+
.build();
27+
28+
afterEach(() => {
29+
jest.resetAllMocks();
30+
});
31+
32+
it('shound render UnreadBadge when there are unread messages', () => {
33+
render(
34+
<SidebarItemBadges room={createRoomWithSubscription({ unread: 1, userMentions: 1, groupMentions: 0 })} roomTitle='Test Room' />,
35+
{ wrapper: appRoot },
36+
);
37+
38+
expect(screen.getByRole('status', { name: '1 mention from Test Room' })).toBeInTheDocument();
39+
});
40+
41+
it('shound not render UnreadBadge when there are no unread messages', () => {
42+
render(
43+
<SidebarItemBadges room={createRoomWithSubscription({ unread: 0, userMentions: 0, groupMentions: 0 })} roomTitle='Test Room' />,
44+
{ wrapper: appRoot },
45+
);
46+
47+
expect(screen.queryByRole('status', { name: 'Test Room' })).not.toBeInTheDocument();
48+
});
49+
50+
it('should render OmnichannelBadges when the room is an omnichannel room', () => {
51+
render(<SidebarItemBadges room={createRoomWithSubscription({ t: 'l' })} />, { wrapper: appRoot });
52+
53+
expect(screen.getByTestId('omnichannel-badges')).toBeInTheDocument();
54+
});
55+
56+
it('should not render OmnichannelBadges when the room is not an omnichannel room', () => {
57+
render(<SidebarItemBadges room={createRoomWithSubscription({ t: 'p' })} />, { wrapper: appRoot });
58+
59+
expect(screen.queryByTestId('omnichannel-badges')).not.toBeInTheDocument();
60+
});
61+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { IRoom, ISubscription } from '@rocket.chat/core-typings';
2+
import { isOmnichannelRoom } from '@rocket.chat/core-typings';
3+
import { Margins } from '@rocket.chat/fuselage';
4+
5+
import OmnichannelBadges from './OmnichannelBadges';
6+
import UnreadBadge from './UnreadBadge';
7+
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
8+
9+
type SidebarItemBadgesProps = {
10+
room: ISubscription & IRoom;
11+
roomTitle?: string;
12+
};
13+
14+
const SidebarItemBadges = ({ room, roomTitle }: SidebarItemBadgesProps) => {
15+
const { unreadCount, unreadTitle, unreadVariant, showUnread } = useUnreadDisplay(room);
16+
17+
return (
18+
<Margins inlineStart={8}>
19+
{showUnread && <UnreadBadge title={unreadTitle} roomTitle={roomTitle} variant={unreadVariant} total={unreadCount.total} />}
20+
21+
{isOmnichannelRoom(room) && <OmnichannelBadges room={room} />}
22+
</Margins>
23+
);
24+
};
25+
26+
export default SidebarItemBadges;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Badge } from '@rocket.chat/fuselage';
2+
import { useTranslation } from 'react-i18next';
3+
4+
type UnreadBadgeProps = {
5+
title: string;
6+
roomTitle?: string;
7+
variant: 'primary' | 'warning' | 'danger' | 'secondary';
8+
total: number;
9+
};
10+
11+
const UnreadBadge = ({ title, variant, total, roomTitle }: UnreadBadgeProps) => {
12+
const { t } = useTranslation();
13+
14+
return (
15+
<Badge
16+
role='status'
17+
{...({ style: { display: 'inline-flex', flexShrink: 0 } } as any)}
18+
variant={variant}
19+
title={title}
20+
aria-label={t('__unreadTitle__from__roomTitle__', { unreadTitle: title, roomTitle })}
21+
>
22+
<span aria-hidden>{total}</span>
23+
</Badge>
24+
);
25+
};
26+
27+
export default UnreadBadge;

0 commit comments

Comments
 (0)