@boost33/ui 0.7.0 → 0.8.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +248 -3
  2. package/dist/buttons.module.css.js +10 -0
  3. package/dist/buttons.module.css.js.map +1 -0
  4. package/dist/components/AppShell/AppShell.d.ts +99 -21
  5. package/dist/components/AppShell/AppShell.js +76 -50
  6. package/dist/components/AppShell/AppShell.js.map +1 -1
  7. package/dist/components/AppShell/AppShell.module.css.js +22 -16
  8. package/dist/components/AppShell/AppShell.module.css.js.map +1 -1
  9. package/dist/components/AppSwitcher/AppSwitcher.d.ts +90 -45
  10. package/dist/components/AppSwitcher/AppSwitcher.js +71 -66
  11. package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
  12. package/dist/components/AppSwitcher/AppSwitcher.module.css.js +42 -36
  13. package/dist/components/AppSwitcher/AppSwitcher.module.css.js.map +1 -1
  14. package/dist/components/AppSwitcher/icons.js +10 -10
  15. package/dist/components/AppSwitcher/icons.js.map +1 -1
  16. package/dist/components/AppSwitcher/labels.d.ts +11 -22
  17. package/dist/components/AppSwitcher/labels.js.map +1 -1
  18. package/dist/components/Boost33Mark/Boost33Mark.d.ts +37 -4
  19. package/dist/components/Boost33Mark/Boost33Mark.js +15 -15
  20. package/dist/components/Boost33Mark/Boost33Mark.js.map +1 -1
  21. package/dist/components/Boost33Mark/Boost33Mark.module.css.js +10 -8
  22. package/dist/components/Boost33Mark/Boost33Mark.module.css.js.map +1 -1
  23. package/dist/components/CommandPalette/CommandPalette.d.ts +8 -2
  24. package/dist/components/CommandPalette/CommandPalette.js.map +1 -1
  25. package/dist/components/NavItem/NavItem.d.ts +15 -2
  26. package/dist/components/NavItem/NavItem.js +14 -11
  27. package/dist/components/NavItem/NavItem.js.map +1 -1
  28. package/dist/components/NavItem/NavItem.module.css.js +6 -2
  29. package/dist/components/NavItem/NavItem.module.css.js.map +1 -1
  30. package/dist/components/NavSection/NavSection.module.css.js +5 -5
  31. package/dist/components/NotificationButton/NotificationButton.d.ts +29 -0
  32. package/dist/components/NotificationButton/NotificationButton.js +23 -0
  33. package/dist/components/NotificationButton/NotificationButton.js.map +1 -0
  34. package/dist/components/NotificationButton/NotificationButton.module.css.js +14 -0
  35. package/dist/components/NotificationButton/NotificationButton.module.css.js.map +1 -0
  36. package/dist/components/NotificationButton/labels.d.ts +12 -0
  37. package/dist/components/NotificationButton/labels.js +15 -0
  38. package/dist/components/NotificationButton/labels.js.map +1 -0
  39. package/dist/components/NotificationCenter/NotificationCenter.js +37 -36
  40. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
  41. package/dist/components/NotificationCenter/NotificationCenter.module.css.js +43 -43
  42. package/dist/components/Sidebar/NavHeightBudgetWarning.js +2 -2
  43. package/dist/components/Sidebar/NavHeightBudgetWarning.js.map +1 -1
  44. package/dist/components/Sidebar/Sidebar.module.css.js +1 -1
  45. package/dist/components/TopBar/TopBar.d.ts +54 -4
  46. package/dist/components/TopBar/TopBar.js +22 -7
  47. package/dist/components/TopBar/TopBar.js.map +1 -1
  48. package/dist/components/TopBar/TopBar.module.css.js +12 -8
  49. package/dist/components/TopBar/TopBar.module.css.js.map +1 -1
  50. package/dist/components/TopBar/labels.d.ts +20 -0
  51. package/dist/components/TopBar/labels.js +15 -0
  52. package/dist/components/TopBar/labels.js.map +1 -0
  53. package/dist/components/WalletChip/WalletChip.d.ts +77 -2
  54. package/dist/components/WalletChip/WalletChip.js +142 -50
  55. package/dist/components/WalletChip/WalletChip.js.map +1 -1
  56. package/dist/components/WalletChip/WalletChip.module.css.js +93 -47
  57. package/dist/components/WalletChip/WalletChip.module.css.js.map +1 -1
  58. package/dist/components/WalletChip/labels.d.ts +63 -1
  59. package/dist/components/WalletChip/labels.js +21 -6
  60. package/dist/components/WalletChip/labels.js.map +1 -1
  61. package/dist/components/Wordmark/Wordmark.d.ts +29 -2
  62. package/dist/components/Wordmark/Wordmark.js +9 -8
  63. package/dist/components/Wordmark/Wordmark.js.map +1 -1
  64. package/dist/components/Wordmark/Wordmark.module.css.js +7 -7
  65. package/dist/index.d.ts +9 -3
  66. package/dist/index.js +55 -49
  67. package/dist/index.js.map +1 -1
  68. package/dist/nav-height-budget.d.ts +12 -9
  69. package/dist/nav-height-budget.js +3 -3
  70. package/dist/nav-height-budget.js.map +1 -1
  71. package/dist/style.css +1 -1
  72. package/dist/theme.css +1 -1
  73. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"NavItem.js","sources":["../../../src/components/NavItem/NavItem.tsx"],"sourcesContent":["import type { ElementType, ReactNode } from 'react'\nimport styles from './NavItem.module.css'\n\nexport interface NavItemProps {\n /** The nav label. Truncates with an ellipsis rather than wrapping. */\n children: ReactNode\n /**\n * The leading icon. Decorative -- the label carries the name, so pass an\n * `aria-hidden` glyph. On the rail it is the only thing that shows, the\n * label having collapsed to the accessibility tree.\n */\n icon?: ReactNode\n /** Marks this row as the current page. Sets `aria-current=\"page\"`. */\n active?: boolean\n /** Sidebar-on-the-rail state. Hides the label and trailing slot visually; the label stays in the accessibility tree. */\n collapsed?: boolean\n /** Trailing slot content. Omit to leave the slot empty -- it still reserves its width so counts line up across rows. */\n count?: number\n /** Renders `count` in the primary color instead of muted. */\n countUrgent?: boolean\n href?: string\n /**\n * The element the linked form renders through. Defaults to a plain `a`; a\n * consumer whose router owns navigation (Next.js, next-intl's localised\n * `Link`) passes its own so the row routes client-side with the locale prefix\n * intact instead of a bare anchor that full-page-reloads (B33-847). Receives\n * `href`, `className`, `onClick` and the row's data/aria attributes unchanged,\n * so the markup is identical to the anchor's. Only used when `href` is set;\n * the action form stays a `button`. See docs/adr/0011.\n */\n linkComponent?: ElementType\n onClick?: () => void\n}\n\nexport function NavItem({ children, icon, active = false, collapsed = false, count, countUrgent = false, href, linkComponent, onClick }: NavItemProps) {\n const dataProps = {\n 'data-active': active ? ('true' as const) : undefined,\n 'data-collapsed': collapsed ? ('true' as const) : undefined,\n 'aria-current': active ? ('page' as const) : undefined,\n }\n\n const content = (\n <>\n {icon != null && (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <span className={styles.label}>{children}</span>\n <span className={styles.trailingSlot}>\n {count != null && <span className={countUrgent ? styles.countUrgent : styles.count}>{count}</span>}\n </span>\n </>\n )\n\n if (href) {\n const Link = linkComponent ?? 'a'\n return (\n <Link className={styles.row} href={href} onClick={onClick} {...dataProps}>\n {content}\n </Link>\n )\n }\n\n return (\n <button type=\"button\" className={styles.row} onClick={onClick} {...dataProps}>\n {content}\n </button>\n )\n}\n"],"names":["NavItem","children","icon","active","collapsed","count","countUrgent","href","linkComponent","onClick","dataProps","content","jsxs","Fragment","styles","jsx"],"mappings":";;AAkCO,SAASA,EAAQ,EAAE,UAAAC,GAAU,MAAAC,GAAM,QAAAC,IAAS,IAAO,WAAAC,IAAY,IAAO,OAAAC,GAAO,aAAAC,IAAc,IAAO,MAAAC,GAAM,eAAAC,GAAe,SAAAC,KAAyB;AACrJ,QAAMC,IAAY;AAAA,IAChB,eAAeP,IAAU,SAAmB;AAAA,IAC5C,kBAAkBC,IAAa,SAAmB;AAAA,IAClD,gBAAgBD,IAAU,SAAmB;AAAA,EAAA,GAGzCQ,IACJ,gBAAAC,EAAAC,GAAA,EACG,UAAA;AAAA,IAAAX,KAAQ,0BACN,QAAA,EAAK,WAAWY,EAAO,MAAM,eAAY,QACvC,UAAAZ,EAAA,CACH;AAAA,IAEF,gBAAAa,EAAC,QAAA,EAAK,WAAWD,EAAO,OAAQ,UAAAb,GAAS;AAAA,sBACxC,QAAA,EAAK,WAAWa,EAAO,cACrB,eAAS,QAAQ,gBAAAC,EAAC,QAAA,EAAK,WAAWT,IAAcQ,EAAO,cAAcA,EAAO,OAAQ,aAAM,EAAA,CAC7F;AAAA,EAAA,GACF;AAGF,SAAIP,IAGA,gBAAAQ,EAFWP,KAAiB,OAEtB,WAAWM,EAAO,KAAK,MAAAP,GAAY,SAAAE,GAAmB,GAAGC,GAC5D,UAAAC,EAAA,CACH,IAKF,gBAAAI,EAAC,UAAA,EAAO,MAAK,UAAS,WAAWD,EAAO,KAAK,SAAAL,GAAmB,GAAGC,GAChE,UAAAC,EAAA,CACH;AAEJ;"}
1
+ {"version":3,"file":"NavItem.js","sources":["../../../src/components/NavItem/NavItem.tsx"],"sourcesContent":["import type { ElementType, ReactNode } from 'react'\nimport styles from './NavItem.module.css'\n\nexport interface NavItemProps {\n /** The nav label. Truncates with an ellipsis rather than wrapping. */\n children: ReactNode\n /**\n * The leading icon. Decorative -- the label carries the name, so pass an\n * `aria-hidden` glyph. On the rail it is the only thing that shows, the\n * label having collapsed to the accessibility tree.\n */\n icon?: ReactNode\n /** Marks this row as the current page. Sets `aria-current=\"page\"`. */\n active?: boolean\n /** Sidebar-on-the-rail state. Hides the label and trailing slot visually; the label stays in the accessibility tree. */\n collapsed?: boolean\n /** Trailing slot content. Omit to leave the slot empty -- it still reserves its width so counts line up across rows. */\n count?: number\n /**\n * Draws `count` as a filled pill rather than muted text -- the \"needs you\"\n * count (board 20 of the 3 Nov 2026 redesign, B33-1711). Until 0.8 it drew the\n * count in --color-primary text, which was 3.45:1 on the grey sidebar and\n * failed AA at the row's size; the pill is white on --color-orange-600, 5.18:1.\n */\n countUrgent?: boolean\n /**\n * A 7px dot in the trailing slot, saying the row needs attention -- an\n * unfinished profile, say (board 20, B33-1711). The dot is colour alone, so\n * the string is what a screen reader hears in its place, in the consumer's\n * language (\"Incomplete\"). Ignored when `count` is set: the slot holds one\n * thing.\n */\n dot?: string\n href?: string\n /**\n * The element the linked form renders through. Defaults to a plain `a`; a\n * consumer whose router owns navigation (Next.js, next-intl's localised\n * `Link`) passes its own so the row routes client-side with the locale prefix\n * intact instead of a bare anchor that full-page-reloads (B33-847). Receives\n * `href`, `className`, `onClick` and the row's data/aria attributes unchanged,\n * so the markup is identical to the anchor's. Only used when `href` is set;\n * the action form stays a `button`. See docs/adr/0011.\n */\n linkComponent?: ElementType\n onClick?: () => void\n}\n\nexport function NavItem({ children, icon, active = false, collapsed = false, count, countUrgent = false, dot, href, linkComponent, onClick }: NavItemProps) {\n const dataProps = {\n 'data-active': active ? ('true' as const) : undefined,\n 'data-collapsed': collapsed ? ('true' as const) : undefined,\n 'aria-current': active ? ('page' as const) : undefined,\n }\n\n const content = (\n <>\n {icon != null && (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <span className={styles.label}>{children}</span>\n <span className={styles.trailingSlot}>\n {count != null ? (\n <span className={countUrgent ? styles.countUrgent : styles.count}>{count}</span>\n ) : (\n dot != null && (\n <>\n <span className={styles.dot} aria-hidden=\"true\" />\n <span className={styles.srOnly}>{dot}</span>\n </>\n )\n )}\n </span>\n </>\n )\n\n if (href) {\n const Link = linkComponent ?? 'a'\n return (\n <Link className={styles.row} href={href} onClick={onClick} {...dataProps}>\n {content}\n </Link>\n )\n }\n\n return (\n <button type=\"button\" className={styles.row} onClick={onClick} {...dataProps}>\n {content}\n </button>\n )\n}\n"],"names":["NavItem","children","icon","active","collapsed","count","countUrgent","dot","href","linkComponent","onClick","dataProps","content","jsxs","Fragment","styles","jsx"],"mappings":";;AA+CO,SAASA,EAAQ,EAAE,UAAAC,GAAU,MAAAC,GAAM,QAAAC,IAAS,IAAO,WAAAC,IAAY,IAAO,OAAAC,GAAO,aAAAC,IAAc,IAAO,KAAAC,GAAK,MAAAC,GAAM,eAAAC,GAAe,SAAAC,KAAyB;AAC1J,QAAMC,IAAY;AAAA,IAChB,eAAeR,IAAU,SAAmB;AAAA,IAC5C,kBAAkBC,IAAa,SAAmB;AAAA,IAClD,gBAAgBD,IAAU,SAAmB;AAAA,EAAA,GAGzCS,IACJ,gBAAAC,EAAAC,GAAA,EACG,UAAA;AAAA,IAAAZ,KAAQ,0BACN,QAAA,EAAK,WAAWa,EAAO,MAAM,eAAY,QACvC,UAAAb,EAAA,CACH;AAAA,IAEF,gBAAAc,EAAC,QAAA,EAAK,WAAWD,EAAO,OAAQ,UAAAd,GAAS;AAAA,sBACxC,QAAA,EAAK,WAAWc,EAAO,cACrB,UAAAV,KAAS,OACR,gBAAAW,EAAC,QAAA,EAAK,WAAWV,IAAcS,EAAO,cAAcA,EAAO,OAAQ,aAAM,IAEzER,KAAO,QACL,gBAAAM,EAAAC,GAAA,EACE,UAAA;AAAA,MAAA,gBAAAE,EAAC,QAAA,EAAK,WAAWD,EAAO,KAAK,eAAY,QAAO;AAAA,MAChD,gBAAAC,EAAC,QAAA,EAAK,WAAWD,EAAO,QAAS,UAAAR,EAAA,CAAI;AAAA,IAAA,EAAA,CACvC,EAAA,CAGN;AAAA,EAAA,GACF;AAGF,SAAIC,IAGA,gBAAAQ,EAFWP,KAAiB,OAEtB,WAAWM,EAAO,KAAK,MAAAP,GAAY,SAAAE,GAAmB,GAAGC,GAC5D,UAAAC,EAAA,CACH,IAKF,gBAAAI,EAAC,UAAA,EAAO,MAAK,UAAS,WAAWD,EAAO,KAAK,SAAAL,GAAmB,GAAGC,GAChE,UAAAC,EAAA,CACH;AAEJ;"}
@@ -1,18 +1,22 @@
1
- const t = "_row_w21mf_4", o = "_icon_w21mf_41", n = "_label_w21mf_66", _ = "_trailingSlot_w21mf_92", c = "_count_w21mf_106", l = "_countUrgent_w21mf_110", r = {
1
+ const t = "_row_1vu2t_4", o = "_icon_1vu2t_41", n = "_label_1vu2t_66", _ = "_trailingSlot_1vu2t_106", c = "_count_1vu2t_122", l = "_countUrgent_1vu2t_134", s = "_dot_1vu2t_152", u = "_srOnly_1vu2t_151", r = {
2
2
  row: t,
3
3
  icon: o,
4
4
  label: n,
5
5
  trailingSlot: _,
6
6
  count: c,
7
- countUrgent: l
7
+ countUrgent: l,
8
+ dot: s,
9
+ srOnly: u
8
10
  };
9
11
  export {
10
12
  c as count,
11
13
  l as countUrgent,
12
14
  r as default,
15
+ s as dot,
13
16
  o as icon,
14
17
  n as label,
15
18
  t as row,
19
+ u as srOnly,
16
20
  _ as trailingSlot
17
21
  };
18
22
  //# sourceMappingURL=NavItem.module.css.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NavItem.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;"}
1
+ {"version":3,"file":"NavItem.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -1,10 +1,10 @@
1
- const s = "_section_3sxsg_5", e = "_header_3sxsg_13", t = {
2
- section: s,
3
- header: e
1
+ const e = "_section_wg5c6_5", s = "_header_wg5c6_13", t = {
2
+ section: e,
3
+ header: s
4
4
  };
5
5
  export {
6
6
  t as default,
7
- e as header,
8
- s as section
7
+ s as header,
8
+ e as section
9
9
  };
10
10
  //# sourceMappingURL=NavSection.module.css.js.map
@@ -0,0 +1,29 @@
1
+ import { ElementType } from 'react';
2
+ import { NotificationButtonLabels } from './labels';
3
+ export interface NotificationButtonProps {
4
+ /**
5
+ * Draws the unread dot, and tells a screen reader (`labels.unread`). The
6
+ * consumer decides what counts as unread; the button fetches nothing.
7
+ */
8
+ unread?: boolean;
9
+ /** What pressing it does -- opening the product's notifications. Renders a `<button>`. */
10
+ onClick?: () => void;
11
+ /** Where it goes, when it navigates rather than opens something. Renders a link. Wins over `onClick`. */
12
+ href?: string;
13
+ /** The element the link form renders through (ADR-0011). Defaults to `a`. */
14
+ linkComponent?: ElementType;
15
+ /** The button's words. Each field defaults to English. */
16
+ labels?: Partial<NotificationButtonLabels>;
17
+ }
18
+ /**
19
+ * The bell in the top bar, next to the avatar (B33-1712, board 20 of the 3 Nov
20
+ * 2026 redesign): a 32px square on the muted fill, a 17px bell, and an orange
21
+ * dot when something is unread.
22
+ *
23
+ * It is the button and nothing more. NotificationCenter, the panel it would
24
+ * open, is Stage A and not releasable (B33-494), so this opens whatever the
25
+ * consumer wires to it -- or nothing is drawn at all: with neither `onClick` nor
26
+ * `href` the bell would be a control that does nothing, so it does not render
27
+ * (the rule B33-1270 set for the wallet's top-up and the switcher's account row).
28
+ */
29
+ export declare function NotificationButton({ unread, onClick, href, linkComponent, labels }: NotificationButtonProps): import("react").JSX.Element | null;
@@ -0,0 +1,23 @@
1
+ import { jsx as t, jsxs as r, Fragment as d } from "react/jsx-runtime";
2
+ import { resolveNotificationButtonLabels as u } from "./labels.js";
3
+ import o from "./NotificationButton.module.css.js";
4
+ function h({ unread: n = !1, onClick: i, href: e, linkComponent: c, labels: l }) {
5
+ const s = u(l);
6
+ if (e === void 0 && i === void 0) return null;
7
+ const a = /* @__PURE__ */ r(d, { children: [
8
+ /* @__PURE__ */ r("svg", { className: o.bell, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
9
+ /* @__PURE__ */ t("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }),
10
+ /* @__PURE__ */ t("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" }),
11
+ n && /* @__PURE__ */ t("circle", { className: o.dot, cx: "18", cy: "6.5", r: "3", stroke: "none" })
12
+ ] }),
13
+ /* @__PURE__ */ r("span", { className: o.srOnly, children: [
14
+ s.name,
15
+ n && `, ${s.unread}`
16
+ ] })
17
+ ] });
18
+ return e !== void 0 ? /* @__PURE__ */ t(c ?? "a", { className: o.button, href: e, "data-unread": n ? "true" : void 0, children: a }) : /* @__PURE__ */ t("button", { type: "button", className: o.button, onClick: i, "data-unread": n ? "true" : void 0, children: a });
19
+ }
20
+ export {
21
+ h as NotificationButton
22
+ };
23
+ //# sourceMappingURL=NotificationButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationButton.js","sources":["../../../src/components/NotificationButton/NotificationButton.tsx"],"sourcesContent":["// Not a client module, on the same footing as NavItem: it holds no state and\n// calls no hook. A consumer passing `onClick` is already a client component; one\n// passing `href` can render this from the server (B33-846).\n\nimport type { ElementType } from 'react'\nimport { resolveNotificationButtonLabels, type NotificationButtonLabels } from './labels'\nimport styles from './NotificationButton.module.css'\n\nexport interface NotificationButtonProps {\n /**\n * Draws the unread dot, and tells a screen reader (`labels.unread`). The\n * consumer decides what counts as unread; the button fetches nothing.\n */\n unread?: boolean\n /** What pressing it does -- opening the product's notifications. Renders a `<button>`. */\n onClick?: () => void\n /** Where it goes, when it navigates rather than opens something. Renders a link. Wins over `onClick`. */\n href?: string\n /** The element the link form renders through (ADR-0011). Defaults to `a`. */\n linkComponent?: ElementType\n /** The button's words. Each field defaults to English. */\n labels?: Partial<NotificationButtonLabels>\n}\n\n/**\n * The bell in the top bar, next to the avatar (B33-1712, board 20 of the 3 Nov\n * 2026 redesign): a 32px square on the muted fill, a 17px bell, and an orange\n * dot when something is unread.\n *\n * It is the button and nothing more. NotificationCenter, the panel it would\n * open, is Stage A and not releasable (B33-494), so this opens whatever the\n * consumer wires to it -- or nothing is drawn at all: with neither `onClick` nor\n * `href` the bell would be a control that does nothing, so it does not render\n * (the rule B33-1270 set for the wallet's top-up and the switcher's account row).\n */\nexport function NotificationButton({ unread = false, onClick, href, linkComponent, labels }: NotificationButtonProps) {\n const t = resolveNotificationButtonLabels(labels)\n if (href === undefined && onClick === undefined) return null\n\n const content = (\n <>\n <svg className={styles.bell} viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\" />\n <path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\" />\n {unread && <circle className={styles.dot} cx=\"18\" cy=\"6.5\" r=\"3\" stroke=\"none\" />}\n </svg>\n <span className={styles.srOnly}>\n {t.name}\n {unread && `, ${t.unread}`}\n </span>\n </>\n )\n\n if (href !== undefined) {\n const Link = linkComponent ?? 'a'\n return (\n <Link className={styles.button} href={href} data-unread={unread ? 'true' : undefined}>\n {content}\n </Link>\n )\n }\n\n return (\n <button type=\"button\" className={styles.button} onClick={onClick} data-unread={unread ? 'true' : undefined}>\n {content}\n </button>\n )\n}\n"],"names":["NotificationButton","unread","onClick","href","linkComponent","labels","t","resolveNotificationButtonLabels","content","jsxs","Fragment","styles","jsx"],"mappings":";;;AAmCO,SAASA,EAAmB,EAAE,QAAAC,IAAS,IAAO,SAAAC,GAAS,MAAAC,GAAM,eAAAC,GAAe,QAAAC,KAAmC;AACpH,QAAMC,IAAIC,EAAgCF,CAAM;AAChD,MAAIF,MAAS,UAAaD,MAAY,OAAW,QAAO;AAExD,QAAMM,IACJ,gBAAAC,EAAAC,GAAA,EACE,UAAA;AAAA,IAAA,gBAAAD,EAAC,SAAI,WAAWE,EAAO,MAAM,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAY,QAC1J,UAAA;AAAA,MAAA,gBAAAC,EAAC,QAAA,EAAK,GAAE,4CAAA,CAA4C;AAAA,MACpD,gBAAAA,EAAC,QAAA,EAAK,GAAE,iCAAA,CAAiC;AAAA,MACxCX,KAAU,gBAAAW,EAAC,UAAA,EAAO,WAAWD,EAAO,KAAK,IAAG,MAAK,IAAG,OAAM,GAAE,KAAI,QAAO,OAAA,CAAO;AAAA,IAAA,GACjF;AAAA,IACA,gBAAAF,EAAC,QAAA,EAAK,WAAWE,EAAO,QACrB,UAAA;AAAA,MAAAL,EAAE;AAAA,MACFL,KAAU,KAAKK,EAAE,MAAM;AAAA,IAAA,EAAA,CAC1B;AAAA,EAAA,GACF;AAGF,SAAIH,MAAS,SAGT,gBAAAS,EAFWR,KAAiB,KAE3B,EAAK,WAAWO,EAAO,QAAQ,MAAAR,GAAY,eAAaF,IAAS,SAAS,QACxE,UAAAO,EAAA,CACH,IAKF,gBAAAI,EAAC,UAAA,EAAO,MAAK,UAAS,WAAWD,EAAO,QAAQ,SAAAT,GAAkB,eAAaD,IAAS,SAAS,QAC9F,UAAAO,GACH;AAEJ;"}
@@ -0,0 +1,14 @@
1
+ const o = "_button_oj9om_6", t = "_bell_oj9om_33", _ = "_dot_oj9om_42", l = "_srOnly_oj9om_41", n = {
2
+ button: o,
3
+ bell: t,
4
+ dot: _,
5
+ srOnly: l
6
+ };
7
+ export {
8
+ t as bell,
9
+ o as button,
10
+ n as default,
11
+ _ as dot,
12
+ l as srOnly
13
+ };
14
+ //# sourceMappingURL=NotificationButton.module.css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationButton.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
@@ -0,0 +1,12 @@
1
+ export interface NotificationButtonLabels {
2
+ /** The button's accessible name. It draws no text, so this is its whole name. Default "Notifications". */
3
+ name: string;
4
+ /**
5
+ * Appended for a screen reader when `unread` is set, because the unread dot is
6
+ * colour alone. Default "unread".
7
+ */
8
+ unread: string;
9
+ }
10
+ export declare const defaultNotificationButtonLabels: NotificationButtonLabels;
11
+ /** Merge partial labels onto the defaults, skipping explicit `undefined`s (B33-873). */
12
+ export declare function resolveNotificationButtonLabels(labels?: Partial<NotificationButtonLabels>): NotificationButtonLabels;
@@ -0,0 +1,15 @@
1
+ const n = {
2
+ name: "Notifications",
3
+ unread: "unread"
4
+ };
5
+ function o(t) {
6
+ const e = Object.fromEntries(
7
+ Object.entries(t ?? {}).filter(([, i]) => i !== void 0)
8
+ );
9
+ return { ...n, ...e };
10
+ }
11
+ export {
12
+ n as defaultNotificationButtonLabels,
13
+ o as resolveNotificationButtonLabels
14
+ };
15
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sources":["../../../src/components/NotificationButton/labels.ts"],"sourcesContent":["// The notification button's words, each a prop with an English default -- the\n// pattern every labels module here follows (B33-849).\n\nexport interface NotificationButtonLabels {\n /** The button's accessible name. It draws no text, so this is its whole name. Default \"Notifications\". */\n name: string\n /**\n * Appended for a screen reader when `unread` is set, because the unread dot is\n * colour alone. Default \"unread\".\n */\n unread: string\n}\n\nexport const defaultNotificationButtonLabels: NotificationButtonLabels = {\n name: 'Notifications',\n unread: 'unread',\n}\n\n/** Merge partial labels onto the defaults, skipping explicit `undefined`s (B33-873). */\nexport function resolveNotificationButtonLabels(labels?: Partial<NotificationButtonLabels>): NotificationButtonLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<NotificationButtonLabels>\n return { ...defaultNotificationButtonLabels, ...supplied }\n}\n"],"names":["defaultNotificationButtonLabels","resolveNotificationButtonLabels","labels","supplied","value"],"mappings":"AAaO,MAAMA,IAA4D;AAAA,EACvE,MAAM;AAAA,EACN,QAAQ;AACV;AAGO,SAASC,EAAgCC,GAAsE;AACpH,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA;AAExE,SAAO,EAAE,GAAGJ,GAAiC,GAAGG,EAAA;AAClD;"}
@@ -1,67 +1,68 @@
1
- import { jsxs as r, jsx as a, Fragment as u } from "react/jsx-runtime";
2
- import { useId as f } from "react";
3
- import { ProductMark as b } from "../ProductMark/ProductMark.js";
4
- import { EmptyBell as v, SourceBellMark as T } from "./icons.js";
5
- import { resolveNotificationCenterLabels as g } from "./labels.js";
1
+ import { jsxs as s, jsx as a, Fragment as u } from "react/jsx-runtime";
2
+ import { useId as b } from "react";
3
+ import { ProductMark as v } from "../ProductMark/ProductMark.js";
4
+ import { EmptyBell as T, SourceBellMark as g } from "./icons.js";
5
+ import { resolveNotificationCenterLabels as k } from "./labels.js";
6
+ import h from "../../buttons.module.css.js";
6
7
  import e from "./NotificationCenter.module.css.js";
7
- const h = 22;
8
- function C({
9
- items: n,
10
- viewAllHref: t,
11
- labels: p,
12
- linkComponent: N
8
+ const p = 22;
9
+ function $({
10
+ items: t,
11
+ viewAllHref: n,
12
+ labels: N,
13
+ linkComponent: f
13
14
  }) {
14
- const s = g(p), c = N ?? "a", d = f(), i = n.filter((l) => l.unread === !0).length, o = n.length === 0;
15
+ const r = k(N), c = f ?? "a", i = b(), d = t.filter((l) => l.unread === !0).length, o = t.length === 0;
15
16
  return (
16
17
  // Named by the heading it already draws rather than by a second copy of the
17
18
  // same word in an aria-label (ADR-0010; see `title` in labels.ts).
18
- /* @__PURE__ */ r("div", { className: e.panel, "aria-labelledby": d, children: [
19
- /* @__PURE__ */ r("div", { className: e.header, children: [
20
- /* @__PURE__ */ a("h2", { className: e.title, id: d, children: s.title }),
21
- !o && /* @__PURE__ */ a("button", { type: "button", className: e.markAll, children: s.markAllAsRead })
19
+ /* @__PURE__ */ s("div", { className: e.panel, "aria-labelledby": i, children: [
20
+ /* @__PURE__ */ s("div", { className: e.header, children: [
21
+ /* @__PURE__ */ a("h2", { className: e.title, id: i, children: r.title }),
22
+ !o && /* @__PURE__ */ a("button", { type: "button", className: `${h.quiet} ${e.markAll}`, children: r.markAllAsRead })
22
23
  ] }),
23
- o ? /* @__PURE__ */ r("div", { className: e.empty, children: [
24
- /* @__PURE__ */ a(v, {}),
25
- /* @__PURE__ */ r("div", { className: e.emptyText, children: [
26
- /* @__PURE__ */ a("p", { className: e.emptyTitle, children: s.emptyTitle }),
27
- /* @__PURE__ */ a("p", { className: e.emptyBody, children: s.emptyBody })
24
+ o ? /* @__PURE__ */ s("div", { className: e.empty, children: [
25
+ /* @__PURE__ */ a(T, {}),
26
+ /* @__PURE__ */ s("div", { className: e.emptyText, children: [
27
+ /* @__PURE__ */ a("p", { className: e.emptyTitle, children: r.emptyTitle }),
28
+ /* @__PURE__ */ a("p", { className: e.emptyBody, children: r.emptyBody })
28
29
  ] })
29
- ] }) : /* @__PURE__ */ r(u, { children: [
30
- /* @__PURE__ */ r("div", { className: e.tabs, children: [
31
- /* @__PURE__ */ a("button", { type: "button", className: e.tab, "aria-pressed": "true", children: s.all }),
32
- /* @__PURE__ */ r("button", { type: "button", className: e.tab, "aria-pressed": "false", children: [
33
- s.unread,
34
- i > 0 && /* @__PURE__ */ a("span", { className: e.count, children: i })
30
+ ] }) : /* @__PURE__ */ s(u, { children: [
31
+ /* @__PURE__ */ s("div", { className: e.tabs, children: [
32
+ /* @__PURE__ */ a("button", { type: "button", className: e.tab, "aria-pressed": "true", children: r.all }),
33
+ /* @__PURE__ */ s("button", { type: "button", className: e.tab, "aria-pressed": "false", children: [
34
+ r.unread,
35
+ d > 0 && /* @__PURE__ */ a("span", { className: e.count, children: d })
35
36
  ] })
36
37
  ] }),
37
- /* @__PURE__ */ a("ul", { className: e.list, children: n.map((l) => {
38
+ /* @__PURE__ */ a("ul", { className: e.list, children: t.map((l) => {
38
39
  const m = l.unread === !0, y = l.href != null ? c : "div";
39
- return /* @__PURE__ */ a("li", { children: /* @__PURE__ */ r(
40
+ return /* @__PURE__ */ a("li", { children: /* @__PURE__ */ s(
40
41
  y,
41
42
  {
42
43
  className: e.row,
43
44
  href: l.href,
44
45
  "data-unread": m ? "true" : void 0,
45
46
  children: [
46
- l.slug != null ? /* @__PURE__ */ a(b, { slug: l.slug, size: h, className: e.sourceMark }) : /* @__PURE__ */ a(T, { size: h }),
47
- /* @__PURE__ */ r("span", { className: e.rowText, children: [
47
+ l.slug != null ? /* @__PURE__ */ a(v, { slug: l.slug, size: p, className: e.sourceMark }) : /* @__PURE__ */ a(g, { size: p }),
48
+ /* @__PURE__ */ s("span", { className: e.rowText, children: [
48
49
  /* @__PURE__ */ a("span", { className: e.rowTitle, children: l.title }),
49
- /* @__PURE__ */ a("span", { className: e.rowMeta, children: l.dateTime != null ? /* @__PURE__ */ a("time", { dateTime: l.dateTime, children: s.meta(l.source, l.time) }) : s.meta(l.source, l.time) })
50
+ /* @__PURE__ */ a("span", { className: e.rowMeta, children: l.dateTime != null ? /* @__PURE__ */ a("time", { dateTime: l.dateTime, children: r.meta(l.source, l.time) }) : r.meta(l.source, l.time) })
50
51
  ] }),
51
- /* @__PURE__ */ a("span", { className: e.unreadSlot, children: m && /* @__PURE__ */ r(u, { children: [
52
+ /* @__PURE__ */ a("span", { className: e.unreadSlot, children: m && /* @__PURE__ */ s(u, { children: [
52
53
  /* @__PURE__ */ a("span", { className: e.dot }),
53
- /* @__PURE__ */ a("span", { className: e.srOnly, children: s.unreadItem })
54
+ /* @__PURE__ */ a("span", { className: e.srOnly, children: r.unreadItem })
54
55
  ] }) })
55
56
  ]
56
57
  }
57
58
  ) }, l.id);
58
59
  }) }),
59
- t != null && /* @__PURE__ */ a("div", { className: e.footer, children: /* @__PURE__ */ a(c, { className: e.viewAll, href: t, children: s.viewAll }) })
60
+ n != null && /* @__PURE__ */ a("div", { className: e.footer, children: /* @__PURE__ */ a(c, { className: `${h.quiet} ${e.viewAll}`, href: n, children: r.viewAll }) })
60
61
  ] })
61
62
  ] })
62
63
  );
63
64
  }
64
65
  export {
65
- C as NotificationCenter
66
+ $ as NotificationCenter
66
67
  };
67
68
  //# sourceMappingURL=NotificationCenter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationCenter.js","sources":["../../../src/components/NotificationCenter/NotificationCenter.tsx"],"sourcesContent":["// Not a client module, and that is a statement about this stage rather than\n// about the component. Stage A holds no state, wires no handler and reads no\n// clock, so there is nothing here a Server Component cannot carry. Stage B adds\n// `useAnchoredOverlay` and the directive with it -- see the note on the panel\n// below, and tests/guards.test.ts, which pins the exact set of built modules\n// carrying \"use client\" so the directive cannot appear here unnoticed.\n\nimport { useId, type ElementType } from 'react'\nimport { ProductMark } from '../ProductMark/ProductMark'\nimport { EmptyBell, SourceBellMark } from './icons'\nimport { resolveNotificationCenterLabels, type NotificationCenterLabels } from './labels'\nimport styles from './NotificationCenter.module.css'\n\n/**\n * The size the source mark renders at, in px.\n *\n * 22, which is `--spacing-5_5` written as a number because `ProductMark.size` is\n * a React prop and cannot read a custom property. ProductMark's own default is\n * 20 and the AppSwitcher's rows deliberately take it -- that is the Wallet's\n * size at the Wallet's call sites, settled against three approved baselines, and\n * its comment warns against passing a size at all. This is the other case the\n * comment contemplates and not a contradiction of it: there is no Wallet\n * rendering of this panel and no approved baseline to move, so 22 is the drawing\n * on page 004 reproduced rather than a settled measurement overruled. The person\n * at the fidelity gate settles it (CLAUDE.md, \"The three components the Wallet\n * never rendered\"), and until then nothing here is authoritative.\n */\nconst SOURCE_MARK_PX = 22\n\n/**\n * One notification, as the host supplies it. **This package invents no backend\n * and fetches nothing** -- the array is the host's, and so is every value in a\n * row (B33-494's data decision, 2026-10-06; CLAUDE.md, \"it renders what it is\n * given\").\n *\n * The shape is deliberately generic. Nothing in it names a lead, a campaign or a\n * prospect, and nothing in it should: a field that carried product meaning is\n * how a shared component turns back into four private ones (CLAUDE.md). A\n * notification here is a title, where it came from, when, whether it has been\n * read, and optionally somewhere to go.\n */\nexport interface NotificationCenterItem {\n /** Stable identity for the row, the host's. Used as the React key, never rendered. */\n id: string\n /** What happened, in the host's words. The row's first line. */\n title: string\n /**\n * Where it came from, in the host's words -- a product's display name, or\n * something that is not a product at all (\"Account\"). The first half of the\n * row's second line.\n *\n * A display string and not an identifier: `slug` below is what selects the\n * artwork, and the two are separate for the same reason `AppSwitcherProduct`\n * keeps `slug` and `name` apart. Identity owns names, and this package does\n * not translate them.\n */\n source: string\n /**\n * When it happened, **already formatted by the host** -- \"12 minutes ago\",\n * \"Yesterday\", \"3 Oct\". The second half of the row's second line.\n *\n * **This package does not format it, and the choice is deliberate.** The chip\n * formats its figures from a `locale` prop (B33-876), so the obvious move is\n * to do the same here and take a `Date`. Three things separate the two.\n *\n * A number is a pure function of its value and a locale; a *relative* time is\n * not. It needs a `now`, so the component would have to read a clock, and then\n * re-read it on a timer or go stale on screen -- which is state, in a\n * component whose whole contract is that it has none.\n *\n * It would also make this component's own fidelity gate unreproducible. The\n * baseline for an unbuilt component is captured from the specimen route by the\n * person approving it (CLAUDE.md, tests/baselines/README.md \"Designed\n * baselines\"), and a panel that renders \"12 minutes ago\" from `Date.now()`\n * renders a different string an hour later, so the diff would fail for reasons\n * no change caused. `4,600` is `4,600` forever; \"12 minutes ago\" is not.\n *\n * And the hard part is editorial rather than linguistic. Where a product cuts\n * over from \"12 minutes ago\" to \"Yesterday\" to a date is a policy each product\n * is entitled to differ on, and `Intl.RelativeTimeFormat` does not decide it\n * -- a caller has to pick the value and the unit before it can be called. The\n * host already holds the raw timestamp under B33-494's decision; it is the\n * side that can answer.\n *\n * `dateTime` below is how the machine-readable value still reaches the page.\n */\n time: string\n /**\n * The machine-readable timestamp for the `<time>` element's `datetime`\n * attribute -- an ISO 8601 string. Optional: supply it and the row's time is a\n * `<time dateTime>`; omit it and the same text renders in a plain `<span>`.\n *\n * This is what `time` being preformatted costs and how the cost is paid back.\n * A relative phrase on its own tells assistive technology and anything else\n * reading the DOM nothing about *when*; the attribute carries the instant\n * without this package deciding how to say it.\n */\n dateTime?: string\n /**\n * Identity's product slug, selecting the ProductMark artwork for the row's\n * tile. Optional, because a notification need not come from a product -- an\n * account or security event does not, and the drawing's own fifth row is one.\n * Omit it and the row draws the panel's bell instead (see `SourceBellMark`).\n *\n * A slug this package has no artwork for degrades to the bare tile rather than\n * throwing, exactly as it does in the switcher (ADR-0003).\n */\n slug?: string\n /**\n * Where selecting this notification goes. Optional: a row with no `href` is\n * not a link and is drawn as plain content, which is the honest rendering for\n * something there is nothing to open for.\n *\n * A row that does navigate renders through `linkComponent` (ADR-0011).\n */\n href?: string\n /**\n * Whether this notification is still unread. Defaults to `false`.\n *\n * **The flag is spelled `unread` and defaults to not-lit on purpose**, which\n * is the same decision the TopBar's bell already carries and for the same\n * reason B33-494 records: a permanently lit marker on a panel nobody has\n * cleared trains people to ignore it, and that attention does not come back.\n * A host that does not track read state passes nothing and gets a quiet panel;\n * a host that does says so row by row.\n */\n unread?: boolean\n}\n\nexport interface NotificationCenterProps {\n /**\n * The notifications, in the order the host wants them read. Rendered whole --\n * this component does not sort, filter, page or truncate, because every one of\n * those is a decision about data it does not own.\n *\n * An empty array is not an error state. It is the empty rendering, and it is\n * the state most consumers see first: a host with no feed wired yet has\n * nothing to pass (B33-494).\n */\n items: NotificationCenterItem[]\n /**\n * Where the footer's \"View all notifications\" link goes. Omit it and the\n * footer is not rendered -- a link to a page the host does not have is worse\n * than no link.\n *\n * The empty rendering never draws the footer, whether this is supplied or not:\n * there is nothing to see more of.\n */\n viewAllHref?: string\n /**\n * Every user-facing string that is not row data. Each field defaults to\n * English, and a field left out -- or supplied as `undefined`, which is what a\n * missed next-intl key looks like -- keeps its default (B33-873).\n */\n labels?: Partial<NotificationCenterLabels>\n /**\n * The element a row with an `href` renders through, and the footer link with\n * it. Defaults to a plain `a`; a router-owning consumer passes its own so the\n * rows route client-side with the locale prefix intact (ADR-0011, B33-847).\n */\n linkComponent?: ElementType\n}\n\n/**\n * The panel behind the top bar's bell: everything the four products did, in one\n * list, with the unread ones marked (B33-494).\n *\n * **It is presentational and it owns no feed.** The notifications arrive as a\n * prop and this package neither fetches, stores nor subscribes to anything --\n * there is no cross-product event source in any repo, and a component that\n * implied one would make the shared shell the place four services integrate,\n * which is the opposite of the dependency direction every other component here\n * has. That was decided on the ticket on 2026-10-06; the feed is separate work,\n * outside 1.0.\n *\n * **This is Stage A: static.** The panel renders, the tabs and the header action\n * draw, and nothing responds to anything (CLAUDE.md, \"Presentational-first\").\n * There is no open state here because there is no trigger here either: the bell\n * is the TopBar's, and connecting the two -- `useAnchoredOverlay` for the shared\n * dismissal contract (ADR-0009), the bell's `aria-haspopup`/`aria-expanded`, the\n * filter the tabs select, the mark-all handler -- is Stage B, after a human has\n * approved what this draws.\n *\n * **A disclosure, not a menu** (ADR-0010): the panel carries no widget role, the\n * tabs are not an ARIA `tablist`, the rows are the real links they already are,\n * and Tab is the document's own order, so the header button, both filters, every\n * row and the footer link are each their own tab stop. An ARIA tab set or a\n * `menu` would promise a composite widget with one tab stop and arrow-key\n * traversal, which is the promise ADR-0010 took the switcher out of.\n */\nexport function NotificationCenter({\n items,\n viewAllHref,\n labels,\n linkComponent,\n}: NotificationCenterProps) {\n const t = resolveNotificationCenterLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const headingId = useId()\n const unreadCount = items.filter((item) => item.unread === true).length\n const isEmpty = items.length === 0\n\n return (\n // Named by the heading it already draws rather than by a second copy of the\n // same word in an aria-label (ADR-0010; see `title` in labels.ts).\n <div className={styles.panel} aria-labelledby={headingId}>\n <div className={styles.header}>\n <h2 className={styles.title} id={headingId}>\n {t.title}\n </h2>\n {/* Nothing to mark, and nothing to mark it on: the empty rendering drops\n the action rather than offering a control that would do nothing even\n once it is wired. */}\n {!isEmpty && (\n <button type=\"button\" className={styles.markAll}>\n {t.markAllAsRead}\n </button>\n )}\n </div>\n\n {isEmpty ? (\n <div className={styles.empty}>\n <EmptyBell />\n <div className={styles.emptyText}>\n <p className={styles.emptyTitle}>{t.emptyTitle}</p>\n <p className={styles.emptyBody}>{t.emptyBody}</p>\n </div>\n </div>\n ) : (\n <>\n {/* Two toggle buttons, not a `role=\"tablist\"`. The ARIA tabs pattern\n requires one tab stop for the set and arrow keys to move between\n them, which contradicts ADR-0010's \"Tab is the document's tab\n order, unchanged and unmanaged\" -- and the APG's own banner, \"No\n ARIA is better than Bad ARIA\". `aria-pressed` says which filter is\n on without claiming a composite widget.\n\n Both are pressed-state-only in Stage A: \"All\" is on because the\n list under it is every row. Which filter is selected becomes state\n when the filtering is wired, and the markup does not change when it\n does. */}\n <div className={styles.tabs}>\n <button type=\"button\" className={styles.tab} aria-pressed=\"true\">\n {t.all}\n </button>\n <button type=\"button\" className={styles.tab} aria-pressed=\"false\">\n {t.unread}\n {/* Counted off `items`, never passed in, so it cannot disagree\n with the list beneath it. Drawn only when there is something to\n count -- a \"0\" pill is a decoration that reads as a state. */}\n {unreadCount > 0 && <span className={styles.count}>{unreadCount}</span>}\n </button>\n </div>\n\n <ul className={styles.list}>\n {items.map((item) => {\n const unread = item.unread === true\n // A row that navigates is a link; a row with nowhere to go is not\n // dressed as one. `linkComponent` is only consulted for the linked\n // form, exactly as ADR-0011 has NavItem consult it.\n const Row: ElementType = item.href != null ? Link : 'div'\n\n return (\n <li key={item.id}>\n <Row\n className={styles.row}\n href={item.href}\n data-unread={unread ? 'true' : undefined}\n >\n {item.slug != null ? (\n <ProductMark slug={item.slug} size={SOURCE_MARK_PX} className={styles.sourceMark} />\n ) : (\n <SourceBellMark size={SOURCE_MARK_PX} />\n )}\n <span className={styles.rowText}>\n <span className={styles.rowTitle}>{item.title}</span>\n <span className={styles.rowMeta}>\n {/* The whole second line through one label, so a locale\n owns both the word order and the separator. The\n machine-readable instant rides on the <time> element\n when the host supplies one; the text is the host's\n either way. */}\n {item.dateTime != null ? (\n <time dateTime={item.dateTime}>{t.meta(item.source, item.time)}</time>\n ) : (\n t.meta(item.source, item.time)\n )}\n </span>\n </span>\n {/* A fixed lane, drawn on every row whether or not it holds\n a dot, so the dots line up in one column and a read row\n does not reflow its text wider than an unread one. */}\n <span className={styles.unreadSlot}>\n {unread && (\n <>\n <span className={styles.dot} />\n {/* The dot is a colour and nothing else, which reaches\n nobody on a screen reader. This is the same state\n in words (CLAUDE.md, \"Accessibility is part of\n done\"). */}\n <span className={styles.srOnly}>{t.unreadItem}</span>\n </>\n )}\n </span>\n </Row>\n </li>\n )\n })}\n </ul>\n\n {viewAllHref != null && (\n <div className={styles.footer}>\n <Link className={styles.viewAll} href={viewAllHref}>\n {t.viewAll}\n </Link>\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],"names":["SOURCE_MARK_PX","NotificationCenter","items","viewAllHref","labels","linkComponent","t","resolveNotificationCenterLabels","Link","headingId","useId","unreadCount","item","isEmpty","styles","jsxs","jsx","EmptyBell","Fragment","unread","Row","ProductMark","SourceBellMark"],"mappings":";;;;;;AA2BA,MAAMA,IAAiB;AAmKhB,SAASC,EAAmB;AAAA,EACjC,OAAAC;AAAA,EACA,aAAAC;AAAA,EACA,QAAAC;AAAA,EACA,eAAAC;AACF,GAA4B;AAC1B,QAAMC,IAAIC,EAAgCH,CAAM,GAC1CI,IAAoBH,KAAiB,KACrCI,IAAYC,EAAA,GACZC,IAAcT,EAAM,OAAO,CAACU,MAASA,EAAK,WAAW,EAAI,EAAE,QAC3DC,IAAUX,EAAM,WAAW;AAEjC;AAAA;AAAA;AAAA,sBAGG,OAAA,EAAI,WAAWY,EAAO,OAAO,mBAAiBL,GAC7C,UAAA;AAAA,MAAA,gBAAAM,EAAC,OAAA,EAAI,WAAWD,EAAO,QACrB,UAAA;AAAA,QAAA,gBAAAE,EAAC,QAAG,WAAWF,EAAO,OAAO,IAAIL,GAC9B,YAAE,MAAA,CACL;AAAA,QAIC,CAACI,KACA,gBAAAG,EAAC,UAAA,EAAO,MAAK,UAAS,WAAWF,EAAO,SACrC,UAAAR,EAAE,cAAA,CACL;AAAA,MAAA,GAEJ;AAAA,MAECO,IACC,gBAAAE,EAAC,OAAA,EAAI,WAAWD,EAAO,OACrB,UAAA;AAAA,QAAA,gBAAAE,EAACC,GAAA,EAAU;AAAA,QACX,gBAAAF,EAAC,OAAA,EAAI,WAAWD,EAAO,WACrB,UAAA;AAAA,UAAA,gBAAAE,EAAC,KAAA,EAAE,WAAWF,EAAO,YAAa,YAAE,YAAW;AAAA,4BAC9C,KAAA,EAAE,WAAWA,EAAO,WAAY,YAAE,UAAA,CAAU;AAAA,QAAA,EAAA,CAC/C;AAAA,MAAA,EAAA,CACF,IAEA,gBAAAC,EAAAG,GAAA,EAYE,UAAA;AAAA,QAAA,gBAAAH,EAAC,OAAA,EAAI,WAAWD,EAAO,MACrB,UAAA;AAAA,UAAA,gBAAAE,EAAC,UAAA,EAAO,MAAK,UAAS,WAAWF,EAAO,KAAK,gBAAa,QACvD,UAAAR,EAAE,IAAA,CACL;AAAA,UACA,gBAAAS,EAAC,YAAO,MAAK,UAAS,WAAWD,EAAO,KAAK,gBAAa,SACvD,UAAA;AAAA,YAAAR,EAAE;AAAA,YAIFK,IAAc,KAAK,gBAAAK,EAAC,UAAK,WAAWF,EAAO,OAAQ,UAAAH,EAAA,CAAY;AAAA,UAAA,EAAA,CAClE;AAAA,QAAA,GACF;AAAA,QAEA,gBAAAK,EAAC,QAAG,WAAWF,EAAO,MACnB,UAAAZ,EAAM,IAAI,CAACU,MAAS;AACnB,gBAAMO,IAASP,EAAK,WAAW,IAIzBQ,IAAmBR,EAAK,QAAQ,OAAOJ,IAAO;AAEpD,mCACG,MAAA,EACC,UAAA,gBAAAO;AAAA,YAACK;AAAA,YAAA;AAAA,cACC,WAAWN,EAAO;AAAA,cAClB,MAAMF,EAAK;AAAA,cACX,eAAaO,IAAS,SAAS;AAAA,cAE9B,UAAA;AAAA,gBAAAP,EAAK,QAAQ,OACZ,gBAAAI,EAACK,GAAA,EAAY,MAAMT,EAAK,MAAM,MAAMZ,GAAgB,WAAWc,EAAO,YAAY,IAElF,gBAAAE,EAACM,GAAA,EAAe,MAAMtB,GAAgB;AAAA,gBAExC,gBAAAe,EAAC,QAAA,EAAK,WAAWD,EAAO,SACtB,UAAA;AAAA,kBAAA,gBAAAE,EAAC,QAAA,EAAK,WAAWF,EAAO,UAAW,YAAK,OAAM;AAAA,kBAC9C,gBAAAE,EAAC,QAAA,EAAK,WAAWF,EAAO,SAMrB,UAAAF,EAAK,YAAY,OAChB,gBAAAI,EAAC,QAAA,EAAK,UAAUJ,EAAK,UAAW,UAAAN,EAAE,KAAKM,EAAK,QAAQA,EAAK,IAAI,EAAA,CAAE,IAE/DN,EAAE,KAAKM,EAAK,QAAQA,EAAK,IAAI,EAAA,CAEjC;AAAA,gBAAA,GACF;AAAA,kCAIC,QAAA,EAAK,WAAWE,EAAO,YACrB,eACC,gBAAAC,EAAAG,GAAA,EACE,UAAA;AAAA,kBAAA,gBAAAF,EAAC,QAAA,EAAK,WAAWF,EAAO,IAAA,CAAK;AAAA,oCAK5B,QAAA,EAAK,WAAWA,EAAO,QAAS,YAAE,WAAA,CAAW;AAAA,gBAAA,EAAA,CAChD,EAAA,CAEJ;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA,EACF,GAzCOF,EAAK,EA0Cd;AAAA,QAEJ,CAAC,EAAA,CACH;AAAA,QAECT,KAAe,QACd,gBAAAa,EAAC,OAAA,EAAI,WAAWF,EAAO,QACrB,UAAA,gBAAAE,EAACR,GAAA,EAAK,WAAWM,EAAO,SAAS,MAAMX,GACpC,UAAAG,EAAE,SACL,EAAA,CACF;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA,EAAA,CAEJ;AAAA;AAEJ;"}
1
+ {"version":3,"file":"NotificationCenter.js","sources":["../../../src/components/NotificationCenter/NotificationCenter.tsx"],"sourcesContent":["// Not a client module, and that is a statement about this stage rather than\n// about the component. Stage A holds no state, wires no handler and reads no\n// clock, so there is nothing here a Server Component cannot carry. Stage B adds\n// `useAnchoredOverlay` and the directive with it -- see the note on the panel\n// below, and tests/guards.test.ts, which pins the exact set of built modules\n// carrying \"use client\" so the directive cannot appear here unnoticed.\n\nimport { useId, type ElementType } from 'react'\nimport { ProductMark } from '../ProductMark/ProductMark'\nimport { EmptyBell, SourceBellMark } from './icons'\nimport { resolveNotificationCenterLabels, type NotificationCenterLabels } from './labels'\nimport buttons from '../../buttons.module.css'\nimport styles from './NotificationCenter.module.css'\n\n/**\n * The size the source mark renders at, in px.\n *\n * 22, which is `--spacing-5_5` written as a number because `ProductMark.size` is\n * a React prop and cannot read a custom property. ProductMark's own default is\n * 20 and the AppSwitcher's rows deliberately take it -- that is the Wallet's\n * size at the Wallet's call sites, settled against three approved baselines, and\n * its comment warns against passing a size at all. This is the other case the\n * comment contemplates and not a contradiction of it: there is no Wallet\n * rendering of this panel and no approved baseline to move, so 22 is the drawing\n * on page 004 reproduced rather than a settled measurement overruled. The person\n * at the fidelity gate settles it (CLAUDE.md, \"The three components the Wallet\n * never rendered\"), and until then nothing here is authoritative.\n */\nconst SOURCE_MARK_PX = 22\n\n/**\n * One notification, as the host supplies it. **This package invents no backend\n * and fetches nothing** -- the array is the host's, and so is every value in a\n * row (B33-494's data decision, 2026-10-06; CLAUDE.md, \"it renders what it is\n * given\").\n *\n * The shape is deliberately generic. Nothing in it names a lead, a campaign or a\n * prospect, and nothing in it should: a field that carried product meaning is\n * how a shared component turns back into four private ones (CLAUDE.md). A\n * notification here is a title, where it came from, when, whether it has been\n * read, and optionally somewhere to go.\n */\nexport interface NotificationCenterItem {\n /** Stable identity for the row, the host's. Used as the React key, never rendered. */\n id: string\n /** What happened, in the host's words. The row's first line. */\n title: string\n /**\n * Where it came from, in the host's words -- a product's display name, or\n * something that is not a product at all (\"Account\"). The first half of the\n * row's second line.\n *\n * A display string and not an identifier: `slug` below is what selects the\n * artwork, and the two are separate for the same reason `AppSwitcherProduct`\n * keeps `slug` and `name` apart. Identity owns names, and this package does\n * not translate them.\n */\n source: string\n /**\n * When it happened, **already formatted by the host** -- \"12 minutes ago\",\n * \"Yesterday\", \"3 Oct\". The second half of the row's second line.\n *\n * **This package does not format it, and the choice is deliberate.** The chip\n * formats its figures from a `locale` prop (B33-876), so the obvious move is\n * to do the same here and take a `Date`. Three things separate the two.\n *\n * A number is a pure function of its value and a locale; a *relative* time is\n * not. It needs a `now`, so the component would have to read a clock, and then\n * re-read it on a timer or go stale on screen -- which is state, in a\n * component whose whole contract is that it has none.\n *\n * It would also make this component's own fidelity gate unreproducible. The\n * baseline for an unbuilt component is captured from the specimen route by the\n * person approving it (CLAUDE.md, tests/baselines/README.md \"Designed\n * baselines\"), and a panel that renders \"12 minutes ago\" from `Date.now()`\n * renders a different string an hour later, so the diff would fail for reasons\n * no change caused. `4,600` is `4,600` forever; \"12 minutes ago\" is not.\n *\n * And the hard part is editorial rather than linguistic. Where a product cuts\n * over from \"12 minutes ago\" to \"Yesterday\" to a date is a policy each product\n * is entitled to differ on, and `Intl.RelativeTimeFormat` does not decide it\n * -- a caller has to pick the value and the unit before it can be called. The\n * host already holds the raw timestamp under B33-494's decision; it is the\n * side that can answer.\n *\n * `dateTime` below is how the machine-readable value still reaches the page.\n */\n time: string\n /**\n * The machine-readable timestamp for the `<time>` element's `datetime`\n * attribute -- an ISO 8601 string. Optional: supply it and the row's time is a\n * `<time dateTime>`; omit it and the same text renders in a plain `<span>`.\n *\n * This is what `time` being preformatted costs and how the cost is paid back.\n * A relative phrase on its own tells assistive technology and anything else\n * reading the DOM nothing about *when*; the attribute carries the instant\n * without this package deciding how to say it.\n */\n dateTime?: string\n /**\n * Identity's product slug, selecting the ProductMark artwork for the row's\n * tile. Optional, because a notification need not come from a product -- an\n * account or security event does not, and the drawing's own fifth row is one.\n * Omit it and the row draws the panel's bell instead (see `SourceBellMark`).\n *\n * A slug this package has no artwork for degrades to the bare tile rather than\n * throwing, exactly as it does in the switcher (ADR-0003).\n */\n slug?: string\n /**\n * Where selecting this notification goes. Optional: a row with no `href` is\n * not a link and is drawn as plain content, which is the honest rendering for\n * something there is nothing to open for.\n *\n * A row that does navigate renders through `linkComponent` (ADR-0011).\n */\n href?: string\n /**\n * Whether this notification is still unread. Defaults to `false`.\n *\n * **The flag is spelled `unread` and defaults to not-lit on purpose**, which\n * is the same decision the TopBar's bell already carries and for the same\n * reason B33-494 records: a permanently lit marker on a panel nobody has\n * cleared trains people to ignore it, and that attention does not come back.\n * A host that does not track read state passes nothing and gets a quiet panel;\n * a host that does says so row by row.\n */\n unread?: boolean\n}\n\nexport interface NotificationCenterProps {\n /**\n * The notifications, in the order the host wants them read. Rendered whole --\n * this component does not sort, filter, page or truncate, because every one of\n * those is a decision about data it does not own.\n *\n * An empty array is not an error state. It is the empty rendering, and it is\n * the state most consumers see first: a host with no feed wired yet has\n * nothing to pass (B33-494).\n */\n items: NotificationCenterItem[]\n /**\n * Where the footer's \"View all notifications\" link goes. Omit it and the\n * footer is not rendered -- a link to a page the host does not have is worse\n * than no link.\n *\n * The empty rendering never draws the footer, whether this is supplied or not:\n * there is nothing to see more of.\n */\n viewAllHref?: string\n /**\n * Every user-facing string that is not row data. Each field defaults to\n * English, and a field left out -- or supplied as `undefined`, which is what a\n * missed next-intl key looks like -- keeps its default (B33-873).\n */\n labels?: Partial<NotificationCenterLabels>\n /**\n * The element a row with an `href` renders through, and the footer link with\n * it. Defaults to a plain `a`; a router-owning consumer passes its own so the\n * rows route client-side with the locale prefix intact (ADR-0011, B33-847).\n */\n linkComponent?: ElementType\n}\n\n/**\n * The panel behind the top bar's bell: everything the four products did, in one\n * list, with the unread ones marked (B33-494).\n *\n * **It is presentational and it owns no feed.** The notifications arrive as a\n * prop and this package neither fetches, stores nor subscribes to anything --\n * there is no cross-product event source in any repo, and a component that\n * implied one would make the shared shell the place four services integrate,\n * which is the opposite of the dependency direction every other component here\n * has. That was decided on the ticket on 2026-10-06; the feed is separate work,\n * outside 1.0.\n *\n * **This is Stage A: static.** The panel renders, the tabs and the header action\n * draw, and nothing responds to anything (CLAUDE.md, \"Presentational-first\").\n * There is no open state here because there is no trigger here either: the bell\n * is the TopBar's, and connecting the two -- `useAnchoredOverlay` for the shared\n * dismissal contract (ADR-0009), the bell's `aria-haspopup`/`aria-expanded`, the\n * filter the tabs select, the mark-all handler -- is Stage B, after a human has\n * approved what this draws.\n *\n * **A disclosure, not a menu** (ADR-0010): the panel carries no widget role, the\n * tabs are not an ARIA `tablist`, the rows are the real links they already are,\n * and Tab is the document's own order, so the header button, both filters, every\n * row and the footer link are each their own tab stop. An ARIA tab set or a\n * `menu` would promise a composite widget with one tab stop and arrow-key\n * traversal, which is the promise ADR-0010 took the switcher out of.\n */\nexport function NotificationCenter({\n items,\n viewAllHref,\n labels,\n linkComponent,\n}: NotificationCenterProps) {\n const t = resolveNotificationCenterLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const headingId = useId()\n const unreadCount = items.filter((item) => item.unread === true).length\n const isEmpty = items.length === 0\n\n return (\n // Named by the heading it already draws rather than by a second copy of the\n // same word in an aria-label (ADR-0010; see `title` in labels.ts).\n <div className={styles.panel} aria-labelledby={headingId}>\n <div className={styles.header}>\n <h2 className={styles.title} id={headingId}>\n {t.title}\n </h2>\n {/* Nothing to mark, and nothing to mark it on: the empty rendering drops\n the action rather than offering a control that would do nothing even\n once it is wired. */}\n {!isEmpty && (\n <button type=\"button\" className={`${buttons.quiet} ${styles.markAll}`}>\n {t.markAllAsRead}\n </button>\n )}\n </div>\n\n {isEmpty ? (\n <div className={styles.empty}>\n <EmptyBell />\n <div className={styles.emptyText}>\n <p className={styles.emptyTitle}>{t.emptyTitle}</p>\n <p className={styles.emptyBody}>{t.emptyBody}</p>\n </div>\n </div>\n ) : (\n <>\n {/* Two toggle buttons, not a `role=\"tablist\"`. The ARIA tabs pattern\n requires one tab stop for the set and arrow keys to move between\n them, which contradicts ADR-0010's \"Tab is the document's tab\n order, unchanged and unmanaged\" -- and the APG's own banner, \"No\n ARIA is better than Bad ARIA\". `aria-pressed` says which filter is\n on without claiming a composite widget.\n\n Both are pressed-state-only in Stage A: \"All\" is on because the\n list under it is every row. Which filter is selected becomes state\n when the filtering is wired, and the markup does not change when it\n does. */}\n <div className={styles.tabs}>\n <button type=\"button\" className={styles.tab} aria-pressed=\"true\">\n {t.all}\n </button>\n <button type=\"button\" className={styles.tab} aria-pressed=\"false\">\n {t.unread}\n {/* Counted off `items`, never passed in, so it cannot disagree\n with the list beneath it. Drawn only when there is something to\n count -- a \"0\" pill is a decoration that reads as a state. */}\n {unreadCount > 0 && <span className={styles.count}>{unreadCount}</span>}\n </button>\n </div>\n\n <ul className={styles.list}>\n {items.map((item) => {\n const unread = item.unread === true\n // A row that navigates is a link; a row with nowhere to go is not\n // dressed as one. `linkComponent` is only consulted for the linked\n // form, exactly as ADR-0011 has NavItem consult it.\n const Row: ElementType = item.href != null ? Link : 'div'\n\n return (\n <li key={item.id}>\n <Row\n className={styles.row}\n href={item.href}\n data-unread={unread ? 'true' : undefined}\n >\n {item.slug != null ? (\n <ProductMark slug={item.slug} size={SOURCE_MARK_PX} className={styles.sourceMark} />\n ) : (\n <SourceBellMark size={SOURCE_MARK_PX} />\n )}\n <span className={styles.rowText}>\n <span className={styles.rowTitle}>{item.title}</span>\n <span className={styles.rowMeta}>\n {/* The whole second line through one label, so a locale\n owns both the word order and the separator. The\n machine-readable instant rides on the <time> element\n when the host supplies one; the text is the host's\n either way. */}\n {item.dateTime != null ? (\n <time dateTime={item.dateTime}>{t.meta(item.source, item.time)}</time>\n ) : (\n t.meta(item.source, item.time)\n )}\n </span>\n </span>\n {/* A fixed lane, drawn on every row whether or not it holds\n a dot, so the dots line up in one column and a read row\n does not reflow its text wider than an unread one. */}\n <span className={styles.unreadSlot}>\n {unread && (\n <>\n <span className={styles.dot} />\n {/* The dot is a colour and nothing else, which reaches\n nobody on a screen reader. This is the same state\n in words (CLAUDE.md, \"Accessibility is part of\n done\"). */}\n <span className={styles.srOnly}>{t.unreadItem}</span>\n </>\n )}\n </span>\n </Row>\n </li>\n )\n })}\n </ul>\n\n {viewAllHref != null && (\n <div className={styles.footer}>\n <Link className={`${buttons.quiet} ${styles.viewAll}`} href={viewAllHref}>\n {t.viewAll}\n </Link>\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],"names":["SOURCE_MARK_PX","NotificationCenter","items","viewAllHref","labels","linkComponent","t","resolveNotificationCenterLabels","Link","headingId","useId","unreadCount","item","isEmpty","styles","jsxs","jsx","buttons","EmptyBell","Fragment","unread","Row","ProductMark","SourceBellMark"],"mappings":"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"}
@@ -1,54 +1,54 @@
1
- const t = "_panel_sl837_36", _ = "_header_sl837_54", s = "_title_sl837_62", o = "_markAll_sl837_73", l = "_tab_sl837_86", e = "_viewAll_sl837_87", r = "_tabs_sl837_96", n = "_count_sl837_131", c = "_list_sl837_141", a = "_row_sl837_156", y = "_sourceMark_sl837_187", p = "_sourceMarkGround_sl837_196", m = "_rowText_sl837_201", d = "_rowTitle_sl837_212", u = "_rowMeta_sl837_223", i = "_unreadSlot_sl837_234", w = "_dot_sl837_243", T = "_empty_sl837_258", k = "_emptyGlyph_sl837_267", M = "_emptyText_sl837_275", x = "_emptyTitle_sl837_282", b = "_emptyBody_sl837_290", h = "_footer_sl837_299", A = "_srOnly_sl837_321", G = {
1
+ const t = "_panel_1hhcn_36", _ = "_header_1hhcn_54", o = "_title_1hhcn_62", n = "_markAll_1hhcn_74", e = "_tab_1hhcn_79", c = "_tabs_1hhcn_88", h = "_count_1hhcn_123", r = "_list_1hhcn_133", s = "_row_1hhcn_148", l = "_sourceMark_1hhcn_179", a = "_sourceMarkGround_1hhcn_188", y = "_rowText_1hhcn_193", p = "_rowTitle_1hhcn_204", m = "_rowMeta_1hhcn_215", d = "_unreadSlot_1hhcn_226", u = "_dot_1hhcn_235", i = "_empty_1hhcn_250", w = "_emptyGlyph_1hhcn_259", T = "_emptyText_1hhcn_267", k = "_emptyTitle_1hhcn_274", M = "_emptyBody_1hhcn_282", x = "_footer_1hhcn_291", b = "_viewAll_1hhcn_300", A = "_srOnly_1hhcn_307", G = {
2
2
  panel: t,
3
3
  header: _,
4
- title: s,
5
- markAll: o,
6
- tab: l,
7
- viewAll: e,
8
- tabs: r,
9
- count: n,
10
- list: c,
11
- row: a,
12
- sourceMark: y,
13
- sourceMarkGround: p,
14
- rowText: m,
15
- rowTitle: d,
16
- rowMeta: u,
17
- unreadSlot: i,
18
- dot: w,
19
- empty: T,
20
- emptyGlyph: k,
21
- emptyText: M,
22
- emptyTitle: x,
23
- emptyBody: b,
24
- footer: h,
4
+ title: o,
5
+ markAll: n,
6
+ tab: e,
7
+ tabs: c,
8
+ count: h,
9
+ list: r,
10
+ row: s,
11
+ sourceMark: l,
12
+ sourceMarkGround: a,
13
+ rowText: y,
14
+ rowTitle: p,
15
+ rowMeta: m,
16
+ unreadSlot: d,
17
+ dot: u,
18
+ empty: i,
19
+ emptyGlyph: w,
20
+ emptyText: T,
21
+ emptyTitle: k,
22
+ emptyBody: M,
23
+ footer: x,
24
+ viewAll: b,
25
25
  srOnly: A
26
26
  };
27
27
  export {
28
- n as count,
28
+ h as count,
29
29
  G as default,
30
- w as dot,
31
- T as empty,
32
- b as emptyBody,
33
- k as emptyGlyph,
34
- M as emptyText,
35
- x as emptyTitle,
36
- h as footer,
30
+ u as dot,
31
+ i as empty,
32
+ M as emptyBody,
33
+ w as emptyGlyph,
34
+ T as emptyText,
35
+ k as emptyTitle,
36
+ x as footer,
37
37
  _ as header,
38
- c as list,
39
- o as markAll,
38
+ r as list,
39
+ n as markAll,
40
40
  t as panel,
41
- a as row,
42
- u as rowMeta,
43
- m as rowText,
44
- d as rowTitle,
45
- y as sourceMark,
46
- p as sourceMarkGround,
41
+ s as row,
42
+ m as rowMeta,
43
+ y as rowText,
44
+ p as rowTitle,
45
+ l as sourceMark,
46
+ a as sourceMarkGround,
47
47
  A as srOnly,
48
- l as tab,
49
- r as tabs,
50
- s as title,
51
- i as unreadSlot,
52
- e as viewAll
48
+ e as tab,
49
+ c as tabs,
50
+ o as title,
51
+ d as unreadSlot,
52
+ b as viewAll
53
53
  };
54
54
  //# sourceMappingURL=NotificationCenter.module.css.js.map
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as a } from "react/jsx-runtime";
3
3
  import { useRef as s, useEffect as u } from "react";
4
- import { checkNavHeightBudget as c, measureNavHeight as d, NAV_HEIGHT_BUDGET_PX as l } from "../../nav-height-budget.js";
4
+ import { checkNavHeightBudget as c, measureNavHeight as d, NAV_HEIGHT_BUDGET_PX as h } from "../../nav-height-budget.js";
5
5
  function v({ label: r }) {
6
6
  const o = s(null);
7
7
  return u(() => {
@@ -18,7 +18,7 @@ function v({ label: r }) {
18
18
  }
19
19
  const e = c(d(n), n.clientHeight);
20
20
  e.fits || console.warn(
21
- `@boost33/ui Sidebar ("${r}"): the nav needs ${e.usedPx}px and the column has ${e.budgetPx}px for it, so it is ${e.overBy}px over and scrolling. Nothing is lost -- the wallet chip and footer stay pinned -- but the shell's nav budget is about ${l}px at a 900px viewport, and a nav past it is a nav worth cutting. navContentHeight() and navBudgetForViewport(), exported from this package, check a nav's shape before it renders.`
21
+ `@boost33/ui Sidebar ("${r}"): the nav needs ${e.usedPx}px and the column has ${e.budgetPx}px for it, so it is ${e.overBy}px over and scrolling. Nothing is lost -- the action and the footer stay pinned -- but the shell's nav budget is about ${h}px at a 900px viewport, and a nav past it is a nav worth cutting. navContentHeight() and navBudgetForViewport(), exported from this package, check a nav's shape before it renders.`
22
22
  );
23
23
  }), process.env.NODE_ENV === "production" ? null : /* @__PURE__ */ a("span", { ref: o, hidden: !0, "aria-hidden": "true" });
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NavHeightBudgetWarning.js","sources":["../../../src/components/Sidebar/NavHeightBudgetWarning.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { NAV_HEIGHT_BUDGET_PX, checkNavHeightBudget, measureNavHeight } from '../../nav-height-budget'\n\n/*\n * The development-mode half of the nav budget, split out as the sidebar's one\n * client module.\n *\n * Sidebar.module.css stops an over-long nav from painting past the column, but\n * silently: the consumer sees a scrollbar and never learns their nav is too\n * long. The budget helpers in nav-height-budget.ts were written for exactly\n * that -- Boost Outbound's admin role has exceeded the budget once already --\n * and until this hook nothing in the shipped components ever called them, so a\n * consumer was protected only if they remembered to check by hand.\n *\n * It lives apart from Sidebar so Sidebar itself -- and the whole shell that\n * renders it -- can stay a Server Component (B33-846). A `console.warn` in\n * an effect is the only reason the sidebar column would otherwise be forced onto\n * the client, and a consumer should not ship the shell's static half to the\n * browser to get a development-only warning. So this is the boundary: it carries\n * \"use client\", and renders nothing a consumer sees. In production it is inert\n * and renders null; in development it renders one empty, hidden marker so the\n * effect has a handle on the sidebar element to measure from.\n *\n * `process.env.NODE_ENV` rather than an `import.meta` flag because every\n * bundler a consumer might use replaces that exact expression at build time\n * and drops the branch, which is how React itself ships its own dev warnings.\n *\n * Best effort by design: it re-measures on every render, after paint. Sidebar\n * re-renders when the consumer's stacked contents change, so a nav that grows\n * is re-checked; a nav that only overflows after a font swap or a window resize\n * is not caught, and does not need to be -- the case this exists for is a nav\n * that is too long for the column the moment it is rendered.\n */\nexport function NavHeightBudgetWarning({ label }: { label: string }) {\n // The marker sits as a direct child of the sidebar, so its parent is the\n // column and `:scope > nav` from there finds the same nav the CSS scroll\n // region rule selects.\n const marker = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n if (process.env.NODE_ENV === 'production') return\n\n const element = marker.current?.parentElement\n if (!element) return\n\n const nav = element.querySelector(':scope > nav')\n if (!(nav instanceof HTMLElement)) {\n // Only worth saying when there is a nav and it is in the wrong place.\n // A sidebar that holds no nav at all is an ordinary thing to render.\n if (element.querySelector('nav')) {\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav is nested inside another element, so it is not the sidebar's scroll ` +\n `region. Make the <nav> a direct child of the Sidebar, or a nav longer than the column will paint past its ` +\n `bottom edge instead of scrolling. See ADR-0005.`,\n )\n }\n return\n }\n\n const budget = checkNavHeightBudget(measureNavHeight(nav), nav.clientHeight)\n if (budget.fits) return\n\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav needs ${budget.usedPx}px and the column has ${budget.budgetPx}px for it, ` +\n `so it is ${budget.overBy}px over and scrolling. Nothing is lost -- the wallet chip and footer stay pinned -- but ` +\n `the shell's nav budget is about ${NAV_HEIGHT_BUDGET_PX}px at a 900px viewport, and a nav past it is a nav worth ` +\n `cutting. navContentHeight() and navBudgetForViewport(), exported from this package, check a nav's shape before ` +\n `it renders.`,\n )\n })\n\n if (process.env.NODE_ENV === 'production') return null\n return <span ref={marker} hidden aria-hidden=\"true\" />\n}\n"],"names":["NavHeightBudgetWarning","label","marker","useRef","useEffect","element","_a","nav","budget","checkNavHeightBudget","measureNavHeight","NAV_HEIGHT_BUDGET_PX"],"mappings":";;;;AAmCO,SAASA,EAAuB,EAAE,OAAAC,KAA4B;AAInE,QAAMC,IAASC,EAAwB,IAAI;AAkC3C,SAhCAC,EAAU,MAAM;;AACd,QAAI,QAAQ,IAAI,aAAa,aAAc;AAE3C,UAAMC,KAAUC,IAAAJ,EAAO,YAAP,gBAAAI,EAAgB;AAChC,QAAI,CAACD,EAAS;AAEd,UAAME,IAAMF,EAAQ,cAAc,cAAc;AAChD,QAAI,EAAEE,aAAe,cAAc;AAGjC,MAAIF,EAAQ,cAAc,KAAK,KAC7B,QAAQ;AAAA,QACN,yBAAyBJ,CAAK;AAAA,MAAA;AAKlC;AAAA,IACF;AAEA,UAAMO,IAASC,EAAqBC,EAAiBH,CAAG,GAAGA,EAAI,YAAY;AAC3E,IAAIC,EAAO,QAEX,QAAQ;AAAA,MACN,yBAAyBP,CAAK,qBAAqBO,EAAO,MAAM,yBAAyBA,EAAO,QAAQ,uBAC1FA,EAAO,MAAM,2HACUG,CAAoB;AAAA,IAAA;AAAA,EAI7D,CAAC,GAEG,QAAQ,IAAI,aAAa,eAAqB,yBAC1C,QAAA,EAAK,KAAKT,GAAQ,QAAM,IAAC,eAAY,QAAO;AACtD;"}
1
+ {"version":3,"file":"NavHeightBudgetWarning.js","sources":["../../../src/components/Sidebar/NavHeightBudgetWarning.tsx"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef } from 'react'\nimport { NAV_HEIGHT_BUDGET_PX, checkNavHeightBudget, measureNavHeight } from '../../nav-height-budget'\n\n/*\n * The development-mode half of the nav budget, split out as the sidebar's one\n * client module.\n *\n * Sidebar.module.css stops an over-long nav from painting past the column, but\n * silently: the consumer sees a scrollbar and never learns their nav is too\n * long. The budget helpers in nav-height-budget.ts were written for exactly\n * that -- Boost Outbound's admin role has exceeded the budget once already --\n * and until this hook nothing in the shipped components ever called them, so a\n * consumer was protected only if they remembered to check by hand.\n *\n * It lives apart from Sidebar so Sidebar itself -- and the whole shell that\n * renders it -- can stay a Server Component (B33-846). A `console.warn` in\n * an effect is the only reason the sidebar column would otherwise be forced onto\n * the client, and a consumer should not ship the shell's static half to the\n * browser to get a development-only warning. So this is the boundary: it carries\n * \"use client\", and renders nothing a consumer sees. In production it is inert\n * and renders null; in development it renders one empty, hidden marker so the\n * effect has a handle on the sidebar element to measure from.\n *\n * `process.env.NODE_ENV` rather than an `import.meta` flag because every\n * bundler a consumer might use replaces that exact expression at build time\n * and drops the branch, which is how React itself ships its own dev warnings.\n *\n * Best effort by design: it re-measures on every render, after paint. Sidebar\n * re-renders when the consumer's stacked contents change, so a nav that grows\n * is re-checked; a nav that only overflows after a font swap or a window resize\n * is not caught, and does not need to be -- the case this exists for is a nav\n * that is too long for the column the moment it is rendered.\n */\nexport function NavHeightBudgetWarning({ label }: { label: string }) {\n // The marker sits as a direct child of the sidebar, so its parent is the\n // column and `:scope > nav` from there finds the same nav the CSS scroll\n // region rule selects.\n const marker = useRef<HTMLSpanElement>(null)\n\n useEffect(() => {\n if (process.env.NODE_ENV === 'production') return\n\n const element = marker.current?.parentElement\n if (!element) return\n\n const nav = element.querySelector(':scope > nav')\n if (!(nav instanceof HTMLElement)) {\n // Only worth saying when there is a nav and it is in the wrong place.\n // A sidebar that holds no nav at all is an ordinary thing to render.\n if (element.querySelector('nav')) {\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav is nested inside another element, so it is not the sidebar's scroll ` +\n `region. Make the <nav> a direct child of the Sidebar, or a nav longer than the column will paint past its ` +\n `bottom edge instead of scrolling. See ADR-0005.`,\n )\n }\n return\n }\n\n const budget = checkNavHeightBudget(measureNavHeight(nav), nav.clientHeight)\n if (budget.fits) return\n\n console.warn(\n `@boost33/ui Sidebar (\"${label}\"): the nav needs ${budget.usedPx}px and the column has ${budget.budgetPx}px for it, ` +\n `so it is ${budget.overBy}px over and scrolling. Nothing is lost -- the action and the footer stay pinned -- but ` +\n `the shell's nav budget is about ${NAV_HEIGHT_BUDGET_PX}px at a 900px viewport, and a nav past it is a nav worth ` +\n `cutting. navContentHeight() and navBudgetForViewport(), exported from this package, check a nav's shape before ` +\n `it renders.`,\n )\n })\n\n if (process.env.NODE_ENV === 'production') return null\n return <span ref={marker} hidden aria-hidden=\"true\" />\n}\n"],"names":["NavHeightBudgetWarning","label","marker","useRef","useEffect","element","_a","nav","budget","checkNavHeightBudget","measureNavHeight","NAV_HEIGHT_BUDGET_PX"],"mappings":";;;;AAmCO,SAASA,EAAuB,EAAE,OAAAC,KAA4B;AAInE,QAAMC,IAASC,EAAwB,IAAI;AAkC3C,SAhCAC,EAAU,MAAM;;AACd,QAAI,QAAQ,IAAI,aAAa,aAAc;AAE3C,UAAMC,KAAUC,IAAAJ,EAAO,YAAP,gBAAAI,EAAgB;AAChC,QAAI,CAACD,EAAS;AAEd,UAAME,IAAMF,EAAQ,cAAc,cAAc;AAChD,QAAI,EAAEE,aAAe,cAAc;AAGjC,MAAIF,EAAQ,cAAc,KAAK,KAC7B,QAAQ;AAAA,QACN,yBAAyBJ,CAAK;AAAA,MAAA;AAKlC;AAAA,IACF;AAEA,UAAMO,IAASC,EAAqBC,EAAiBH,CAAG,GAAGA,EAAI,YAAY;AAC3E,IAAIC,EAAO,QAEX,QAAQ;AAAA,MACN,yBAAyBP,CAAK,qBAAqBO,EAAO,MAAM,yBAAyBA,EAAO,QAAQ,uBAC1FA,EAAO,MAAM,0HACUG,CAAoB;AAAA,IAAA;AAAA,EAI7D,CAAC,GAEG,QAAQ,IAAI,aAAa,eAAqB,yBAC1C,QAAA,EAAK,KAAKT,GAAQ,QAAM,IAAC,eAAY,QAAO;AACtD;"}
@@ -1,4 +1,4 @@
1
- const s = "_sidebar_17kel_7", e = {
1
+ const s = "_sidebar_197wz_7", e = {
2
2
  sidebar: s
3
3
  };
4
4
  export {