@apptegy/nuxt-navigation 0.1.117 → 0.1.119

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 CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.0.0"
5
5
  },
6
6
  "configKey": "@apptegy/nuxt-navigation",
7
- "version": "0.1.117",
7
+ "version": "0.1.119",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.1",
10
10
  "unbuild": "unknown"
@@ -70,8 +70,10 @@
70
70
  :loading="notificationsLoading"
71
71
  :active-tab-id="activeTabId"
72
72
  :active-filter-id="activeFilterId"
73
+ :filter-catalog="filterCatalog"
73
74
  @update:active-tab-id="handleTabChange"
74
75
  @update:active-filter-id="handleFilterChange"
76
+ @update:filter-catalog="filterCatalog = $event"
75
77
  @clear-all="(payload) => $emit('notifications-clear-all', payload)"
76
78
  @dismiss="(payload) => $emit('notifications-dismiss', payload)"
77
79
  @select="handleNotificationSelect"
@@ -90,8 +92,10 @@
90
92
  :loading="notificationsLoading"
91
93
  :active-tab-id="activeTabId"
92
94
  :active-filter-id="activeFilterId"
95
+ :filter-catalog="filterCatalog"
93
96
  @update:active-tab-id="handleTabChange"
94
97
  @update:active-filter-id="handleFilterChange"
98
+ @update:filter-catalog="filterCatalog = $event"
95
99
  @clear-all="(payload) => $emit('notifications-clear-all', payload)"
96
100
  @dismiss="(payload) => $emit('notifications-dismiss', payload)"
97
101
  @select="handleNotificationSelect"
@@ -173,6 +177,7 @@ const notificationsLoading = computed(() => props.notifications?.loading ?? fals
173
177
  const panelOpen = ref(props.notifications?.panelOpen ?? false);
174
178
  const activeTabId = ref(props.notifications?.activeTabId || "new");
175
179
  const activeFilterId = ref(props.notifications?.activeFilterId || "all-types");
180
+ const filterCatalog = ref([]);
176
181
  const popoverPt = {
177
182
  content: {
178
183
  class: "notifications-popover__content",
@@ -32,11 +32,21 @@
32
32
  :aria-label="$t('navigation.notificationsPanel.close')"
33
33
  @click="$emit('close')"
34
34
  >
35
- <Icon
36
- name="lucide:x"
37
- size="20"
35
+ <svg
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ width="20"
38
+ height="20"
39
+ viewBox="0 0 24 24"
40
+ fill="none"
41
+ stroke="currentColor"
42
+ stroke-width="2"
43
+ stroke-linecap="round"
44
+ stroke-linejoin="round"
38
45
  aria-hidden="true"
39
- />
46
+ >
47
+ <path d="M18 6 6 18" />
48
+ <path d="m6 6 12 12" />
49
+ </svg>
40
50
  </Button>
41
51
  </div>
42
52
 
@@ -102,11 +112,20 @@
102
112
  :aria-label="$t('navigation.notificationsPanel.scrollFilters')"
103
113
  @click="scrollFilters"
104
114
  >
105
- <Icon
106
- name="lucide:chevron-right"
107
- size="16"
115
+ <svg
116
+ xmlns="http://www.w3.org/2000/svg"
117
+ width="16"
118
+ height="16"
119
+ viewBox="0 0 24 24"
120
+ fill="none"
121
+ stroke="currentColor"
122
+ stroke-width="2"
123
+ stroke-linecap="round"
124
+ stroke-linejoin="round"
108
125
  aria-hidden="true"
109
- />
126
+ >
127
+ <path d="m9 18 6-6-6-6" />
128
+ </svg>
110
129
  </Button>
111
130
  </div>
112
131
 
@@ -193,14 +212,18 @@ const props = defineProps({
193
212
  loading: { type: Boolean, required: false, default: false },
194
213
  activeTabId: { type: String, required: false },
195
214
  activeFilterId: { type: String, required: false },
215
+ filterCatalog: { type: Array, required: false, default: () => [] },
196
216
  embedded: { type: Boolean, required: false, default: false },
197
217
  mobile: { type: Boolean, required: false, default: false }
198
218
  });
199
- const emit = defineEmits(["update:activeTabId", "update:activeFilterId", "clear-all", "dismiss", "select", "close"]);
219
+ const emit = defineEmits(["update:activeTabId", "update:activeFilterId", "update:filterCatalog", "clear-all", "dismiss", "select", "close"]);
200
220
  const filtersRef = ref();
201
221
  const activeTabId = ref(props.activeTabId || props.data.tabs[0]?.id || "");
202
222
  const activeFilterId = ref(props.activeFilterId || props.data.filters?.[0]?.id || "all-types");
203
- const stickyFilters = ref([]);
223
+ const stickyFilters = ref([...props.filterCatalog ?? []]);
224
+ function filterCatalogKey(filters) {
225
+ return filters.map((filter) => filter.id).join("|");
226
+ }
204
227
  watch(
205
228
  () => props.activeTabId,
206
229
  (value) => {
@@ -217,6 +240,18 @@ watch(
217
240
  }
218
241
  }
219
242
  );
243
+ watch(
244
+ () => props.filterCatalog,
245
+ (catalog) => {
246
+ if (!catalog?.length) {
247
+ return;
248
+ }
249
+ if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(catalog)) {
250
+ stickyFilters.value = [...catalog];
251
+ }
252
+ },
253
+ { deep: true }
254
+ );
220
255
  watch(
221
256
  () => props.data.tabs,
222
257
  (tabs) => {
@@ -231,9 +266,6 @@ const usesDynamicFilters = computed(() => !props.data.filters?.length);
231
266
  const activeTab = computed(
232
267
  () => props.data.tabs.find((tab) => tab.id === activeTabId.value)
233
268
  );
234
- function filterCatalogKey(filters) {
235
- return filters.map((filter) => filter.id).join("|");
236
- }
237
269
  watch(
238
270
  [allNotifications, activeTab, activeFilterId, () => props.data.filters],
239
271
  () => {
@@ -246,6 +278,7 @@ watch(
246
278
  });
247
279
  if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(nextStickyFilters)) {
248
280
  stickyFilters.value = nextStickyFilters;
281
+ emit("update:filterCatalog", [...nextStickyFilters]);
249
282
  }
250
283
  },
251
284
  { immediate: true, deep: true }
@@ -481,6 +514,11 @@ function scrollFilters() {
481
514
  width: 28px;
482
515
  height: 28px;
483
516
  padding: 0;
517
+ color: var(--grey-60, #6b7280);
518
+ }
519
+ .notifications-panel__filters-next :deep(svg) {
520
+ display: block;
521
+ flex-shrink: 0;
484
522
  }
485
523
  .notifications-panel__body {
486
524
  overflow-y: auto;
@@ -1,9 +1,14 @@
1
- import type { INotificationsPanelData } from '../../types/index.js';
1
+ import type { INotificationsPanelData, INotificationFilter } from '../../types/index.js';
2
2
  type __VLS_Props = {
3
3
  data: INotificationsPanelData;
4
4
  loading?: boolean;
5
5
  activeTabId?: string;
6
6
  activeFilterId?: string;
7
+ /**
8
+ * Last "all types" pill catalog, owned by the header so it survives panel remount.
9
+ * Lets a kind filter stay selected on reopen without inventing the full default catalog.
10
+ */
11
+ filterCatalog?: INotificationFilter[];
7
12
  embedded?: boolean;
8
13
  mobile?: boolean;
9
14
  };
@@ -15,6 +20,7 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
15
20
  }) => any;
16
21
  "update:activeTabId": (id: string) => any;
17
22
  "update:activeFilterId": (id: string) => any;
23
+ "update:filterCatalog": (filters: INotificationFilter[]) => any;
18
24
  "clear-all": (payload: {
19
25
  groupId: string;
20
26
  tabId: string;
@@ -34,6 +40,7 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
34
40
  }) => any) | undefined;
35
41
  "onUpdate:activeTabId"?: ((id: string) => any) | undefined;
36
42
  "onUpdate:activeFilterId"?: ((id: string) => any) | undefined;
43
+ "onUpdate:filterCatalog"?: ((filters: INotificationFilter[]) => any) | undefined;
37
44
  "onClear-all"?: ((payload: {
38
45
  groupId: string;
39
46
  tabId: string;
@@ -47,6 +54,7 @@ declare const _default: import("vue").DefineComponent<__VLS_Props, void, {}, {},
47
54
  onClose?: (() => any) | undefined;
48
55
  }>, {
49
56
  loading: boolean;
57
+ filterCatalog: INotificationFilter[];
50
58
  embedded: boolean;
51
59
  mobile: boolean;
52
60
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -19,6 +19,11 @@ export interface INotificationApiRecord {
19
19
  detail_link?: string | null;
20
20
  notification_state: NotificationApiState | string;
21
21
  created_at: string;
22
+ /**
23
+ * Most recent activity on the notification (e.g. new reaction/comment).
24
+ * Prefer this over `created_at` for relative time and date grouping.
25
+ */
26
+ last_activity_at?: string | null;
22
27
  }
23
28
  export interface INotificationsPagination {
24
29
  page: number;
@@ -48,7 +53,10 @@ export interface INotificationItem {
48
53
  actors: INotificationActor[];
49
54
  actionText: string;
50
55
  timestampLabel: string;
51
- /** ISO-8601 timestamp from the API — used to derive group labels and relative time. */
56
+ /**
57
+ * ISO-8601 timestamp used for group labels and relative time.
58
+ * Derived from API `last_activity_at`, falling back to `created_at`.
59
+ */
52
60
  createdAt?: string;
53
61
  contextLabel?: string;
54
62
  /** When false, the notification counts toward the bell badge and appears in the New tab. */
@@ -5,6 +5,11 @@ export declare const DEFAULT_NOTIFICATION_TABS: INotificationTab[];
5
5
  export declare function normalizeNotificationKind(value: string): string;
6
6
  export declare function mapSourceRecordType(sourceRecordType: string): NotificationType;
7
7
  export declare function mapNotificationKind(kind: string | null | undefined, fallbackType: NotificationType): string;
8
+ /** Prefer `last_activity_at` for display/grouping; fall back to `created_at`. */
9
+ export declare function resolveNotificationActivityAt(record: {
10
+ created_at: string;
11
+ last_activity_at?: string | null;
12
+ }): string;
8
13
  export declare function mapNotificationApiActor(actor: INotificationApiActor): {
9
14
  name: string;
10
15
  avatarUrl: string | undefined;
@@ -32,6 +32,9 @@ export function mapNotificationKind(kind, fallbackType) {
32
32
  }
33
33
  return normalizeNotificationKind(kind) || normalizeNotificationKind(fallbackType);
34
34
  }
35
+ export function resolveNotificationActivityAt(record) {
36
+ return record.last_activity_at || record.created_at;
37
+ }
35
38
  export function mapNotificationApiActor(actor) {
36
39
  const name = [actor.first_name, actor.last_name].filter(Boolean).join(" ").trim();
37
40
  return {
@@ -42,13 +45,14 @@ export function mapNotificationApiActor(actor) {
42
45
  export function mapNotificationApiRecord(record, options) {
43
46
  const state = record.notification_state;
44
47
  const type = mapSourceRecordType(record.source_record_type);
48
+ const activityAt = resolveNotificationActivityAt(record);
45
49
  return {
46
50
  id: String(record.id),
47
51
  type,
48
52
  kind: mapNotificationKind(record.kind, type),
49
53
  actors: (record.actors ?? []).map(mapNotificationApiActor),
50
54
  actionText: record.title,
51
- timestampLabel: formatNotificationTimeAgo(record.created_at, {
55
+ timestampLabel: formatNotificationTimeAgo(activityAt, {
52
56
  locale: options.locale,
53
57
  now: options.now,
54
58
  labels: {
@@ -56,7 +60,7 @@ export function mapNotificationApiRecord(record, options) {
56
60
  yesterdayTimeAgo: options.dateLabels.yesterdayTimeAgo
57
61
  }
58
62
  }),
59
- createdAt: record.created_at,
63
+ createdAt: activityAt,
60
64
  contextLabel: record.summary,
61
65
  dismissed: state === "dismissed",
62
66
  read: state === "read",
@@ -1,7 +1,4 @@
1
- import {
2
- DEFAULT_NOTIFICATION_FILTERS,
3
- NOTIFICATION_FILTER_CATEGORIES
4
- } from "../constants/notification-types.js";
1
+ import { NOTIFICATION_FILTER_CATEGORIES } from "../constants/notification-types.js";
5
2
  import { normalizeNotificationKind } from "./notification-api.js";
6
3
  export function flattenNotifications(groups) {
7
4
  return groups.flatMap((group) => group.notifications);
@@ -103,7 +100,7 @@ export function resolveStickyNotificationFilters({
103
100
  if (activeFilterId === "all-types") {
104
101
  nextStickyFilters = dynamicFilters;
105
102
  } else if (!stickyFilters.length) {
106
- nextStickyFilters = dynamicFilters.length ? dynamicFilters : [...DEFAULT_NOTIFICATION_FILTERS];
103
+ nextStickyFilters = dynamicFilters;
107
104
  }
108
105
  if (activeFilterId !== "all-types" && nextStickyFilters.length) {
109
106
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apptegy/nuxt-navigation",
3
- "version": "0.1.117",
3
+ "version": "0.1.119",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {