{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "notification-center",
  "title": "Notification Center",
  "description": "Tabbed activity inbox with unread state, avatars, and inline actions.",
  "registryDependencies": [
    "https://components.exe.xyz/r/avatar.json",
    "https://components.exe.xyz/r/button.json",
    "https://components.exe.xyz/r/segmented-control.json",
    "https://components.exe.xyz/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/blocks/notification-center.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  SegmentedControl,\n  SegmentedControlItem,\n} from \"@/components/ui/segmented-control\"\n\nexport type NotificationCategory = \"mentions\" | \"system\" | \"activity\"\n\nexport type InboxNotification = {\n  id: string\n  category: NotificationCategory\n  group: string\n  title: string\n  description?: string\n  timestamp: string\n  unread?: boolean\n  status?: \"neutral\" | \"information\" | \"success\" | \"error\"\n  avatar?: { src?: string; alt?: string; initials?: string }\n  actions?: {\n    id: string\n    label: string\n    variant?: \"primary\" | \"secondary\" | \"danger\"\n  }[]\n}\n\nconst statusDot: Record<NonNullable<InboxNotification[\"status\"]>, string> = {\n  neutral: \"bg-muted-foreground\",\n  information: \"bg-primary\",\n  success: \"bg-[var(--chart-2)]\",\n  error: \"bg-destructive\",\n}\n\nconst actionVariant = {\n  primary: \"default\",\n  secondary: \"outline\",\n  danger: \"destructive\",\n} as const\n\nexport function NotificationCenter({\n  notifications,\n  tab,\n  defaultTab = \"all\",\n  onTabChange,\n  onAction,\n  onMarkAllRead,\n  className,\n}: {\n  notifications: InboxNotification[]\n  tab?: string\n  defaultTab?: string\n  onTabChange?: (tab: string) => void\n  onAction?: (notificationId: string, actionId: string) => void\n  onMarkAllRead?: () => void\n  className?: string\n}) {\n  const [items, setItems] = React.useState(notifications)\n  const [uncontrolledTab, setUncontrolledTab] = React.useState(defaultTab)\n\n  React.useEffect(() => {\n    setItems(notifications)\n  }, [notifications])\n\n  const currentTab = tab ?? uncontrolledTab\n\n  function changeTab(next: string) {\n    if (tab === undefined) setUncontrolledTab(next)\n    onTabChange?.(next)\n  }\n\n  function markAllRead() {\n    setItems((current) => current.map((item) => ({ ...item, unread: false })))\n    onMarkAllRead?.()\n  }\n\n  function runAction(notificationId: string, actionId: string) {\n    setItems((current) =>\n      current.map((item) =>\n        item.id === notificationId ? { ...item, unread: false } : item\n      )\n    )\n    onAction?.(notificationId, actionId)\n  }\n\n  const unreadCount = items.filter((item) => item.unread).length\n  const showActivity = items.some((item) => item.category === \"activity\")\n  const filtered =\n    currentTab === \"all\"\n      ? items\n      : items.filter((item) => item.category === currentTab)\n\n  const groups: { heading: string; items: InboxNotification[] }[] = []\n  for (const item of filtered) {\n    const last = groups.at(-1)\n    if (last?.heading === item.group) last.items.push(item)\n    else groups.push({ heading: item.group, items: [item] })\n  }\n\n  return (\n    <section\n      data-slot=\"notification-center\"\n      aria-label=\"Notifications\"\n      className={cn(\n        \"flex w-full max-w-md flex-col rounded-[2px] border border-border bg-card\",\n        className\n      )}\n    >\n      <header className=\"flex items-center justify-between gap-3 px-3 pt-3\">\n        <div className=\"min-w-0\">\n          <h2 className=\"font-heading text-sm font-medium\">Notifications</h2>\n          <p className=\"font-mono text-[11px] text-muted-foreground\">\n            {unreadCount} unread\n          </p>\n        </div>\n        <Button\n          type=\"button\"\n          variant=\"ghost\"\n          size=\"sm\"\n          onClick={markAllRead}\n          disabled={unreadCount === 0}\n        >\n          Mark all read\n        </Button>\n      </header>\n      <div className=\"px-3 pt-3\">\n        <SegmentedControl\n          selectedKey={currentTab}\n          onSelectionChange={changeTab}\n          size=\"sm\"\n          aria-label=\"Notification filters\"\n          className=\"w-full\"\n        >\n          <SegmentedControlItem id=\"all\" className=\"flex-1\">\n            All {items.length}\n          </SegmentedControlItem>\n          <SegmentedControlItem id=\"mentions\" className=\"flex-1\">\n            Mentions\n          </SegmentedControlItem>\n          <SegmentedControlItem id=\"system\" className=\"flex-1\">\n            System\n          </SegmentedControlItem>\n          {showActivity ? (\n            <SegmentedControlItem id=\"activity\" className=\"flex-1\">\n              Activity\n            </SegmentedControlItem>\n          ) : null}\n        </SegmentedControl>\n      </div>\n      <div className=\"max-h-96 overflow-y-auto py-2\">\n        {filtered.length === 0 ? (\n          <p className=\"px-3 py-8 text-center text-sm text-muted-foreground\">\n            No notifications\n          </p>\n        ) : (\n          groups.map((group) => (\n            <section key={group.heading} className=\"px-3 py-2\">\n              <h3 className=\"mb-1 font-mono text-[11px] tracking-wide text-muted-foreground uppercase\">\n                {group.heading}\n              </h3>\n              <ul className=\"flex flex-col gap-1\">\n                {group.items.map((item) => (\n                  <li\n                    key={item.id}\n                    aria-label={`${item.title}${item.unread ? \", unread\" : \"\"}`}\n                    className=\"rounded-[2px] px-2 py-2 hover:bg-muted/50\"\n                  >\n                    <div className=\"flex gap-2.5\">\n                      {item.avatar ? (\n                        <Avatar size=\"sm\">\n                          {item.avatar.src ? (\n                            <AvatarImage\n                              src={item.avatar.src}\n                              alt={item.avatar.alt ?? \"\"}\n                            />\n                          ) : null}\n                          <AvatarFallback>\n                            {item.avatar.initials ?? \"?\"}\n                          </AvatarFallback>\n                        </Avatar>\n                      ) : (\n                        <span\n                          className={cn(\n                            \"mt-1.5 size-2 shrink-0 rounded-full\",\n                            statusDot[item.status ?? \"neutral\"]\n                          )}\n                          aria-hidden\n                        />\n                      )}\n                      <div className=\"min-w-0 flex-1\">\n                        <div className=\"flex items-start justify-between gap-2\">\n                          <p\n                            className={cn(\n                              \"text-sm\",\n                              item.unread && \"font-medium\"\n                            )}\n                          >\n                            {item.unread ? (\n                              <span className=\"sr-only\">unread </span>\n                            ) : null}\n                            {item.title}\n                          </p>\n                          <time className=\"font-mono text-[11px] whitespace-nowrap text-muted-foreground\">\n                            {item.timestamp}\n                          </time>\n                        </div>\n                        {item.description ? (\n                          <p className=\"mt-0.5 text-sm text-muted-foreground\">\n                            {item.description}\n                          </p>\n                        ) : null}\n                        {item.actions?.length ? (\n                          <div className=\"mt-2 flex flex-wrap gap-1.5\">\n                            {item.actions.map((action) => (\n                              <Button\n                                key={action.id}\n                                type=\"button\"\n                                size=\"xs\"\n                                variant={\n                                  actionVariant[action.variant ?? \"secondary\"]\n                                }\n                                onClick={() => runAction(item.id, action.id)}\n                              >\n                                {action.label}\n                              </Button>\n                            ))}\n                          </div>\n                        ) : null}\n                      </div>\n                    </div>\n                  </li>\n                ))}\n              </ul>\n            </section>\n          ))\n        )}\n      </div>\n    </section>\n  )\n}\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}