{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "notification-center",
  "title": "Notification Center",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-svelte/blocks/notification-center/NotificationCenter.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { Component } from 'svelte'\n\n  export type NotificationCategory = 'mention' | 'comment' | 'system' | 'security'\n\n  export interface Notification {\n    id: string\n    category: NotificationCategory\n    title: string\n    body: string\n    date: Date\n    read: boolean\n  }\n\n  export interface NotificationCenterProps {\n    notifications?: Notification[]\n    class?: string\n  }\n</script>\n\n<script lang=\"ts\">\n  import { BellOff, Check, CheckCheck, MessageSquare, ShieldAlert, UserPlus, X } from '@lucide/svelte'\n  import { cn } from '$lib/utils'\n  import { Badge } from '$lib/components/ui/badge'\n  import { Button } from '$lib/components/ui/button'\n  import { RelativeTime } from '$lib/components/ui/relative-time'\n  import { SectionCard } from '$lib/components/ui/section-card'\n  import { Tabs, TabsList, TabsTrigger } from '$lib/components/ui/tabs'\n\n  let { notifications: notificationsProp, class: className }: NotificationCenterProps = $props()\n\n  const 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\n  const now = new Date()\n  const minutesAgo = (m: number) => new Date(now.getTime() - m * 60_000)\n  const hoursAgo = (h: number) => new Date(now.getTime() - h * 3_600_000)\n  const daysAgo = (d: number) => new Date(now.getTime() - d * 86_400_000)\n\n  const 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\n  let items = $state<Notification[]>(stubNotifications)\n  $effect(() => {\n    items = notificationsProp ?? stubNotifications\n  })\n  let filter = $state<'all' | 'unread' | 'mentions'>('all')\n\n  const unreadCount = $derived(items.filter((n) => !n.read).length)\n\n  const filtered = $derived(\n    items.filter((n) => {\n      if (filter === 'unread') return !n.read\n      if (filter === 'mentions') return n.category === 'mention'\n      return true\n    }),\n  )\n\n  const groups = $derived.by(() => {\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) {\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\n  function markAllRead() {\n    for (const notification of items) notification.read = true\n  }\n</script>\n\n<SectionCard\n  data-slot=\"notification-center\"\n  title=\"Notifications\"\n  description=\"Everything that needs your attention.\"\n  class={className}\n>\n  {#snippet headerAction()}\n    <div class=\"flex flex-wrap items-center gap-2\">\n      {#if unreadCount > 0}\n        <Badge variant=\"default\">{unreadCount} new</Badge>\n      {/if}\n      <Button variant=\"ghost\" size=\"sm\" disabled={unreadCount === 0} onclick={markAllRead}>\n        <CheckCheck aria-hidden=\"true\" />\n        Mark all read\n      </Button>\n    </div>\n  {/snippet}\n\n  <Tabs\n    value={filter}\n    onValueChange={(v) => {\n      if (v === 'all' || v === 'unread' || v === 'mentions') filter = v\n    }}\n    class=\"mb-2\"\n  >\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  {#if groups.length === 0}\n    <div 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      {#if filter === 'mentions'}\n        No mentions — you're all caught up.\n      {:else if filter === 'unread'}\n        Nothing unread. Nice work.\n      {:else}\n        Your inbox is empty.\n      {/if}\n    </div>\n  {/if}\n\n  {#each groups as group (group.label)}\n    <div 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        {#each group.items as notification (notification.id)}\n          {@const Icon = categoryConfig[notification.category].icon}\n          <li\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            onclick={() => (notification.read = true)}\n          >\n            <span\n              class={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 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 {notification.read ? 'font-normal' : 'font-medium'}\">\n                  {notification.title}\n                </p>\n                {#if !notification.read}\n                  <span class=\"bg-primary size-1.5 shrink-0 rounded-full\" aria-label=\"Unread\"></span>\n                {/if}\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                {#if !notification.read}\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon-sm\"\n                    aria-label=\"Mark as read\"\n                    onclick={(e) => {\n                      e.stopPropagation()\n                      notification.read = true\n                    }}\n                  >\n                    <Check aria-hidden=\"true\" />\n                  </Button>\n                {/if}\n                <Button\n                  variant=\"ghost\"\n                  size=\"icon-sm\"\n                  aria-label=\"Dismiss\"\n                  onclick={(e) => e.stopPropagation()}\n                >\n                  <X aria-hidden=\"true\" />\n                </Button>\n              </span>\n            </div>\n          </li>\n        {/each}\n      </ul>\n    </div>\n  {/each}\n</SectionCard>\n",
      "type": "registry:block",
      "target": "~/src/lib/components/blocks/notification-center/NotificationCenter.svelte"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/badge.json",
    "https://uipkge.dev/r/svelte/button.json",
    "https://uipkge.dev/r/svelte/relative-time.json",
    "https://uipkge.dev/r/svelte/section-card.json",
    "https://uipkge.dev/r/svelte/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"
  ]
}