{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "notification-center",
  "title": "Notification Center",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/notification-center/NotificationCenter.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { BellOff, Check, CheckCheck, MessageSquare, ShieldAlert, UserPlus, X } from 'lucide-react'\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\nexport interface NotificationCenterItem {\n  id: string\n  category: NotificationCategory\n  title: string\n  body: string\n  date: Date\n  read: boolean\n}\n\nexport interface NotificationCenterProps {\n  notifications?: NotificationCenterItem[]\n  className?: string\n}\n\nconst categoryConfig: Record<\n  NotificationCategory,\n  { icon: React.ComponentType<{ className?: string }>; tileClass: string }\n> = {\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: NotificationCenterItem[] = [\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\nexport function NotificationCenter({ notifications, className }: NotificationCenterProps) {\n  const [items, setItems] = React.useState<NotificationCenterItem[]>(notifications ?? stubNotifications)\n  const [filter, setFilter] = React.useState<'all' | 'unread' | 'mentions'>('all')\n\n  function patch(id: string, changes: Partial<NotificationCenterItem>) {\n    setItems((list) => list.map((n) => (n.id === id ? { ...n, ...changes } : n)))\n  }\n\n  const unreadCount = items.filter((n) => !n.read).length\n\n  const filtered = items.filter((n) => {\n    if (filter === 'unread') return !n.read\n    if (filter === 'mentions') return n.category === 'mention'\n    return true\n  })\n\n  const startOfToday = new Date()\n  startOfToday.setHours(0, 0, 0, 0)\n  const groups = [\n    { label: 'Today', items: filtered.filter((n) => n.date >= startOfToday) },\n    { label: 'Earlier', items: filtered.filter((n) => n.date < startOfToday) },\n  ].filter((group) => group.items.length > 0)\n\n  return (\n    <SectionCard\n      data-slot=\"notification-center\"\n      title=\"Notifications\"\n      description=\"Everything that needs your attention.\"\n      className={className}\n      headerAction={\n        <div className=\"flex flex-wrap items-center gap-2\">\n          {unreadCount > 0 && <Badge variant=\"default\">{unreadCount} new</Badge>}\n          <Button\n            variant=\"ghost\"\n            size=\"sm\"\n            disabled={unreadCount === 0}\n            onClick={() => setItems((l) => l.map((n) => ({ ...n, read: true })))}\n          >\n            <CheckCheck aria-hidden=\"true\" />\n            Mark all read\n          </Button>\n        </div>\n      }\n    >\n      <Tabs value={filter} onValueChange={(value) => setFilter(value as typeof filter)} className=\"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      {groups.length === 0 && (\n        <div className=\"text-muted-foreground flex flex-col items-center gap-2 py-12 text-sm\">\n          <BellOff className=\"size-6 opacity-50\" aria-hidden=\"true\" />\n          {filter === 'mentions'\n            ? \"No mentions — you're all caught up.\"\n            : filter === 'unread'\n              ? 'Nothing unread. Nice work.'\n              : 'Your inbox is empty.'}\n        </div>\n      )}\n\n      {groups.map((group) => (\n        <div key={group.label} className=\"mb-2\">\n          <p className=\"text-muted-foreground py-1.5 text-xs font-medium tracking-widest uppercase\">{group.label}</p>\n          <ul className=\"divide-y\">\n            {group.items.map((notification) => {\n              const Icon = categoryConfig[notification.category].icon\n              return (\n                <li\n                  key={notification.id}\n                  className=\"group hover:bg-accent/40 -mx-2 flex cursor-pointer items-start gap-3 rounded-md px-2 py-3 transition-colors\"\n                  onClick={() => patch(notification.id, { read: true })}\n                >\n                  <span\n                    className={cn(\n                      'mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md',\n                      categoryConfig[notification.category].tileClass,\n                    )}\n                    aria-hidden=\"true\"\n                  >\n                    <Icon className=\"size-4\" />\n                  </span>\n                  <div className=\"min-w-0 flex-1\">\n                    <div className=\"flex flex-wrap items-center gap-2\">\n                      <p className={cn('truncate text-sm', notification.read ? 'font-normal' : 'font-medium')}>\n                        {notification.title}\n                      </p>\n                      {!notification.read && (\n                        <span className=\"bg-primary size-1.5 shrink-0 rounded-full\" aria-label=\"Unread\" />\n                      )}\n                    </div>\n                    <p className=\"text-muted-foreground truncate text-xs\">{notification.body}</p>\n                  </div>\n                  <div className=\"flex shrink-0 items-center gap-1\">\n                    <RelativeTime date={notification.date} className=\"text-muted-foreground text-xs\" />\n                    <span className=\"flex opacity-0 transition-opacity group-hover:opacity-100\">\n                      {!notification.read && (\n                        <Button\n                          variant=\"ghost\"\n                          size=\"icon-sm\"\n                          aria-label=\"Mark as read\"\n                          onClick={(e) => {\n                            e.stopPropagation()\n                            patch(notification.id, { read: true })\n                          }}\n                        >\n                          <Check aria-hidden=\"true\" />\n                        </Button>\n                      )}\n                      <Button variant=\"ghost\" size=\"icon-sm\" aria-label=\"Dismiss\" onClick={(e) => e.stopPropagation()}>\n                        <X aria-hidden=\"true\" />\n                      </Button>\n                    </span>\n                  </div>\n                </li>\n              )\n            })}\n          </ul>\n        </div>\n      ))}\n    </SectionCard>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/NotificationCenter.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/relative-time.json",
    "https://uipkge.dev/r/react/section-card.json",
    "https://uipkge.dev/r/react/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"
  ]
}