{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "notification-centre",
  "title": "Notification Centre",
  "description": "A bell trigger with an unread badge opening a 420px popover inbox: All/Unread/Archived tabs, rows date-grouped under sticky blurred headers, a scroll fade, hover quick actions, inline invite decisions, mark-all-read and per-tab empty states. Exports NotificationRow for standalone feeds.",
  "dependencies": [
    "@untitledui/icons@^0.0.22",
    "date-fns@^4.4.0",
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@nx-ui/avatar",
    "@nx-ui/button",
    "@nx-ui/empty-state",
    "@nx-ui/motion",
    "@nx-ui/notification",
    "@nx-ui/popover",
    "@nx-ui/tabs",
    "@nx-ui/utils"
  ],
  "files": [
    {
      "path": "components/blocks/notification-centre.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  Archive,\n  Bell01,\n  Check,\n  CheckDone02,\n  CheckCircle,\n  Eye,\n  Inbox01,\n  Mail01,\n  Settings01,\n  Trash01,\n} from \"@untitledui/icons\"\nimport {\n  differenceInCalendarDays,\n  format,\n  formatDistanceToNow,\n  isThisYear,\n  isToday,\n  isYesterday,\n} from \"date-fns\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Notification,\n  NotificationActions,\n  NotificationBadge,\n  NotificationBody,\n  NotificationDescription,\n  NotificationDot,\n  NotificationIcon,\n  NotificationLabel,\n  NotificationLeading,\n  NotificationQuickAction,\n  NotificationQuickActions,\n  NotificationTime,\n  NotificationTitle,\n  type NotificationSeverity,\n} from \"@/components/ui/notification\"\nimport {\n  Popover,\n  PopoverContent,\n  PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\"\nimport { EmptyState } from \"@/components/blocks/empty-state\"\nimport { EASE } from \"@/components/blocks/marketing/motion\"\n\n/**\n * NotificationCentre — a bell trigger with an unread-count badge that opens a\n * 420px popover inbox, rebuilt on the production nexus4 anatomy:\n *\n *   - header: title, conditional \"Mark all read\", optional settings button;\n *   - All / Unread / Archived segmented tabs (Unread carries a count pill);\n *   - a scroll area capped at `min(560px, 85vh − 96px)` with a bottom fade\n *     that appears while more content is scrollable;\n *   - rows grouped under sticky, backdrop-blurred date headers (Today /\n *     Yesterday / This week / Older), staggered in with the shared EASE curve\n *     (reduced-motion collapses all movement);\n *   - rows per the `notification` primitives: unread edge bar + tint, icon\n *     resolution image → avatar → severity tile (with a mini type badge\n *     overlaid on images/avatars), two-line title bold-while-unread, preview,\n *     org label, right-hand relative + absolute time column, hover-revealed\n *     quick-action pill (view / read-toggle / archive-toggle / delete), and\n *     inline Accept/Decline for `kind: \"invite\"` rows;\n *   - per-tab empty states composed on the empty-state pattern.\n *\n * Fully presentational and props-driven: pass `items` (see\n * `NotificationItem`) and optional callbacks. Read/archive/delete state is\n * mirrored into internal state so the block is interactive out of the box;\n * every change also fires the matching callback (`onRead`, `onArchive`,\n * `onDelete`, `onMarkAllRead`, `onAction`) so a host app can persist it.\n * Passing a new `items` array resets the internal copy.\n *\n * `NotificationRow` is exported standalone so activity feeds can reuse the\n * exact row outside the popover.\n */\n\ntype NotificationCentreTab = \"all\" | \"unread\" | \"archived\"\n\n/** An action surfaced from a row: the eye button, or an invite decision. */\ntype NotificationRowAction = \"view\" | \"accept\" | \"decline\"\n\ninterface NotificationItem {\n  id: string\n  title: string\n  /** One-to-two-line preview under the title. */\n  preview?: string\n  /** ISO string, epoch milliseconds or Date. */\n  timestamp: string | number | Date\n  read?: boolean\n  archived?: boolean\n  /** Tints the severity tile / overlay badge. Defaults to \"info\". */\n  severity?: NotificationSeverity\n  /** Optional glyph for the severity tile / overlay badge (defaults to the\n   * severity's own glyph). nexus4 derives this from a domain category map;\n   * the nx-ui block takes the icon directly to stay domain-agnostic. */\n  icon?: React.ComponentType<{ className?: string }>\n  /** Person the notification is about — renders an avatar. */\n  actor?: { name: string; avatarUrl?: string }\n  /** Organisation/site image — wins over `actor` in the leading slot. */\n  imageUrl?: string\n  /** Context line under the preview, e.g. the organisation name. */\n  orgLabel?: string\n  /** \"invite\" rows get inline Accept / Decline while unread. */\n  kind?: \"default\" | \"invite\"\n  /** Makes the title a link and adds a View quick action. */\n  href?: string\n}\n\n// ---------------------------------------------------------------------------\n// Time helpers\n// ---------------------------------------------------------------------------\n\ntype TimeGroupKey = \"today\" | \"yesterday\" | \"thisWeek\" | \"older\"\n\nconst TIME_GROUP_LABELS: Record<TimeGroupKey, string> = {\n  today: \"Today\",\n  yesterday: \"Yesterday\",\n  thisWeek: \"This week\",\n  older: \"Older\",\n}\n\ninterface NotificationGroup {\n  key: TimeGroupKey\n  label: string\n  items: NotificationItem[]\n}\n\n/** Bucket items (sorted newest-first) into Today / Yesterday / This week /\n * Older groups; only non-empty groups are returned. \"This week\" is a rolling\n * seven calendar days (nexus4 uses the calendar week, which empties the\n * bucket every Monday and relegates Friday's rows to \"Older\" — deliberately\n * improved here). */\nfunction groupNotificationsByTime(\n  items: NotificationItem[]\n): NotificationGroup[] {\n  const buckets: Record<TimeGroupKey, NotificationItem[]> = {\n    today: [],\n    yesterday: [],\n    thisWeek: [],\n    older: [],\n  }\n  const sorted = [...items].sort(\n    (a, b) =>\n      new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()\n  )\n  for (const item of sorted) {\n    const date = new Date(item.timestamp)\n    if (isToday(date)) buckets.today.push(item)\n    else if (isYesterday(date)) buckets.yesterday.push(item)\n    else if (differenceInCalendarDays(new Date(), date) < 7)\n      buckets.thisWeek.push(item)\n    else buckets.older.push(item)\n  }\n  return (Object.keys(buckets) as TimeGroupKey[])\n    .filter((key) => buckets[key].length > 0)\n    .map((key) => ({\n      key,\n      label: TIME_GROUP_LABELS[key],\n      items: buckets[key],\n    }))\n}\n\nfunction relativeTime(date: Date): string {\n  return formatDistanceToNow(date, { addSuffix: true })\n}\n\nfunction absoluteTime(date: Date): string {\n  return isThisYear(date)\n    ? format(date, \"d MMM, HH:mm\")\n    : format(date, \"d MMM yyyy, HH:mm\")\n}\n\nfunction initialsOf(name: string): string {\n  return name\n    .split(/\\s+/)\n    .map((word) => word.charAt(0))\n    .slice(0, 2)\n    .join(\"\")\n    .toUpperCase()\n}\n\n// ---------------------------------------------------------------------------\n// Row\n// ---------------------------------------------------------------------------\n\ninterface NotificationRowCallbacks {\n  /** Read-state toggle: `read` is the requested new state. */\n  onRead?: (id: string, read: boolean) => void\n  /** Archive toggle: `archived` is the requested new state. */\n  onArchive?: (id: string, archived: boolean) => void\n  onDelete?: (id: string) => void\n  /** View / invite Accept / invite Decline. */\n  onAction?: (item: NotificationItem, action: NotificationRowAction) => void\n}\n\n/**\n * A single, fully-assembled inbox row — the popover uses it, and feeds can\n * too. Quick actions render only for the callbacks you provide.\n */\nfunction NotificationRow({\n  item,\n  className,\n  onRead,\n  onArchive,\n  onDelete,\n  onAction,\n}: NotificationRowCallbacks & {\n  item: NotificationItem\n  className?: string\n}) {\n  const unread = !item.read\n  const date = new Date(item.timestamp)\n  const severity = item.severity ?? \"info\"\n  const Glyph = item.icon\n  const isInvite = item.kind === \"invite\" && unread && !item.archived\n  // The overlay badge only appears when an image/avatar occupies the leading\n  // slot AND the item declares a type (severity or icon) worth signalling.\n  const showBadge =\n    Boolean(item.imageUrl || item.actor) && Boolean(item.severity || item.icon)\n  const hasQuickActions = Boolean(\n    onRead || onArchive || onDelete || (item.href && onAction)\n  )\n\n  const handleNavigate = () => {\n    // Pending invites stay unread until explicitly actioned.\n    if (unread && !isInvite) onRead?.(item.id, true)\n    onAction?.(item, \"view\")\n  }\n\n  return (\n    <Notification unread={unread} className={className}>\n      {unread ? <NotificationDot /> : null}\n\n      <NotificationLeading>\n        {item.imageUrl ? (\n          // eslint-disable-next-line @next/next/no-img-element\n          <img\n            src={item.imageUrl}\n            alt=\"\"\n            className=\"size-9 rounded-full object-cover ring-1 ring-border/50\"\n          />\n        ) : item.actor ? (\n          <Avatar className=\"size-9\">\n            <AvatarImage src={item.actor.avatarUrl} alt={item.actor.name} />\n            <AvatarFallback className=\"text-xs\">\n              {initialsOf(item.actor.name)}\n            </AvatarFallback>\n          </Avatar>\n        ) : (\n          <NotificationIcon severity={severity}>\n            {Glyph ? <Glyph /> : undefined}\n          </NotificationIcon>\n        )}\n        {showBadge ? (\n          <NotificationBadge severity={severity}>\n            {Glyph ? <Glyph /> : undefined}\n          </NotificationBadge>\n        ) : null}\n      </NotificationLeading>\n\n      <NotificationBody>\n        <NotificationTitle>\n          {item.href ? (\n            <a\n              href={item.href}\n              onClick={handleNavigate}\n              className=\"hover:underline\"\n            >\n              {item.title}\n            </a>\n          ) : (\n            item.title\n          )}\n        </NotificationTitle>\n        {item.preview ? (\n          <NotificationDescription>{item.preview}</NotificationDescription>\n        ) : null}\n        {item.orgLabel ? (\n          <NotificationLabel>{item.orgLabel}</NotificationLabel>\n        ) : null}\n        {isInvite ? (\n          <NotificationActions>\n            <Button\n              size=\"sm\"\n              className=\"h-7 min-w-[78px] gap-1.5 px-3 py-0 text-xs\"\n              onClick={() => {\n                onAction?.(item, \"accept\")\n                onRead?.(item.id, true)\n              }}\n            >\n              Accept\n            </Button>\n            <Button\n              size=\"sm\"\n              variant=\"ghost\"\n              className=\"h-7 min-w-[78px] gap-1.5 px-3 py-0 text-xs\"\n              onClick={() => {\n                onAction?.(item, \"decline\")\n                onRead?.(item.id, true)\n              }}\n            >\n              Decline\n            </Button>\n          </NotificationActions>\n        ) : null}\n      </NotificationBody>\n\n      <NotificationTime>\n        <span suppressHydrationWarning>{relativeTime(date)}</span>\n        <time\n          dateTime={date.toISOString()}\n          suppressHydrationWarning\n          className=\"text-muted-foreground/40\"\n        >\n          {absoluteTime(date)}\n        </time>\n      </NotificationTime>\n\n      {hasQuickActions ? (\n        <NotificationQuickActions>\n          {item.href && onAction ? (\n            <NotificationQuickAction\n              title=\"View\"\n              aria-label=\"View\"\n              onClick={handleNavigate}\n            >\n              <Eye />\n            </NotificationQuickAction>\n          ) : null}\n          {onRead ? (\n            unread ? (\n              <NotificationQuickAction\n                title=\"Mark as read\"\n                aria-label=\"Mark as read\"\n                onClick={() => onRead(item.id, true)}\n              >\n                <Check />\n              </NotificationQuickAction>\n            ) : (\n              <NotificationQuickAction\n                title=\"Mark as unread\"\n                aria-label=\"Mark as unread\"\n                onClick={() => onRead(item.id, false)}\n              >\n                <Mail01 />\n              </NotificationQuickAction>\n            )\n          ) : null}\n          {onArchive ? (\n            item.archived ? (\n              <NotificationQuickAction\n                title=\"Move back to inbox\"\n                aria-label=\"Move back to inbox\"\n                onClick={() => onArchive(item.id, false)}\n              >\n                <Inbox01 />\n              </NotificationQuickAction>\n            ) : (\n              <NotificationQuickAction\n                title=\"Archive\"\n                aria-label=\"Archive\"\n                onClick={() => onArchive(item.id, true)}\n              >\n                <Archive />\n              </NotificationQuickAction>\n            )\n          ) : null}\n          {onDelete ? (\n            <NotificationQuickAction\n              destructive\n              title=\"Delete\"\n              aria-label=\"Delete\"\n              onClick={() => onDelete(item.id)}\n            >\n              <Trash01 />\n            </NotificationQuickAction>\n          ) : null}\n        </NotificationQuickActions>\n      ) : null}\n    </Notification>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Feed internals (scroll fade, groups, empty states)\n// ---------------------------------------------------------------------------\n\n/** Scroll area with an explicit max-height (engages before the popover's\n * open animation settles) and a bottom fade while more content is below. */\nfunction ScrollFade({ children }: { children: React.ReactNode }) {\n  const scrollRef = React.useRef<HTMLDivElement | null>(null)\n  const [showFade, setShowFade] = React.useState(false)\n\n  const updateFade = React.useCallback(() => {\n    const el = scrollRef.current\n    if (!el) return\n    setShowFade(el.scrollHeight - el.scrollTop - el.clientHeight > 8)\n  }, [])\n\n  // Re-evaluate after every render — row removal/expansion changes\n  // scrollHeight without firing a scroll event.\n  React.useEffect(() => {\n    updateFade()\n  })\n\n  return (\n    <div className=\"relative\">\n      <div\n        ref={scrollRef}\n        onScroll={updateFade}\n        style={{ maxHeight: \"min(560px, calc(85vh - 96px))\" }}\n        className=\"overflow-y-auto overscroll-contain\"\n      >\n        {children}\n      </div>\n      <div\n        aria-hidden=\"true\"\n        className={cn(\n          \"pointer-events-none absolute inset-x-0 bottom-0 h-8 bg-linear-to-b from-transparent to-popover transition-opacity duration-200\",\n          showFade ? \"opacity-100\" : \"opacity-0\"\n        )}\n      />\n    </div>\n  )\n}\n\nfunction FeedGroup({\n  group,\n  ...callbacks\n}: NotificationRowCallbacks & { group: NotificationGroup }) {\n  const reduce = useReducedMotion()\n  return (\n    <section role=\"group\" aria-label={group.label}>\n      <div className=\"sticky top-0 z-10 bg-popover/40 px-4 pt-2.5 pb-2 backdrop-blur-2xl backdrop-saturate-150 after:pointer-events-none after:absolute after:inset-x-0 after:top-full after:h-4 after:bg-linear-to-b after:from-popover/40 after:to-transparent\">\n        <span className=\"text-[10px] font-semibold tracking-[0.14em] text-muted-foreground/70 uppercase\">\n          {group.label}\n        </span>\n      </div>\n      <motion.div\n        initial=\"hidden\"\n        animate=\"visible\"\n        variants={{\n          hidden: {},\n          visible: {\n            transition: {\n              staggerChildren: reduce ? 0 : 0.03,\n              delayChildren: reduce ? 0 : 0.04,\n            },\n          },\n        }}\n      >\n        <AnimatePresence initial={false}>\n          {group.items.map((item) => (\n            <motion.div\n              key={item.id}\n              layout={!reduce}\n              style={{ overflow: \"hidden\" }}\n              variants={{\n                hidden: { opacity: 0, y: reduce ? 0 : 6 },\n                visible: {\n                  opacity: 1,\n                  y: 0,\n                  transition: { duration: reduce ? 0 : 0.25, ease: EASE },\n                },\n              }}\n              exit={{\n                opacity: 0,\n                height: 0,\n                transition: { duration: reduce ? 0 : 0.18, ease: \"easeInOut\" },\n              }}\n            >\n              <NotificationRow item={item} {...callbacks} />\n            </motion.div>\n          ))}\n        </AnimatePresence>\n      </motion.div>\n    </section>\n  )\n}\n\nconst EMPTY_STATES: Record<\n  NotificationCentreTab,\n  {\n    icon: React.ComponentType<{ className?: string }>\n    color: \"gray\" | \"success\"\n    title: string\n    description: string\n  }\n> = {\n  all: {\n    icon: Bell01,\n    color: \"gray\",\n    title: \"No notifications yet\",\n    description: \"When something important happens, you'll see it here.\",\n  },\n  unread: {\n    icon: CheckCircle,\n    color: \"success\",\n    title: \"All caught up\",\n    description: \"You have no unread notifications. Nice work.\",\n  },\n  archived: {\n    icon: Archive,\n    color: \"gray\",\n    title: \"No archived notifications\",\n    description: \"Notifications you archive will appear here.\",\n  },\n}\n\nfunction FeedEmptyState({ tab }: { tab: NotificationCentreTab }) {\n  const config = EMPTY_STATES[tab]\n  return (\n    <div className=\"flex min-h-80 items-center justify-center px-6 py-10\">\n      <EmptyState size=\"sm\" className=\"max-w-60\">\n        <EmptyState.Header pattern=\"circle\" patternSize=\"sm\">\n          <EmptyState.FeaturedIcon\n            icon={config.icon}\n            color={config.color}\n            theme=\"light\"\n            size=\"lg\"\n          />\n        </EmptyState.Header>\n        <EmptyState.Content className=\"mb-0 gap-1\">\n          <EmptyState.Title className=\"text-sm\">\n            {config.title}\n          </EmptyState.Title>\n          <EmptyState.Description className=\"max-w-[220px] text-xs leading-relaxed\">\n            {config.description}\n          </EmptyState.Description>\n        </EmptyState.Content>\n      </EmptyState>\n    </div>\n  )\n}\n\nfunction Feed({\n  items,\n  tab,\n  ...callbacks\n}: NotificationRowCallbacks & {\n  items: NotificationItem[]\n  tab: NotificationCentreTab\n}) {\n  if (items.length === 0) return <FeedEmptyState tab={tab} />\n  const groups = groupNotificationsByTime(items)\n  return (\n    <ScrollFade>\n      <div role=\"feed\" aria-label=\"Notifications\">\n        {groups.map((group) => (\n          <FeedGroup key={group.key} group={group} {...callbacks} />\n        ))}\n      </div>\n    </ScrollFade>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// The centre\n// ---------------------------------------------------------------------------\n\nfunction NotificationCentre({\n  items,\n  onRead,\n  onArchive,\n  onDelete,\n  onMarkAllRead,\n  onAction,\n  onSettings,\n  className,\n}: NotificationRowCallbacks & {\n  items: NotificationItem[]\n  onMarkAllRead?: () => void\n  /** Renders a settings icon button in the header when provided. */\n  onSettings?: () => void\n  className?: string\n}) {\n  const [internal, setInternal] = React.useState(items)\n  React.useEffect(() => setInternal(items), [items])\n  const [tab, setTab] = React.useState<NotificationCentreTab>(\"all\")\n\n  const inbox = internal.filter((n) => !n.archived)\n  const unreadItems = inbox.filter((n) => !n.read)\n  const archivedItems = internal.filter((n) => n.archived)\n  const unreadCount = unreadItems.length\n\n  const handleRead = (id: string, read: boolean) => {\n    setInternal((prev) => prev.map((n) => (n.id === id ? { ...n, read } : n)))\n    onRead?.(id, read)\n  }\n  const handleArchive = (id: string, archived: boolean) => {\n    setInternal((prev) =>\n      prev.map((n) => (n.id === id ? { ...n, archived } : n))\n    )\n    onArchive?.(id, archived)\n  }\n  const handleDelete = (id: string) => {\n    setInternal((prev) => prev.filter((n) => n.id !== id))\n    onDelete?.(id)\n  }\n  const handleMarkAllRead = () => {\n    setInternal((prev) =>\n      prev.map((n) => (n.archived ? n : { ...n, read: true }))\n    )\n    onMarkAllRead?.()\n  }\n\n  const callbacks: NotificationRowCallbacks = {\n    onRead: handleRead,\n    onArchive: handleArchive,\n    onDelete: handleDelete,\n    onAction,\n  }\n\n  return (\n    <Popover>\n      <PopoverTrigger\n        render={\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            aria-label={\n              unreadCount > 0\n                ? `Notifications (${unreadCount} unread)`\n                : \"Notifications\"\n            }\n            className={cn(\"relative\", className)}\n          >\n            <Bell01 className=\"size-5\" />\n            {unreadCount > 0 ? (\n              <span className=\"pointer-events-none absolute top-1 right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-bg-brand-solid px-1 text-[10px] leading-none font-semibold text-text-on-brand-solid tabular-nums ring-2 ring-background\">\n                {unreadCount > 99 ? \"99+\" : unreadCount}\n              </span>\n            ) : null}\n          </Button>\n        }\n      />\n      <PopoverContent\n        align=\"end\"\n        sideOffset={8}\n        className=\"w-[420px] max-w-[calc(100vw-1rem)] gap-0 overflow-hidden p-0 shadow-xl\"\n      >\n        {/* Header */}\n        <div className=\"flex items-center justify-between gap-2 px-4 py-3\">\n          <h2 className=\"text-sm font-semibold text-foreground\">\n            Notifications\n          </h2>\n          <div className=\"flex items-center gap-1\">\n            {unreadCount > 0 ? (\n              <Button\n                variant=\"link-color\"\n                size=\"xs\"\n                className=\"gap-1 text-xs\"\n                onClick={handleMarkAllRead}\n              >\n                <CheckDone02 className=\"size-3.5\" />\n                Mark all read\n              </Button>\n            ) : null}\n            {onSettings ? (\n              <Button\n                variant=\"ghost\"\n                size=\"icon-xs\"\n                aria-label=\"Notification settings\"\n                onClick={onSettings}\n              >\n                <Settings01 className=\"size-3.5\" />\n              </Button>\n            ) : null}\n          </div>\n        </div>\n\n        {/* Tabs. `flex-col` is set explicitly: the tabs root relies on a\n            `data-horizontal:` variant that never matches Base UI's\n            `data-orientation` attribute, so without it the list and panels\n            render side by side. */}\n        <Tabs\n          value={tab}\n          onValueChange={(value) => setTab(value as NotificationCentreTab)}\n          className=\"flex-col gap-0\"\n        >\n          <div className=\"px-3\">\n            <TabsList className=\"h-8 w-full\">\n              <TabsTrigger value=\"all\" className=\"gap-1.5 text-xs\">\n                <Bell01 className=\"size-3.5\" />\n                All\n              </TabsTrigger>\n              <TabsTrigger value=\"unread\" className=\"gap-1.5 text-xs\">\n                <Mail01 className=\"size-3.5\" />\n                Unread\n                {unreadCount > 0 ? (\n                  <span className=\"ml-0.5 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-foreground/15 px-1.5 text-[10px] leading-none font-semibold text-foreground/80 tabular-nums\">\n                    {unreadCount > 99 ? \"99+\" : unreadCount}\n                  </span>\n                ) : null}\n              </TabsTrigger>\n              <TabsTrigger value=\"archived\" className=\"gap-1.5 text-xs\">\n                <Archive className=\"size-3.5\" />\n                Archived\n              </TabsTrigger>\n            </TabsList>\n          </div>\n\n          <div className=\"mt-2 h-px bg-border/60\" />\n\n          <TabsContent value=\"all\">\n            <Feed items={inbox} tab=\"all\" {...callbacks} />\n          </TabsContent>\n          <TabsContent value=\"unread\">\n            <Feed items={unreadItems} tab=\"unread\" {...callbacks} />\n          </TabsContent>\n          <TabsContent value=\"archived\">\n            <Feed items={archivedItems} tab=\"archived\" {...callbacks} />\n          </TabsContent>\n        </Tabs>\n      </PopoverContent>\n    </Popover>\n  )\n}\n\nexport { NotificationCentre, NotificationRow, groupNotificationsByTime }\nexport type {\n  NotificationItem,\n  NotificationCentreTab,\n  NotificationRowAction,\n  NotificationRowCallbacks,\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}