Files
Ronni Baslund 9fac11e668 feat(operator): notification drawer behind the topbar bell
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).
2026-05-24 17:08:14 +02:00

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
},
})