pixelize-design-library 3.0.0 → 3.1.0
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 +64 -1
- package/COMPONENT-GOTCHAS.md +37 -13
- package/DESIGN-GUIDE.md +4 -1
- package/dist/Components/NotificationBell/NotificationBellProps.d.ts +3 -3
- package/dist/Components/NotificationBell/NotificationItem.d.ts +1 -1
- package/dist/Components/NotificationBell/NotificationItem.js +1 -1
- package/dist/Components/NotificationBell/NotificationList.d.ts +1 -1
- package/dist/Components/NotificationBell/NotificationList.js +1 -1
- package/dist/Components/NotificationBell/NotificationPanel.d.ts +1 -1
- package/dist/Components/NotificationBell/NotificationPanel.js +1 -1
- package/dist/Components/NotificationCenter/center/CenterPanel.d.ts +15 -0
- package/dist/Components/NotificationCenter/center/CenterPanel.js +87 -0
- package/dist/Components/NotificationCenter/center/NotificationCenter.d.ts +4 -0
- package/dist/Components/NotificationCenter/center/NotificationCenter.js +130 -0
- package/dist/Components/NotificationCenter/center/NotificationCenterProps.d.ts +71 -0
- package/dist/Components/NotificationCenter/center/NotificationCenterProps.js +2 -0
- package/dist/Components/NotificationCenter/center/PanelRows.d.ts +10 -0
- package/dist/Components/NotificationCenter/center/PanelRows.js +35 -0
- package/dist/Components/NotificationCenter/center/PanelStates.d.ts +17 -0
- package/dist/Components/NotificationCenter/center/PanelStates.js +21 -0
- package/dist/Components/NotificationCenter/center/centerLabels.d.ts +4 -0
- package/dist/Components/NotificationCenter/center/centerLabels.js +38 -0
- package/dist/Components/NotificationCenter/center/useArrivals.d.ts +14 -0
- package/dist/Components/NotificationCenter/center/useArrivals.js +71 -0
- package/dist/Components/NotificationCenter/center/useIsSheet.d.ts +2 -0
- package/dist/Components/NotificationCenter/center/useIsSheet.js +20 -0
- package/dist/Components/NotificationCenter/core/NotificationCenterProvider.d.ts +17 -0
- package/dist/Components/NotificationCenter/core/NotificationCenterProvider.js +46 -0
- package/dist/Components/NotificationCenter/core/NotificationEngineProps.d.ts +182 -0
- package/dist/Components/NotificationCenter/core/NotificationEngineProps.js +2 -0
- package/dist/Components/NotificationCenter/core/NotificationSourceProps.d.ts +284 -0
- package/dist/Components/NotificationCenter/core/NotificationSourceProps.js +2 -0
- package/dist/Components/NotificationCenter/core/actionError.d.ts +4 -0
- package/dist/Components/NotificationCenter/core/actionError.js +45 -0
- package/dist/Components/NotificationCenter/core/format.d.ts +4 -0
- package/dist/Components/NotificationCenter/core/format.js +91 -0
- package/dist/Components/NotificationCenter/core/grouping.d.ts +14 -0
- package/dist/Components/NotificationCenter/core/grouping.js +102 -0
- package/dist/Components/NotificationCenter/core/labels.d.ts +6 -0
- package/dist/Components/NotificationCenter/core/labels.js +94 -0
- package/dist/Components/NotificationCenter/core/overlay.d.ts +109 -0
- package/dist/Components/NotificationCenter/core/overlay.js +281 -0
- package/dist/Components/NotificationCenter/core/useNotificationEngine.d.ts +13 -0
- package/dist/Components/NotificationCenter/core/useNotificationEngine.js +415 -0
- package/dist/Components/NotificationCenter/inbox/InboxList.d.ts +17 -0
- package/dist/Components/NotificationCenter/inbox/InboxList.js +25 -0
- package/dist/Components/NotificationCenter/inbox/InboxStates.d.ts +17 -0
- package/dist/Components/NotificationCenter/inbox/InboxStates.js +27 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInbox.d.ts +4 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInbox.js +434 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInboxProps.d.ts +106 -0
- package/dist/Components/NotificationCenter/inbox/NotificationInboxProps.js +2 -0
- package/dist/Components/NotificationCenter/inbox/ShortcutsDialog.d.ts +8 -0
- package/dist/Components/NotificationCenter/inbox/ShortcutsDialog.js +63 -0
- package/dist/Components/NotificationCenter/inbox/inboxLabels.d.ts +4 -0
- package/dist/Components/NotificationCenter/inbox/inboxLabels.js +56 -0
- package/dist/Components/NotificationCenter/inbox/inboxModel.d.ts +17 -0
- package/dist/Components/NotificationCenter/inbox/inboxModel.js +47 -0
- package/dist/Components/NotificationCenter/inbox/useControllable.d.ts +1 -0
- package/dist/Components/NotificationCenter/inbox/useControllable.js +15 -0
- package/dist/Components/NotificationCenter/index.d.ts +8 -0
- package/dist/Components/NotificationCenter/index.js +26 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferences.d.ts +4 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferences.js +72 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferencesProps.d.ts +111 -0
- package/dist/Components/NotificationCenter/preferences/NotificationPreferencesProps.js +2 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceCells.d.ts +16 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceCells.js +35 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceGroups.d.ts +15 -0
- package/dist/Components/NotificationCenter/preferences/PreferenceGroups.js +26 -0
- package/dist/Components/NotificationCenter/preferences/preferencesLabels.d.ts +2 -0
- package/dist/Components/NotificationCenter/preferences/preferencesLabels.js +28 -0
- package/dist/Components/NotificationCenter/preferences/useAutosave.d.ts +8 -0
- package/dist/Components/NotificationCenter/preferences/useAutosave.js +41 -0
- package/dist/Components/NotificationCenter/row/NotificationDetail.d.ts +4 -0
- package/dist/Components/NotificationCenter/row/NotificationDetail.js +65 -0
- package/dist/Components/NotificationCenter/row/NotificationDetailProps.d.ts +15 -0
- package/dist/Components/NotificationCenter/row/NotificationDetailProps.js +2 -0
- package/dist/Components/NotificationCenter/row/NotificationRow.d.ts +3 -0
- package/dist/Components/NotificationCenter/row/NotificationRow.js +124 -0
- package/dist/Components/NotificationCenter/row/NotificationRowProps.d.ts +33 -0
- package/dist/Components/NotificationCenter/row/NotificationRowProps.js +2 -0
- package/dist/Components/NotificationCenter/row/ReasonForm.d.ts +14 -0
- package/dist/Components/NotificationCenter/row/ReasonForm.js +59 -0
- package/dist/Components/NotificationCenter/row/RowActions.d.ts +16 -0
- package/dist/Components/NotificationCenter/row/RowActions.js +36 -0
- package/dist/Components/NotificationCenter/row/RowQuickActions.d.ts +17 -0
- package/dist/Components/NotificationCenter/row/RowQuickActions.js +57 -0
- package/dist/Components/NotificationCenter/row/RowStatus.d.ts +10 -0
- package/dist/Components/NotificationCenter/row/RowStatus.js +42 -0
- package/dist/Components/NotificationCenter/row/category.d.ts +8 -0
- package/dist/Components/NotificationCenter/row/category.js +13 -0
- package/dist/Components/NotificationCenter/row/rowMenu.d.ts +14 -0
- package/dist/Components/NotificationCenter/row/rowMenu.js +37 -0
- package/dist/Components/NotificationCenter/row/rowParts.d.ts +40 -0
- package/dist/Components/NotificationCenter/row/rowParts.js +72 -0
- package/dist/Components/NotificationCenter/row/rowStyles.d.ts +11 -0
- package/dist/Components/NotificationCenter/row/rowStyles.js +25 -0
- package/dist/Components/SegmentedControl/segmentedInk.d.ts +4 -0
- package/dist/Components/SegmentedControl/segmentedInk.js +16 -2
- package/dist/Components/SideBar/SecondaryBar.js +2 -2
- package/dist/Components/SideBar/SideBar.js +18 -8
- package/dist/Components/SideBar/SideBarProps.d.ts +4 -2
- package/dist/Components/SideBar/components/MenuItemBox.d.ts +0 -2
- package/dist/Components/SideBar/components/MenuItemBox.js +2 -3
- package/dist/Components/SideBar/components/MenuItems.js +2 -2
- package/dist/Components/SideBar/components/PinnedSection.js +3 -4
- package/dist/Components/SideBar/sidebarScale.d.ts +0 -2
- package/dist/Components/SideBar/sidebarScale.js +0 -2
- package/dist/Components/Switch/Switch.d.ts +1 -1
- package/dist/Components/Switch/Switch.js +2 -2
- package/dist/Components/Switch/SwitchProps.d.ts +3 -0
- package/dist/Components/Toggle/TableToggle.js +4 -5
- package/dist/Components/Toggle/tableToggleInk.d.ts +7 -0
- package/dist/Components/Toggle/tableToggleInk.js +28 -0
- package/dist/esm/Components/NotificationBell/NotificationItem.js +1 -1
- package/dist/esm/Components/NotificationBell/NotificationList.js +1 -1
- package/dist/esm/Components/NotificationBell/NotificationPanel.js +1 -1
- package/dist/esm/Components/NotificationCenter/center/CenterPanel.js +81 -0
- package/dist/esm/Components/NotificationCenter/center/NotificationCenter.js +92 -0
- package/dist/esm/Components/NotificationCenter/center/NotificationCenterProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/center/PanelRows.js +28 -0
- package/dist/esm/Components/NotificationCenter/center/PanelStates.js +11 -0
- package/dist/esm/Components/NotificationCenter/center/centerLabels.js +34 -0
- package/dist/esm/Components/NotificationCenter/center/useArrivals.js +67 -0
- package/dist/esm/Components/NotificationCenter/center/useIsSheet.js +16 -0
- package/dist/esm/Components/NotificationCenter/core/NotificationCenterProvider.js +35 -0
- package/dist/esm/Components/NotificationCenter/core/NotificationEngineProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/core/NotificationSourceProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/core/actionError.js +39 -0
- package/dist/esm/Components/NotificationCenter/core/format.js +87 -0
- package/dist/esm/Components/NotificationCenter/core/grouping.js +88 -0
- package/dist/esm/Components/NotificationCenter/core/labels.js +88 -0
- package/dist/esm/Components/NotificationCenter/core/overlay.js +274 -0
- package/dist/esm/Components/NotificationCenter/core/useNotificationEngine.js +408 -0
- package/dist/esm/Components/NotificationCenter/inbox/InboxList.js +18 -0
- package/dist/esm/Components/NotificationCenter/inbox/InboxStates.js +18 -0
- package/dist/esm/Components/NotificationCenter/inbox/NotificationInbox.js +396 -0
- package/dist/esm/Components/NotificationCenter/inbox/NotificationInboxProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/inbox/ShortcutsDialog.js +23 -0
- package/dist/esm/Components/NotificationCenter/inbox/inboxLabels.js +52 -0
- package/dist/esm/Components/NotificationCenter/inbox/inboxModel.js +37 -0
- package/dist/esm/Components/NotificationCenter/inbox/useControllable.js +11 -0
- package/dist/esm/Components/NotificationCenter/index.js +8 -0
- package/dist/esm/Components/NotificationCenter/preferences/NotificationPreferences.js +67 -0
- package/dist/esm/Components/NotificationCenter/preferences/NotificationPreferencesProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/preferences/PreferenceCells.js +27 -0
- package/dist/esm/Components/NotificationCenter/preferences/PreferenceGroups.js +21 -0
- package/dist/esm/Components/NotificationCenter/preferences/preferencesLabels.js +24 -0
- package/dist/esm/Components/NotificationCenter/preferences/useAutosave.js +37 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationDetail.js +60 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationDetailProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationRow.js +119 -0
- package/dist/esm/Components/NotificationCenter/row/NotificationRowProps.js +1 -0
- package/dist/esm/Components/NotificationCenter/row/ReasonForm.js +52 -0
- package/dist/esm/Components/NotificationCenter/row/RowActions.js +28 -0
- package/dist/esm/Components/NotificationCenter/row/RowQuickActions.js +49 -0
- package/dist/esm/Components/NotificationCenter/row/RowStatus.js +35 -0
- package/dist/esm/Components/NotificationCenter/row/category.js +9 -0
- package/dist/esm/Components/NotificationCenter/row/rowMenu.js +30 -0
- package/dist/esm/Components/NotificationCenter/row/rowParts.js +59 -0
- package/dist/esm/Components/NotificationCenter/row/rowStyles.js +19 -0
- package/dist/esm/Components/SegmentedControl/segmentedInk.js +15 -2
- package/dist/esm/Components/SideBar/SecondaryBar.js +3 -3
- package/dist/esm/Components/SideBar/SideBar.js +18 -8
- package/dist/esm/Components/SideBar/components/MenuItemBox.js +2 -3
- package/dist/esm/Components/SideBar/components/MenuItems.js +2 -2
- package/dist/esm/Components/SideBar/components/PinnedSection.js +3 -4
- package/dist/esm/Components/SideBar/sidebarScale.js +0 -2
- package/dist/esm/Components/Switch/Switch.js +2 -2
- package/dist/esm/Components/Toggle/TableToggle.js +4 -5
- package/dist/esm/Components/Toggle/tableToggleInk.js +24 -0
- package/dist/esm/index.js +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +16 -3
- package/package.json +1 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import type { ElementType, KeyboardEvent, MouseEvent, ReactNode } from "react";
|
|
2
|
+
import type { Tone } from "../../../Theme/tokens/builders/tone";
|
|
3
|
+
/** A run of title text: a plain string, or `{ b }` for a bold run. Always rendered as text, never as markup. */
|
|
4
|
+
export type NotificationTitlePart = string | {
|
|
5
|
+
b: string;
|
|
6
|
+
};
|
|
7
|
+
/** The person behind an entry; shown as an avatar with the category glyph on its corner. */
|
|
8
|
+
export interface NotificationActor {
|
|
9
|
+
/** Initials, the avatar colour and the alt text come from this. */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Avatar image; initials show until it loads, or when it fails. */
|
|
12
|
+
avatarUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
/** How a decision button reads: positive gets a check, negative asks in red, primary is the solid button in the detail. */
|
|
15
|
+
export type NotificationActionIntent = "positive" | "negative" | "primary" | "neutral";
|
|
16
|
+
/** One button on an entry: a decision sent through `onAction`, or a link (with `href`) opened through `onOpen`. */
|
|
17
|
+
export interface NotificationAction {
|
|
18
|
+
/** Stable id passed to `onAction`; a bulk decision needs every selected entry to carry it. */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Button text, e.g. "Approve"; the accessible name adds the entry's subject. */
|
|
21
|
+
label: string;
|
|
22
|
+
/** Past tense for the status line and the toast, e.g. "Approved". Defaults to `label`. */
|
|
23
|
+
doneLabel?: string;
|
|
24
|
+
/** Defaults to "neutral". */
|
|
25
|
+
intent?: NotificationActionIntent;
|
|
26
|
+
/** "required" asks inline and blocks an empty send; "optional" asks but allows one. Omit to decide in one click. */
|
|
27
|
+
reason?: "required" | "optional";
|
|
28
|
+
/** Label over the inline reason box; defaults to `labels.reasonLabel`. */
|
|
29
|
+
reasonLabel?: string;
|
|
30
|
+
/** Makes this a link: the row calls `onOpen` with this href instead of deciding. */
|
|
31
|
+
href?: string;
|
|
32
|
+
}
|
|
33
|
+
/** One notification as the app passes it in; each app writes one adapter from its rows to this shape. */
|
|
34
|
+
export interface NotificationEntry {
|
|
35
|
+
/** Unique across everything the app passes; duplicates are dropped, first one wins. */
|
|
36
|
+
id: string;
|
|
37
|
+
/** Key into `categories`; an unknown key renders as a neutral Activity entry. */
|
|
38
|
+
category: string;
|
|
39
|
+
/** Bold parts as `{ b }`, so apps can translate templates without markup. */
|
|
40
|
+
title: NotificationTitlePart[];
|
|
41
|
+
/** Short name for button labels and toasts, e.g. "Priya's leave". */
|
|
42
|
+
subject: string;
|
|
43
|
+
/** Epoch ms. */
|
|
44
|
+
createdAt: number;
|
|
45
|
+
/** Leave out when the entry has no read state (most decisions). */
|
|
46
|
+
isRead?: boolean;
|
|
47
|
+
/** Shows an avatar with the category glyph; without it the row shows the category tile. */
|
|
48
|
+
actor?: NotificationActor;
|
|
49
|
+
/** A short code before the title, e.g. "PT-231". */
|
|
50
|
+
eyebrow?: string;
|
|
51
|
+
/** One muted line under the title. */
|
|
52
|
+
context?: string;
|
|
53
|
+
/** A quoted excerpt, clamped to two lines in rows. */
|
|
54
|
+
preview?: string;
|
|
55
|
+
/** Full text for the detail view; falls back to `preview`. */
|
|
56
|
+
body?: string;
|
|
57
|
+
/** Label/value facts for the detail view. */
|
|
58
|
+
fields?: Array<[label: string, value: string]>;
|
|
59
|
+
/** A from-to change, shown as two chips. */
|
|
60
|
+
change?: {
|
|
61
|
+
from: string;
|
|
62
|
+
to: string;
|
|
63
|
+
};
|
|
64
|
+
/** The app route; the row renders a real link, so a modifier-click opens a new tab. */
|
|
65
|
+
href?: string;
|
|
66
|
+
/** Entries sharing a key collapse into one thread in Activity. */
|
|
67
|
+
groupKey?: string;
|
|
68
|
+
/** Decisions and links shown on the row; decisions also need `onAction`. */
|
|
69
|
+
actions?: NotificationAction[];
|
|
70
|
+
/** Why it can't be opened any more; the detail shows this instead of the facts. */
|
|
71
|
+
blocked?: string;
|
|
72
|
+
/** True for entries in the Archived list; only meaningful with `onArchive`. */
|
|
73
|
+
archived?: boolean;
|
|
74
|
+
/** Test id root: the row gets it, its controls get `${testId}-link`, `-read`, `-archive`, `-restore`, `-more`, `-<action id>`. */
|
|
75
|
+
testId?: string;
|
|
76
|
+
}
|
|
77
|
+
/** Where a category's entries land: "always" in Needs you (decisions), "untilRead" while unread (mentions, assignments). */
|
|
78
|
+
export type NotificationNeedsYouRule = "always" | "untilRead";
|
|
79
|
+
/** How one category looks and which list its entries land in. */
|
|
80
|
+
export interface NotificationCategory {
|
|
81
|
+
/** Group heading in Needs you, the type filter and the detail kicker. */
|
|
82
|
+
label: string;
|
|
83
|
+
/** A lucide-react icon component; pass the component, not an element. */
|
|
84
|
+
icon: ElementType;
|
|
85
|
+
/** Tints the icon tile; defaults to "neutral". */
|
|
86
|
+
tone?: Tone;
|
|
87
|
+
/** Omit to land in Activity. */
|
|
88
|
+
needsYou?: NotificationNeedsYouRule;
|
|
89
|
+
}
|
|
90
|
+
/** The app's category map; its key order is the group order in Needs you. */
|
|
91
|
+
export type NotificationCategories = Record<string, NotificationCategory>;
|
|
92
|
+
/** Server counts, never computed from loaded rows; the library moves them optimistically and never below 0. */
|
|
93
|
+
export interface NotificationCounts {
|
|
94
|
+
/** Open decisions plus unread `untilRead` entries; the bell number. */
|
|
95
|
+
needsYou: number;
|
|
96
|
+
/** Unread entries in Activity; the bell dot and the Activity tab count. */
|
|
97
|
+
unread: number;
|
|
98
|
+
}
|
|
99
|
+
/** Why a decision did not go through: conflict = already decided, forbidden = no longer the approver, reasonRequired = the server wants a comment. */
|
|
100
|
+
export type NotificationActionErrorKind = "conflict" | "forbidden" | "reasonRequired" | "failed";
|
|
101
|
+
/** What `onAction` rejects with; build it with `notificationActionError`. An HTTP 409 maps to conflict and 403 to forbidden. */
|
|
102
|
+
export interface NotificationActionError {
|
|
103
|
+
/** Picks the row state: denied for conflict and forbidden, the reason form for reasonRequired, retry for failed. */
|
|
104
|
+
kind: NotificationActionErrorKind;
|
|
105
|
+
/** Shown in the row and the toast; defaults per kind from `labels`. */
|
|
106
|
+
message?: string;
|
|
107
|
+
}
|
|
108
|
+
/** The lists the panel and the inbox show. */
|
|
109
|
+
export type NotificationZoneKey = "needsYou" | "activity" | "archived";
|
|
110
|
+
/** What "Mark all read" covers: one list plus the inbox filters. No category and no query means the whole list, loaded or not. */
|
|
111
|
+
export interface NotificationMarkAllScope {
|
|
112
|
+
/** The list the button sits in. */
|
|
113
|
+
zone: Exclude<NotificationZoneKey, "archived">;
|
|
114
|
+
/** The inbox type filter, a key of `categories`. */
|
|
115
|
+
category?: string;
|
|
116
|
+
/** The inbox search text. */
|
|
117
|
+
query?: string;
|
|
118
|
+
}
|
|
119
|
+
/** An app-specific row action, e.g. tickets' "Stop watching PT-108"; shown on hover and in the phone menu. */
|
|
120
|
+
export interface NotificationExtraAction {
|
|
121
|
+
/** Unique within the entry; also the test id suffix. */
|
|
122
|
+
id: string;
|
|
123
|
+
/** Accessible name and tooltip. */
|
|
124
|
+
label: string;
|
|
125
|
+
/** A lucide-react icon component. */
|
|
126
|
+
icon: ElementType;
|
|
127
|
+
/** Shown as a success toast once `onSelect` resolves. */
|
|
128
|
+
doneLabel?: string;
|
|
129
|
+
/** Runs the action; a rejected promise shows "Couldn't …" in a toast. */
|
|
130
|
+
onSelect: (entry: NotificationEntry) => void | Promise<unknown>;
|
|
131
|
+
}
|
|
132
|
+
/** The list's load state; "noAccess" means the person's role can't see notifications at all. */
|
|
133
|
+
export type NotificationLoadStatus = "loading" | "ready" | "error" | "noAccess";
|
|
134
|
+
/** Every string the shared parts render; English defaults in `DEFAULT_NOTIFICATION_LABELS`. Lane components add their own. */
|
|
135
|
+
export interface NotificationLabels {
|
|
136
|
+
/** Screen-reader prefix of an unread row, and the detail's Unread chip. */
|
|
137
|
+
unread: string;
|
|
138
|
+
/** A decision button's accessible name, e.g. "Approve: Priya's leave". */
|
|
139
|
+
actionName: (actionLabel: string, subject: string) => string;
|
|
140
|
+
markRead: string;
|
|
141
|
+
markUnread: string;
|
|
142
|
+
archive: string;
|
|
143
|
+
restore: string;
|
|
144
|
+
/** The phone row's menu button. */
|
|
145
|
+
moreActions: (title: string) => string;
|
|
146
|
+
/** The inbox row checkbox. */
|
|
147
|
+
select: (title: string) => string;
|
|
148
|
+
/** `count` is how many older rows sit behind the thread's newest. */
|
|
149
|
+
threadShow: (count: number) => string;
|
|
150
|
+
threadHide: (count: number) => string;
|
|
151
|
+
/** Chip on a decision waiting 3 days or more. */
|
|
152
|
+
waiting: (days: number) => string;
|
|
153
|
+
/** Chip on a row whose entry is `blocked`. */
|
|
154
|
+
noAccess: string;
|
|
155
|
+
/** Spoken between the two change chips. */
|
|
156
|
+
changedTo: string;
|
|
157
|
+
/** Beside the disabled buttons while offline. */
|
|
158
|
+
offlineNote: string;
|
|
159
|
+
reasonLabel: string;
|
|
160
|
+
reasonOptional: string;
|
|
161
|
+
/** Placeholder of the reason box on a negative action. */
|
|
162
|
+
reasonPlaceholder: string;
|
|
163
|
+
/** A required reason left empty on a negative action. */
|
|
164
|
+
reasonMissingNegative: string;
|
|
165
|
+
/** A required reason left empty on any other action. */
|
|
166
|
+
reasonMissing: string;
|
|
167
|
+
keyCtrl: string;
|
|
168
|
+
keyEnter: string;
|
|
169
|
+
keyEsc: string;
|
|
170
|
+
toSend: string;
|
|
171
|
+
toCancel: string;
|
|
172
|
+
cancel: string;
|
|
173
|
+
undo: string;
|
|
174
|
+
/** Status line while a decision is on its way. */
|
|
175
|
+
sending: string;
|
|
176
|
+
/** Status line lead-in when a decision failed, e.g. "Couldn't approve." */
|
|
177
|
+
couldNot: (actionLabel: string) => string;
|
|
178
|
+
retry: string;
|
|
179
|
+
dismiss: string;
|
|
180
|
+
conflictMessage: string;
|
|
181
|
+
forbiddenMessage: string;
|
|
182
|
+
failedMessage: string;
|
|
183
|
+
reasonRequiredMessage: string;
|
|
184
|
+
offlineMessage: string;
|
|
185
|
+
/** Toast title of a decision, e.g. "Approved · Priya's leave". */
|
|
186
|
+
decided: (doneLabel: string, subject: string) => string;
|
|
187
|
+
/** Spoken when a decision is made, with the undo hint. */
|
|
188
|
+
decidedAnnouncement: (doneLabel: string, subject: string) => string;
|
|
189
|
+
/** The subject of a bulk decision, e.g. "3 requests". */
|
|
190
|
+
requests: (count: number) => string;
|
|
191
|
+
/** Toast description when a reason was given. */
|
|
192
|
+
reasonQuote: (reason: string) => string;
|
|
193
|
+
/** Toast title when decisions failed; `count` is above 1 for a bulk decision. */
|
|
194
|
+
decisionFailed: (actionLabel: string, count: number) => string;
|
|
195
|
+
/** Toast title when someone else decided first. */
|
|
196
|
+
conflicted: (count: number) => string;
|
|
197
|
+
/** Toast title when the person may no longer decide. */
|
|
198
|
+
forbidden: (count: number) => string;
|
|
199
|
+
/** Toast title when the server asked for a comment. */
|
|
200
|
+
reasonNeeded: (actionLabel: string) => string;
|
|
201
|
+
archived: (count: number) => string;
|
|
202
|
+
restored: string;
|
|
203
|
+
markedRead: (count: number) => string;
|
|
204
|
+
markAllFailed: string;
|
|
205
|
+
readFailed: (read: boolean) => string;
|
|
206
|
+
archiveFailed: string;
|
|
207
|
+
restoreFailed: string;
|
|
208
|
+
/** Toast title when an extra action rejects. */
|
|
209
|
+
extraFailed: (actionLabel: string) => string;
|
|
210
|
+
undone: string;
|
|
211
|
+
decisionUndone: string;
|
|
212
|
+
arrivedOne: (title: string) => string;
|
|
213
|
+
arrivedMany: (count: number) => string;
|
|
214
|
+
/** The detail's footer button; `appName` comes from the source. */
|
|
215
|
+
openIn: (appName?: string) => string;
|
|
216
|
+
blockedTitle: string;
|
|
217
|
+
back: string;
|
|
218
|
+
/** Category label of an entry whose key is not in `categories`. */
|
|
219
|
+
otherCategory: string;
|
|
220
|
+
now: string;
|
|
221
|
+
justNow: string;
|
|
222
|
+
todayAt: (time: string) => string;
|
|
223
|
+
yesterdayAt: (time: string) => string;
|
|
224
|
+
today: string;
|
|
225
|
+
yesterday: string;
|
|
226
|
+
thisWeek: string;
|
|
227
|
+
older: string;
|
|
228
|
+
}
|
|
229
|
+
/** Second argument of `onOpen`: the click or key press that opened the entry. */
|
|
230
|
+
export type NotificationOpenEvent = MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>;
|
|
231
|
+
/** The data and callbacks every notification component on a page shares; a feature appears only when its callback is passed. */
|
|
232
|
+
export interface NotificationSourceProps {
|
|
233
|
+
/** All loaded entries, any order; the app's cache stays the source of truth. */
|
|
234
|
+
entries: NotificationEntry[];
|
|
235
|
+
/** From the server, never counted from `entries`. */
|
|
236
|
+
counts: NotificationCounts;
|
|
237
|
+
/** Label, icon, tone and list rule per category key; keep it stable, a new object re-derives every row. */
|
|
238
|
+
categories: NotificationCategories;
|
|
239
|
+
/** Row click and "Open in …"; the app navigates. An unread entry is marked read first. For a link action, `entry.href` is the action's. */
|
|
240
|
+
onOpen: (entry: NotificationEntry, event?: NotificationOpenEvent, action?: NotificationAction) => void;
|
|
241
|
+
/** Sends a decision once its 6 s undo window ends. Reject with a `NotificationActionError` or an HTTP error. Omit for no inline decisions. */
|
|
242
|
+
onAction?: (entry: NotificationEntry, action: NotificationAction, reason?: string) => Promise<unknown>;
|
|
243
|
+
/** Read and unread, one or many, sent at once. Omit for no read toggles. */
|
|
244
|
+
onMarkRead?: (ids: string[], read: boolean) => Promise<unknown>;
|
|
245
|
+
/** Everything in the scope, loaded or not; sent after the undo window. Omit to hide "Mark all read". */
|
|
246
|
+
onMarkAllRead?: (scope: NotificationMarkAllScope) => Promise<unknown>;
|
|
247
|
+
/** Sent after the undo window; Needs-you entries can't be archived. Omit for no archive and no Archived tab. */
|
|
248
|
+
onArchive?: (ids: string[]) => Promise<unknown>;
|
|
249
|
+
/** Moves archived entries back to Activity, sent at once. Omit to hide "Move back". */
|
|
250
|
+
onRestore?: (ids: string[]) => Promise<unknown>;
|
|
251
|
+
/** Next page by cursor. Omit and the list ends at what is loaded. */
|
|
252
|
+
onLoadMore?: () => void;
|
|
253
|
+
/** Shows the load-more control. */
|
|
254
|
+
hasMore?: boolean;
|
|
255
|
+
/** Puts the load-more control in its busy state. */
|
|
256
|
+
isLoadingMore?: boolean;
|
|
257
|
+
/** App-specific row actions for one entry. */
|
|
258
|
+
extraActions?: (entry: NotificationEntry) => NotificationExtraAction[];
|
|
259
|
+
/** Defaults to "ready". Pass "loading" until the first fetch resolves, so it is not announced as new arrivals. */
|
|
260
|
+
status?: NotificationLoadStatus;
|
|
261
|
+
/** Retries a failed load from the error state. */
|
|
262
|
+
onRetry?: () => void;
|
|
263
|
+
/** Disables every change and shows the offline banner. */
|
|
264
|
+
isOffline?: boolean;
|
|
265
|
+
/** When the loaded entries were fetched, epoch ms; the offline banner names it. */
|
|
266
|
+
loadedAt?: number;
|
|
267
|
+
/** As in "Open in hrms". */
|
|
268
|
+
appName?: string;
|
|
269
|
+
/** Overrides for the shared strings, anything left out stays English; keep it stable across renders. */
|
|
270
|
+
labels?: Partial<NotificationLabels>;
|
|
271
|
+
/** BCP 47 tag for dates, relative times and numbers. Defaults to "en-IN". */
|
|
272
|
+
locale?: string;
|
|
273
|
+
}
|
|
274
|
+
/** Shares one overlay (undo windows, sends, optimistic counts, the announcer) across every notification component below it. Needs a `ToasterProvider` above. */
|
|
275
|
+
export interface NotificationCenterProviderProps extends NotificationSourceProps {
|
|
276
|
+
/** The bell, panel, inbox or custom layout that reads `useNotificationCenter()`. */
|
|
277
|
+
children: ReactNode;
|
|
278
|
+
}
|
|
279
|
+
/** No source props at all: the component reads the nearest `NotificationCenterProvider`. `labels` stays free for the component's own strings. */
|
|
280
|
+
export type NotificationSourceOmitted = {
|
|
281
|
+
[K in Exclude<keyof NotificationSourceProps, "labels">]?: undefined;
|
|
282
|
+
};
|
|
283
|
+
/** Pass every source prop when there is no provider above, or none inside one; a mix is a type error. */
|
|
284
|
+
export type NotificationSourceBinding = NotificationSourceProps | NotificationSourceOmitted;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NotificationActionError, NotificationActionErrorKind } from "./NotificationSourceProps";
|
|
2
|
+
export declare const isNotificationActionError: (value: unknown) => value is NotificationActionError;
|
|
3
|
+
export declare const notificationActionError: (kind: NotificationActionErrorKind, message?: string) => Error & NotificationActionError;
|
|
4
|
+
export declare const toNotificationActionError: (error: unknown) => NotificationActionError;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toNotificationActionError = exports.notificationActionError = exports.isNotificationActionError = void 0;
|
|
4
|
+
const apiError_1 = require("../../../Utils/apiError");
|
|
5
|
+
const KINDS = ["conflict", "forbidden", "reasonRequired", "failed"];
|
|
6
|
+
const asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
7
|
+
const text = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
8
|
+
const isNotificationActionError = (value) => KINDS.includes(asRecord(value).kind);
|
|
9
|
+
exports.isNotificationActionError = isNotificationActionError;
|
|
10
|
+
const notificationActionError = (kind, message) => {
|
|
11
|
+
const error = new Error(message !== null && message !== void 0 ? message : "");
|
|
12
|
+
error.name = "NotificationActionError";
|
|
13
|
+
error.kind = kind;
|
|
14
|
+
return error;
|
|
15
|
+
};
|
|
16
|
+
exports.notificationActionError = notificationActionError;
|
|
17
|
+
const statusOf = (error) => {
|
|
18
|
+
const direct = (0, apiError_1.getApiErrorStatus)(error);
|
|
19
|
+
if (direct !== null)
|
|
20
|
+
return direct;
|
|
21
|
+
const status = asRecord(asRecord(error).response).status;
|
|
22
|
+
return typeof status === "number" ? status : null;
|
|
23
|
+
};
|
|
24
|
+
const serverMessage = (error) => {
|
|
25
|
+
var _a;
|
|
26
|
+
const record = asRecord(error);
|
|
27
|
+
return (_a = text(asRecord(record.data).message)) !== null && _a !== void 0 ? _a : text(asRecord(asRecord(record.response).data).message);
|
|
28
|
+
};
|
|
29
|
+
const kindFor = (status) => {
|
|
30
|
+
if (status === 409)
|
|
31
|
+
return "conflict";
|
|
32
|
+
if (status === 403)
|
|
33
|
+
return "forbidden";
|
|
34
|
+
return "failed";
|
|
35
|
+
};
|
|
36
|
+
const toNotificationActionError = (error) => {
|
|
37
|
+
if ((0, exports.isNotificationActionError)(error)) {
|
|
38
|
+
const message = text(error.message);
|
|
39
|
+
return message ? { kind: error.kind, message } : { kind: error.kind };
|
|
40
|
+
}
|
|
41
|
+
const message = serverMessage(error);
|
|
42
|
+
const kind = kindFor(statusOf(error));
|
|
43
|
+
return message ? { kind, message } : { kind };
|
|
44
|
+
};
|
|
45
|
+
exports.toNotificationActionError = toNotificationActionError;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NotificationLabels } from "./NotificationSourceProps";
|
|
2
|
+
import type { NotificationFormatter } from "./NotificationEngineProps";
|
|
3
|
+
export declare const DAY_MS: number;
|
|
4
|
+
export declare const createNotificationFormatter: (locale: string, labels: NotificationLabels) => NotificationFormatter;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createNotificationFormatter = exports.DAY_MS = void 0;
|
|
4
|
+
const MINUTE_MS = 60000;
|
|
5
|
+
const HOUR_MS = 60 * MINUTE_MS;
|
|
6
|
+
exports.DAY_MS = 24 * HOUR_MS;
|
|
7
|
+
const WEEK_DAYS = 7;
|
|
8
|
+
const startOfDay = (at) => {
|
|
9
|
+
const date = new Date(at);
|
|
10
|
+
date.setHours(0, 0, 0, 0);
|
|
11
|
+
return date.getTime();
|
|
12
|
+
};
|
|
13
|
+
const daysBefore = (dayStart, days) => {
|
|
14
|
+
const date = new Date(dayStart);
|
|
15
|
+
date.setDate(date.getDate() - days);
|
|
16
|
+
return date.getTime();
|
|
17
|
+
};
|
|
18
|
+
const sameYear = (a, b) => new Date(a).getFullYear() === new Date(b).getFullYear();
|
|
19
|
+
const createNotificationFormatter = (locale, labels) => {
|
|
20
|
+
const time = new Intl.DateTimeFormat(locale, { hour: "numeric", minute: "2-digit" });
|
|
21
|
+
const shortDate = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short" });
|
|
22
|
+
const shortDateWithYear = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", year: "numeric" });
|
|
23
|
+
const full = new Intl.DateTimeFormat(locale, {
|
|
24
|
+
weekday: "short",
|
|
25
|
+
day: "numeric",
|
|
26
|
+
month: "short",
|
|
27
|
+
hour: "numeric",
|
|
28
|
+
minute: "2-digit",
|
|
29
|
+
});
|
|
30
|
+
const relative = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
|
31
|
+
const narrow = (unit) => new Intl.NumberFormat(locale, { style: "unit", unit, unitDisplay: "narrow" });
|
|
32
|
+
const minutes = narrow("minute");
|
|
33
|
+
const hours = narrow("hour");
|
|
34
|
+
const days = narrow("day");
|
|
35
|
+
const digits = new Intl.NumberFormat(locale);
|
|
36
|
+
const elapsed = (at, now) => Math.max(0, now - at);
|
|
37
|
+
const dayBucket = (at, now) => {
|
|
38
|
+
const today = startOfDay(now);
|
|
39
|
+
if (at >= today)
|
|
40
|
+
return "today";
|
|
41
|
+
if (at >= daysBefore(today, 1))
|
|
42
|
+
return "yesterday";
|
|
43
|
+
if (at >= daysBefore(today, WEEK_DAYS - 1))
|
|
44
|
+
return "thisWeek";
|
|
45
|
+
return "older";
|
|
46
|
+
};
|
|
47
|
+
const dayLabels = {
|
|
48
|
+
today: labels.today,
|
|
49
|
+
yesterday: labels.yesterday,
|
|
50
|
+
thisWeek: labels.thisWeek,
|
|
51
|
+
older: labels.older,
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
shortAge: (at, now) => {
|
|
55
|
+
const gap = elapsed(at, now);
|
|
56
|
+
if (gap < MINUTE_MS)
|
|
57
|
+
return labels.now;
|
|
58
|
+
if (gap < HOUR_MS)
|
|
59
|
+
return minutes.format(Math.floor(gap / MINUTE_MS));
|
|
60
|
+
if (gap < exports.DAY_MS)
|
|
61
|
+
return hours.format(Math.floor(gap / HOUR_MS));
|
|
62
|
+
if (gap < WEEK_DAYS * exports.DAY_MS)
|
|
63
|
+
return days.format(Math.floor(gap / exports.DAY_MS));
|
|
64
|
+
return (sameYear(at, now) ? shortDate : shortDateWithYear).format(at);
|
|
65
|
+
},
|
|
66
|
+
longAge: (at, now) => {
|
|
67
|
+
const gap = elapsed(at, now);
|
|
68
|
+
if (gap < MINUTE_MS)
|
|
69
|
+
return labels.justNow;
|
|
70
|
+
if (gap < HOUR_MS)
|
|
71
|
+
return relative.format(-Math.floor(gap / MINUTE_MS), "minute");
|
|
72
|
+
if (gap < exports.DAY_MS)
|
|
73
|
+
return relative.format(-Math.floor(gap / HOUR_MS), "hour");
|
|
74
|
+
return full.format(at);
|
|
75
|
+
},
|
|
76
|
+
absolute: (at, now) => {
|
|
77
|
+
const bucket = dayBucket(at, now);
|
|
78
|
+
if (bucket === "today")
|
|
79
|
+
return labels.todayAt(time.format(at));
|
|
80
|
+
if (bucket === "yesterday")
|
|
81
|
+
return labels.yesterdayAt(time.format(at));
|
|
82
|
+
return full.format(at);
|
|
83
|
+
},
|
|
84
|
+
dayBucket,
|
|
85
|
+
dayLabel: (bucket) => dayLabels[bucket],
|
|
86
|
+
daysSince: (at, now) => Math.floor(elapsed(at, now) / exports.DAY_MS),
|
|
87
|
+
number: (value) => digits.format(value),
|
|
88
|
+
iso: (at) => new Date(at).toISOString(),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
exports.createNotificationFormatter = createNotificationFormatter;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NotificationCategories, NotificationCategory, NotificationEntry, NotificationTitlePart, NotificationZoneKey } from "./NotificationSourceProps";
|
|
2
|
+
import type { LiveNotificationEntry, NotificationFormatter, NotificationGroup, NotificationThread } from "./NotificationEngineProps";
|
|
3
|
+
export declare const OTHER_CATEGORY_KEY = "__other";
|
|
4
|
+
export declare const plainTitle: (parts: NotificationTitlePart[]) => string;
|
|
5
|
+
export declare const findCategory: (categories: NotificationCategories, key: string) => NotificationCategory | undefined;
|
|
6
|
+
export declare const zoneFor: (entry: Pick<NotificationEntry, "category" | "isRead" | "archived">, categories: NotificationCategories) => NotificationZoneKey;
|
|
7
|
+
export declare const byNewest: (a: LiveNotificationEntry, b: LiveNotificationEntry) => number;
|
|
8
|
+
export declare const matchesNotificationQuery: (entry: NotificationEntry, query: string) => boolean;
|
|
9
|
+
export declare const threadify: (items: LiveNotificationEntry[]) => NotificationThread[];
|
|
10
|
+
export declare const threadEntries: (thread: NotificationThread) => LiveNotificationEntry[];
|
|
11
|
+
export declare const threadIds: (thread: NotificationThread) => string[];
|
|
12
|
+
export declare const threadHasUnread: (thread: NotificationThread) => boolean;
|
|
13
|
+
export declare const groupByCategory: (items: LiveNotificationEntry[], categories: NotificationCategories, otherLabel: string) => NotificationGroup[];
|
|
14
|
+
export declare const groupByDay: (items: LiveNotificationEntry[], format: NotificationFormatter, now: number) => NotificationGroup[];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.groupByDay = exports.groupByCategory = exports.threadHasUnread = exports.threadIds = exports.threadEntries = exports.threadify = exports.matchesNotificationQuery = exports.byNewest = exports.zoneFor = exports.findCategory = exports.plainTitle = exports.OTHER_CATEGORY_KEY = void 0;
|
|
4
|
+
exports.OTHER_CATEGORY_KEY = "__other";
|
|
5
|
+
const plainTitle = (parts) => parts.map((part) => (typeof part === "string" ? part : part.b)).join("");
|
|
6
|
+
exports.plainTitle = plainTitle;
|
|
7
|
+
const findCategory = (categories, key) => Object.prototype.hasOwnProperty.call(categories, key) ? categories[key] : undefined;
|
|
8
|
+
exports.findCategory = findCategory;
|
|
9
|
+
const zoneFor = (entry, categories) => {
|
|
10
|
+
var _a;
|
|
11
|
+
if (entry.archived)
|
|
12
|
+
return "archived";
|
|
13
|
+
const rule = (_a = (0, exports.findCategory)(categories, entry.category)) === null || _a === void 0 ? void 0 : _a.needsYou;
|
|
14
|
+
if (rule === "always")
|
|
15
|
+
return "needsYou";
|
|
16
|
+
if (rule === "untilRead" && entry.isRead === false)
|
|
17
|
+
return "needsYou";
|
|
18
|
+
return "activity";
|
|
19
|
+
};
|
|
20
|
+
exports.zoneFor = zoneFor;
|
|
21
|
+
const byNewest = (a, b) => b.entry.createdAt - a.entry.createdAt;
|
|
22
|
+
exports.byNewest = byNewest;
|
|
23
|
+
const matchesNotificationQuery = (entry, query) => {
|
|
24
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
25
|
+
if (!words.length)
|
|
26
|
+
return true;
|
|
27
|
+
const haystack = [(0, exports.plainTitle)(entry.title), entry.context, entry.preview, entry.eyebrow, entry.subject, entry.body]
|
|
28
|
+
.filter((part) => Boolean(part))
|
|
29
|
+
.join(" ")
|
|
30
|
+
.toLowerCase();
|
|
31
|
+
return words.every((word) => haystack.includes(word));
|
|
32
|
+
};
|
|
33
|
+
exports.matchesNotificationQuery = matchesNotificationQuery;
|
|
34
|
+
const threadify = (items) => {
|
|
35
|
+
const threads = [];
|
|
36
|
+
const byKey = new Map();
|
|
37
|
+
items.forEach((item) => {
|
|
38
|
+
const key = item.entry.groupKey;
|
|
39
|
+
const existing = key === undefined ? undefined : byKey.get(key);
|
|
40
|
+
if (existing) {
|
|
41
|
+
existing.rest.push(item);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const thread = { key: item.entry.id, lead: item, rest: [] };
|
|
45
|
+
threads.push(thread);
|
|
46
|
+
if (key !== undefined)
|
|
47
|
+
byKey.set(key, thread);
|
|
48
|
+
});
|
|
49
|
+
return threads;
|
|
50
|
+
};
|
|
51
|
+
exports.threadify = threadify;
|
|
52
|
+
const threadEntries = (thread) => [thread.lead, ...thread.rest];
|
|
53
|
+
exports.threadEntries = threadEntries;
|
|
54
|
+
const threadIds = (thread) => (0, exports.threadEntries)(thread).map((item) => item.entry.id);
|
|
55
|
+
exports.threadIds = threadIds;
|
|
56
|
+
const threadHasUnread = (thread) => (0, exports.threadEntries)(thread).some((item) => item.entry.isRead === false);
|
|
57
|
+
exports.threadHasUnread = threadHasUnread;
|
|
58
|
+
const isShown = (item) => !item.state.isHidden;
|
|
59
|
+
const single = (item) => ({ key: item.entry.id, lead: item, rest: [] });
|
|
60
|
+
const groupByCategory = (items, categories, otherLabel) => {
|
|
61
|
+
const byCategory = new Map();
|
|
62
|
+
items.forEach((item) => {
|
|
63
|
+
const key = (0, exports.findCategory)(categories, item.entry.category) ? item.entry.category : exports.OTHER_CATEGORY_KEY;
|
|
64
|
+
const list = byCategory.get(key);
|
|
65
|
+
if (list)
|
|
66
|
+
list.push(item);
|
|
67
|
+
else
|
|
68
|
+
byCategory.set(key, [item]);
|
|
69
|
+
});
|
|
70
|
+
const order = [...Object.keys(categories), exports.OTHER_CATEGORY_KEY].filter((key) => byCategory.has(key));
|
|
71
|
+
const groups = order.flatMap((key) => {
|
|
72
|
+
const list = byCategory.get(key);
|
|
73
|
+
if (!list || !list.some(isShown))
|
|
74
|
+
return [];
|
|
75
|
+
const category = (0, exports.findCategory)(categories, key);
|
|
76
|
+
return [
|
|
77
|
+
{
|
|
78
|
+
key,
|
|
79
|
+
label: category ? category.label : otherLabel,
|
|
80
|
+
count: list.filter((item) => item.state.isPending).length,
|
|
81
|
+
threads: list.map(single),
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
});
|
|
85
|
+
return groups.length === 1 ? [{ ...groups[0], label: null }] : groups;
|
|
86
|
+
};
|
|
87
|
+
exports.groupByCategory = groupByCategory;
|
|
88
|
+
const groupByDay = (items, format, now) => {
|
|
89
|
+
const buckets = new Map();
|
|
90
|
+
items.forEach((item) => {
|
|
91
|
+
const bucket = format.dayBucket(item.entry.createdAt, now);
|
|
92
|
+
const list = buckets.get(bucket);
|
|
93
|
+
if (list)
|
|
94
|
+
list.push(item);
|
|
95
|
+
else
|
|
96
|
+
buckets.set(bucket, [item]);
|
|
97
|
+
});
|
|
98
|
+
return [...buckets]
|
|
99
|
+
.filter(([, list]) => list.some(isShown))
|
|
100
|
+
.map(([bucket, list]) => ({ key: bucket, label: format.dayLabel(bucket), count: 0, threads: (0, exports.threadify)(list) }));
|
|
101
|
+
};
|
|
102
|
+
exports.groupByDay = groupByDay;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { NotificationLabels } from "./NotificationSourceProps";
|
|
2
|
+
export declare const DEFAULT_NOTIFICATION_LOCALE = "en-IN";
|
|
3
|
+
export declare const defaultNotificationLabels: (locale?: string) => NotificationLabels;
|
|
4
|
+
export declare const DEFAULT_NOTIFICATION_LABELS: NotificationLabels;
|
|
5
|
+
export declare const mergeDefined: <T extends object>(base: T, overrides?: Partial<T>) => T;
|
|
6
|
+
export declare const resolveNotificationLabels: (locale?: string, overrides?: Partial<NotificationLabels>) => NotificationLabels;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveNotificationLabels = exports.mergeDefined = exports.DEFAULT_NOTIFICATION_LABELS = exports.defaultNotificationLabels = exports.DEFAULT_NOTIFICATION_LOCALE = void 0;
|
|
4
|
+
exports.DEFAULT_NOTIFICATION_LOCALE = "en-IN";
|
|
5
|
+
const defaultNotificationLabels = (locale = exports.DEFAULT_NOTIFICATION_LOCALE) => {
|
|
6
|
+
const english = new Intl.PluralRules("en");
|
|
7
|
+
const digits = new Intl.NumberFormat(locale);
|
|
8
|
+
const n = (count) => digits.format(count);
|
|
9
|
+
const many = (count, one, other) => (english.select(count) === "one" ? one : other);
|
|
10
|
+
const verb = (label) => label.toLowerCase();
|
|
11
|
+
return {
|
|
12
|
+
unread: "Unread",
|
|
13
|
+
actionName: (actionLabel, subject) => `${actionLabel}: ${subject}`,
|
|
14
|
+
markRead: "Mark as read",
|
|
15
|
+
markUnread: "Mark as unread",
|
|
16
|
+
archive: "Archive",
|
|
17
|
+
restore: "Move back to Activity",
|
|
18
|
+
moreActions: (title) => `More actions: ${title}`,
|
|
19
|
+
select: (title) => `Select: ${title}`,
|
|
20
|
+
threadShow: (count) => `Show ${n(count)} more from this thread`,
|
|
21
|
+
threadHide: (count) => `Hide ${n(count)} more from this thread`,
|
|
22
|
+
waiting: (days) => `Waiting ${n(days)} ${many(days, "day", "days")}`,
|
|
23
|
+
noAccess: "No access",
|
|
24
|
+
changedTo: "changed to",
|
|
25
|
+
offlineNote: "Reconnect to respond",
|
|
26
|
+
reasonLabel: "Add a note",
|
|
27
|
+
reasonOptional: "(optional)",
|
|
28
|
+
reasonPlaceholder: "The requester sees this",
|
|
29
|
+
reasonMissingNegative: "Add a reason so they know what to change.",
|
|
30
|
+
reasonMissing: "This needs a comment before it can be sent.",
|
|
31
|
+
keyCtrl: "Ctrl",
|
|
32
|
+
keyEnter: "Enter",
|
|
33
|
+
keyEsc: "Esc",
|
|
34
|
+
toSend: "to send",
|
|
35
|
+
toCancel: "to cancel",
|
|
36
|
+
cancel: "Cancel",
|
|
37
|
+
undo: "Undo",
|
|
38
|
+
sending: "Saving…",
|
|
39
|
+
couldNot: (actionLabel) => `Couldn't ${verb(actionLabel)}.`,
|
|
40
|
+
retry: "Try again",
|
|
41
|
+
dismiss: "Dismiss",
|
|
42
|
+
conflictMessage: "Someone already decided this.",
|
|
43
|
+
forbiddenMessage: "You can't decide this anymore.",
|
|
44
|
+
failedMessage: "The server didn't respond. Nothing was changed.",
|
|
45
|
+
reasonRequiredMessage: "This needs a comment before it can be sent.",
|
|
46
|
+
offlineMessage: "You're offline. Reconnect, then try again.",
|
|
47
|
+
decided: (doneLabel, subject) => `${doneLabel} · ${subject}`,
|
|
48
|
+
decidedAnnouncement: (doneLabel, subject) => `${doneLabel}: ${subject}. Undo available for 6 seconds.`,
|
|
49
|
+
requests: (count) => `${n(count)} ${many(count, "request", "requests")}`,
|
|
50
|
+
reasonQuote: (reason) => `Reason: ${reason}`,
|
|
51
|
+
decisionFailed: (actionLabel, count) => count > 1 ? `Couldn't ${verb(actionLabel)} ${n(count)} requests` : `Couldn't ${verb(actionLabel)}`,
|
|
52
|
+
conflicted: (count) => (count > 1 ? `Someone already decided ${n(count)} of these` : "Someone already decided this"),
|
|
53
|
+
forbidden: (count) => (count > 1 ? `You can't decide ${n(count)} of these anymore` : "You can't decide this anymore"),
|
|
54
|
+
reasonNeeded: (actionLabel) => `${actionLabel} needs a comment`,
|
|
55
|
+
archived: (count) => (count > 1 ? `${n(count)} notifications archived` : "Archived"),
|
|
56
|
+
restored: "Moved back to Activity",
|
|
57
|
+
markedRead: (count) => `Marked ${n(count)} as read`,
|
|
58
|
+
markAllFailed: "Couldn't mark them as read",
|
|
59
|
+
readFailed: (read) => (read ? "Couldn't mark as read" : "Couldn't mark as unread"),
|
|
60
|
+
archiveFailed: "Couldn't archive",
|
|
61
|
+
restoreFailed: "Couldn't move it back",
|
|
62
|
+
extraFailed: (actionLabel) => `Couldn't ${verb(actionLabel)}`,
|
|
63
|
+
undone: "Undone",
|
|
64
|
+
decisionUndone: "Undone. The request is back in Needs you.",
|
|
65
|
+
arrivedOne: (title) => `New notification: ${title}`,
|
|
66
|
+
arrivedMany: (count) => `${n(count)} new notifications`,
|
|
67
|
+
openIn: (appName) => (appName ? `Open in ${appName}` : "Open"),
|
|
68
|
+
blockedTitle: "You can't open this anymore",
|
|
69
|
+
back: "Back",
|
|
70
|
+
otherCategory: "Other",
|
|
71
|
+
now: "now",
|
|
72
|
+
justNow: "just now",
|
|
73
|
+
todayAt: (time) => `Today, ${time}`,
|
|
74
|
+
yesterdayAt: (time) => `Yesterday, ${time}`,
|
|
75
|
+
today: "Today",
|
|
76
|
+
yesterday: "Yesterday",
|
|
77
|
+
thisWeek: "This week",
|
|
78
|
+
older: "Older",
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
exports.defaultNotificationLabels = defaultNotificationLabels;
|
|
82
|
+
exports.DEFAULT_NOTIFICATION_LABELS = (0, exports.defaultNotificationLabels)();
|
|
83
|
+
const mergeDefined = (base, overrides = {}) => {
|
|
84
|
+
const merged = { ...base };
|
|
85
|
+
Object.keys(overrides).forEach((key) => {
|
|
86
|
+
const value = overrides[key];
|
|
87
|
+
if (value !== undefined)
|
|
88
|
+
merged[key] = value;
|
|
89
|
+
});
|
|
90
|
+
return merged;
|
|
91
|
+
};
|
|
92
|
+
exports.mergeDefined = mergeDefined;
|
|
93
|
+
const resolveNotificationLabels = (locale = exports.DEFAULT_NOTIFICATION_LOCALE, overrides = {}) => (0, exports.mergeDefined)((0, exports.defaultNotificationLabels)(locale), overrides);
|
|
94
|
+
exports.resolveNotificationLabels = resolveNotificationLabels;
|