Notification center

Pro

Everything that happened while you were away, behind a bell. The count is unread notices only. Opening one marks it read; "Mark all read" clears the rest. Each notice names the one thing to do next; one whose action has an href is a link to it, so it can open in a new tab too.

Markdown

Preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/notification-center

Brings in: locale, popover, relative-time, team, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { NotificationCenter } from "@/components/notification-center"
import type { Notice } from "@/lib/team"

const minutesAgo = (m: number) => new Date(Date.now() - m * 60_000).toISOString()

const initial: Notice[] = [
  { id: "n1", kind: "approval-waiting", title: "Dev agent wants to run a migration on Production", detail: "Add region to orders", at: minutesAgo(3), read: false, action: { label: "Review" } },
  { id: "n2", kind: "guardrail-stopped", title: "A run was stopped at its budget", detail: "Backfill customers.region used its 30 minutes", at: minutesAgo(41), read: false, action: { label: "See the run" } },
  { id: "n3", kind: "run-finished", title: "Index on orders.customer_id is live on Staging", at: minutesAgo(180), read: true },
  { id: "n4", kind: "invite", title: "Priya invited you to the Payments workspace", at: minutesAgo(60 * 26), read: true, action: { label: "Accept" } },
]

export default function Example() {
  const [notices, setNotices] = React.useState(initial)
  return (
    <div className="flex justify-end">
      <NotificationCenter
        notices={notices}
        onOpen={(id) => setNotices((ns) => ns.map((n) => (n.id === id ? { ...n, read: true } : n)))}
        onMarkAllRead={() => setNotices((ns) => ns.map((n) => ({ ...n, read: true })))}
      />
    </div>
  )
}

<NotificationCenter>

PropTypeDefaultDescription
noticesrequiredNotice[]—The notices, unread first and then newest; only unread ones count on the bell.
onOpen((id: string) => void) | undefined—Called with a notice's id when it's opened; the app marks it read. A notice with `action.href` also follows its link.
onMarkAllRead(() => void) | undefined—Shows "Mark all read" while there are unread notices; called when it's pressed.
classNamestring | undefined—Extra classes for the outer element.

Strings

The fixed words, in English by default. Change any of them under notificationCenter in <LocaleProvider strings={{ … }}>; the table is exported as notificationCenterStrings.

KeyEnglish
notificationsNotifications
notificationsUnreadNotifications, {count} unread
markAllReadMark all read
allCaughtUpYou’re all caught up.
unreadUnread