@pushwoosh/dumb-components 1.1.97 → 1.1.99

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.
@@ -1,3 +1,3 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { NotificationBarProps } from './types';
3
- export declare function NotificationBar({ type, isOpened, read, count, onClick, className, }: NotificationBarProps): ReactElement;
3
+ export declare function NotificationBar({ type, isOpened, read, count, onClick, }: NotificationBarProps): ReactElement;
@@ -13,8 +13,7 @@ export function NotificationBar({
13
13
  isOpened = false,
14
14
  read = false,
15
15
  count = 0,
16
- onClick,
17
- className
16
+ onClick
18
17
  }) {
19
18
  const hasNotifications = count > 0 && type !== 'no-notifications';
20
19
  const shouldUseFilledIcon = !isOpened && (!read && type !== 'no-notifications' || read);
@@ -30,7 +29,6 @@ export function NotificationBar({
30
29
  return Color.CLEAR;
31
30
  };
32
31
  return React.createElement(NotificationBarButton, {
33
- className: className,
34
32
  "$type": type,
35
33
  "$isOpened": isOpened,
36
34
  "$read": read,
@@ -1,3 +1,3 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { NotificationBellProps } from './types';
3
- export declare function NotificationBell({ count, severity, isOpened, onClick, className, }: NotificationBellProps): ReactElement;
3
+ export declare function NotificationBell({ count, severity, isOpened, onClick, }: NotificationBellProps): ReactElement;
@@ -6,8 +6,7 @@ export function NotificationBell({
6
6
  count = 0,
7
7
  severity,
8
8
  isOpened = false,
9
- onClick,
10
- className
9
+ onClick
11
10
  }) {
12
11
  const hasNotifications = count > 0;
13
12
  const showBadge = hasNotifications || severity;
@@ -18,7 +17,6 @@ export function NotificationBell({
18
17
  return Color.PRIMARY;
19
18
  };
20
19
  return React.createElement(NotificationBellButton, {
21
- className: className,
22
20
  "$isOpened": isOpened,
23
21
  onClick: onClick,
24
22
  type: "button",
@@ -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, }: 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,
@@ -30,11 +17,14 @@ export function NotificationInbox({
30
17
  onNotificationClick,
31
18
  onMarkAsRead,
32
19
  emptyStateTitle = 'No notifications yet',
33
- emptyStateDescription = 'You\'ll see updates and alerts here once something requires your attention.',
34
- emptyStateIcon,
35
- className
20
+ emptyStateDescription = 'You\'ll see updates and alerts here once something requires your attention.'
36
21
  }) {
37
- const unreadCount = notifications.filter(n => !n.isRead).length;
22
+ const descriptionRefs = useRef({});
23
+ const [expandedId, setExpandedId] = useState(null);
24
+ const [showExpandMap, setShowExpandMap] = useState({});
25
+ const unreadCount = notifications.filter(({
26
+ isRead
27
+ }) => !isRead).length;
38
28
  const handleNotificationClick = useCallback(notification => {
39
29
  if (!notification.isRead && onMarkAsRead) {
40
30
  onMarkAsRead(notification.id);
@@ -46,24 +36,29 @@ export function NotificationInbox({
46
36
  isLoading,
47
37
  onLoadMore
48
38
  });
39
+ useLayoutEffect(() => {
40
+ const newMap = {};
41
+ notifications.forEach(n => {
42
+ const element = descriptionRefs.current[n.id];
43
+ if (!element) return;
44
+ const lineHeight = parseFloat(getComputedStyle(element).lineHeight);
45
+ const maxHeight = lineHeight * VISIBLE_LINES;
46
+ newMap[n.id] = element.scrollHeight > maxHeight + 1;
47
+ });
48
+ setShowExpandMap(newMap);
49
+ }, [notifications]);
49
50
  if (notifications.length === 0 && !isLoading) {
50
- return React.createElement(NotificationInboxContainer, {
51
- className: className
52
- }, React.createElement(NotificationInboxHeader, null, React.createElement(NotificationInboxTitle, {
51
+ return React.createElement(NotificationInboxContainer, null, React.createElement(NotificationInboxHeader, null, React.createElement(NotificationInboxTitle, {
53
52
  "$variant": "h4",
54
53
  "$color": Color.MAIN
55
54
  }, "Notifications")), React.createElement(Vertical, {
56
55
  "$alignItems": "center",
57
56
  "$gap": 24,
58
57
  "$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, {
58
+ }, React.createElement(NotificationInboxEmptyIcon, {
59
+ alt: emptyStateTitle,
60
+ src: emptyNotificationsIcon
61
+ }), React.createElement(Vertical, {
67
62
  "$alignItems": "center",
68
63
  "$gap": 6,
69
64
  "$padding": "24px",
@@ -78,9 +73,7 @@ export function NotificationInbox({
78
73
  "$color": Color.LOCKED
79
74
  }, emptyStateDescription))));
80
75
  }
81
- return React.createElement(NotificationInboxContainer, {
82
- className: className
83
- }, React.createElement(NotificationInboxHeader, null, React.createElement(NotificationInboxTitle, {
76
+ return React.createElement(NotificationInboxContainer, null, React.createElement(NotificationInboxHeader, null, React.createElement(NotificationInboxTitle, {
84
77
  "$variant": "h4",
85
78
  "$color": Color.MAIN
86
79
  }, React.createElement("span", null, "Notifications"), unreadCount > 0 && React.createElement(React.Fragment, null, ' ', React.createElement(Text, {
@@ -90,7 +83,8 @@ export function NotificationInbox({
90
83
  "data-notification-list": true
91
84
  }, notifications.map(notification => {
92
85
  const isRead = notification.isRead ?? false;
93
- const badgeColor = severityToBadgeColorMap[notification.severity] || 'gray';
86
+ const badgeColor = badgeColorMap[notification.severity] || 'gray';
87
+ const showExpandButton = showExpandMap[notification.id] ?? false;
94
88
  return React.createElement(NotificationItem, {
95
89
  key: notification.id,
96
90
  onClick: () => handleNotificationClick(notification)
@@ -115,10 +109,14 @@ export function NotificationInbox({
115
109
  }, notification.severity.toUpperCase())), React.createElement(Text, {
116
110
  "$variant": "h4",
117
111
  "$color": Color.MAIN
118
- }, notification.title), notification.description && React.createElement(Text, {
112
+ }, notification.title), notification.description && React.createElement(NotificationDescription, {
113
+ as: "div",
114
+ ref: element => descriptionRefs.current[notification.id] = element || null,
119
115
  "$variant": "standard",
120
- "$color": Color.MAIN
121
- }, notification.description)), (notification.actionButton || notification.expandButton) && React.createElement(Horizontal, {
116
+ "$color": Color.MAIN,
117
+ "$visibleLines": VISIBLE_LINES,
118
+ "$expanded": expandedId === notification.id
119
+ }, notification.description)), (notification.actionButton || showExpandButton) && React.createElement(Horizontal, {
122
120
  "$alignItems": "center",
123
121
  "$justifyContent": "start",
124
122
  "$gap": 12
@@ -129,13 +127,12 @@ export function NotificationInbox({
129
127
  e.stopPropagation();
130
128
  (_notification$actionB = notification.actionButton) === null || _notification$actionB === void 0 || _notification$actionB.onClick();
131
129
  }
132
- }, notification.actionButton.label), notification.expandButton && React.createElement(NotificationActionLink, {
130
+ }, notification.actionButton.label), showExpandButton && React.createElement(NotificationActionLink, {
133
131
  onClick: e => {
134
- var _notification$expandB;
135
132
  e.stopPropagation();
136
- (_notification$expandB = notification.expandButton) === null || _notification$expandB === void 0 || _notification$expandB.onClick();
133
+ setExpandedId(prev => prev === notification.id ? null : notification.id);
137
134
  }
138
- }, notification.expandButton.label))));
135
+ }, expandedId === notification.id ? 'Collapse' : 'Expand'))));
139
136
  }), hasMore && isLoading && React.createElement(Horizontal, {
140
137
  ref: loadMoreRef,
141
138
  "$alignItems": "center",
@@ -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,11 +1,11 @@
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';
5
5
  export const NotificationInboxContainer = styled(Vertical).withConfig({
6
6
  displayName: "NotificationInboxContainer",
7
7
  componentId: "sc-1nrene1-0"
8
- })(["width:100%;min-width:360px;max-width:360px;max-height:600px;background:", ";border:1px solid ", ";border-radius:", ";box-shadow:", ";overflow:hidden;display:flex;flex-direction:column;"], Color.CLEAR, Color.FORM, ShapeRadius.DIALOG, Shadow.REGULAR);
8
+ })(["display:flex;flex-direction:column;width:100%;min-width:360px;max-width:360px;max-height:600px;background:", ";border:1px solid ", ";border-radius:", ";box-shadow:", ";overflow:scroll;"], Color.CLEAR, Color.FORM, ShapeRadius.DIALOG, Shadow.REGULAR);
9
9
  export const NotificationInboxHeader = styled(Horizontal).withConfig({
10
10
  displayName: "NotificationInboxHeader",
11
11
  componentId: "sc-1nrene1-1"
@@ -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,8 +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
- readonly className?: string;
31
24
  }
32
25
  export type NotificationBarType = 'important' | 'warning' | 'notifications' | 'no-notifications';
33
26
  export interface NotificationBarProps {
@@ -36,12 +29,10 @@ export interface NotificationBarProps {
36
29
  readonly read?: boolean;
37
30
  readonly count?: number;
38
31
  readonly onClick?: () => void;
39
- readonly className?: string;
40
32
  }
41
33
  export interface NotificationBellProps {
42
34
  readonly count?: number;
43
35
  readonly severity?: NotificationSeverity;
44
36
  readonly isOpened?: boolean;
45
37
  readonly onClick?: () => void;
46
- readonly className?: string;
47
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/dumb-components",
3
- "version": "1.1.97",
3
+ "version": "1.1.99",
4
4
  "description": "React components to build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",