{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "notification-center",
  "title": "Notification Center",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/notification-center/NotificationCenter.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport type { Component, HTMLAttributes } from 'vue'\nimport { BellOff, Check, CheckCheck, MessageSquare, ShieldAlert, UserPlus, X } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { RelativeTime } from '@/components/ui/relative-time'\nimport { SectionCard } from '@/components/ui/section-card'\nimport { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'\n\ntype NotificationCategory = 'mention' | 'comment' | 'system' | 'security'\n\ninterface Notification {\n  id: string\n  category: NotificationCategory\n  title: string\n  body: string\n  date: Date\n  read: boolean\n}\n\nconst props = withDefaults(\n  defineProps<{\n    notifications?: Notification[]\n    class?: HTMLAttributes['class']\n  }>(),\n  {},\n)\n\nconst categoryConfig: Record<NotificationCategory, { icon: Component; tileClass: string }> = {\n  mention: { icon: UserPlus, tileClass: 'bg-primary/10 text-primary' },\n  comment: { icon: MessageSquare, tileClass: 'bg-chart-3/10 text-chart-3' },\n  system: { icon: BellOff, tileClass: 'bg-muted text-muted-foreground' },\n  security: { icon: ShieldAlert, tileClass: 'bg-warning/10 text-warning' },\n}\n\nconst now = new Date()\nconst minutesAgo = (m: number) => new Date(now.getTime() - m * 60_000)\nconst hoursAgo = (h: number) => new Date(now.getTime() - h * 3_600_000)\nconst daysAgo = (d: number) => new Date(now.getTime() - d * 86_400_000)\n\nconst stubNotifications: Notification[] = [\n  {\n    id: 'n1',\n    category: 'mention',\n    title: 'Priya mentioned you',\n    body: '@amara can you review the onboarding flow?',\n    date: minutesAgo(6),\n    read: false,\n  },\n  {\n    id: 'n2',\n    category: 'security',\n    title: 'New sign-in from Berlin',\n    body: 'Chrome on macOS · 84.190.201.3',\n    date: minutesAgo(52),\n    read: false,\n  },\n  {\n    id: 'n3',\n    category: 'comment',\n    title: 'Jonas commented',\n    body: 'Shipped the fix — closing the ticket.',\n    date: hoursAgo(2),\n    read: false,\n  },\n  {\n    id: 'n4',\n    category: 'system',\n    title: 'Weekly digest ready',\n    body: 'Your workspace summary for last week.',\n    date: hoursAgo(20),\n    read: true,\n  },\n  {\n    id: 'n5',\n    category: 'mention',\n    title: 'Marcus mentioned you',\n    body: 'Design review moved to 15:00, @amara.',\n    date: daysAgo(1),\n    read: false,\n  },\n  {\n    id: 'n6',\n    category: 'comment',\n    title: 'Amara commented',\n    body: 'Copy updated per legal review.',\n    date: daysAgo(1),\n    read: true,\n  },\n  {\n    id: 'n7',\n    category: 'security',\n    title: 'API key rotated',\n    body: 'uipkge_live_…4f2a was rotated automatically.',\n    date: daysAgo(2),\n    read: true,\n  },\n]\n\nconst notifications = computed(() => props.notifications ?? stubNotifications)\nconst filter = ref<'all' | 'unread' | 'mentions'>('all')\n\nconst unreadCount = computed(() => notifications.value.filter((n) => !n.read).length)\n\nconst filtered = computed(() =>\n  notifications.value.filter((n) => {\n    if (filter.value === 'unread') return !n.read\n    if (filter.value === 'mentions') return n.category === 'mention'\n    return true\n  }),\n)\n\nconst groups = computed(() => {\n  const today: Notification[] = []\n  const earlier: Notification[] = []\n  const startOfToday = new Date(now)\n  startOfToday.setHours(0, 0, 0, 0)\n  for (const notification of filtered.value) {\n    ;(notification.date >= startOfToday ? today : earlier).push(notification)\n  }\n  return [\n    { label: 'Today', items: today },\n    { label: 'Earlier', items: earlier },\n  ].filter((group) => group.items.length > 0)\n})\n\nfunction markAllRead() {\n  for (const notification of notifications.value) notification.read = true\n}\n</script>\n\n<template>\n  <SectionCard\n    data-slot=\"notification-center\"\n    title=\"Notifications\"\n    description=\"Everything that needs your attention.\"\n    :class=\"props.class\"\n  >\n    <template #header-action>\n      <div class=\"flex flex-wrap items-center gap-2\">\n        <Badge v-if=\"unreadCount > 0\" variant=\"default\">{{ unreadCount }} new</Badge>\n        <Button variant=\"ghost\" size=\"sm\" :disabled=\"unreadCount === 0\" @click=\"markAllRead\">\n          <CheckCheck aria-hidden=\"true\" />\n          Mark all read\n        </Button>\n      </div>\n    </template>\n\n    <Tabs v-model=\"filter\" class=\"mb-2\">\n      <TabsList>\n        <TabsTrigger value=\"all\">All</TabsTrigger>\n        <TabsTrigger value=\"unread\">Unread</TabsTrigger>\n        <TabsTrigger value=\"mentions\">Mentions</TabsTrigger>\n      </TabsList>\n    </Tabs>\n\n    <div v-if=\"groups.length === 0\" class=\"text-muted-foreground flex flex-col items-center gap-2 py-12 text-sm\">\n      <BellOff class=\"size-6 opacity-50\" aria-hidden=\"true\" />\n      <template v-if=\"filter === 'mentions'\">No mentions — you're all caught up.</template>\n      <template v-else-if=\"filter === 'unread'\">Nothing unread. Nice work.</template>\n      <template v-else>Your inbox is empty.</template>\n    </div>\n\n    <div v-for=\"group in groups\" :key=\"group.label\" class=\"mb-2\">\n      <p class=\"text-muted-foreground py-1.5 text-xs font-medium tracking-widest uppercase\">{{ group.label }}</p>\n      <ul class=\"divide-y\">\n        <li\n          v-for=\"notification in group.items\"\n          :key=\"notification.id\"\n          class=\"group hover:bg-accent/40 -mx-2 flex cursor-pointer items-start gap-3 rounded-md px-2 py-3 transition-colors\"\n          @click=\"notification.read = true\"\n        >\n          <span\n            :class=\"\n              cn(\n                'mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md',\n                categoryConfig[notification.category].tileClass,\n              )\n            \"\n            aria-hidden=\"true\"\n          >\n            <component :is=\"categoryConfig[notification.category].icon\" class=\"size-4\" />\n          </span>\n          <div class=\"min-w-0 flex-1\">\n            <div class=\"flex flex-wrap items-center gap-2\">\n              <p class=\"truncate text-sm\" :class=\"notification.read ? 'font-normal' : 'font-medium'\">\n                {{ notification.title }}\n              </p>\n              <span v-if=\"!notification.read\" class=\"bg-primary size-1.5 shrink-0 rounded-full\" aria-label=\"Unread\" />\n            </div>\n            <p class=\"text-muted-foreground truncate text-xs\">{{ notification.body }}</p>\n          </div>\n          <div class=\"flex shrink-0 items-center gap-1\">\n            <RelativeTime :date=\"notification.date\" class=\"text-muted-foreground text-xs\" />\n            <span class=\"flex opacity-0 transition-opacity group-hover:opacity-100\">\n              <Button\n                v-if=\"!notification.read\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                aria-label=\"Mark as read\"\n                @click.stop=\"notification.read = true\"\n              >\n                <Check aria-hidden=\"true\" />\n              </Button>\n              <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Dismiss\" @click.stop>\n                <X aria-hidden=\"true\" />\n              </Button>\n            </span>\n          </div>\n        </li>\n      </ul>\n    </div>\n  </SectionCard>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/NotificationCenter.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/relative-time.json",
    "https://uipkge.dev/r/vue/section-card.json",
    "https://uipkge.dev/r/vue/tabs.json"
  ],
  "description": "Full-page notification center: unread badge, mark-all-read, All/Unread/Mentions tabs with functional filtering, day-grouped rows (Today / Earlier) pairing category icon tiles with titles, bodies, relative timestamps, unread dots and hover actions (mark read, dismiss). Per-tab empty states included; swap `notifications` for your source.",
  "categories": [
    "communication",
    "dashboard"
  ]
}