9fac11e668
Right-anchored slide-in inbox triggered by the bell button. Backend is a follow-up — for now this is a visual + behavior shell with mock fixtures, same pattern as INCIDENT / FLAGS / OP_AUDIT. - data/fixtures.ts: new NotificationItem type + 6 seed rows from the design (DMARC, invitation, invoice, SAML, ticket reply, failed sign-in) - useNotifications composable: isOpen + items + unreadCount + markRead + markAllRead. Items deep-clone the fixture on first import so toggling unread doesn't mutate the shared seed. - NotificationDrawer component: Teleport + scrim + slide animation, header/list/footer. Each row shows tone-tinted icon tile + title + description + timestamp + left-rail unread dot. Click a row to mark read; click Mark all read or Preferences in the footer. - OpTopbar: bell now opens the drawer and only shows .icon-btn-dot when unreadCount > 0. - Layout mounts <NotificationDrawer /> alongside the other floating components. Dismissal: backdrop click, Escape, X, and route-change watcher (so Preferences → /settings closes the drawer cleanly).
34 lines
1.1 KiB
TypeScript
34 lines
1.1 KiB
TypeScript
// Shared notification-drawer state. The bell in the topbar opens the drawer
|
|
// via `open()`; the drawer reads from `items` and toggles `unread` via
|
|
// `markRead` / `markAllRead`. `unreadCount` powers the red dot on the bell.
|
|
//
|
|
// All state is in-memory and re-seeds from the NOTIFICATIONS fixture on each
|
|
// full reload — there's no backend yet. When the real notifications source
|
|
// lands (see follow-ups in NEXT-STEPS.md), swap `items` for a useFetch.
|
|
|
|
import { NOTIFICATIONS, type NotificationItem } from '~/data/fixtures'
|
|
|
|
const isOpen = ref(false)
|
|
const items = ref<NotificationItem[]>(NOTIFICATIONS.map((n) => ({ ...n })))
|
|
|
|
const unreadCount = computed(() => items.value.filter((n) => n.unread).length)
|
|
|
|
export const useNotifications = () => ({
|
|
isOpen,
|
|
items,
|
|
unreadCount,
|
|
open: () => {
|
|
isOpen.value = true
|
|
},
|
|
close: () => {
|
|
isOpen.value = false
|
|
},
|
|
markRead: (id: string) => {
|
|
const i = items.value.find((n) => n.id === id)
|
|
if (i) i.unread = false
|
|
},
|
|
markAllRead: () => {
|
|
for (const n of items.value) n.unread = false
|
|
},
|
|
})
|