@apptegy/nuxt-navigation 0.1.115 → 0.1.117
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/dist/module.json +1 -1
- package/dist/runtime/components/Nav/Header.vue +29 -22
- package/dist/runtime/components/Nav/Header.vue.d.ts +8 -19
- package/dist/runtime/components/Nav/NotificationItem.vue +18 -4
- package/dist/runtime/components/Nav/NotificationsPanel.vue +33 -7
- package/dist/runtime/constants/notification-types.d.ts +2 -0
- package/dist/runtime/constants/notification-types.js +7 -0
- package/dist/runtime/types/index.d.ts +1 -1
- package/dist/runtime/types/notifications.d.ts +8 -0
- package/dist/runtime/utils/notifications.d.ts +14 -0
- package/dist/runtime/utils/notifications.js +35 -1
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -148,12 +148,7 @@ import {
|
|
|
148
148
|
} from "../../utils/notification-api";
|
|
149
149
|
const props = defineProps({
|
|
150
150
|
sticky: { type: Boolean, required: false },
|
|
151
|
-
|
|
152
|
-
notificationsResponse: { type: [Object, null], required: false, default: null },
|
|
153
|
-
notificationsLoading: { type: Boolean, required: false, default: false },
|
|
154
|
-
notificationPanelOpen: { type: Boolean, required: false, default: false },
|
|
155
|
-
activeTabId: { type: String, required: false, default: "" },
|
|
156
|
-
activeFilterId: { type: String, required: false, default: "" },
|
|
151
|
+
notifications: { type: [Object, null], required: false, default: null },
|
|
157
152
|
user: { type: Object, required: true },
|
|
158
153
|
impersonatedUser: { type: Object, required: false },
|
|
159
154
|
useLogoutEmit: { type: Boolean, required: false },
|
|
@@ -163,9 +158,7 @@ const nuxtApp = useNuxtApp();
|
|
|
163
158
|
const emit = defineEmits([
|
|
164
159
|
"selected-option",
|
|
165
160
|
"user-logout",
|
|
166
|
-
"update:
|
|
167
|
-
"update:activeTabId",
|
|
168
|
-
"update:activeFilterId",
|
|
161
|
+
"update:notifications",
|
|
169
162
|
"notifications-clear-all",
|
|
170
163
|
"notifications-dismiss",
|
|
171
164
|
"notifications-select",
|
|
@@ -173,8 +166,13 @@ const emit = defineEmits([
|
|
|
173
166
|
]);
|
|
174
167
|
const popoverRef = ref(null);
|
|
175
168
|
const isMobilePanel = useMediaQuery("(max-width: 960px)");
|
|
176
|
-
const panelOpen = ref(props.notificationPanelOpen);
|
|
177
169
|
const isBellShaking = ref(false);
|
|
170
|
+
const showNotifications = computed(() => props.notifications != null);
|
|
171
|
+
const notificationsResponse = computed(() => props.notifications?.response ?? null);
|
|
172
|
+
const notificationsLoading = computed(() => props.notifications?.loading ?? false);
|
|
173
|
+
const panelOpen = ref(props.notifications?.panelOpen ?? false);
|
|
174
|
+
const activeTabId = ref(props.notifications?.activeTabId || "new");
|
|
175
|
+
const activeFilterId = ref(props.notifications?.activeFilterId || "all-types");
|
|
178
176
|
const popoverPt = {
|
|
179
177
|
content: {
|
|
180
178
|
class: "notifications-popover__content",
|
|
@@ -188,10 +186,10 @@ const notificationDateLabels = computed(() => ({
|
|
|
188
186
|
justNow: nuxtApp.$t("navigation.notificationsPanel.justNow")
|
|
189
187
|
}));
|
|
190
188
|
const notificationsPanel = computed(() => {
|
|
191
|
-
if (!
|
|
189
|
+
if (!notificationsResponse.value) {
|
|
192
190
|
return null;
|
|
193
191
|
}
|
|
194
|
-
return mapNotificationsApiResponse(
|
|
192
|
+
return mapNotificationsApiResponse(notificationsResponse.value, {
|
|
195
193
|
locale: nuxtApp.$intl.locale,
|
|
196
194
|
dateLabels: notificationDateLabels.value,
|
|
197
195
|
retentionLabel: nuxtApp.$t("navigation.notificationsPanel.retentionLabel"),
|
|
@@ -202,10 +200,17 @@ const notificationsPanel = computed(() => {
|
|
|
202
200
|
});
|
|
203
201
|
});
|
|
204
202
|
const notificationCount = computed(
|
|
205
|
-
() => getNotificationsTotalCount(
|
|
203
|
+
() => getNotificationsTotalCount(notificationsResponse.value)
|
|
206
204
|
);
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
function patchNotifications(patch) {
|
|
206
|
+
if (!props.notifications) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
emit("update:notifications", {
|
|
210
|
+
...props.notifications,
|
|
211
|
+
...patch
|
|
212
|
+
});
|
|
213
|
+
}
|
|
209
214
|
watch(
|
|
210
215
|
notificationCount,
|
|
211
216
|
(newCount, oldCount) => {
|
|
@@ -223,19 +228,21 @@ function onBellShakeEnd() {
|
|
|
223
228
|
isBellShaking.value = false;
|
|
224
229
|
}
|
|
225
230
|
watch(
|
|
226
|
-
() => props.
|
|
231
|
+
() => props.notifications?.panelOpen,
|
|
227
232
|
(value) => {
|
|
228
|
-
|
|
233
|
+
if (value !== void 0) {
|
|
234
|
+
panelOpen.value = value;
|
|
235
|
+
}
|
|
229
236
|
}
|
|
230
237
|
);
|
|
231
238
|
watch(panelOpen, (value) => {
|
|
232
|
-
|
|
239
|
+
patchNotifications({ panelOpen: value });
|
|
233
240
|
if (!value && !isMobilePanel.value) {
|
|
234
241
|
popoverRef.value?.hide();
|
|
235
242
|
}
|
|
236
243
|
});
|
|
237
244
|
watch(
|
|
238
|
-
() => props.activeTabId,
|
|
245
|
+
() => props.notifications?.activeTabId,
|
|
239
246
|
(value) => {
|
|
240
247
|
if (value) {
|
|
241
248
|
activeTabId.value = value;
|
|
@@ -243,7 +250,7 @@ watch(
|
|
|
243
250
|
}
|
|
244
251
|
);
|
|
245
252
|
watch(
|
|
246
|
-
() => props.activeFilterId,
|
|
253
|
+
() => props.notifications?.activeFilterId,
|
|
247
254
|
(value) => {
|
|
248
255
|
if (value) {
|
|
249
256
|
activeFilterId.value = value;
|
|
@@ -279,11 +286,11 @@ function handleNotificationSelect(payload) {
|
|
|
279
286
|
}
|
|
280
287
|
function handleTabChange(id) {
|
|
281
288
|
activeTabId.value = id;
|
|
282
|
-
|
|
289
|
+
patchNotifications({ activeTabId: id });
|
|
283
290
|
}
|
|
284
291
|
function handleFilterChange(id) {
|
|
285
292
|
activeFilterId.value = id;
|
|
286
|
-
|
|
293
|
+
patchNotifications({ activeFilterId: id });
|
|
287
294
|
}
|
|
288
295
|
</script>
|
|
289
296
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { INotificationsHeaderModel, IUser } from '../../types/index.js';
|
|
2
2
|
interface IHeaderProps {
|
|
3
3
|
sticky?: boolean;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
activeTabId?: string;
|
|
9
|
-
activeFilterId?: string;
|
|
4
|
+
/**
|
|
5
|
+
* Host-owned notification center state. Pass `null`/`undefined` to hide the bell.
|
|
6
|
+
*/
|
|
7
|
+
notifications?: INotificationsHeaderModel | null;
|
|
10
8
|
}
|
|
11
9
|
interface IUserDropdown {
|
|
12
10
|
user: {
|
|
@@ -32,9 +30,7 @@ type __VLS_Slots = {} & {
|
|
|
32
30
|
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
33
31
|
"selected-option": (...args: any[]) => void;
|
|
34
32
|
"user-logout": (...args: any[]) => void;
|
|
35
|
-
"update:
|
|
36
|
-
"update:activeTabId": (...args: any[]) => void;
|
|
37
|
-
"update:activeFilterId": (...args: any[]) => void;
|
|
33
|
+
"update:notifications": (...args: any[]) => void;
|
|
38
34
|
"notifications-clear-all": (...args: any[]) => void;
|
|
39
35
|
"notifications-dismiss": (...args: any[]) => void;
|
|
40
36
|
"notifications-select": (...args: any[]) => void;
|
|
@@ -42,20 +38,13 @@ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void,
|
|
|
42
38
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
43
39
|
"onSelected-option"?: ((...args: any[]) => any) | undefined;
|
|
44
40
|
"onUser-logout"?: ((...args: any[]) => any) | undefined;
|
|
45
|
-
"onUpdate:
|
|
46
|
-
"onUpdate:activeTabId"?: ((...args: any[]) => any) | undefined;
|
|
47
|
-
"onUpdate:activeFilterId"?: ((...args: any[]) => any) | undefined;
|
|
41
|
+
"onUpdate:notifications"?: ((...args: any[]) => any) | undefined;
|
|
48
42
|
"onNotifications-clear-all"?: ((...args: any[]) => any) | undefined;
|
|
49
43
|
"onNotifications-dismiss"?: ((...args: any[]) => any) | undefined;
|
|
50
44
|
"onNotifications-select"?: ((...args: any[]) => any) | undefined;
|
|
51
45
|
"onNotification-click"?: ((...args: any[]) => any) | undefined;
|
|
52
46
|
}>, {
|
|
53
|
-
|
|
54
|
-
notificationsResponse: INotificationsApiResponse | null;
|
|
55
|
-
notificationsLoading: boolean;
|
|
56
|
-
notificationPanelOpen: boolean;
|
|
57
|
-
activeTabId: string;
|
|
58
|
-
activeFilterId: string;
|
|
47
|
+
notifications: INotificationsHeaderModel | null;
|
|
59
48
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
60
49
|
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
61
50
|
export default _default;
|
|
@@ -76,11 +76,21 @@
|
|
|
76
76
|
:aria-label="$t('navigation.notificationsPanel.dismiss')"
|
|
77
77
|
@click="handleDismiss"
|
|
78
78
|
>
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
<svg
|
|
80
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
81
|
+
width="16"
|
|
82
|
+
height="16"
|
|
83
|
+
viewBox="0 0 24 24"
|
|
84
|
+
fill="none"
|
|
85
|
+
stroke="currentColor"
|
|
86
|
+
stroke-width="2"
|
|
87
|
+
stroke-linecap="round"
|
|
88
|
+
stroke-linejoin="round"
|
|
82
89
|
aria-hidden="true"
|
|
83
|
-
|
|
90
|
+
>
|
|
91
|
+
<path d="M18 6 6 18" />
|
|
92
|
+
<path d="m6 6 12 12" />
|
|
93
|
+
</svg>
|
|
84
94
|
</Button>
|
|
85
95
|
</div>
|
|
86
96
|
</div>
|
|
@@ -317,4 +327,8 @@ function handleDismiss(event) {
|
|
|
317
327
|
padding: 0;
|
|
318
328
|
color: var(--grey-50, #9ca3af);
|
|
319
329
|
}
|
|
330
|
+
.notification-item__dismiss :deep(svg) {
|
|
331
|
+
display: block;
|
|
332
|
+
flex-shrink: 0;
|
|
333
|
+
}
|
|
320
334
|
</style>
|
|
@@ -185,7 +185,7 @@ import {
|
|
|
185
185
|
getVisibleFilters,
|
|
186
186
|
isInBadge,
|
|
187
187
|
notificationMatchesFilter,
|
|
188
|
-
|
|
188
|
+
resolveStickyNotificationFilters,
|
|
189
189
|
shouldShowFilterChips
|
|
190
190
|
} from "../../utils/notifications";
|
|
191
191
|
const props = defineProps({
|
|
@@ -200,6 +200,7 @@ const emit = defineEmits(["update:activeTabId", "update:activeFilterId", "clear-
|
|
|
200
200
|
const filtersRef = ref();
|
|
201
201
|
const activeTabId = ref(props.activeTabId || props.data.tabs[0]?.id || "");
|
|
202
202
|
const activeFilterId = ref(props.activeFilterId || props.data.filters?.[0]?.id || "all-types");
|
|
203
|
+
const stickyFilters = ref([]);
|
|
203
204
|
watch(
|
|
204
205
|
() => props.activeTabId,
|
|
205
206
|
(value) => {
|
|
@@ -230,19 +231,44 @@ const usesDynamicFilters = computed(() => !props.data.filters?.length);
|
|
|
230
231
|
const activeTab = computed(
|
|
231
232
|
() => props.data.tabs.find((tab) => tab.id === activeTabId.value)
|
|
232
233
|
);
|
|
233
|
-
|
|
234
|
-
() =>
|
|
234
|
+
function filterCatalogKey(filters) {
|
|
235
|
+
return filters.map((filter) => filter.id).join("|");
|
|
236
|
+
}
|
|
237
|
+
watch(
|
|
238
|
+
[allNotifications, activeTab, activeFilterId, () => props.data.filters],
|
|
239
|
+
() => {
|
|
240
|
+
const { nextStickyFilters } = resolveStickyNotificationFilters({
|
|
241
|
+
notifications: allNotifications.value,
|
|
242
|
+
tab: activeTab.value,
|
|
243
|
+
providedFilters: props.data.filters,
|
|
244
|
+
activeFilterId: activeFilterId.value,
|
|
245
|
+
stickyFilters: stickyFilters.value
|
|
246
|
+
});
|
|
247
|
+
if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(nextStickyFilters)) {
|
|
248
|
+
stickyFilters.value = nextStickyFilters;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{ immediate: true, deep: true }
|
|
252
|
+
);
|
|
253
|
+
const displayFilters = computed(
|
|
254
|
+
() => resolveStickyNotificationFilters({
|
|
255
|
+
notifications: allNotifications.value,
|
|
256
|
+
tab: activeTab.value,
|
|
257
|
+
providedFilters: props.data.filters,
|
|
258
|
+
activeFilterId: activeFilterId.value,
|
|
259
|
+
stickyFilters: stickyFilters.value
|
|
260
|
+
}).filters
|
|
235
261
|
);
|
|
236
262
|
const visibleFilters = computed(() => {
|
|
237
263
|
if (usesDynamicFilters.value) {
|
|
238
|
-
return
|
|
264
|
+
return displayFilters.value;
|
|
239
265
|
}
|
|
240
|
-
return getVisibleFilters(allNotifications.value,
|
|
266
|
+
return getVisibleFilters(allNotifications.value, displayFilters.value, activeTab.value);
|
|
241
267
|
});
|
|
242
268
|
const showFilterChips = computed(
|
|
243
269
|
() => shouldShowFilterChips(
|
|
244
270
|
allNotifications.value,
|
|
245
|
-
|
|
271
|
+
displayFilters.value,
|
|
246
272
|
activeTab.value,
|
|
247
273
|
usesDynamicFilters.value
|
|
248
274
|
)
|
|
@@ -269,7 +295,7 @@ function formatCount(count) {
|
|
|
269
295
|
return formatNotificationCount(count);
|
|
270
296
|
}
|
|
271
297
|
const activeFilter = computed(
|
|
272
|
-
() => visibleFilters.value.find((filter) => filter.id === activeFilterId.value) ||
|
|
298
|
+
() => visibleFilters.value.find((filter) => filter.id === activeFilterId.value) || displayFilters.value.find((filter) => filter.id === activeFilterId.value)
|
|
273
299
|
);
|
|
274
300
|
function showClearAll(groupIndex) {
|
|
275
301
|
return groupIndex === 0 && (activeTabId.value === "new" || activeTab.value?.unreadOnly);
|
|
@@ -17,3 +17,5 @@ export declare const DEFAULT_NOTIFICATION_FILTERS: ({
|
|
|
17
17
|
id: string;
|
|
18
18
|
label: string;
|
|
19
19
|
})[];
|
|
20
|
+
/** Resolve `filter[kind][]` values for a notification center filter pill id. */
|
|
21
|
+
export declare function getKindsForNotificationFilter(filterId: string | null | undefined): string[];
|
|
@@ -46,3 +46,10 @@ export const DEFAULT_NOTIFICATION_FILTERS = [
|
|
|
46
46
|
{ id: "all-types", label: "All types" },
|
|
47
47
|
...NOTIFICATION_FILTER_CATEGORIES
|
|
48
48
|
];
|
|
49
|
+
export function getKindsForNotificationFilter(filterId) {
|
|
50
|
+
if (!filterId || filterId === "all-types") {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const category = NOTIFICATION_FILTER_CATEGORIES.find((filter) => filter.id === filterId);
|
|
54
|
+
return category?.kinds ? [...category.kinds] : [];
|
|
55
|
+
}
|
|
@@ -11,4 +11,4 @@ export interface IUser {
|
|
|
11
11
|
client: string;
|
|
12
12
|
admin_email: string;
|
|
13
13
|
}
|
|
14
|
-
export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, } from './notifications.js';
|
|
14
|
+
export type { NotificationType, NotificationApiState, INotificationApiActor, INotificationApiRecord, INotificationsPagination, INotificationsApiResponse, INotificationActor, INotificationItem, INotificationGroup, INotificationTab, INotificationFilter, INotificationsPanelData, INotificationsHeaderModel, } from './notifications.js';
|
|
@@ -96,3 +96,11 @@ export interface INotificationsPanelData {
|
|
|
96
96
|
filters?: INotificationFilter[];
|
|
97
97
|
groups: INotificationGroup[];
|
|
98
98
|
}
|
|
99
|
+
/** Host-owned notification center state bound to NavHeader via `v-model:notifications`. */
|
|
100
|
+
export interface INotificationsHeaderModel {
|
|
101
|
+
response: INotificationsApiResponse | null;
|
|
102
|
+
loading?: boolean;
|
|
103
|
+
panelOpen?: boolean;
|
|
104
|
+
activeTabId?: string;
|
|
105
|
+
activeFilterId?: string;
|
|
106
|
+
}
|
|
@@ -14,6 +14,20 @@ export declare function getScopedNotifications(notifications: INotificationItem[
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function buildFiltersFromNotifications(notifications: INotificationItem[], tab: INotificationTab | undefined): INotificationFilter[];
|
|
16
16
|
export declare function resolveNotificationFilters(notifications: INotificationItem[], tab: INotificationTab | undefined, filters?: INotificationFilter[]): INotificationFilter[];
|
|
17
|
+
/**
|
|
18
|
+
* Keeps the last "all types" pill catalog while a kind filter is active, so chips
|
|
19
|
+
* don't collapse when results are already filtered (e.g. via `filter[kind][]`).
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveStickyNotificationFilters({ notifications, tab, providedFilters, activeFilterId, stickyFilters, }: {
|
|
22
|
+
notifications: INotificationItem[];
|
|
23
|
+
tab: INotificationTab | undefined;
|
|
24
|
+
providedFilters?: INotificationFilter[];
|
|
25
|
+
activeFilterId: string;
|
|
26
|
+
stickyFilters: INotificationFilter[];
|
|
27
|
+
}): {
|
|
28
|
+
filters: INotificationFilter[];
|
|
29
|
+
nextStickyFilters: INotificationFilter[];
|
|
30
|
+
};
|
|
17
31
|
export declare function filterHasContent(notifications: INotificationItem[], filter: INotificationFilter, tab: INotificationTab | undefined): boolean;
|
|
18
32
|
export declare function getVisibleFilters(notifications: INotificationItem[], filters: INotificationFilter[], tab: INotificationTab | undefined): INotificationFilter[];
|
|
19
33
|
export declare function shouldShowFilterChips(notifications: INotificationItem[], filters: INotificationFilter[], tab: INotificationTab | undefined, usesDynamicFilters?: boolean): boolean;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_NOTIFICATION_FILTERS,
|
|
3
|
+
NOTIFICATION_FILTER_CATEGORIES
|
|
4
|
+
} from "../constants/notification-types.js";
|
|
2
5
|
import { normalizeNotificationKind } from "./notification-api.js";
|
|
3
6
|
export function flattenNotifications(groups) {
|
|
4
7
|
return groups.flatMap((group) => group.notifications);
|
|
@@ -82,6 +85,37 @@ export function resolveNotificationFilters(notifications, tab, filters) {
|
|
|
82
85
|
}
|
|
83
86
|
return buildFiltersFromNotifications(notifications, tab);
|
|
84
87
|
}
|
|
88
|
+
export function resolveStickyNotificationFilters({
|
|
89
|
+
notifications,
|
|
90
|
+
tab,
|
|
91
|
+
providedFilters,
|
|
92
|
+
activeFilterId,
|
|
93
|
+
stickyFilters
|
|
94
|
+
}) {
|
|
95
|
+
if (providedFilters?.length) {
|
|
96
|
+
return {
|
|
97
|
+
filters: providedFilters,
|
|
98
|
+
nextStickyFilters: stickyFilters
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const dynamicFilters = buildFiltersFromNotifications(notifications, tab);
|
|
102
|
+
let nextStickyFilters = stickyFilters;
|
|
103
|
+
if (activeFilterId === "all-types") {
|
|
104
|
+
nextStickyFilters = dynamicFilters;
|
|
105
|
+
} else if (!stickyFilters.length) {
|
|
106
|
+
nextStickyFilters = dynamicFilters.length ? dynamicFilters : [...DEFAULT_NOTIFICATION_FILTERS];
|
|
107
|
+
}
|
|
108
|
+
if (activeFilterId !== "all-types" && nextStickyFilters.length) {
|
|
109
|
+
return {
|
|
110
|
+
filters: nextStickyFilters,
|
|
111
|
+
nextStickyFilters
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
filters: dynamicFilters.length ? dynamicFilters : nextStickyFilters,
|
|
116
|
+
nextStickyFilters
|
|
117
|
+
};
|
|
118
|
+
}
|
|
85
119
|
export function filterHasContent(notifications, filter, tab) {
|
|
86
120
|
const scoped = getScopedNotifications(notifications, tab);
|
|
87
121
|
return scoped.some((notification) => notificationMatchesFilter(notification, filter));
|