{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "notification-center",
  "type": "registry:block",
  "title": "Notification center",
  "description": "Accessible inbox with asynchronous read persistence, retry and keyboard dismissal.",
  "registryDependencies": [
    "button",
    "https://shine-blond.vercel.app/r/detail-sheet.json"
  ],
  "files": [
    {
      "path": "blocks/notification-center.tsx",
      "type": "registry:component",
      "target": "components/shine/notification-center.tsx",
      "content": "\"use client\";\nimport { useRef, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { DetailSheet } from \"@/components/shine/detail-sheet\";\nexport type NotificationItem = { id: string; title: string; description: string; read: boolean; href?: string };\n/** The consumer persists read state. Failures keep the item unread and retryable. */\nexport function NotificationCenter({ items, onMarkRead, loading = false, error, onRetry }: {\n items: NotificationItem[]; onMarkRead: (ids: string[]) => Promise<void>; loading?: boolean; error?: string; onRetry: () => void;\n}) {\n const [open, setOpen] = useState(false), [busy, setBusy] = useState(false), [failure, setFailure] = useState(\"\"), [status, setStatus] = useState(\"\"); const lock = useRef(false);\n const unread = items.filter(item => !item.read);\n async function mark(ids: string[]) { if (lock.current || !ids.length) return; lock.current = true; setBusy(true); setFailure(\"\"); setStatus(\"\"); try { await onMarkRead(ids); setStatus(ids.length + \" notifications marked read.\"); } catch (cause) { setFailure(cause instanceof Error ? cause.message : \"Could not mark read. Try again.\"); } finally { lock.current = false; setBusy(false); } }\n return <div data-product-pattern=\"notification-center\"><Button type=\"button\" variant=\"outline\" onClick={() => setOpen(true)}>Notifications ({unread.length})</Button><DetailSheet open={open} onOpenChange={setOpen} title=\"Notifications\" description={unread.length + \" unread\"} actions={<Button type=\"button\" disabled={busy || loading || Boolean(error) || !unread.length} onClick={() => void mark(unread.map(item => item.id))}>Mark all read</Button>}>\n  <p role=\"status\" className=\"text-sm\">{loading ? \"Loading notifications…\" : status}</p>{(error || failure) && <p role=\"alert\" className=\"mb-3 text-sm text-destructive\">{error || failure}{error && <Button type=\"button\" variant=\"outline\" onClick={onRetry}>Retry notifications</Button>}</p>}\n  {!loading && !error && !items.length && <p>No notifications.</p>}\n  {!loading && !error && <ul className=\"space-y-3\">{items.map(item => <li key={item.id} className=\"space-y-2 rounded-md border border-border p-3\"><h3 className=\"font-medium\">{item.title}</h3><p className=\"text-sm text-muted-foreground\">{item.description}</p><div className=\"flex flex-wrap items-center gap-2\">{item.href && <Button asChild variant=\"outline\"><a href={item.href}>Open {item.title}</a></Button>}{item.read ? <span className=\"text-sm\">Read</span> : <Button type=\"button\" disabled={busy} variant=\"ghost\" aria-label={\"Mark \" + item.title + \" read\"} onClick={() => void mark([item.id])}>Mark read</Button>}</div></li>)}</ul>}\n </DetailSheet></div>;\n}\n"
    }
  ]
}
