@stack-spot/portal-components 2.27.0 → 2.27.2
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/CHANGELOG.md +635 -621
- package/dist/components/AnimatedHeight.d.ts +1 -1
- package/dist/components/AnimatedHeight.js +26 -26
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/BannerWarning.d.ts +1 -1
- package/dist/components/BannerWarning.js +1 -1
- package/dist/components/Breadcrumb/index.d.ts +2 -2
- package/dist/components/Breadcrumb/index.js +1 -1
- package/dist/components/Breadcrumb/styled.js +31 -31
- package/dist/components/ButtonLoading.d.ts +1 -1
- package/dist/components/ButtonLoading.js +1 -1
- package/dist/components/ChatBot.d.ts +1 -1
- package/dist/components/ChatBot.js +1 -1
- package/dist/components/ContentValidateFilter.d.ts +1 -1
- package/dist/components/ContentValidateFilter.js +1 -1
- package/dist/components/FadingOverflow.d.ts +1 -1
- package/dist/components/FadingOverflow.js +69 -69
- package/dist/components/FileTreeView/More.d.ts +1 -1
- package/dist/components/FileTreeView/More.js +1 -1
- package/dist/components/FileTreeView/index.d.ts +1 -1
- package/dist/components/FileTreeView/index.js +1 -1
- package/dist/components/InfiniteScroll.d.ts +1 -1
- package/dist/components/InfiniteScroll.js +1 -1
- package/dist/components/InfoMaintenanceBanner.d.ts +1 -1
- package/dist/components/InfoMaintenanceBanner.js +2 -2
- package/dist/components/LazyMarkdown/BlockquoteMd.d.ts +1 -1
- package/dist/components/LazyMarkdown/BlockquoteMd.js +1 -1
- package/dist/components/LazyMarkdown/CodeViewer.d.ts +1 -1
- package/dist/components/LazyMarkdown/CodeViewer.js +76 -76
- package/dist/components/LazyMarkdown/Markdown.d.ts +1 -1
- package/dist/components/LazyMarkdown/Markdown.js +1 -1
- package/dist/components/LazyMarkdown/MarkdownButton.d.ts +1 -1
- package/dist/components/LazyMarkdown/MarkdownButton.js +1 -1
- package/dist/components/LazyMarkdown/Video.d.ts +1 -1
- package/dist/components/LazyMarkdown/Video.js +1 -1
- package/dist/components/LazyMarkdown/index.d.ts +1 -1
- package/dist/components/LazyMarkdown/index.js +1 -1
- package/dist/components/Placeholder.d.ts +7 -3
- package/dist/components/Placeholder.d.ts.map +1 -1
- package/dist/components/Placeholder.js +3 -3
- package/dist/components/Placeholder.js.map +1 -1
- package/dist/components/ScrollView.js +16 -16
- package/dist/components/Select/BadgeItem.d.ts +1 -1
- package/dist/components/Select/BadgeItem.js +1 -1
- package/dist/components/Select/ClearInput.d.ts +1 -1
- package/dist/components/Select/ClearInput.js +1 -1
- package/dist/components/Select/CloseItem.d.ts +1 -1
- package/dist/components/Select/CloseItem.js +1 -1
- package/dist/components/Select/CreatableSelect.js +1 -1
- package/dist/components/Select/CustomMenu.d.ts +1 -1
- package/dist/components/Select/CustomMenu.js +1 -1
- package/dist/components/Select/LabelItem.d.ts +1 -1
- package/dist/components/Select/LabelItem.js +1 -1
- package/dist/components/Select/MultiValue.d.ts +1 -1
- package/dist/components/Select/MultiValue.js +1 -1
- package/dist/components/Select/SelectInfiniteScroll.d.ts +1 -1
- package/dist/components/Select/SelectInfiniteScroll.js +1 -1
- package/dist/components/Select/SelectSearch.d.ts +1 -1
- package/dist/components/Select/SelectSearch.js +1 -1
- package/dist/components/SelectionList.d.ts +1 -1
- package/dist/components/SelectionList.js +61 -61
- package/dist/components/StatusCircle.d.ts +1 -1
- package/dist/components/StatusCircle.js +6 -6
- package/dist/components/Stepper/Navigation.js +4 -4
- package/dist/components/Stepper/Step.js +3 -3
- package/dist/components/Stepper/Stepper.js +6 -6
- package/dist/components/Stepper/headers.js +22 -22
- package/dist/components/Table/HeaderItem.js +1 -1
- package/dist/components/Table/SettingsVerticalMenu.d.ts +1 -1
- package/dist/components/Table/SettingsVerticalMenu.js +1 -1
- package/dist/components/Table/StyledLinkTable.d.ts +1 -1
- package/dist/components/Table/StyledLinkTable.js +5 -5
- package/dist/components/Table/TableData.d.ts +1 -1
- package/dist/components/Table/TableData.js +25 -25
- package/dist/components/TimelineSection.d.ts +1 -1
- package/dist/components/TimelineSection.js +14 -14
- package/dist/components/error/ErrorFeedback.d.ts +1 -1
- package/dist/components/error/ErrorFeedback.js +35 -35
- package/dist/components/error/NotFound.d.ts +1 -1
- package/dist/components/error/NotFound.js +1 -1
- package/dist/components/error/UnderMaintenance.d.ts +1 -1
- package/dist/components/error/UnderMaintenance.js +1 -1
- package/dist/components/form/Form/Form.d.ts +1 -1
- package/dist/components/form/Form/Form.js +1 -1
- package/dist/components/form/Form/FormGroup.d.ts +2 -2
- package/dist/components/form/Form/FormGroup.js +1 -1
- package/dist/components/form/SearchInput.d.ts +1 -1
- package/dist/components/form/SearchInput.js +1 -1
- package/dist/components/form/Select/CustomSelect.d.ts +1 -1
- package/dist/components/form/Select/CustomSelect.js +1 -1
- package/dist/components/form/Select/DetailedSelect.d.ts +1 -1
- package/dist/components/form/Select/DetailedSelect.js +1 -1
- package/dist/components/form/Select/Select.d.ts +1 -1
- package/dist/components/form/Select/Select.js +1 -1
- package/dist/components/form/Select/styled.js +161 -161
- package/dist/components/form/Select/utils.js +1 -1
- package/dist/components/notification/NotificationComponent.d.ts +1 -1
- package/dist/components/notification/NotificationComponent.js +54 -54
- package/dist/components/notification/NotificationItem.d.ts +1 -1
- package/dist/components/notification/NotificationItem.d.ts.map +1 -1
- package/dist/components/notification/NotificationItem.js +11 -5
- package/dist/components/notification/NotificationItem.js.map +1 -1
- package/dist/components/notification/NotificationList.d.ts +1 -1
- package/dist/components/notification/NotificationList.d.ts.map +1 -1
- package/dist/components/notification/NotificationList.js +44 -44
- package/dist/components/notification/NotificationList.js.map +1 -1
- package/dist/components/notification/NotificationPlaceholder.d.ts +1 -1
- package/dist/components/notification/NotificationPlaceholder.d.ts.map +1 -1
- package/dist/components/notification/NotificationPlaceholder.js +2 -2
- package/dist/components/notification/NotificationPlaceholder.js.map +1 -1
- package/dist/containers/NotificationsPage.d.ts +1 -1
- package/dist/containers/NotificationsPage.d.ts.map +1 -1
- package/dist/containers/NotificationsPage.js +24 -11
- package/dist/containers/NotificationsPage.js.map +1 -1
- package/dist/context/anchor.d.ts +1 -1
- package/dist/context/anchor.js +1 -1
- package/dist/context/loading.d.ts +1 -1
- package/dist/context/loading.js +1 -1
- package/dist/context/notification/context.d.ts +1 -1
- package/dist/context/notification/context.js +1 -1
- package/dist/context/notification/types.d.ts +1 -0
- package/dist/context/notification/types.d.ts.map +1 -1
- package/dist/hooks/date.js +1 -1
- package/dist/hooks/service-now.js +28 -28
- package/dist/svg/AI.d.ts +1 -1
- package/dist/svg/AI.js +1 -1
- package/dist/svg/CS.d.ts +1 -1
- package/dist/svg/CS.js +1 -1
- package/dist/svg/EDP.d.ts +1 -1
- package/dist/svg/EDP.js +1 -1
- package/dist/svg/Forbidden.d.ts +1 -1
- package/dist/svg/Forbidden.js +1 -1
- package/dist/svg/GenericPlaceholder.d.ts +4 -2
- package/dist/svg/GenericPlaceholder.d.ts.map +1 -1
- package/dist/svg/GenericPlaceholder.js +2 -2
- package/dist/svg/GenericPlaceholder.js.map +1 -1
- package/dist/svg/HUB.d.ts +1 -1
- package/dist/svg/HUB.js +1 -1
- package/dist/svg/Logo.d.ts +1 -1
- package/dist/svg/Logo.js +1 -1
- package/dist/svg/MiniLogo.d.ts +1 -1
- package/dist/svg/MiniLogo.js +1 -1
- package/dist/svg/NotFound.d.ts +1 -1
- package/dist/svg/NotFound.js +1 -1
- package/dist/svg/ServerError.d.ts +1 -1
- package/dist/svg/ServerError.js +1 -1
- package/dist/svg/Unauthenticated.d.ts +1 -1
- package/dist/svg/Unauthenticated.js +1 -1
- package/package.json +6 -6
- package/readme.md +66 -66
- package/src/components/AnimatedHeight.tsx +174 -174
- package/src/components/AsyncContent.tsx +78 -78
- package/src/components/BannerWarning.tsx +91 -91
- package/src/components/Breadcrumb/index.tsx +76 -76
- package/src/components/Breadcrumb/styled.ts +37 -37
- package/src/components/ButtonLoading.tsx +29 -29
- package/src/components/ChatBot.tsx +82 -82
- package/src/components/ContentValidateFilter.tsx +15 -15
- package/src/components/FadingOverflow.tsx +265 -265
- package/src/components/FileTreeView/More.tsx +114 -114
- package/src/components/FileTreeView/index.tsx +186 -186
- package/src/components/InfiniteScroll.tsx +24 -24
- package/src/components/InfoMaintenanceBanner.tsx +29 -29
- package/src/components/LazyMarkdown/BlockquoteMd.tsx +107 -107
- package/src/components/LazyMarkdown/CodeViewer.tsx +161 -161
- package/src/components/LazyMarkdown/Markdown.tsx +122 -122
- package/src/components/LazyMarkdown/MarkdownButton.tsx +24 -24
- package/src/components/LazyMarkdown/Video.tsx +13 -13
- package/src/components/LazyMarkdown/index.tsx +21 -21
- package/src/components/Placeholder.tsx +123 -118
- package/src/components/ScrollView.tsx +57 -57
- package/src/components/Select/BadgeItem.tsx +58 -58
- package/src/components/Select/ClearInput.tsx +24 -24
- package/src/components/Select/CloseItem.tsx +38 -38
- package/src/components/Select/CreatableSelect.tsx +155 -155
- package/src/components/Select/CustomMenu.tsx +16 -16
- package/src/components/Select/LabelItem.tsx +8 -8
- package/src/components/Select/MultiValue.tsx +49 -49
- package/src/components/Select/SelectInfiniteScroll.tsx +82 -82
- package/src/components/Select/SelectSearch.tsx +195 -195
- package/src/components/Select/index.tsx +7 -7
- package/src/components/Select/types.ts +8 -8
- package/src/components/SelectionList.tsx +427 -427
- package/src/components/StatusCircle.tsx +67 -67
- package/src/components/Stepper/Navigation.tsx +97 -97
- package/src/components/Stepper/Step.tsx +30 -30
- package/src/components/Stepper/Stepper.tsx +113 -113
- package/src/components/Stepper/headers.tsx +64 -64
- package/src/components/Stepper/index.ts +3 -3
- package/src/components/Table/HeaderItem.tsx +52 -52
- package/src/components/Table/SettingsVerticalMenu.tsx +50 -50
- package/src/components/Table/StyledLinkTable.tsx +22 -22
- package/src/components/Table/TableData.tsx +251 -251
- package/src/components/Table/index.tsx +2 -2
- package/src/components/TimelineSection.tsx +66 -66
- package/src/components/error/ErrorFeedback.tsx +217 -217
- package/src/components/error/NotFound.tsx +24 -24
- package/src/components/error/UnderMaintenance.tsx +30 -30
- package/src/components/error/index.ts +4 -4
- package/src/components/form/Form/Form.tsx +101 -101
- package/src/components/form/Form/FormGroup.tsx +221 -221
- package/src/components/form/Form/index.ts +2 -2
- package/src/components/form/SearchInput.tsx +69 -69
- package/src/components/form/Select/CustomSelect.tsx +232 -232
- package/src/components/form/Select/DetailedSelect.tsx +85 -85
- package/src/components/form/Select/Select.tsx +67 -67
- package/src/components/form/Select/index.ts +4 -4
- package/src/components/form/Select/styled.ts +165 -165
- package/src/components/form/Select/types.ts +112 -112
- package/src/components/form/Select/utils.tsx +28 -28
- package/src/components/notification/NotificationComponent.tsx +340 -340
- package/src/components/notification/NotificationItem.tsx +345 -336
- package/src/components/notification/NotificationList.tsx +179 -178
- package/src/components/notification/NotificationPlaceholder.tsx +44 -43
- package/src/components/notification/types.ts +72 -72
- package/src/containers/NotificationsPage.tsx +119 -98
- package/src/context/anchor.tsx +37 -37
- package/src/context/loading.tsx +36 -36
- package/src/context/notification/LazyNotificationList.ts +103 -103
- package/src/context/notification/NotificationController.ts +104 -104
- package/src/context/notification/context.tsx +23 -23
- package/src/context/notification/hooks.ts +98 -98
- package/src/context/notification/types.ts +66 -65
- package/src/hooks/date.ts +31 -31
- package/src/hooks/keyboard.tsx +128 -128
- package/src/hooks/manual-render.tsx +10 -10
- package/src/hooks/service-now.tsx +233 -233
- package/src/hooks/text.tsx +30 -30
- package/src/hooks/title.tsx +28 -28
- package/src/hooks/use-effect-once.tsx +43 -43
- package/src/index.ts +19 -19
- package/src/notifications.ts +11 -11
- package/src/svg/AI.tsx +41 -41
- package/src/svg/CS.tsx +48 -48
- package/src/svg/EDP.tsx +31 -31
- package/src/svg/Forbidden.tsx +22 -22
- package/src/svg/GenericPlaceholder.tsx +20 -20
- package/src/svg/HUB.tsx +48 -48
- package/src/svg/Logo.tsx +16 -16
- package/src/svg/MiniLogo.tsx +12 -12
- package/src/svg/NotFound.tsx +16 -16
- package/src/svg/ServerError.tsx +33 -33
- package/src/svg/Unauthenticated.tsx +16 -16
- package/src/svg/index.ts +11 -11
- package/src/utils/accessibility.ts +135 -135
- package/src/utils/cookie.ts +73 -73
- package/src/utils/promise.ts +5 -5
- package/src/utils/read-file.ts +16 -16
- package/tsconfig.json +10 -10
|
@@ -1,178 +1,179 @@
|
|
|
1
|
-
import { Box, Flex } from '@citric/core'
|
|
2
|
-
import { listToClass } from '@stack-spot/portal-theme'
|
|
3
|
-
import { Month } from '@stack-spot/portal-translate'
|
|
4
|
-
import { last } from 'lodash'
|
|
5
|
-
import { useMemo } from 'react'
|
|
6
|
-
import { styled } from 'styled-components'
|
|
7
|
-
import { InfiniteScroll } from '../InfiniteScroll'
|
|
8
|
-
import { TimelineSection } from '../TimelineSection'
|
|
9
|
-
import { NotificationItem } from './NotificationItem'
|
|
10
|
-
import { NotificationPlaceholder } from './NotificationPlaceholder'
|
|
11
|
-
import { GetTenantNotificationsResponse, InfiniteScrollConfig } from './types'
|
|
12
|
-
|
|
13
|
-
export interface NotificationListProps {
|
|
14
|
-
/**
|
|
15
|
-
* Function to call when the message is marked as read (committed).
|
|
16
|
-
* @param id the id of the notification where the read status changed.
|
|
17
|
-
*/
|
|
18
|
-
onCommit: (id: string) => void,
|
|
19
|
-
/**
|
|
20
|
-
* Optional. Function called when the button to perform the notification action is clicked. This function will be run in addition to
|
|
21
|
-
* the notification action.
|
|
22
|
-
* @param id the id of the notification where the button was clicked
|
|
23
|
-
*/
|
|
24
|
-
onClickAction?: (id: string) => void,
|
|
25
|
-
/**
|
|
26
|
-
* Optional. Function called when the button read more in notification is clicked.
|
|
27
|
-
*/
|
|
28
|
-
onClickViewNotification?: () => void,
|
|
29
|
-
/**
|
|
30
|
-
* If you need this notification list to be have an infinite scroll behavior, set this option.
|
|
31
|
-
*/
|
|
32
|
-
infiniteScroll?: InfiniteScrollConfig,
|
|
33
|
-
/**
|
|
34
|
-
* The notifications themselves.
|
|
35
|
-
*/
|
|
36
|
-
items: GetTenantNotificationsResponse[],
|
|
37
|
-
/**
|
|
38
|
-
* A compact notification list don't show date headers (as a timeline) or descriptions of notifications.
|
|
39
|
-
*/
|
|
40
|
-
compact?: boolean,
|
|
41
|
-
/**
|
|
42
|
-
* Whether or not the content is loading. If this is true, the content becomes transparent and the cursor turns into the progress cursor.
|
|
43
|
-
*/
|
|
44
|
-
loading?: boolean,
|
|
45
|
-
/**
|
|
46
|
-
* If true, when the list is empty, the placeholder will say "nothing found" instead of "no notifications".
|
|
47
|
-
*/
|
|
48
|
-
showEmptySearch?: boolean,
|
|
49
|
-
style?: React.CSSProperties,
|
|
50
|
-
className?: string,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface NotificationGroup {
|
|
54
|
-
month: Month,
|
|
55
|
-
day: number,
|
|
56
|
-
year: number,
|
|
57
|
-
items: GetTenantNotificationsResponse[],
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const StyledBox = styled(Box)`
|
|
61
|
-
width: 100%;
|
|
62
|
-
position: relative;
|
|
63
|
-
transition: opacity 0.3s;
|
|
64
|
-
|
|
65
|
-
> div:first-child{
|
|
66
|
-
width: 100%;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
&.loading {
|
|
70
|
-
opacity: 0.6;
|
|
71
|
-
.loading-mask {
|
|
72
|
-
pointer-events: auto;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.loading-mask {
|
|
77
|
-
opacity: 0;
|
|
78
|
-
position: absolute;
|
|
79
|
-
pointer-events: none;
|
|
80
|
-
top: 0;
|
|
81
|
-
right: 0;
|
|
82
|
-
left: 0;
|
|
83
|
-
bottom: 0;
|
|
84
|
-
cursor: progress;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
.placeholder.compact {
|
|
88
|
-
& > div {
|
|
89
|
-
padding-block: 0;
|
|
90
|
-
gap:
|
|
91
|
-
& > div {
|
|
92
|
-
text-align: center;
|
|
93
|
-
h4 {
|
|
94
|
-
margin-bottom: 10px;
|
|
95
|
-
}
|
|
96
|
-
p {
|
|
97
|
-
margin: 0;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
`
|
|
103
|
-
|
|
104
|
-
// this assumes the data from the backend is ordered by date (trigger_at)
|
|
105
|
-
function groupNotificationsByDate(notifications: GetTenantNotificationsResponse[]): NotificationGroup[] {
|
|
106
|
-
const groups: NotificationGroup[] = []
|
|
107
|
-
for (const n of notifications) {
|
|
108
|
-
let currentGroup = last(groups)
|
|
109
|
-
const date = new Date(n.trigger_at)
|
|
110
|
-
const year = date.getFullYear()
|
|
111
|
-
const month = date.getMonth() as Month
|
|
112
|
-
const day = date.getDate()
|
|
113
|
-
if (!currentGroup || currentGroup.day !== day || currentGroup.month !== month || currentGroup.year !== year) {
|
|
114
|
-
currentGroup = { year, month, day, items: [] }
|
|
115
|
-
groups.push(currentGroup)
|
|
116
|
-
}
|
|
117
|
-
currentGroup.items.push(n)
|
|
118
|
-
}
|
|
119
|
-
return groups
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export const NotificationList = (
|
|
123
|
-
{ items, compact = false, onCommit, onClickAction, onClickViewNotification,
|
|
124
|
-
infiniteScroll, loading, showEmptySearch, style, className }: NotificationListProps,
|
|
125
|
-
) => {
|
|
126
|
-
const groups: NotificationGroup[] = useMemo(
|
|
127
|
-
() => compact ? [{ day: 0, month: 0, year: 0, items }] : groupNotificationsByDate(items),
|
|
128
|
-
[compact, items],
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
const renderNotifications = (notifications: GetTenantNotificationsResponse[], key?: string) => (
|
|
132
|
-
<Flex sx={{ gap: '4px' }} flexDirection="column" key={key}>
|
|
133
|
-
{notifications?.map((item) => (
|
|
134
|
-
<NotificationItem
|
|
135
|
-
key={item.id}
|
|
136
|
-
id={key ? '' : `notification-item-${item.id}`}
|
|
137
|
-
notification={item}
|
|
138
|
-
isSummary={compact}
|
|
139
|
-
onClickViewNotification={onClickViewNotification}
|
|
140
|
-
onCommit={() => onCommit(item.id)}
|
|
141
|
-
onClickAction={() => onClickAction?.(item.id)}
|
|
142
|
-
/>
|
|
143
|
-
))}
|
|
144
|
-
</Flex>
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
return infiniteScroll?.scrollableTarget !== null && (
|
|
148
|
-
<StyledBox style={style} className={listToClass([className, loading && 'loading'])}>
|
|
149
|
-
{items.length ? (
|
|
150
|
-
<InfiniteScroll
|
|
151
|
-
dataLength={items.length || 0}
|
|
152
|
-
next={infiniteScroll?.loadMore ?? (() => { })}
|
|
153
|
-
hasMore={infiniteScroll?.hasMore ?? false}
|
|
154
|
-
// @ts-ignore: the library is wrongly typed and abandoned, meaning, it will never be fixed. The source code clearly accepts
|
|
155
|
-
// HTMLElements as scrollable targets:
|
|
156
|
-
// https://github.com/ankeetmaini/react-infinite-scroll-component/blob/master/src/index.tsx#L168
|
|
157
|
-
scrollableTarget={infiniteScroll?.scrollableTarget}
|
|
158
|
-
>
|
|
159
|
-
<Flex sx={{ gap: '24px' }} flexDirection="column">
|
|
160
|
-
{groups.map((group) => compact
|
|
161
|
-
? renderNotifications(group.items, 'compact')
|
|
162
|
-
: (
|
|
163
|
-
<TimelineSection key={`${group.day}-${group.month}-${group.year}`} month={group.month} day={group.day}>
|
|
164
|
-
{renderNotifications(group.items)}
|
|
165
|
-
</TimelineSection>
|
|
166
|
-
),
|
|
167
|
-
)}
|
|
168
|
-
</Flex>
|
|
169
|
-
</InfiniteScroll>
|
|
170
|
-
) : <NotificationPlaceholder
|
|
171
|
-
isSearch={showEmptySearch}
|
|
172
|
-
className={listToClass(['placeholder', compact && 'compact'])}
|
|
173
|
-
titleAs="p"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
1
|
+
import { Box, Flex } from '@citric/core'
|
|
2
|
+
import { listToClass } from '@stack-spot/portal-theme'
|
|
3
|
+
import { Month } from '@stack-spot/portal-translate'
|
|
4
|
+
import { last } from 'lodash'
|
|
5
|
+
import { useMemo } from 'react'
|
|
6
|
+
import { styled } from 'styled-components'
|
|
7
|
+
import { InfiniteScroll } from '../InfiniteScroll'
|
|
8
|
+
import { TimelineSection } from '../TimelineSection'
|
|
9
|
+
import { NotificationItem } from './NotificationItem'
|
|
10
|
+
import { NotificationPlaceholder } from './NotificationPlaceholder'
|
|
11
|
+
import { GetTenantNotificationsResponse, InfiniteScrollConfig } from './types'
|
|
12
|
+
|
|
13
|
+
export interface NotificationListProps {
|
|
14
|
+
/**
|
|
15
|
+
* Function to call when the message is marked as read (committed).
|
|
16
|
+
* @param id the id of the notification where the read status changed.
|
|
17
|
+
*/
|
|
18
|
+
onCommit: (id: string) => void,
|
|
19
|
+
/**
|
|
20
|
+
* Optional. Function called when the button to perform the notification action is clicked. This function will be run in addition to
|
|
21
|
+
* the notification action.
|
|
22
|
+
* @param id the id of the notification where the button was clicked
|
|
23
|
+
*/
|
|
24
|
+
onClickAction?: (id: string) => void,
|
|
25
|
+
/**
|
|
26
|
+
* Optional. Function called when the button read more in notification is clicked.
|
|
27
|
+
*/
|
|
28
|
+
onClickViewNotification?: () => void,
|
|
29
|
+
/**
|
|
30
|
+
* If you need this notification list to be have an infinite scroll behavior, set this option.
|
|
31
|
+
*/
|
|
32
|
+
infiniteScroll?: InfiniteScrollConfig,
|
|
33
|
+
/**
|
|
34
|
+
* The notifications themselves.
|
|
35
|
+
*/
|
|
36
|
+
items: GetTenantNotificationsResponse[],
|
|
37
|
+
/**
|
|
38
|
+
* A compact notification list don't show date headers (as a timeline) or descriptions of notifications.
|
|
39
|
+
*/
|
|
40
|
+
compact?: boolean,
|
|
41
|
+
/**
|
|
42
|
+
* Whether or not the content is loading. If this is true, the content becomes transparent and the cursor turns into the progress cursor.
|
|
43
|
+
*/
|
|
44
|
+
loading?: boolean,
|
|
45
|
+
/**
|
|
46
|
+
* If true, when the list is empty, the placeholder will say "nothing found" instead of "no notifications".
|
|
47
|
+
*/
|
|
48
|
+
showEmptySearch?: boolean,
|
|
49
|
+
style?: React.CSSProperties,
|
|
50
|
+
className?: string,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface NotificationGroup {
|
|
54
|
+
month: Month,
|
|
55
|
+
day: number,
|
|
56
|
+
year: number,
|
|
57
|
+
items: GetTenantNotificationsResponse[],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const StyledBox = styled(Box)`
|
|
61
|
+
width: 100%;
|
|
62
|
+
position: relative;
|
|
63
|
+
transition: opacity 0.3s;
|
|
64
|
+
|
|
65
|
+
> div:first-child{
|
|
66
|
+
width: 100%;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&.loading {
|
|
70
|
+
opacity: 0.6;
|
|
71
|
+
.loading-mask {
|
|
72
|
+
pointer-events: auto;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.loading-mask {
|
|
77
|
+
opacity: 0;
|
|
78
|
+
position: absolute;
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
top: 0;
|
|
81
|
+
right: 0;
|
|
82
|
+
left: 0;
|
|
83
|
+
bottom: 0;
|
|
84
|
+
cursor: progress;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.placeholder.compact {
|
|
88
|
+
& > div {
|
|
89
|
+
padding-block: 0;
|
|
90
|
+
gap: 16px;
|
|
91
|
+
& > div {
|
|
92
|
+
text-align: center;
|
|
93
|
+
h4 {
|
|
94
|
+
margin-bottom: 10px;
|
|
95
|
+
}
|
|
96
|
+
p {
|
|
97
|
+
margin: 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
`
|
|
103
|
+
|
|
104
|
+
// this assumes the data from the backend is ordered by date (trigger_at)
|
|
105
|
+
function groupNotificationsByDate(notifications: GetTenantNotificationsResponse[]): NotificationGroup[] {
|
|
106
|
+
const groups: NotificationGroup[] = []
|
|
107
|
+
for (const n of notifications) {
|
|
108
|
+
let currentGroup = last(groups)
|
|
109
|
+
const date = new Date(n.trigger_at)
|
|
110
|
+
const year = date.getFullYear()
|
|
111
|
+
const month = date.getMonth() as Month
|
|
112
|
+
const day = date.getDate()
|
|
113
|
+
if (!currentGroup || currentGroup.day !== day || currentGroup.month !== month || currentGroup.year !== year) {
|
|
114
|
+
currentGroup = { year, month, day, items: [] }
|
|
115
|
+
groups.push(currentGroup)
|
|
116
|
+
}
|
|
117
|
+
currentGroup.items.push(n)
|
|
118
|
+
}
|
|
119
|
+
return groups
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const NotificationList = (
|
|
123
|
+
{ items, compact = false, onCommit, onClickAction, onClickViewNotification,
|
|
124
|
+
infiniteScroll, loading, showEmptySearch, style, className }: NotificationListProps,
|
|
125
|
+
) => {
|
|
126
|
+
const groups: NotificationGroup[] = useMemo(
|
|
127
|
+
() => compact ? [{ day: 0, month: 0, year: 0, items }] : groupNotificationsByDate(items),
|
|
128
|
+
[compact, items],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const renderNotifications = (notifications: GetTenantNotificationsResponse[], key?: string) => (
|
|
132
|
+
<Flex sx={{ gap: '4px' }} flexDirection="column" key={key}>
|
|
133
|
+
{notifications?.map((item) => (
|
|
134
|
+
<NotificationItem
|
|
135
|
+
key={item.id}
|
|
136
|
+
id={key ? '' : `notification-item-${item.id}`}
|
|
137
|
+
notification={item}
|
|
138
|
+
isSummary={compact}
|
|
139
|
+
onClickViewNotification={onClickViewNotification}
|
|
140
|
+
onCommit={() => onCommit(item.id)}
|
|
141
|
+
onClickAction={() => onClickAction?.(item.id)}
|
|
142
|
+
/>
|
|
143
|
+
))}
|
|
144
|
+
</Flex>
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return infiniteScroll?.scrollableTarget !== null && (
|
|
148
|
+
<StyledBox style={style} className={listToClass([className, loading && 'loading'])}>
|
|
149
|
+
{items.length ? (
|
|
150
|
+
<InfiniteScroll
|
|
151
|
+
dataLength={items.length || 0}
|
|
152
|
+
next={infiniteScroll?.loadMore ?? (() => { })}
|
|
153
|
+
hasMore={infiniteScroll?.hasMore ?? false}
|
|
154
|
+
// @ts-ignore: the library is wrongly typed and abandoned, meaning, it will never be fixed. The source code clearly accepts
|
|
155
|
+
// HTMLElements as scrollable targets:
|
|
156
|
+
// https://github.com/ankeetmaini/react-infinite-scroll-component/blob/master/src/index.tsx#L168
|
|
157
|
+
scrollableTarget={infiniteScroll?.scrollableTarget}
|
|
158
|
+
>
|
|
159
|
+
<Flex sx={{ gap: '24px' }} flexDirection="column">
|
|
160
|
+
{groups.map((group) => compact
|
|
161
|
+
? renderNotifications(group.items, 'compact')
|
|
162
|
+
: (
|
|
163
|
+
<TimelineSection key={`${group.day}-${group.month}-${group.year}`} month={group.month} day={group.day}>
|
|
164
|
+
{renderNotifications(group.items)}
|
|
165
|
+
</TimelineSection>
|
|
166
|
+
),
|
|
167
|
+
)}
|
|
168
|
+
</Flex>
|
|
169
|
+
</InfiniteScroll>
|
|
170
|
+
) : <NotificationPlaceholder
|
|
171
|
+
isSearch={showEmptySearch}
|
|
172
|
+
className={listToClass(['placeholder', compact && 'compact'])}
|
|
173
|
+
titleAs="p"
|
|
174
|
+
sx={{ minHeight: '350px' }}
|
|
175
|
+
/>}
|
|
176
|
+
<div className="loading-mask"></div>
|
|
177
|
+
</StyledBox>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
@@ -1,43 +1,44 @@
|
|
|
1
|
-
import { AsProp } from '@citric/core/dist/forward-ref-as'
|
|
2
|
-
import { SxProperties } from '@citric/core/dist/sx'
|
|
3
|
-
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
4
|
-
import { PlaceholderCallToAction } from '../Placeholder'
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
isSearch?: boolean,
|
|
8
|
-
style?: React.CSSProperties,
|
|
9
|
-
className?: string,
|
|
10
|
-
sx?: SxProperties,
|
|
11
|
-
sxCard?: SxProperties,
|
|
12
|
-
titleAs?: AsProp,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const NotificationPlaceholder = ({ isSearch, sx, sxCard, className, style, titleAs }: Props) => {
|
|
16
|
-
const t = useTranslate(dictionary)
|
|
17
|
-
|
|
18
|
-
return <PlaceholderCallToAction
|
|
19
|
-
title={isSearch ? t.placeholderSearchTitle : t.placeholderTitle}
|
|
20
|
-
description={isSearch ? t.placeholderSearchDescription : t.placeholderDescription}
|
|
21
|
-
fullWidth
|
|
22
|
-
sx={sx}
|
|
23
|
-
sxCard={sxCard}
|
|
24
|
-
className={className}
|
|
25
|
-
style={style}
|
|
26
|
-
titleAs={titleAs}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
1
|
+
import { AsProp } from '@citric/core/dist/forward-ref-as'
|
|
2
|
+
import { SxProperties } from '@citric/core/dist/sx'
|
|
3
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
4
|
+
import { PlaceholderCallToAction } from '../Placeholder'
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
isSearch?: boolean,
|
|
8
|
+
style?: React.CSSProperties,
|
|
9
|
+
className?: string,
|
|
10
|
+
sx?: SxProperties,
|
|
11
|
+
sxCard?: SxProperties,
|
|
12
|
+
titleAs?: AsProp,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const NotificationPlaceholder = ({ isSearch, sx, sxCard, className, style, titleAs }: Props) => {
|
|
16
|
+
const t = useTranslate(dictionary)
|
|
17
|
+
|
|
18
|
+
return <PlaceholderCallToAction
|
|
19
|
+
title={isSearch ? t.placeholderSearchTitle : t.placeholderTitle}
|
|
20
|
+
description={isSearch ? t.placeholderSearchDescription : t.placeholderDescription}
|
|
21
|
+
fullWidth
|
|
22
|
+
sx={sx}
|
|
23
|
+
sxCard={sxCard}
|
|
24
|
+
className={className}
|
|
25
|
+
style={style}
|
|
26
|
+
titleAs={titleAs}
|
|
27
|
+
placeholderImageConfig={{ width: '200', height: '150' }}
|
|
28
|
+
/>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const dictionary = {
|
|
32
|
+
en: {
|
|
33
|
+
placeholderTitle: 'You don’t have any notifications yet.',
|
|
34
|
+
placeholderDescription: 'Relevant updates will show up here as soon as they’re available.',
|
|
35
|
+
placeholderSearchTitle: 'No notifications match your filters',
|
|
36
|
+
placeholderSearchDescription: 'Try changing the filters to see more results.',
|
|
37
|
+
},
|
|
38
|
+
pt: {
|
|
39
|
+
placeholderTitle: 'Você ainda não tem notificações.',
|
|
40
|
+
placeholderDescription: 'Quando houver alguma atualização relevante, ela será exibida aqui.',
|
|
41
|
+
placeholderSearchTitle: 'Nenhuma notificação corresponde aos filtros',
|
|
42
|
+
placeholderSearchDescription: 'Experimente mudar os filtros para encontrar mais notificações.',
|
|
43
|
+
},
|
|
44
|
+
} satisfies Dictionary
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
export type NotificationType = 'LOW' | 'MEDIUM' | 'HIGH'
|
|
2
|
-
|
|
3
|
-
export type NotificationContext = 'ACCOUNT' | 'STUDIO' | 'WORKSPACE' | 'AI'
|
|
4
|
-
|
|
5
|
-
//The following three notification types comes from the api swagger
|
|
6
|
-
|
|
7
|
-
export type NotificationContentResponse = {
|
|
8
|
-
title: string,
|
|
9
|
-
description: string,
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type GetTenantNotificationsResponse = {
|
|
13
|
-
id: string,
|
|
14
|
-
content: {
|
|
15
|
-
[key: string]: NotificationContentResponse,
|
|
16
|
-
},
|
|
17
|
-
broadcast_level: 'ACCOUNT' | 'INDIVIDUAL' | 'PLATFORM' | 'RESOURCE',
|
|
18
|
-
context: 'ACCOUNT' | 'STUDIO' | 'WORKSPACE' | 'AI',
|
|
19
|
-
target?: string,
|
|
20
|
-
criticality: 'LOW' | 'MEDIUM' | 'HIGH',
|
|
21
|
-
call_to_action: string,
|
|
22
|
-
persistent: boolean,
|
|
23
|
-
committed: boolean,
|
|
24
|
-
last_until?: string,
|
|
25
|
-
trigger_at: string,
|
|
26
|
-
created_at?: string,
|
|
27
|
-
isBanner?: boolean,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type ResponseModelGetTenantNotificationsResponse = {
|
|
31
|
-
items: GetTenantNotificationsResponse[],
|
|
32
|
-
total: number,
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type NotificationTypeFilters = NotificationType | UnreadType
|
|
36
|
-
|
|
37
|
-
export interface NotificationFilters {
|
|
38
|
-
filterBy?: 'criticality' | 'context',
|
|
39
|
-
filterValue?: string,
|
|
40
|
-
sort?: string,
|
|
41
|
-
direction?: string,
|
|
42
|
-
page?: any,
|
|
43
|
-
size?: string,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface NotificationCommitted {
|
|
47
|
-
id: string,
|
|
48
|
-
committed: boolean,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// fixme: please, do not ignore lint rules. They exist for a reason. Next major: transform this enum into a disjoint string type.
|
|
52
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
53
|
-
export enum UnreadType {
|
|
54
|
-
Unread = 'unread',
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface InfiniteScrollConfig {
|
|
58
|
-
/**
|
|
59
|
-
* Function to load more items into the list. Called when the scroll is almost reaching its end.
|
|
60
|
-
*/
|
|
61
|
-
loadMore: () => void,
|
|
62
|
-
/**
|
|
63
|
-
* Set this to false to prevent the scroll from loading more items when it reaches the end.
|
|
64
|
-
*/
|
|
65
|
-
hasMore: boolean,
|
|
66
|
-
/**
|
|
67
|
-
* Defines which scroll will be used as the target of the infinite scroll.
|
|
68
|
-
*
|
|
69
|
-
* If null, nothing renders, it waits until it has a value.
|
|
70
|
-
*/
|
|
71
|
-
scrollableTarget?: string | HTMLElement | null,
|
|
72
|
-
}
|
|
1
|
+
export type NotificationType = 'LOW' | 'MEDIUM' | 'HIGH'
|
|
2
|
+
|
|
3
|
+
export type NotificationContext = 'ACCOUNT' | 'STUDIO' | 'WORKSPACE' | 'AI'
|
|
4
|
+
|
|
5
|
+
//The following three notification types comes from the api swagger
|
|
6
|
+
|
|
7
|
+
export type NotificationContentResponse = {
|
|
8
|
+
title: string,
|
|
9
|
+
description: string,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type GetTenantNotificationsResponse = {
|
|
13
|
+
id: string,
|
|
14
|
+
content: {
|
|
15
|
+
[key: string]: NotificationContentResponse,
|
|
16
|
+
},
|
|
17
|
+
broadcast_level: 'ACCOUNT' | 'INDIVIDUAL' | 'PLATFORM' | 'RESOURCE',
|
|
18
|
+
context: 'ACCOUNT' | 'STUDIO' | 'WORKSPACE' | 'AI',
|
|
19
|
+
target?: string,
|
|
20
|
+
criticality: 'LOW' | 'MEDIUM' | 'HIGH',
|
|
21
|
+
call_to_action: string,
|
|
22
|
+
persistent: boolean,
|
|
23
|
+
committed: boolean,
|
|
24
|
+
last_until?: string,
|
|
25
|
+
trigger_at: string,
|
|
26
|
+
created_at?: string,
|
|
27
|
+
isBanner?: boolean,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type ResponseModelGetTenantNotificationsResponse = {
|
|
31
|
+
items: GetTenantNotificationsResponse[],
|
|
32
|
+
total: number,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type NotificationTypeFilters = NotificationType | UnreadType
|
|
36
|
+
|
|
37
|
+
export interface NotificationFilters {
|
|
38
|
+
filterBy?: 'criticality' | 'context',
|
|
39
|
+
filterValue?: string,
|
|
40
|
+
sort?: string,
|
|
41
|
+
direction?: string,
|
|
42
|
+
page?: any,
|
|
43
|
+
size?: string,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface NotificationCommitted {
|
|
47
|
+
id: string,
|
|
48
|
+
committed: boolean,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// fixme: please, do not ignore lint rules. They exist for a reason. Next major: transform this enum into a disjoint string type.
|
|
52
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
53
|
+
export enum UnreadType {
|
|
54
|
+
Unread = 'unread',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface InfiniteScrollConfig {
|
|
58
|
+
/**
|
|
59
|
+
* Function to load more items into the list. Called when the scroll is almost reaching its end.
|
|
60
|
+
*/
|
|
61
|
+
loadMore: () => void,
|
|
62
|
+
/**
|
|
63
|
+
* Set this to false to prevent the scroll from loading more items when it reaches the end.
|
|
64
|
+
*/
|
|
65
|
+
hasMore: boolean,
|
|
66
|
+
/**
|
|
67
|
+
* Defines which scroll will be used as the target of the infinite scroll.
|
|
68
|
+
*
|
|
69
|
+
* If null, nothing renders, it waits until it has a value.
|
|
70
|
+
*/
|
|
71
|
+
scrollableTarget?: string | HTMLElement | null,
|
|
72
|
+
}
|