{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "notification-center",
  "type": "registry:component",
  "title": "Notification center",
  "description": "Keeps persistent notifications with read, unread, action, and dismissal state.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/button.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/notification-center.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"./tokens.stylex\";\nimport { rs } from \"./rs\";\nimport { Button } from \"./button\";\n\nexport interface NotificationItem {\n  id: string;\n  title: string;\n  description?: React.ReactNode;\n  dateTime?: string;\n  timeLabel?: string;\n  read?: boolean;\n  action?: { label: string; onAction: () => void };\n}\nexport interface NotificationCenterProps extends Omit<React.HTMLAttributes<HTMLElement>, \"defaultValue\"> {\n  value?: NotificationItem[];\n  defaultValue?: NotificationItem[];\n  onValueChange?: (items: NotificationItem[]) => void;\n  label?: string;\n  emptyLabel?: string;\n}\nconst styles = stylex.create({\n  root: { color: vlak.ink, width: \"100%\", minWidth: 0 },\n  header: { display: \"flex\", flexWrap: \"wrap\", gap: \"0.75rem\", alignItems: \"center\", justifyContent: \"space-between\" },\n  title: { margin: 0, fontSize: \"1rem\", fontWeight: 600, lineHeight: 1.45 },\n  list: { listStyleType: \"none\", margin: 0, padding: 0 },\n  item: { padding: \"1rem\", borderBottomWidth: vlak.hairline, borderBottomStyle: \"solid\", borderBottomColor: vlak.divider, overflowWrap: \"anywhere\" },\n  unread: { backgroundColor: vlak.controlFill },\n  detail: { marginBlock: \"0.5rem\", color: vlak.ink, lineHeight: 1.45 },\n  actions: { display: \"flex\", flexWrap: \"wrap\", gap: \"0.5rem\", marginTop: \"0.75rem\" },\n});\n\n/** Persistent notifications with controlled read/unread and dismissal state. */\nexport const NotificationCenter = React.forwardRef<HTMLElement, NotificationCenterProps>(function NotificationCenter({ value, defaultValue = [], onValueChange, label = \"Notifications\", emptyLabel = \"You're up to date\", className, style, ...props }, ref) {\n  const [inner, setInner] = React.useState(defaultValue);\n  const items = value ?? inner;\n  const [status, setStatus] = React.useState(\"\");\n  const pending = React.useRef<{ items: NotificationItem[]; announcement: string; onCommit?: () => void } | null>(null);\n  React.useLayoutEffect(() => {\n    const request = pending.current;\n    if (!request || request.items.length !== items.length || !request.items.every((item, index) => items[index]?.id === item.id && Boolean(items[index]?.read) === Boolean(item.read))) return;\n    pending.current = null;\n    setStatus(request.announcement);\n    request.onCommit?.();\n  }, [items]);\n  const id = React.useId();\n  const change = (next: NotificationItem[], announcement: string, onCommit?: () => void) => { pending.current = { items: next, announcement, onCommit }; setStatus(\"\"); if (value === undefined) setInner(next); onValueChange?.(next); };\n  const root = rs([\"rs-notification-center\", className], styles.root);\n  const header = rs([\"rs-notification-center-header\"], styles.header);\n  const title = rs([\"rs-notification-center-title\"], styles.title);\n  const list = rs([\"rs-notification-center-list\"], styles.list);\n  const detail = rs([\"rs-notification-center-detail\"], styles.detail);\n  const actions = rs([\"rs-notification-center-actions\"], styles.actions);\n  const unread = items.filter(item => !item.read).length;\n  return <section ref={ref} tabIndex={-1} aria-labelledby={id} {...props} className={root.className} style={{ ...root.style, ...style }}>\n    <div {...header}><h2 id={id} {...title}>{label} <span>({unread} unread)</span></h2><Button variant=\"ghost\" disabled={!unread} onClick={() => change(items.map(item => ({ ...item, read: true })), \"All notifications marked as read\")}>Mark all as read</Button></div>\n    <p role=\"status\" {...detail}>{status}</p>\n    {!items.length && <p>{emptyLabel}</p>}\n    <ul {...list}>{items.map(item => {\n      const unseen = !item.read;\n      const row = rs([\"rs-notification-center-item\", unseen && \"rs-notification-center-unread\"], styles.item, unseen && styles.unread);\n      return <li key={item.id} {...row}><h3 {...title}>{item.title}</h3>{item.dateTime && <time {...detail} dateTime={item.dateTime}>{item.timeLabel ?? item.dateTime}</time>}{item.description != null && <div {...detail}>{item.description}</div>}<div {...actions}>\n        <Button variant=\"ghost\" aria-label={`Mark ${item.title} as ${item.read ? \"unread\" : \"read\"}`} onClick={() => change(items.map(entry => entry.id === item.id ? { ...entry, read: !entry.read } : entry), `${item.title} marked as ${item.read ? \"unread\" : \"read\"}`)}>{item.read ? \"Mark unread\" : \"Mark read\"}</Button>\n        {item.action && <Button variant=\"ghost\" onClick={item.action.onAction}>{item.action.label}</Button>}\n        <Button variant=\"ghost\" aria-label={`Dismiss ${item.title}`} onClick={event => { const section = event.currentTarget.closest(\"section\"); const nextButton = event.currentTarget.closest(\"li\")?.nextElementSibling?.querySelector(\"button\") ?? event.currentTarget.closest(\"li\")?.previousElementSibling?.querySelector(\"button\"); change(items.filter(entry => entry.id !== item.id), `${item.title} dismissed`, () => (nextButton ?? section)?.focus()); }}>Dismiss</Button>\n      </div></li>;\n    })}</ul>\n  </section>;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/notification-center.tsx"
    },
    {
      "path": "vlak/styles/notification-center.css",
      "content": "/* ── notification-center: generated from packages/react/src/components/notification-center.tsx ── */\n.rs-notification-center{color:var(--text);width:100%;min-width:0}\n.rs-notification-center-header{display:flex;flex-wrap:wrap;gap:0.75rem;align-items:center;justify-content:space-between}\n.rs-notification-center-title{margin:0;font-size:1rem;font-weight:600;line-height:1.45}\n.rs-notification-center-list{list-style-type:none;margin:0;padding:0}\n.rs-notification-center-item{padding:1rem;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:var(--divider);overflow-wrap:anywhere}\n.rs-notification-center-unread{background-color:var(--control-fill)}\n.rs-notification-center-detail{margin-block:0.5rem;color:var(--text);line-height:1.45}\n.rs-notification-center-actions{display:flex;flex-wrap:wrap;gap:0.5rem;margin-top:0.75rem}\n",
      "type": "registry:file",
      "target": "styles/vlak/notification-center.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "feedback",
      "classes": [
        "rs-notification-center",
        "rs-notification-center-header",
        "rs-notification-center-title",
        "rs-notification-center-list",
        "rs-notification-center-item",
        "rs-notification-center-unread",
        "rs-notification-center-detail",
        "rs-notification-center-actions"
      ],
      "snippet": "<section class=\"rs-notification-center\" aria-label=\"Notifications\"><ul class=\"rs-notification-center-list\"><li class=\"rs-notification-center-item rs-notification-center-unread\"><h3 class=\"rs-notification-center-title\">Export ready</h3><p class=\"rs-notification-center-detail\">Unread</p></li></ul></section>",
      "cssOnly": false,
      "registryDependencies": [
        "button",
        "vlak-lib"
      ],
      "aliases": [
        "NotificationCenter"
      ],
      "example": "import { NotificationCenter } from \"@noorddev/vlak-react\";\n\n<NotificationCenter defaultValue={[{ id: \"export\", title: \"Export ready\", description: \"Your study is ready to download\" }, { id: \"invite\", title: \"Studio invitation\", read: true }]} />",
      "usage": {
        "use": [
          "An inbox of persistent application notifications."
        ],
        "avoid": [
          "Ephemeral confirmation; use Toast."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab, Enter, Space",
          "does": "Tab reaches read, action, and dismiss buttons; Enter or Space activates."
        }
      ],
      "a11y": [
        "Unread state is also exposed in text and control names. User changes are announced through a polite status."
      ]
    }
  }
}
