@pushwoosh/dumb-components 1.1.96 → 1.1.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NotificationInbox/NotificationBar.js +6 -5
- package/NotificationInbox/NotificationInbox.d.ts +1 -1
- package/NotificationInbox/NotificationInbox.js +38 -36
- package/NotificationInbox/constants.d.ts +2 -0
- package/NotificationInbox/constants.js +6 -0
- package/NotificationInbox/helpers.d.ts +1 -0
- package/NotificationInbox/helpers.js +10 -0
- package/NotificationInbox/styles.d.ts +10 -1
- package/NotificationInbox/styles.js +21 -14
- package/NotificationInbox/types.d.ts +0 -6
- package/package.json +1 -1
|
@@ -2,8 +2,9 @@ import React from 'react';
|
|
|
2
2
|
import { Color } from '@pushwoosh/kit-constants';
|
|
3
3
|
import { NotificationIcon } from '@pushwoosh/kit-icons';
|
|
4
4
|
import { NotificationBarButton, NotificationBarIcon, NotificationBarBadge, NotificationBarText, NotificationBarBadgeText } from './styles';
|
|
5
|
-
function getIconView(isOpened, filled) {
|
|
6
|
-
if (isOpened) return 'lined';
|
|
5
|
+
function getIconView(isOpened, filled, hasNotifications) {
|
|
6
|
+
if (isOpened && !hasNotifications) return 'lined';
|
|
7
|
+
if (isOpened) return 'filled';
|
|
7
8
|
if (filled) return 'filled';
|
|
8
9
|
return 'lined';
|
|
9
10
|
}
|
|
@@ -15,7 +16,7 @@ export function NotificationBar({
|
|
|
15
16
|
onClick,
|
|
16
17
|
className
|
|
17
18
|
}) {
|
|
18
|
-
const
|
|
19
|
+
const hasNotifications = count > 0 && type !== 'no-notifications';
|
|
19
20
|
const shouldUseFilledIcon = !isOpened && (!read && type !== 'no-notifications' || read);
|
|
20
21
|
const getIconColor = () => {
|
|
21
22
|
if (isOpened) return Color.CLEAR;
|
|
@@ -38,9 +39,9 @@ export function NotificationBar({
|
|
|
38
39
|
}, React.createElement(NotificationBarIcon, null, React.createElement(NotificationIcon, {
|
|
39
40
|
type: "enabled",
|
|
40
41
|
size: "small",
|
|
41
|
-
view: getIconView(isOpened, shouldUseFilledIcon),
|
|
42
|
+
view: getIconView(isOpened, shouldUseFilledIcon, hasNotifications),
|
|
42
43
|
color: getIconColor()
|
|
43
|
-
})), React.createElement(NotificationBarText, null, "Notification center"),
|
|
44
|
+
})), React.createElement(NotificationBarText, null, "Notification center"), hasNotifications && React.createElement(NotificationBarBadge, null, React.createElement(NotificationBarBadgeText, {
|
|
44
45
|
"$variant": "h5"
|
|
45
46
|
}, count)));
|
|
46
47
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type ReactElement } from 'react';
|
|
2
2
|
import type { NotificationInboxProps } from './types';
|
|
3
|
-
export declare function NotificationInbox({ notifications, isLoading, hasMore, onLoadMore, onNotificationClick, onMarkAsRead, emptyStateTitle, emptyStateDescription,
|
|
3
|
+
export declare function NotificationInbox({ notifications, isLoading, hasMore, onLoadMore, onNotificationClick, onMarkAsRead, emptyStateTitle, emptyStateDescription, className, }: NotificationInboxProps): ReactElement;
|
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
import React, { useCallback } from 'react';
|
|
1
|
+
import React, { useRef, useState, useLayoutEffect, useCallback } from 'react';
|
|
2
2
|
import { Color } from '@pushwoosh/kit-constants';
|
|
3
3
|
import { Horizontal, Vertical } from '@pushwoosh/kit-helpers';
|
|
4
4
|
import { Text } from '@pushwoosh/kit-typography';
|
|
5
|
+
import { VISIBLE_LINES, badgeColorMap } from './constants';
|
|
6
|
+
import { formatTimestamp } from './helpers';
|
|
5
7
|
import { useInfiniteScroll } from './useInfiniteScroll';
|
|
6
8
|
import { Badge } from '../Badge';
|
|
7
9
|
import { Spinner } from '../Spinner';
|
|
8
10
|
import emptyNotificationsIcon from './assets/EmptyNotifications.svg?url';
|
|
9
|
-
import { NotificationInboxContainer, NotificationInboxHeader, NotificationInboxTitle, NotificationInboxList, NotificationItem, NotificationIndicator, NotificationHeader, NotificationActionLink, NotificationInboxEmptyIcon, NotificationInboxEmptyText } from './styles';
|
|
10
|
-
const severityToBadgeColorMap = {
|
|
11
|
-
critical: 'red',
|
|
12
|
-
warning: 'orange',
|
|
13
|
-
info: 'gray'
|
|
14
|
-
};
|
|
15
|
-
function formatTimestamp(timestamp) {
|
|
16
|
-
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
|
|
17
|
-
const day = date.getDate();
|
|
18
|
-
const month = date.toLocaleString('en-US', {
|
|
19
|
-
month: 'short'
|
|
20
|
-
});
|
|
21
|
-
const hours = date.getHours();
|
|
22
|
-
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
23
|
-
return `${day} ${month}, ${hours}:${minutes}`;
|
|
24
|
-
}
|
|
11
|
+
import { NotificationInboxContainer, NotificationInboxHeader, NotificationInboxTitle, NotificationInboxList, NotificationItem, NotificationIndicator, NotificationHeader, NotificationDescription, NotificationActionLink, NotificationInboxEmptyIcon, NotificationInboxEmptyText } from './styles';
|
|
25
12
|
export function NotificationInbox({
|
|
26
13
|
notifications,
|
|
27
14
|
isLoading = false,
|
|
@@ -31,10 +18,14 @@ export function NotificationInbox({
|
|
|
31
18
|
onMarkAsRead,
|
|
32
19
|
emptyStateTitle = 'No notifications yet',
|
|
33
20
|
emptyStateDescription = 'You\'ll see updates and alerts here once something requires your attention.',
|
|
34
|
-
emptyStateIcon,
|
|
35
21
|
className
|
|
36
22
|
}) {
|
|
37
|
-
const
|
|
23
|
+
const descriptionRefs = useRef({});
|
|
24
|
+
const [expandedId, setExpandedId] = useState(null);
|
|
25
|
+
const [showExpandMap, setShowExpandMap] = useState({});
|
|
26
|
+
const unreadCount = notifications.filter(({
|
|
27
|
+
isRead
|
|
28
|
+
}) => !isRead).length;
|
|
38
29
|
const handleNotificationClick = useCallback(notification => {
|
|
39
30
|
if (!notification.isRead && onMarkAsRead) {
|
|
40
31
|
onMarkAsRead(notification.id);
|
|
@@ -46,6 +37,17 @@ export function NotificationInbox({
|
|
|
46
37
|
isLoading,
|
|
47
38
|
onLoadMore
|
|
48
39
|
});
|
|
40
|
+
useLayoutEffect(() => {
|
|
41
|
+
const newMap = {};
|
|
42
|
+
notifications.forEach(n => {
|
|
43
|
+
const element = descriptionRefs.current[n.id];
|
|
44
|
+
if (!element) return;
|
|
45
|
+
const lineHeight = parseFloat(getComputedStyle(element).lineHeight);
|
|
46
|
+
const maxHeight = lineHeight * VISIBLE_LINES;
|
|
47
|
+
newMap[n.id] = element.scrollHeight > maxHeight + 1;
|
|
48
|
+
});
|
|
49
|
+
setShowExpandMap(newMap);
|
|
50
|
+
}, [notifications]);
|
|
49
51
|
if (notifications.length === 0 && !isLoading) {
|
|
50
52
|
return React.createElement(NotificationInboxContainer, {
|
|
51
53
|
className: className
|
|
@@ -56,14 +58,10 @@ export function NotificationInbox({
|
|
|
56
58
|
"$alignItems": "center",
|
|
57
59
|
"$gap": 24,
|
|
58
60
|
"$padding": "24px"
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
width: '120px',
|
|
64
|
-
height: '120px'
|
|
65
|
-
}
|
|
66
|
-
})), React.createElement(Vertical, {
|
|
61
|
+
}, React.createElement(NotificationInboxEmptyIcon, {
|
|
62
|
+
alt: emptyStateTitle,
|
|
63
|
+
src: emptyNotificationsIcon
|
|
64
|
+
}), React.createElement(Vertical, {
|
|
67
65
|
"$alignItems": "center",
|
|
68
66
|
"$gap": 6,
|
|
69
67
|
"$padding": "24px",
|
|
@@ -90,7 +88,8 @@ export function NotificationInbox({
|
|
|
90
88
|
"data-notification-list": true
|
|
91
89
|
}, notifications.map(notification => {
|
|
92
90
|
const isRead = notification.isRead ?? false;
|
|
93
|
-
const badgeColor =
|
|
91
|
+
const badgeColor = badgeColorMap[notification.severity] || 'gray';
|
|
92
|
+
const showExpandButton = showExpandMap[notification.id] ?? false;
|
|
94
93
|
return React.createElement(NotificationItem, {
|
|
95
94
|
key: notification.id,
|
|
96
95
|
onClick: () => handleNotificationClick(notification)
|
|
@@ -115,10 +114,14 @@ export function NotificationInbox({
|
|
|
115
114
|
}, notification.severity.toUpperCase())), React.createElement(Text, {
|
|
116
115
|
"$variant": "h4",
|
|
117
116
|
"$color": Color.MAIN
|
|
118
|
-
}, notification.title), notification.description && React.createElement(
|
|
117
|
+
}, notification.title), notification.description && React.createElement(NotificationDescription, {
|
|
118
|
+
as: "div",
|
|
119
|
+
ref: element => descriptionRefs.current[notification.id] = element || null,
|
|
119
120
|
"$variant": "standard",
|
|
120
|
-
"$color": Color.MAIN
|
|
121
|
-
|
|
121
|
+
"$color": Color.MAIN,
|
|
122
|
+
"$visibleLines": VISIBLE_LINES,
|
|
123
|
+
"$expanded": expandedId === notification.id
|
|
124
|
+
}, notification.description)), (notification.actionButton || showExpandButton) && React.createElement(Horizontal, {
|
|
122
125
|
"$alignItems": "center",
|
|
123
126
|
"$justifyContent": "start",
|
|
124
127
|
"$gap": 12
|
|
@@ -129,17 +132,16 @@ export function NotificationInbox({
|
|
|
129
132
|
e.stopPropagation();
|
|
130
133
|
(_notification$actionB = notification.actionButton) === null || _notification$actionB === void 0 || _notification$actionB.onClick();
|
|
131
134
|
}
|
|
132
|
-
}, notification.actionButton.label),
|
|
135
|
+
}, notification.actionButton.label), showExpandButton && React.createElement(NotificationActionLink, {
|
|
133
136
|
onClick: e => {
|
|
134
|
-
var _notification$expandB;
|
|
135
137
|
e.stopPropagation();
|
|
136
|
-
(
|
|
138
|
+
setExpandedId(prev => prev === notification.id ? null : notification.id);
|
|
137
139
|
}
|
|
138
|
-
}, notification.
|
|
140
|
+
}, expandedId === notification.id ? 'Collapse' : 'Expand'))));
|
|
139
141
|
}), hasMore && isLoading && React.createElement(Horizontal, {
|
|
140
142
|
ref: loadMoreRef,
|
|
141
143
|
"$alignItems": "center",
|
|
142
|
-
"$justifyContent": "
|
|
144
|
+
"$justifyContent": "center",
|
|
143
145
|
"$padding": 16,
|
|
144
146
|
"$gap": 8
|
|
145
147
|
}, React.createElement(Spinner, {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatTimestamp(timestamp: string | Date): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function formatTimestamp(timestamp) {
|
|
2
|
+
const date = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
|
|
3
|
+
const day = date.getDate();
|
|
4
|
+
const month = date.toLocaleString('en-US', {
|
|
5
|
+
month: 'short'
|
|
6
|
+
});
|
|
7
|
+
const hours = date.getHours();
|
|
8
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
9
|
+
return `${day} ${month}, ${hours}:${minutes}`;
|
|
10
|
+
}
|
|
@@ -342,10 +342,19 @@ export declare const NotificationHeader: import("styled-components").StyledCompo
|
|
|
342
342
|
} & {
|
|
343
343
|
$gridAutoFlow: string;
|
|
344
344
|
}, "$gridAutoFlow">;
|
|
345
|
+
export declare const NotificationDescription: import("styled-components").StyledComponent<"span", any, {
|
|
346
|
+
$color?: string | undefined;
|
|
347
|
+
$uppercase?: boolean | undefined;
|
|
348
|
+
$ellipsis?: boolean | undefined;
|
|
349
|
+
$variant?: import("@pushwoosh/kit-typography").TypographyVariant | undefined;
|
|
350
|
+
} & {
|
|
351
|
+
$expanded?: boolean;
|
|
352
|
+
$visibleLines: number;
|
|
353
|
+
}, never>;
|
|
345
354
|
export declare const NotificationActionLink: import("styled-components").StyledComponent<"button", any, {
|
|
346
355
|
$isPrimary?: boolean;
|
|
347
356
|
}, never>;
|
|
348
|
-
export declare const NotificationInboxEmptyIcon: import("styled-components").StyledComponent<"
|
|
357
|
+
export declare const NotificationInboxEmptyIcon: import("styled-components").StyledComponent<"img", any, {}, never>;
|
|
349
358
|
export declare const NotificationInboxEmptyText: import("styled-components").StyledComponent<"span", any, {
|
|
350
359
|
$color?: string | undefined;
|
|
351
360
|
$uppercase?: boolean | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import styled from 'styled-components';
|
|
1
|
+
import styled, { css } from 'styled-components';
|
|
2
2
|
import { Color, FontSize, FontWeight, FontFamily, ShapeRadius, Shadow } from '@pushwoosh/kit-constants';
|
|
3
3
|
import { Horizontal, Vertical } from '@pushwoosh/kit-helpers';
|
|
4
4
|
import { Text } from '@pushwoosh/kit-typography';
|
|
@@ -32,23 +32,30 @@ export const NotificationHeader = styled(Horizontal).withConfig({
|
|
|
32
32
|
displayName: "NotificationHeader",
|
|
33
33
|
componentId: "sc-1nrene1-6"
|
|
34
34
|
})(["justify-content:space-between;align-items:center;width:100%;gap:4px;height:20px;"]);
|
|
35
|
+
export const NotificationDescription = styled(Text).withConfig({
|
|
36
|
+
displayName: "NotificationDescription",
|
|
37
|
+
componentId: "sc-1nrene1-7"
|
|
38
|
+
})(["display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;", ""], ({
|
|
39
|
+
$expanded,
|
|
40
|
+
$visibleLines
|
|
41
|
+
}) => !$expanded && css(["-webkit-line-clamp:", ";text-overflow:ellipsis;"], $visibleLines));
|
|
35
42
|
export const NotificationActionLink = styled.button.withConfig({
|
|
36
43
|
displayName: "NotificationActionLink",
|
|
37
|
-
componentId: "sc-1nrene1-
|
|
44
|
+
componentId: "sc-1nrene1-8"
|
|
38
45
|
})(["background:none;border:none;padding:0;margin:0;font-size:12px;font-family:", ";font-weight:", ";line-height:1.3;color:", ";text-decoration:none;text-transform:uppercase;cursor:pointer;transition:opacity 0.2s;white-space:nowrap;&:hover{opacity:0.8;}&:active{opacity:0.6;}"], FontFamily.MAIN, FontWeight.BOLD, ({
|
|
39
46
|
$isPrimary
|
|
40
47
|
}) => $isPrimary ? Color.PRIMARY : Color.SOFT);
|
|
41
|
-
export const NotificationInboxEmptyIcon = styled.
|
|
48
|
+
export const NotificationInboxEmptyIcon = styled.img.withConfig({
|
|
42
49
|
displayName: "NotificationInboxEmptyIcon",
|
|
43
|
-
componentId: "sc-1nrene1-
|
|
44
|
-
})(["
|
|
50
|
+
componentId: "sc-1nrene1-9"
|
|
51
|
+
})(["display:flex;align-items:center;justify-content:center;width:120px;height:120px;margin:0 auto;object-fit:contain;"]);
|
|
45
52
|
export const NotificationInboxEmptyText = styled(Text).withConfig({
|
|
46
53
|
displayName: "NotificationInboxEmptyText",
|
|
47
|
-
componentId: "sc-1nrene1-
|
|
54
|
+
componentId: "sc-1nrene1-10"
|
|
48
55
|
})(["text-align:center;"]);
|
|
49
56
|
export const NotificationBarButton = styled.button.withConfig({
|
|
50
57
|
displayName: "NotificationBarButton",
|
|
51
|
-
componentId: "sc-1nrene1-
|
|
58
|
+
componentId: "sc-1nrene1-11"
|
|
52
59
|
})(["display:flex;gap:8px;align-items:center;padding:8px 16px 8px 12px;width:260px;border:none;border-top:1px solid ", ";border-bottom:0;border-left:0;border-right:0;cursor:pointer;transition:background-color 0.2s,opacity 0.2s;background-color:", ";--icon-color:", ";--text-color:", ";--badge-bg:", ";--badge-text-color:", ";&:hover:not(:disabled){background-color:", ";--icon-color:", ";--text-color:", ";--badge-bg:", ";--badge-text-color:", ";}&:active:not(:disabled){opacity:0.5;--icon-color:", ";--text-color:", ";--badge-bg:", ";}"], Color.FORM, ({
|
|
53
60
|
$type,
|
|
54
61
|
$isOpened,
|
|
@@ -212,23 +219,23 @@ export const NotificationBarButton = styled.button.withConfig({
|
|
|
212
219
|
});
|
|
213
220
|
export const NotificationBarIcon = styled.div.withConfig({
|
|
214
221
|
displayName: "NotificationBarIcon",
|
|
215
|
-
componentId: "sc-1nrene1-
|
|
222
|
+
componentId: "sc-1nrene1-12"
|
|
216
223
|
})(["width:16px;height:16px;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:color 0.2s;color:var(--icon-color);"]);
|
|
217
224
|
export const NotificationBarText = styled.div.withConfig({
|
|
218
225
|
displayName: "NotificationBarText",
|
|
219
|
-
componentId: "sc-1nrene1-
|
|
226
|
+
componentId: "sc-1nrene1-13"
|
|
220
227
|
})(["display:flex;align-items:center;justify-content:start;flex:1 0 0;font-size:", ";font-family:", ";font-weight:", ";line-height:1.4;transition:color 0.2s;color:var(--text-color);white-space:pre-wrap;"], FontSize.REGULAR, FontFamily.MAIN, FontWeight.MEDIUM);
|
|
221
228
|
export const NotificationBarBadge = styled.div.withConfig({
|
|
222
229
|
displayName: "NotificationBarBadge",
|
|
223
|
-
componentId: "sc-1nrene1-
|
|
230
|
+
componentId: "sc-1nrene1-14"
|
|
224
231
|
})(["flex-shrink:0;transition:background-color 0.2s;background-color:var(--badge-bg);border-radius:", ";padding:2px 6px;display:flex;align-items:center;justify-content:center;"], ShapeRadius.CONTROL);
|
|
225
232
|
export const NotificationBarBadgeText = styled(Text).withConfig({
|
|
226
233
|
displayName: "NotificationBarBadgeText",
|
|
227
|
-
componentId: "sc-1nrene1-
|
|
234
|
+
componentId: "sc-1nrene1-15"
|
|
228
235
|
})(["text-transform:uppercase;transition:color 0.2s;color:var(--badge-text-color);"]);
|
|
229
236
|
export const NotificationBellButton = styled.button.withConfig({
|
|
230
237
|
displayName: "NotificationBellButton",
|
|
231
|
-
componentId: "sc-1nrene1-
|
|
238
|
+
componentId: "sc-1nrene1-16"
|
|
232
239
|
})(["position:relative;width:36px;height:36px;padding:6px;border:none;background:", ";border-radius:", ";cursor:pointer;display:flex;align-items:center;justify-content:center;color:", ";transition:background-color 0.2s,color 0.2s;&:hover:not(:disabled){background-color:", ";color:", ";}"], ({
|
|
233
240
|
$isOpened
|
|
234
241
|
}) => $isOpened ? Color.MAIN : 'transparent', ShapeRadius.CONTROL, ({
|
|
@@ -240,11 +247,11 @@ export const NotificationBellButton = styled.button.withConfig({
|
|
|
240
247
|
}) => $isOpened ? Color.CLEAR : Color.PRIMARY_HOVER);
|
|
241
248
|
export const NotificationBellBadge = styled.div.withConfig({
|
|
242
249
|
displayName: "NotificationBellBadge",
|
|
243
|
-
componentId: "sc-1nrene1-
|
|
250
|
+
componentId: "sc-1nrene1-17"
|
|
244
251
|
})(["position:absolute;right:4px;top:4px;z-index:1;"]);
|
|
245
252
|
export const NotificationBellBadgeDot = styled.div.withConfig({
|
|
246
253
|
displayName: "NotificationBellBadgeDot",
|
|
247
|
-
componentId: "sc-1nrene1-
|
|
254
|
+
componentId: "sc-1nrene1-18"
|
|
248
255
|
})(["background-color:", ";border-radius:100px;", " display:flex;align-items:center;justify-content:center;font-size:10px;font-family:", ";font-weight:", ";line-height:1;color:", ";white-space:nowrap;"], ({
|
|
249
256
|
$color
|
|
250
257
|
}) => $color, ({
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
1
|
export type NotificationSeverity = 'critical' | 'warning' | 'info';
|
|
3
2
|
export interface Notification {
|
|
4
3
|
readonly id: string;
|
|
@@ -12,10 +11,6 @@ export interface Notification {
|
|
|
12
11
|
readonly label: string;
|
|
13
12
|
readonly onClick: () => void;
|
|
14
13
|
};
|
|
15
|
-
readonly expandButton?: {
|
|
16
|
-
readonly label: string;
|
|
17
|
-
readonly onClick: () => void;
|
|
18
|
-
};
|
|
19
14
|
}
|
|
20
15
|
export interface NotificationInboxProps {
|
|
21
16
|
readonly notifications: Notification[];
|
|
@@ -26,7 +21,6 @@ export interface NotificationInboxProps {
|
|
|
26
21
|
readonly onMarkAsRead?: (notificationId: string) => void;
|
|
27
22
|
readonly emptyStateTitle?: string;
|
|
28
23
|
readonly emptyStateDescription?: string;
|
|
29
|
-
readonly emptyStateIcon?: ReactNode;
|
|
30
24
|
readonly className?: string;
|
|
31
25
|
}
|
|
32
26
|
export type NotificationBarType = 'important' | 'warning' | 'notifications' | 'no-notifications';
|