@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.
@@ -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 showBadge = count > 0 && type !== 'no-notifications';
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"), showBadge && React.createElement(NotificationBarBadge, null, React.createElement(NotificationBarBadgeText, {
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, emptyStateIcon, className, }: NotificationInboxProps): ReactElement;
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 unreadCount = notifications.filter(n => !n.isRead).length;
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
- }, emptyStateIcon ? React.createElement(NotificationInboxEmptyIcon, null, emptyStateIcon) : React.createElement(NotificationInboxEmptyIcon, null, React.createElement("img", {
60
- src: emptyNotificationsIcon,
61
- alt: "No notifications",
62
- style: {
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 = severityToBadgeColorMap[notification.severity] || 'gray';
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(Text, {
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
- }, notification.description)), (notification.actionButton || notification.expandButton) && React.createElement(Horizontal, {
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), notification.expandButton && React.createElement(NotificationActionLink, {
135
+ }, notification.actionButton.label), showExpandButton && React.createElement(NotificationActionLink, {
133
136
  onClick: e => {
134
- var _notification$expandB;
135
137
  e.stopPropagation();
136
- (_notification$expandB = notification.expandButton) === null || _notification$expandB === void 0 || _notification$expandB.onClick();
138
+ setExpandedId(prev => prev === notification.id ? null : notification.id);
137
139
  }
138
- }, notification.expandButton.label))));
140
+ }, expandedId === notification.id ? 'Collapse' : 'Expand'))));
139
141
  }), hasMore && isLoading && React.createElement(Horizontal, {
140
142
  ref: loadMoreRef,
141
143
  "$alignItems": "center",
142
- "$justifyContent": "start",
144
+ "$justifyContent": "center",
143
145
  "$padding": 16,
144
146
  "$gap": 8
145
147
  }, React.createElement(Spinner, {
@@ -0,0 +1,2 @@
1
+ export declare const VISIBLE_LINES = 3;
2
+ export declare const badgeColorMap: Record<string, 'red' | 'orange' | 'gray'>;
@@ -0,0 +1,6 @@
1
+ export const VISIBLE_LINES = 3;
2
+ export const badgeColorMap = {
3
+ critical: 'red',
4
+ warning: 'orange',
5
+ info: 'gray'
6
+ };
@@ -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<"div", any, {}, never>;
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-7"
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.div.withConfig({
48
+ export const NotificationInboxEmptyIcon = styled.img.withConfig({
42
49
  displayName: "NotificationInboxEmptyIcon",
43
- componentId: "sc-1nrene1-8"
44
- })(["width:120px;height:120px;display:flex;align-items:center;justify-content:center;color:", ";margin:0 auto;img{width:100%;height:100%;object-fit:contain;}"], Color.PHANTOM);
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-9"
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-10"
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-11"
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-12"
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-13"
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-14"
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-15"
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-16"
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-17"
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/dumb-components",
3
- "version": "1.1.96",
3
+ "version": "1.1.98",
4
4
  "description": "React components to build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",