Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 95430ce

Browse files
authored
Add notification dots to thread summary icons (#12146)
* Add notification dots to thread summary icons Adopts new IndicatorIcon from compound to have threads icons with indicator dot (that aren't also buttons). Adds green & red dots on the threads icon in the thread summary to indicate notifications. Changes the notification level dots colours in the threads panel to be green to match. * Update test for new CSS class * Update snapshots with new class name * Another snapshot update for new class name * Replace more uses of old class name in tests * More snapshot updates for new class name * Unsure how this ever worked in chronological mode * More snapshot updates * Fix dot colours * Upgrade to compound-web 3 * Fix computed notification levels * Add test for notificationLevelToIndicator
1 parent f684ad5 commit 95430ce

File tree

19 files changed

+130
-54
lines changed

19 files changed

+130
-54
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"@sentry/browser": "^7.0.0",
7575
"@testing-library/react-hooks": "^8.0.1",
7676
"@vector-im/compound-design-tokens": "^0.1.0",
77-
"@vector-im/compound-web": "2.0.0",
77+
"@vector-im/compound-web": "3.0.0",
7878
"@zxcvbn-ts/core": "^3.0.4",
7979
"@zxcvbn-ts/language-common": "^3.0.4",
8080
"@zxcvbn-ts/language-en": "^3.0.2",

res/css/views/rooms/_NotificationBadge.pcss

+18-5
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,34 @@ limitations under the License.
3333
align-items: center;
3434
justify-content: center;
3535

36-
&.mx_NotificationBadge_highlighted {
37-
/* TODO: Use a more specific variable */
38-
background-color: $alert;
39-
}
40-
4136
/* These are the 3 background types */
4237

4338
&.mx_NotificationBadge_dot {
4439
width: 8px;
4540
height: 8px;
4641
border-radius: 8px;
42+
background-color: var(--cpd-color-text-primary);
4743

4844
.mx_NotificationBadge_count {
4945
display: none;
5046
}
47+
48+
/* Redundant sounding name, but a notification badge that indicates there is a regular,
49+
* non-highlight notification
50+
* The green colour only applies for notification dot: badges indicating the same notification
51+
* level are the standard grey.
52+
*/
53+
&.mx_NotificationBadge_level_notification {
54+
background-color: var(--cpd-color-icon-success-primary);
55+
}
56+
}
57+
58+
/* Badges for highlight notifications. Style for notification level
59+
* badges is in _EventTile.scss because it applies only to notification
60+
* dots, not badges.
61+
*/
62+
&.mx_NotificationBadge_level_highlight {
63+
background-color: var(--cpd-color-icon-critical-primary);
5164
}
5265

5366
&.mx_NotificationBadge_knocked {

src/components/views/rooms/NotificationBadge/StatelessNotificationBadge.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export const StatelessNotificationBadge = forwardRef<HTMLDivElement, XOR<Props,
5959
const classes = classNames({
6060
mx_NotificationBadge: true,
6161
mx_NotificationBadge_visible: isEmptyBadge || knocked ? true : hasUnreadCount,
62-
mx_NotificationBadge_highlighted: level >= NotificationLevel.Highlight,
62+
mx_NotificationBadge_level_notification: level == NotificationLevel.Notification,
63+
mx_NotificationBadge_level_highlight: level >= NotificationLevel.Highlight,
6364
mx_NotificationBadge_dot: (isEmptyBadge && !knocked) || type === "dot",
6465
mx_NotificationBadge_knocked: knocked,
6566
mx_NotificationBadge_2char: type === "badge" && symbol && symbol.length > 0 && symbol.length < 3,

src/components/views/rooms/RoomHeader.tsx

+1-15
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import { Flex } from "../../utils/Flex";
3737
import { Box } from "../../utils/Box";
3838
import { useRoomCall } from "../../../hooks/room/useRoomCall";
3939
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
40-
import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
4140
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
4241
import SdkConfig from "../../../SdkConfig";
4342
import { useFeatureEnabled } from "../../../hooks/useSettings";
@@ -52,20 +51,7 @@ import { Linkify, topicToHtml } from "../../../HtmlUtils";
5251
import PosthogTrackers from "../../../PosthogTrackers";
5352
import { VideoRoomChatButton } from "./RoomHeader/VideoRoomChatButton";
5453
import { RoomKnocksBar } from "./RoomKnocksBar";
55-
56-
/**
57-
* A helper to transform a notification color to the what the Compound Icon Button
58-
* expects
59-
*/
60-
function notificationLevelToIndicator(color: NotificationLevel): React.ComponentProps<typeof IconButton>["indicator"] {
61-
if (color <= NotificationLevel.None) {
62-
return undefined;
63-
} else if (color <= NotificationLevel.Notification) {
64-
return "default";
65-
} else {
66-
return "highlight";
67-
}
68-
}
54+
import { notificationLevelToIndicator } from "../../../utils/notifications";
6955

7056
export default function RoomHeader({
7157
room,

src/components/views/rooms/ThreadSummary.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ limitations under the License.
1616

1717
import React, { useContext, useState } from "react";
1818
import { Thread, ThreadEvent, IContent, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/matrix";
19+
import { IndicatorIcon } from "@vector-im/compound-web";
20+
import { Icon as ThreadIconSolid } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
1921

2022
import { _t } from "../../../languageHandler";
2123
import { CardContext } from "../right_panel/context";
@@ -30,6 +32,8 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
3032
import { Action } from "../../../dispatcher/actions";
3133
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
3234
import defaultDispatcher from "../../../dispatcher/dispatcher";
35+
import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications";
36+
import { notificationLevelToIndicator } from "../../../utils/notifications";
3337

3438
interface IProps {
3539
mxEvent: MatrixEvent;
@@ -40,6 +44,8 @@ const ThreadSummary: React.FC<IProps> = ({ mxEvent, thread, ...props }) => {
4044
const roomContext = useContext(RoomContext);
4145
const cardContext = useContext(CardContext);
4246
const count = useTypedEventEmitterState(thread, ThreadEvent.Update, () => thread.length);
47+
const { level } = useUnreadNotifications(thread.room, thread.id);
48+
4349
if (!count) return null; // We don't want to show a thread summary if the thread doesn't have replies yet
4450

4551
let countSection: string | number = count;
@@ -61,6 +67,9 @@ const ThreadSummary: React.FC<IProps> = ({ mxEvent, thread, ...props }) => {
6167
}}
6268
aria-label={_t("threads|open_thread")}
6369
>
70+
<IndicatorIcon size="24px" indicator={notificationLevelToIndicator(level)}>
71+
<ThreadIconSolid />
72+
</IndicatorIcon>
6473
<span className="mx_ThreadSummary_replies_amount">{countSection}</span>
6574
<ThreadMessagePreview thread={thread} showDisplayname={!roomContext.narrow} />
6675
<div className="mx_ThreadSummary_chevron" />

src/hooks/room/useRoomThreadNotifications.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ export const useRoomThreadNotifications = (room: Room): NotificationLevel => {
3333
switch (room?.threadsAggregateNotificationType) {
3434
case NotificationCountType.Highlight:
3535
setNotificationLevel(NotificationLevel.Highlight);
36-
break;
36+
return;
3737
case NotificationCountType.Total:
3838
setNotificationLevel(NotificationLevel.Notification);
39-
break;
39+
return;
4040
}
4141
// We don't have any notified messages, but we might have unread messages. Let's
4242
// find out.

src/utils/notifications.ts

+20
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import {
2222
LocalNotificationSettings,
2323
ReceiptType,
2424
} from "matrix-js-sdk/src/matrix";
25+
import { IndicatorIcon } from "@vector-im/compound-web";
2526

2627
import SettingsStore from "../settings/SettingsStore";
28+
import { NotificationLevel } from "../stores/notifications/NotificationLevel";
2729

2830
export const deviceNotificationSettingsKeys = [
2931
"notificationsEnabled",
@@ -113,3 +115,21 @@ export function clearAllNotifications(client: MatrixClient): Promise<Array<{} |
113115

114116
return Promise.all(receiptPromises);
115117
}
118+
119+
/**
120+
* A helper to transform a notification color to the what the Compound Icon Button
121+
* expects
122+
*/
123+
export function notificationLevelToIndicator(
124+
level: NotificationLevel,
125+
): React.ComponentPropsWithRef<typeof IndicatorIcon>["indicator"] {
126+
if (level <= NotificationLevel.None) {
127+
return undefined;
128+
} else if (level <= NotificationLevel.Activity) {
129+
return "default";
130+
} else if (level <= NotificationLevel.Notification) {
131+
return "success";
132+
} else {
133+
return "critical";
134+
}
135+
}

test/components/structures/TimelinePanel-test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ describe("TimelinePanel", () => {
817817
client = MatrixClientPeg.safeGet();
818818

819819
Thread.hasServerSideSupport = FeatureSupport.Stable;
820-
room = new Room("roomId", client, "userId");
820+
room = new Room("roomId", client, "userId", { pendingEventOrdering: PendingEventOrdering.Detached });
821821
allThreads = new EventTimelineSet(
822822
room,
823823
{

test/components/structures/__snapshots__/RoomStatusBar-test.tsx.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ exports[`RoomStatusBar <RoomStatusBar /> unsent messages should render warning w
1212
class="mx_RoomStatusBar_unsentBadge"
1313
>
1414
<div
15-
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char"
15+
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_highlight mx_NotificationBadge_2char"
1616
>
1717
<span
1818
class="mx_NotificationBadge_count"
@@ -81,7 +81,7 @@ exports[`RoomStatusBar <RoomStatusBar /> unsent messages should render warning w
8181
class="mx_RoomStatusBar_unsentBadge"
8282
>
8383
<div
84-
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char"
84+
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_highlight mx_NotificationBadge_2char"
8585
>
8686
<span
8787
class="mx_NotificationBadge_count"

test/components/structures/__snapshots__/RoomView-test.tsx.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`]
215215
class="mx_RoomStatusBar_unsentBadge"
216216
>
217217
<div
218-
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char"
218+
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_highlight mx_NotificationBadge_2char"
219219
>
220220
<span
221221
class="mx_NotificationBadge_count"

test/components/views/rooms/EventTile-test.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,14 @@ describe("EventTile", () => {
163163
});
164164

165165
expect(container.getElementsByClassName("mx_NotificationBadge")).toHaveLength(1);
166-
expect(container.getElementsByClassName("mx_NotificationBadge_highlighted")).toHaveLength(0);
166+
expect(container.getElementsByClassName("mx_NotificationBadge_level_highlight")).toHaveLength(0);
167167

168168
act(() => {
169169
room.setThreadUnreadNotificationCount(mxEvent.getId()!, NotificationCountType.Highlight, 1);
170170
});
171171

172172
expect(container.getElementsByClassName("mx_NotificationBadge")).toHaveLength(1);
173-
expect(container.getElementsByClassName("mx_NotificationBadge_highlighted")).toHaveLength(1);
173+
expect(container.getElementsByClassName("mx_NotificationBadge_level_highlight")).toHaveLength(1);
174174
});
175175
});
176176

test/components/views/rooms/NotificationBadge/StatelessNotificationBadge-test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe("StatelessNotificationBadge", () => {
2525
const { container } = render(
2626
<StatelessNotificationBadge symbol="!" count={0} level={NotificationLevel.Unsent} />,
2727
);
28-
expect(container.querySelector(".mx_NotificationBadge_highlighted")).not.toBe(null);
28+
expect(container.querySelector(".mx_NotificationBadge_level_highlight")).not.toBe(null);
2929
});
3030

3131
it("has knock style", () => {

test/components/views/rooms/NotificationBadge/UnreadNotificationBadge-test.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -92,26 +92,26 @@ describe("UnreadNotificationBadge", () => {
9292
const { container } = render(getComponent());
9393

9494
expect(container.querySelector(".mx_NotificationBadge_visible")).toBeTruthy();
95-
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeFalsy();
95+
expect(container.querySelector(".mx_NotificationBadge_level_highlight")).toBeFalsy();
9696

9797
act(() => {
9898
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
9999
});
100100

101-
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeTruthy();
101+
expect(container.querySelector(".mx_NotificationBadge_level_highlight")).toBeTruthy();
102102
});
103103

104104
it("renders unread thread notification badge", () => {
105105
const { container } = render(getComponent(THREAD_ID));
106106

107107
expect(container.querySelector(".mx_NotificationBadge_visible")).toBeTruthy();
108-
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeFalsy();
108+
expect(container.querySelector(".mx_NotificationBadge_level_highlight")).toBeFalsy();
109109

110110
act(() => {
111111
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 1);
112112
});
113113

114-
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeTruthy();
114+
expect(container.querySelector(".mx_NotificationBadge_level_highlight")).toBeTruthy();
115115
});
116116

117117
it("hides unread notification badge", () => {
@@ -177,6 +177,6 @@ describe("UnreadNotificationBadge", () => {
177177
const { container } = render(getComponent(THREAD_ID));
178178
expect(container.querySelector(".mx_NotificationBadge_dot")).toBeTruthy();
179179
expect(container.querySelector(".mx_NotificationBadge_visible")).toBeTruthy();
180-
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeFalsy();
180+
expect(container.querySelector(".mx_NotificationBadge_level_highlight")).toBeFalsy();
181181
});
182182
});

test/components/views/rooms/RoomHeader/__snapshots__/VideoRoomChatButton-test.tsx.snap

+15-4
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,37 @@
33
exports[`<VideoRoomChatButton /> renders button when room is a video room 1`] = `
44
<button
55
aria-label="Chat"
6-
class="_icon-button_ur2sw_17"
6+
class="_icon-button_16nk7_17"
77
data-state="closed"
88
role="button"
99
style="--cpd-icon-button-size: 32px;"
1010
tabindex="0"
1111
>
12-
<div />
12+
<div
13+
class="_indicator-icon_jtb4d_26"
14+
style="--cpd-icon-button-size: 100%;"
15+
>
16+
<div />
17+
</div>
1318
</button>
1419
`;
1520

1621
exports[`<VideoRoomChatButton /> renders button with an unread marker when room is unread 1`] = `
1722
<button
1823
aria-label="Chat"
19-
class="_icon-button_ur2sw_17"
24+
class="_icon-button_16nk7_17"
2025
data-indicator="default"
2126
data-state="closed"
2227
role="button"
2328
style="--cpd-icon-button-size: 32px;"
2429
tabindex="0"
2530
>
26-
<div />
31+
<div
32+
class="_indicator-icon_jtb4d_26"
33+
data-indicator="default"
34+
style="--cpd-icon-button-size: 100%;"
35+
>
36+
<div />
37+
</div>
2738
</button>
2839
`;

test/components/views/rooms/__snapshots__/RoomHeader-test.tsx.snap

+21-6
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,49 @@ exports[`RoomHeader does not show the face pile for DMs 1`] = `
4646
<button
4747
aria-disabled="true"
4848
aria-label="There's no one here to call"
49-
class="_icon-button_ur2sw_17"
49+
class="_icon-button_16nk7_17"
5050
data-state="closed"
5151
role="button"
5252
style="--cpd-icon-button-size: 32px;"
5353
tabindex="0"
5454
>
55-
<div />
55+
<div
56+
class="_indicator-icon_jtb4d_26"
57+
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
58+
>
59+
<div />
60+
</div>
5661
</button>
5762
<button
5863
aria-disabled="true"
5964
aria-label="There's no one here to call"
60-
class="_icon-button_ur2sw_17"
65+
class="_icon-button_16nk7_17"
6166
data-state="closed"
6267
role="button"
6368
style="--cpd-icon-button-size: 32px;"
6469
tabindex="0"
6570
>
66-
<div />
71+
<div
72+
class="_indicator-icon_jtb4d_26"
73+
style="--cpd-icon-button-size: 100%; --cpd-color-icon-tertiary: var(--cpd-color-icon-disabled);"
74+
>
75+
<div />
76+
</div>
6777
</button>
6878
<button
6979
aria-label="Threads"
70-
class="_icon-button_ur2sw_17"
80+
class="_icon-button_16nk7_17"
7181
data-state="closed"
7282
role="button"
7383
style="--cpd-icon-button-size: 32px;"
7484
tabindex="0"
7585
>
76-
<div />
86+
<div
87+
class="_indicator-icon_jtb4d_26"
88+
style="--cpd-icon-button-size: 100%;"
89+
>
90+
<div />
91+
</div>
7792
</button>
7893
</div>
7994
</header>

test/components/views/settings/notifications/__snapshots__/Notifications2-test.tsx.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ exports[`<Notifications /> correctly handles the loading/disabled state 1`] = `
480480
<span>
481481
Show a badge
482482
<div
483-
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_2char"
483+
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_notification mx_NotificationBadge_2char"
484484
>
485485
<span
486486
class="mx_NotificationBadge_count"
@@ -1190,7 +1190,7 @@ exports[`<Notifications /> matches the snapshot 1`] = `
11901190
<span>
11911191
Show a badge
11921192
<div
1193-
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_2char"
1193+
class="mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_notification mx_NotificationBadge_2char"
11941194
>
11951195
<span
11961196
class="mx_NotificationBadge_count"

test/components/views/spaces/__snapshots__/SpaceTreeLevel-test.tsx.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ exports[`SpaceButton metaspace should render notificationState if one is provide
2626
class="mx_SpacePanel_badgeContainer"
2727
>
2828
<div
29-
class="mx_AccessibleButton mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_2char"
29+
class="mx_AccessibleButton mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_level_notification mx_NotificationBadge_2char"
3030
role="button"
3131
tabindex="-1"
3232
>

0 commit comments

Comments
 (0)