@apptegy/nuxt-navigation 0.1.118 → 0.1.120

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.118",
7
+ "version": "0.1.120",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.1",
10
10
  "unbuild": "unknown"
@@ -9,11 +9,13 @@
9
9
  </div>
10
10
  <slot name="org-select" />
11
11
  <slot name="user-controls" />
12
- <div class="user-control-actions">
13
- <div
14
- v-if="showNotifications"
15
- class="header__notifications"
16
- >
12
+ <div class="header__end">
13
+ <slot name="searchbar" />
14
+ <div class="user-control-actions">
15
+ <div
16
+ v-if="showNotifications"
17
+ class="header__notifications"
18
+ >
17
19
  <Button
18
20
  type="button"
19
21
  severity="secondary"
@@ -70,8 +72,10 @@
70
72
  :loading="notificationsLoading"
71
73
  :active-tab-id="activeTabId"
72
74
  :active-filter-id="activeFilterId"
75
+ :filter-catalog="filterCatalog"
73
76
  @update:active-tab-id="handleTabChange"
74
77
  @update:active-filter-id="handleFilterChange"
78
+ @update:filter-catalog="filterCatalog = $event"
75
79
  @clear-all="(payload) => $emit('notifications-clear-all', payload)"
76
80
  @dismiss="(payload) => $emit('notifications-dismiss', payload)"
77
81
  @select="handleNotificationSelect"
@@ -90,8 +94,10 @@
90
94
  :loading="notificationsLoading"
91
95
  :active-tab-id="activeTabId"
92
96
  :active-filter-id="activeFilterId"
97
+ :filter-catalog="filterCatalog"
93
98
  @update:active-tab-id="handleTabChange"
94
99
  @update:active-filter-id="handleFilterChange"
100
+ @update:filter-catalog="filterCatalog = $event"
95
101
  @clear-all="(payload) => $emit('notifications-clear-all', payload)"
96
102
  @dismiss="(payload) => $emit('notifications-dismiss', payload)"
97
103
  @select="handleNotificationSelect"
@@ -132,6 +138,7 @@
132
138
  />
133
139
  </NuxtLink>
134
140
  </template>
141
+ </div>
135
142
  </div>
136
143
  </header>
137
144
  </template>
@@ -173,6 +180,7 @@ const notificationsLoading = computed(() => props.notifications?.loading ?? fals
173
180
  const panelOpen = ref(props.notifications?.panelOpen ?? false);
174
181
  const activeTabId = ref(props.notifications?.activeTabId || "new");
175
182
  const activeFilterId = ref(props.notifications?.activeFilterId || "all-types");
183
+ const filterCatalog = ref([]);
176
184
  const popoverPt = {
177
185
  content: {
178
186
  class: "notifications-popover__content",
@@ -236,16 +244,8 @@ watch(
236
244
  }
237
245
  );
238
246
  watch(panelOpen, (value) => {
239
- if (value) {
240
- patchNotifications({ panelOpen: true });
241
- return;
242
- }
243
- activeFilterId.value = "all-types";
244
- patchNotifications({
245
- panelOpen: false,
246
- activeFilterId: "all-types"
247
- });
248
- if (!isMobilePanel.value) {
247
+ patchNotifications({ panelOpen: value });
248
+ if (!value && !isMobilePanel.value) {
249
249
  popoverRef.value?.hide();
250
250
  }
251
251
  });
@@ -316,10 +316,18 @@ header.header {
316
316
  background-color: var(--primary-white);
317
317
  font: var(--font-regular);
318
318
  }
319
+ header.header .header__end {
320
+ display: flex;
321
+ align-items: center;
322
+ justify-content: flex-end;
323
+ gap: 24px;
324
+ min-width: 0;
325
+ }
319
326
  header.header .user-control-actions {
320
327
  display: flex;
321
328
  align-items: center;
322
329
  gap: 8px;
330
+ flex-shrink: 0;
323
331
  }
324
332
  header.header .header__notifications {
325
333
  position: relative;
@@ -17,7 +17,7 @@ interface IUserDropdown {
17
17
  userDropdownOptions: Array<unknown>;
18
18
  }
19
19
  type __VLS_Props = IHeaderProps & IUserDropdown;
20
- declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_15: {};
20
+ declare var __VLS_1: {}, __VLS_3: {}, __VLS_5: {}, __VLS_7: {}, __VLS_17: {};
21
21
  type __VLS_Slots = {} & {
22
22
  breadcrumbs?: (props: typeof __VLS_1) => any;
23
23
  } & {
@@ -25,7 +25,9 @@ type __VLS_Slots = {} & {
25
25
  } & {
26
26
  'user-controls'?: (props: typeof __VLS_5) => any;
27
27
  } & {
28
- notifications?: (props: typeof __VLS_15) => any;
28
+ searchbar?: (props: typeof __VLS_7) => any;
29
+ } & {
30
+ notifications?: (props: typeof __VLS_17) => any;
29
31
  };
30
32
  declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
31
33
  "selected-option": (...args: any[]) => void;
@@ -212,14 +212,18 @@ const props = defineProps({
212
212
  loading: { type: Boolean, required: false, default: false },
213
213
  activeTabId: { type: String, required: false },
214
214
  activeFilterId: { type: String, required: false },
215
+ filterCatalog: { type: Array, required: false, default: () => [] },
215
216
  embedded: { type: Boolean, required: false, default: false },
216
217
  mobile: { type: Boolean, required: false, default: false }
217
218
  });
218
- 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"]);
219
220
  const filtersRef = ref();
220
221
  const activeTabId = ref(props.activeTabId || props.data.tabs[0]?.id || "");
221
222
  const activeFilterId = ref(props.activeFilterId || props.data.filters?.[0]?.id || "all-types");
222
- const stickyFilters = ref([]);
223
+ const stickyFilters = ref([...props.filterCatalog ?? []]);
224
+ function filterCatalogKey(filters) {
225
+ return filters.map((filter) => filter.id).join("|");
226
+ }
223
227
  watch(
224
228
  () => props.activeTabId,
225
229
  (value) => {
@@ -236,6 +240,18 @@ watch(
236
240
  }
237
241
  }
238
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
+ );
239
255
  watch(
240
256
  () => props.data.tabs,
241
257
  (tabs) => {
@@ -250,9 +266,6 @@ const usesDynamicFilters = computed(() => !props.data.filters?.length);
250
266
  const activeTab = computed(
251
267
  () => props.data.tabs.find((tab) => tab.id === activeTabId.value)
252
268
  );
253
- function filterCatalogKey(filters) {
254
- return filters.map((filter) => filter.id).join("|");
255
- }
256
269
  watch(
257
270
  [allNotifications, activeTab, activeFilterId, () => props.data.filters],
258
271
  () => {
@@ -265,6 +278,7 @@ watch(
265
278
  });
266
279
  if (filterCatalogKey(stickyFilters.value) !== filterCatalogKey(nextStickyFilters)) {
267
280
  stickyFilters.value = nextStickyFilters;
281
+ emit("update:filterCatalog", [...nextStickyFilters]);
268
282
  }
269
283
  },
270
284
  { immediate: true, deep: true }
@@ -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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apptegy/nuxt-navigation",
3
- "version": "0.1.118",
3
+ "version": "0.1.120",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {