@boost33/ui 0.4.0 → 0.5.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 (59) hide show
  1. package/README.md +27 -1
  2. package/dist/components/AccountMenu/AccountMenu.d.ts +131 -0
  3. package/dist/components/AccountMenu/icons.d.ts +11 -0
  4. package/dist/components/AccountMenu/labels.d.ts +39 -0
  5. package/dist/components/AppShell/AppShell.d.ts +71 -18
  6. package/dist/components/AppShell/AppShell.js +33 -35
  7. package/dist/components/AppShell/AppShell.js.map +1 -1
  8. package/dist/components/AppShell/AppShell.module.css.js +16 -16
  9. package/dist/components/AppSwitcher/AppSwitcher.d.ts +23 -1
  10. package/dist/components/AppSwitcher/AppSwitcher.js +49 -43
  11. package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
  12. package/dist/components/AppSwitcher/AppSwitcher.module.css.js +28 -28
  13. package/dist/components/AppSwitcher/labels.js.map +1 -1
  14. package/dist/components/CommandPalette/CommandPalette.d.ts +127 -0
  15. package/dist/components/CommandPalette/icons.d.ts +8 -0
  16. package/dist/components/CommandPalette/labels.d.ts +94 -0
  17. package/dist/components/DotMatrixLoader/DotMatrixLoader.d.ts +21 -0
  18. package/dist/components/DotMatrixLoader/DotMatrixLoader.js +18 -0
  19. package/dist/components/DotMatrixLoader/DotMatrixLoader.js.map +1 -0
  20. package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js +12 -0
  21. package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js.map +1 -0
  22. package/dist/components/LaunchLoader/LaunchLoader.d.ts +50 -0
  23. package/dist/components/LaunchLoader/LaunchLoader.js +70 -0
  24. package/dist/components/LaunchLoader/LaunchLoader.js.map +1 -0
  25. package/dist/components/LaunchLoader/LaunchLoader.module.css.js +26 -0
  26. package/dist/components/LaunchLoader/LaunchLoader.module.css.js.map +1 -0
  27. package/dist/components/NavItem/NavItem.module.css.js +4 -4
  28. package/dist/components/NotificationCenter/NotificationCenter.d.ts +162 -0
  29. package/dist/components/NotificationCenter/icons.d.ts +34 -0
  30. package/dist/components/NotificationCenter/labels.d.ts +112 -0
  31. package/dist/components/ProductMark/ProductMark.d.ts +54 -1
  32. package/dist/components/ProductMark/ProductMark.js +23 -19
  33. package/dist/components/ProductMark/ProductMark.js.map +1 -1
  34. package/dist/components/Sidebar/Sidebar.d.ts +17 -3
  35. package/dist/components/Sidebar/Sidebar.js +5 -5
  36. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  37. package/dist/components/TopBar/TopBar.module.css.js +4 -4
  38. package/dist/components/WalletChip/WalletChip.d.ts +150 -27
  39. package/dist/components/WalletChip/WalletChip.js +38 -35
  40. package/dist/components/WalletChip/WalletChip.js.map +1 -1
  41. package/dist/components/WalletChip/WalletChip.module.css.js +31 -31
  42. package/dist/components/WalletChip/labels.d.ts +65 -47
  43. package/dist/components/WalletChip/labels.js +15 -20
  44. package/dist/components/WalletChip/labels.js.map +1 -1
  45. package/dist/fill-template.d.ts +10 -0
  46. package/dist/fill-template.js +10 -0
  47. package/dist/fill-template.js.map +1 -0
  48. package/dist/index.d.ts +10 -4
  49. package/dist/index.js +41 -41
  50. package/dist/index.js.map +1 -1
  51. package/dist/launch-gradient.d.ts +112 -0
  52. package/dist/launch-gradient.js +60 -0
  53. package/dist/launch-gradient.js.map +1 -0
  54. package/dist/nav-height-budget.d.ts +4 -0
  55. package/dist/nav-height-budget.js +18 -19
  56. package/dist/nav-height-budget.js.map +1 -1
  57. package/dist/style.css +1 -1
  58. package/dist/theme.css +40 -14
  59. package/package.json +2 -1
@@ -0,0 +1,50 @@
1
+ import { ReactNode } from 'react';
2
+ import { LaunchGradientStops } from '../../launch-gradient';
3
+ export interface LaunchLoaderProps {
4
+ /**
5
+ * What is happening, in words -- "Opening Boost Score". It may be one line for
6
+ * the whole launch, which is what the first consumers pass, or rewritten as
7
+ * real milestones land, in which case each rewrite is announced. It is the
8
+ * only text on the screen and the progress bar's name.
9
+ *
10
+ * Taken whole rather than assembled from a destination name, because where a
11
+ * product's name sits in the sentence is the language's to decide, not this
12
+ * package's -- the same reason WalletChip's labels take whole templates.
13
+ */
14
+ caption: string;
15
+ /** The destination's four gradient stops, darkest first. See LaunchGradientStops. */
16
+ stops: LaunchGradientStops;
17
+ /**
18
+ * 0-100, or `null`/omitted while the page has no honest number. Indeterminate
19
+ * omits `aria-valuenow` entirely: passing 0 to mean "unknown" announces 0%.
20
+ * Values outside 0-100 are clamped.
21
+ */
22
+ progress?: number | null;
23
+ /**
24
+ * The colour the dots and the bar are drawn in, before blending. Defaults to
25
+ * white, the caption's colour (decided 14 Oct 2026). Both are drawn with
26
+ * `soft-light` at 75% opacity, which a colour passes through too.
27
+ */
28
+ dotColor?: string;
29
+ /**
30
+ * Whether the content behind this screen is still loading. Sets `aria-busy`
31
+ * on the root; set it to false the moment the real content lands.
32
+ */
33
+ busy?: boolean;
34
+ /**
35
+ * An optional layer painted over the mesh and under the content --
36
+ * where an animated backdrop goes. Never required: the screen is complete
37
+ * without it, and dropped entirely under `prefers-contrast`.
38
+ */
39
+ backdrop?: ReactNode;
40
+ className?: string;
41
+ }
42
+ /**
43
+ * The launch screen every product shows while another product opens (B33-1389).
44
+ * Presentational: it knows no product, fetches nothing and owns no timing. The
45
+ * page drives the caption and the progress; this draws them.
46
+ *
47
+ * No "use client" and no client-only hook: useId is in React's server build, so
48
+ * this renders -- and its dots animate -- straight from server HTML.
49
+ */
50
+ export declare function LaunchLoader({ caption, stops, progress, dotColor, busy, backdrop, className, }: LaunchLoaderProps): import("react").JSX.Element;
@@ -0,0 +1,70 @@
1
+ import { jsxs as t, jsx as e } from "react/jsx-runtime";
2
+ import { useId as m } from "react";
3
+ import { DotMatrixLoader as y } from "../DotMatrixLoader/DotMatrixLoader.js";
4
+ import { launchRamp as f, launchSolid as x, launchGround as M, LAUNCH_MESH as S, launchShapeStyle as L } from "../../launch-gradient.js";
5
+ import a from "./LaunchLoader.module.css.js";
6
+ function I(r) {
7
+ return Math.min(100, Math.max(0, r));
8
+ }
9
+ function A({
10
+ caption: r,
11
+ stops: i,
12
+ progress: n = null,
13
+ dotColor: c,
14
+ busy: h = !0,
15
+ backdrop: o,
16
+ className: d
17
+ }) {
18
+ const s = m(), u = m(), l = typeof n == "number" && Number.isFinite(n) ? Math.round(I(n)) : null, p = f(i), v = {
19
+ "--launch-ground": M(i),
20
+ "--launch-solid": x(i),
21
+ ...c ? { "--launch-accent-input": c } : {}
22
+ };
23
+ return /* @__PURE__ */ t(
24
+ "div",
25
+ {
26
+ className: d ? `${a.root} ${d}` : a.root,
27
+ style: v,
28
+ "aria-busy": h,
29
+ "aria-describedby": s,
30
+ "data-launch-loader": "",
31
+ children: [
32
+ /* @__PURE__ */ e("div", { className: a.mesh, "aria-hidden": "true", children: S.map((b, N) => /* @__PURE__ */ e(
33
+ "div",
34
+ {
35
+ className: a.shape,
36
+ style: L(b, p)
37
+ },
38
+ N
39
+ )) }),
40
+ o ? /* @__PURE__ */ e("div", { className: a.backdrop, children: o }) : null,
41
+ /* @__PURE__ */ t("div", { className: a.content, children: [
42
+ /* @__PURE__ */ e(y, { label: null, className: a.dots }),
43
+ /* @__PURE__ */ t(
44
+ "div",
45
+ {
46
+ id: s,
47
+ className: a.bar,
48
+ role: "progressbar",
49
+ "aria-labelledby": u,
50
+ "aria-valuemin": 0,
51
+ "aria-valuemax": 100,
52
+ "aria-valuenow": l ?? void 0,
53
+ "data-indeterminate": l === null,
54
+ style: l === null ? void 0 : { "--launch-progress": l },
55
+ children: [
56
+ /* @__PURE__ */ e("div", { className: a.track }),
57
+ /* @__PURE__ */ e("div", { className: a.fill })
58
+ ]
59
+ }
60
+ ),
61
+ /* @__PURE__ */ e("p", { id: u, className: a.caption, "aria-live": "polite", children: r })
62
+ ] })
63
+ ]
64
+ }
65
+ );
66
+ }
67
+ export {
68
+ A as LaunchLoader
69
+ };
70
+ //# sourceMappingURL=LaunchLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LaunchLoader.js","sources":["../../../src/components/LaunchLoader/LaunchLoader.tsx"],"sourcesContent":["import { useId, type CSSProperties, type ReactNode } from 'react'\nimport { DotMatrixLoader } from '../DotMatrixLoader/DotMatrixLoader'\nimport {\n LAUNCH_MESH,\n launchGround,\n launchRamp,\n launchShapeStyle,\n launchSolid,\n type LaunchGradientStops,\n} from '../../launch-gradient'\nimport styles from './LaunchLoader.module.css'\n\nexport interface LaunchLoaderProps {\n /**\n * What is happening, in words -- \"Opening Boost Score\". It may be one line for\n * the whole launch, which is what the first consumers pass, or rewritten as\n * real milestones land, in which case each rewrite is announced. It is the\n * only text on the screen and the progress bar's name.\n *\n * Taken whole rather than assembled from a destination name, because where a\n * product's name sits in the sentence is the language's to decide, not this\n * package's -- the same reason WalletChip's labels take whole templates.\n */\n caption: string\n /** The destination's four gradient stops, darkest first. See LaunchGradientStops. */\n stops: LaunchGradientStops\n /**\n * 0-100, or `null`/omitted while the page has no honest number. Indeterminate\n * omits `aria-valuenow` entirely: passing 0 to mean \"unknown\" announces 0%.\n * Values outside 0-100 are clamped.\n */\n progress?: number | null\n /**\n * The colour the dots and the bar are drawn in, before blending. Defaults to\n * white, the caption's colour (decided 14 Oct 2026). Both are drawn with\n * `soft-light` at 75% opacity, which a colour passes through too.\n */\n dotColor?: string\n /**\n * Whether the content behind this screen is still loading. Sets `aria-busy`\n * on the root; set it to false the moment the real content lands.\n */\n busy?: boolean\n /**\n * An optional layer painted over the mesh and under the content --\n * where an animated backdrop goes. Never required: the screen is complete\n * without it, and dropped entirely under `prefers-contrast`.\n */\n backdrop?: ReactNode\n className?: string\n}\n\nfunction clampProgress(value: number): number {\n return Math.min(100, Math.max(0, value))\n}\n\n/**\n * The launch screen every product shows while another product opens (B33-1389).\n * Presentational: it knows no product, fetches nothing and owns no timing. The\n * page drives the caption and the progress; this draws them.\n *\n * No \"use client\" and no client-only hook: useId is in React's server build, so\n * this renders -- and its dots animate -- straight from server HTML.\n */\nexport function LaunchLoader({\n caption,\n stops,\n progress = null,\n dotColor,\n busy = true,\n backdrop,\n className,\n}: LaunchLoaderProps) {\n const barId = useId()\n const captionId = useId()\n const determinate = typeof progress === 'number' && Number.isFinite(progress)\n const value = determinate ? Math.round(clampProgress(progress)) : null\n\n const ramp = launchRamp(stops)\n const rootStyle = {\n '--launch-ground': launchGround(stops),\n '--launch-solid': launchSolid(stops),\n ...(dotColor ? { '--launch-accent-input': dotColor } : {}),\n } as CSSProperties\n\n return (\n <div\n className={className ? `${styles.root} ${className}` : styles.root}\n style={rootStyle}\n aria-busy={busy}\n aria-describedby={barId}\n data-launch-loader=\"\"\n >\n {/* The mesh: solid shapes melted by one blur on their container. Every\n shape is styled inline, so the composition paints from the HTML\n alone, in the same chunk as the rest of the component -- on both\n sides of a hop, from the same props. It drifts once and settles back\n on this exact frame (see the module's .mesh rule). */}\n <div className={styles.mesh} aria-hidden=\"true\">\n {LAUNCH_MESH.map((shape, i) => (\n <div\n key={i}\n className={styles.shape}\n style={launchShapeStyle(shape, ramp)}\n />\n ))}\n </div>\n\n {backdrop ? <div className={styles.backdrop}>{backdrop}</div> : null}\n\n <div className={styles.content}>\n {/* Decorative: the caption already says everything the dots do. */}\n {/* Coloured by the .dots class, not DotMatrixLoader's `color` prop: an\n inline colour would outrank the prefers-contrast branch. */}\n <DotMatrixLoader label={null} className={styles.dots} />\n\n {/* Outside the live region on purpose. The caption's region is atomic,\n so a bar inside it would re-announce everything on every tick. */}\n <div\n id={barId}\n className={styles.bar}\n role=\"progressbar\"\n aria-labelledby={captionId}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={value ?? undefined}\n data-indeterminate={value === null}\n style={value === null ? undefined : ({ '--launch-progress': value } as CSSProperties)}\n >\n {/* Separate layers on purpose: the track blends into the mesh, the\n fill does not. A fill inside the track's blend group would blend\n with it and lose the 3:1 it needs against it. */}\n <div className={styles.track} />\n <div className={styles.fill} />\n </div>\n\n {/* The single source of the words: the bar's name and the live region\n in one node, so nothing is read twice.\n\n aria-live stays even though the first consumers pass one static\n line (\"Opening Boost Score\", decided 14 Oct 2026 -- a prerendered\n arrival gives no honest signal for a middle phase). It is inert\n while the text does not change, and it is what makes a changing\n caption announce if a consumer passes one: `caption` is a prop, and\n phased captions are the case this markup was designed for. */}\n <p id={captionId} className={styles.caption} aria-live=\"polite\">\n {caption}\n </p>\n </div>\n </div>\n )\n}\n"],"names":["clampProgress","value","LaunchLoader","caption","stops","progress","dotColor","busy","backdrop","className","barId","useId","captionId","ramp","launchRamp","rootStyle","launchGround","launchSolid","jsxs","styles","jsx","LAUNCH_MESH","shape","i","launchShapeStyle","DotMatrixLoader"],"mappings":";;;;;AAoDA,SAASA,EAAcC,GAAuB;AAC5C,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAGA,CAAK,CAAC;AACzC;AAUO,SAASC,EAAa;AAAA,EAC3B,SAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC,IAAW;AAAA,EACX,UAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,UAAAC;AAAA,EACA,WAAAC;AACF,GAAsB;AACpB,QAAMC,IAAQC,EAAA,GACRC,IAAYD,EAAA,GAEZV,IADc,OAAOI,KAAa,YAAY,OAAO,SAASA,CAAQ,IAChD,KAAK,MAAML,EAAcK,CAAQ,CAAC,IAAI,MAE5DQ,IAAOC,EAAWV,CAAK,GACvBW,IAAY;AAAA,IAChB,mBAAmBC,EAAaZ,CAAK;AAAA,IACrC,kBAAkBa,EAAYb,CAAK;AAAA,IACnC,GAAIE,IAAW,EAAE,yBAAyBA,MAAa,CAAA;AAAA,EAAC;AAG1D,SACE,gBAAAY;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWT,IAAY,GAAGU,EAAO,IAAI,IAAIV,CAAS,KAAKU,EAAO;AAAA,MAC9D,OAAOJ;AAAA,MACP,aAAWR;AAAA,MACX,oBAAkBG;AAAA,MAClB,sBAAmB;AAAA,MAOnB,UAAA;AAAA,QAAA,gBAAAU,EAAC,OAAA,EAAI,WAAWD,EAAO,MAAM,eAAY,QACtC,UAAAE,EAAY,IAAI,CAACC,GAAOC,MACvB,gBAAAH;AAAA,UAAC;AAAA,UAAA;AAAA,YAEC,WAAWD,EAAO;AAAA,YAClB,OAAOK,EAAiBF,GAAOT,CAAI;AAAA,UAAA;AAAA,UAF9BU;AAAA,QAAA,CAIR,GACH;AAAA,QAECf,IAAW,gBAAAY,EAAC,OAAA,EAAI,WAAWD,EAAO,UAAW,aAAS,IAAS;AAAA,QAEhE,gBAAAD,EAAC,OAAA,EAAI,WAAWC,EAAO,SAIrB,UAAA;AAAA,UAAA,gBAAAC,EAACK,GAAA,EAAgB,OAAO,MAAM,WAAWN,EAAO,MAAM;AAAA,UAItD,gBAAAD;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,IAAIR;AAAA,cACJ,WAAWS,EAAO;AAAA,cAClB,MAAK;AAAA,cACL,mBAAiBP;AAAA,cACjB,iBAAe;AAAA,cACf,iBAAe;AAAA,cACf,iBAAeX,KAAS;AAAA,cACxB,sBAAoBA,MAAU;AAAA,cAC9B,OAAOA,MAAU,OAAO,SAAa,EAAE,qBAAqBA,EAAA;AAAA,cAK5D,UAAA;AAAA,gBAAA,gBAAAmB,EAAC,OAAA,EAAI,WAAWD,EAAO,MAAA,CAAO;AAAA,gBAC9B,gBAAAC,EAAC,OAAA,EAAI,WAAWD,EAAO,KAAA,CAAM;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,UAY/B,gBAAAC,EAAC,OAAE,IAAIR,GAAW,WAAWO,EAAO,SAAS,aAAU,UACpD,UAAAhB,EAAA,CACH;AAAA,QAAA,EAAA,CACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -0,0 +1,26 @@
1
+ const t = "_root_1xhf4_6", o = "_mesh_1xhf4_8", _ = "_shape_1xhf4_81", c = "_backdrop_1xhf4_90", s = "_content_1xhf4_13", n = "_dots_1xhf4_127", a = "_bar_1xhf4_160", h = "_track_1xhf4_168", f = "_fill_1xhf4_176", r = "_caption_1xhf4_202", e = {
2
+ root: t,
3
+ mesh: o,
4
+ shape: _,
5
+ backdrop: c,
6
+ content: s,
7
+ dots: n,
8
+ bar: a,
9
+ track: h,
10
+ fill: f,
11
+ caption: r
12
+ };
13
+ export {
14
+ c as backdrop,
15
+ a as bar,
16
+ r as caption,
17
+ s as content,
18
+ e as default,
19
+ n as dots,
20
+ f as fill,
21
+ o as mesh,
22
+ t as root,
23
+ _ as shape,
24
+ h as track
25
+ };
26
+ //# sourceMappingURL=LaunchLoader.module.css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LaunchLoader.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
@@ -1,15 +1,15 @@
1
- const t = "_row_16hr7_4", o = "_icon_16hr7_41", n = "_label_16hr7_66", _ = "_trailingSlot_16hr7_92", c = "_count_16hr7_106", r = "_countUrgent_16hr7_110", l = {
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 = {
2
2
  row: t,
3
3
  icon: o,
4
4
  label: n,
5
5
  trailingSlot: _,
6
6
  count: c,
7
- countUrgent: r
7
+ countUrgent: l
8
8
  };
9
9
  export {
10
10
  c as count,
11
- r as countUrgent,
12
- l as default,
11
+ l as countUrgent,
12
+ r as default,
13
13
  o as icon,
14
14
  n as label,
15
15
  t as row,
@@ -0,0 +1,162 @@
1
+ import { ElementType } from 'react';
2
+ import { NotificationCenterLabels } from './labels';
3
+ /**
4
+ * One notification, as the host supplies it. **This package invents no backend
5
+ * and fetches nothing** -- the array is the host's, and so is every value in a
6
+ * row (B33-494's data decision, 2026-10-06; CLAUDE.md, "it renders what it is
7
+ * given").
8
+ *
9
+ * The shape is deliberately generic. Nothing in it names a lead, a campaign or a
10
+ * prospect, and nothing in it should: a field that carried product meaning is
11
+ * how a shared component turns back into four private ones (CLAUDE.md). A
12
+ * notification here is a title, where it came from, when, whether it has been
13
+ * read, and optionally somewhere to go.
14
+ */
15
+ export interface NotificationCenterItem {
16
+ /** Stable identity for the row, the host's. Used as the React key, never rendered. */
17
+ id: string;
18
+ /** What happened, in the host's words. The row's first line. */
19
+ title: string;
20
+ /**
21
+ * Where it came from, in the host's words -- a product's display name, or
22
+ * something that is not a product at all ("Account"). The first half of the
23
+ * row's second line.
24
+ *
25
+ * A display string and not an identifier: `slug` below is what selects the
26
+ * artwork, and the two are separate for the same reason `AppSwitcherProduct`
27
+ * keeps `slug` and `name` apart. Identity owns names, and this package does
28
+ * not translate them.
29
+ */
30
+ source: string;
31
+ /**
32
+ * When it happened, **already formatted by the host** -- "12 minutes ago",
33
+ * "Yesterday", "3 Oct". The second half of the row's second line.
34
+ *
35
+ * **This package does not format it, and the choice is deliberate.** The chip
36
+ * formats its figures from a `locale` prop (B33-876), so the obvious move is
37
+ * to do the same here and take a `Date`. Three things separate the two.
38
+ *
39
+ * A number is a pure function of its value and a locale; a *relative* time is
40
+ * not. It needs a `now`, so the component would have to read a clock, and then
41
+ * re-read it on a timer or go stale on screen -- which is state, in a
42
+ * component whose whole contract is that it has none.
43
+ *
44
+ * It would also make this component's own fidelity gate unreproducible. The
45
+ * baseline for an unbuilt component is captured from the specimen route by the
46
+ * person approving it (CLAUDE.md, tests/baselines/README.md "Designed
47
+ * baselines"), and a panel that renders "12 minutes ago" from `Date.now()`
48
+ * renders a different string an hour later, so the diff would fail for reasons
49
+ * no change caused. `4,600` is `4,600` forever; "12 minutes ago" is not.
50
+ *
51
+ * And the hard part is editorial rather than linguistic. Where a product cuts
52
+ * over from "12 minutes ago" to "Yesterday" to a date is a policy each product
53
+ * is entitled to differ on, and `Intl.RelativeTimeFormat` does not decide it
54
+ * -- a caller has to pick the value and the unit before it can be called. The
55
+ * host already holds the raw timestamp under B33-494's decision; it is the
56
+ * side that can answer.
57
+ *
58
+ * `dateTime` below is how the machine-readable value still reaches the page.
59
+ */
60
+ time: string;
61
+ /**
62
+ * The machine-readable timestamp for the `<time>` element's `datetime`
63
+ * attribute -- an ISO 8601 string. Optional: supply it and the row's time is a
64
+ * `<time dateTime>`; omit it and the same text renders in a plain `<span>`.
65
+ *
66
+ * This is what `time` being preformatted costs and how the cost is paid back.
67
+ * A relative phrase on its own tells assistive technology and anything else
68
+ * reading the DOM nothing about *when*; the attribute carries the instant
69
+ * without this package deciding how to say it.
70
+ */
71
+ dateTime?: string;
72
+ /**
73
+ * Identity's product slug, selecting the ProductMark artwork for the row's
74
+ * tile. Optional, because a notification need not come from a product -- an
75
+ * account or security event does not, and the drawing's own fifth row is one.
76
+ * Omit it and the row draws the panel's bell instead (see `SourceBellMark`).
77
+ *
78
+ * A slug this package has no artwork for degrades to the bare tile rather than
79
+ * throwing, exactly as it does in the switcher (ADR-0003).
80
+ */
81
+ slug?: string;
82
+ /**
83
+ * Where selecting this notification goes. Optional: a row with no `href` is
84
+ * not a link and is drawn as plain content, which is the honest rendering for
85
+ * something there is nothing to open for.
86
+ *
87
+ * A row that does navigate renders through `linkComponent` (ADR-0011).
88
+ */
89
+ href?: string;
90
+ /**
91
+ * Whether this notification is still unread. Defaults to `false`.
92
+ *
93
+ * **The flag is spelled `unread` and defaults to not-lit on purpose**, which
94
+ * is the same decision the TopBar's bell already carries and for the same
95
+ * reason B33-494 records: a permanently lit marker on a panel nobody has
96
+ * cleared trains people to ignore it, and that attention does not come back.
97
+ * A host that does not track read state passes nothing and gets a quiet panel;
98
+ * a host that does says so row by row.
99
+ */
100
+ unread?: boolean;
101
+ }
102
+ export interface NotificationCenterProps {
103
+ /**
104
+ * The notifications, in the order the host wants them read. Rendered whole --
105
+ * this component does not sort, filter, page or truncate, because every one of
106
+ * those is a decision about data it does not own.
107
+ *
108
+ * An empty array is not an error state. It is the empty rendering, and it is
109
+ * the state most consumers see first: a host with no feed wired yet has
110
+ * nothing to pass (B33-494).
111
+ */
112
+ items: NotificationCenterItem[];
113
+ /**
114
+ * Where the footer's "View all notifications" link goes. Omit it and the
115
+ * footer is not rendered -- a link to a page the host does not have is worse
116
+ * than no link.
117
+ *
118
+ * The empty rendering never draws the footer, whether this is supplied or not:
119
+ * there is nothing to see more of.
120
+ */
121
+ viewAllHref?: string;
122
+ /**
123
+ * Every user-facing string that is not row data. Each field defaults to
124
+ * English, and a field left out -- or supplied as `undefined`, which is what a
125
+ * missed next-intl key looks like -- keeps its default (B33-873).
126
+ */
127
+ labels?: Partial<NotificationCenterLabels>;
128
+ /**
129
+ * The element a row with an `href` renders through, and the footer link with
130
+ * it. Defaults to a plain `a`; a router-owning consumer passes its own so the
131
+ * rows route client-side with the locale prefix intact (ADR-0011, B33-847).
132
+ */
133
+ linkComponent?: ElementType;
134
+ }
135
+ /**
136
+ * The panel behind the top bar's bell: everything the four products did, in one
137
+ * list, with the unread ones marked (B33-494).
138
+ *
139
+ * **It is presentational and it owns no feed.** The notifications arrive as a
140
+ * prop and this package neither fetches, stores nor subscribes to anything --
141
+ * there is no cross-product event source in any repo, and a component that
142
+ * implied one would make the shared shell the place four services integrate,
143
+ * which is the opposite of the dependency direction every other component here
144
+ * has. That was decided on the ticket on 2026-10-06; the feed is separate work,
145
+ * outside 1.0.
146
+ *
147
+ * **This is Stage A: static.** The panel renders, the tabs and the header action
148
+ * draw, and nothing responds to anything (CLAUDE.md, "Presentational-first").
149
+ * There is no open state here because there is no trigger here either: the bell
150
+ * is the TopBar's, and connecting the two -- `useAnchoredOverlay` for the shared
151
+ * dismissal contract (ADR-0009), the bell's `aria-haspopup`/`aria-expanded`, the
152
+ * filter the tabs select, the mark-all handler -- is Stage B, after a human has
153
+ * approved what this draws.
154
+ *
155
+ * **A disclosure, not a menu** (ADR-0010): the panel carries no widget role, the
156
+ * tabs are not an ARIA `tablist`, the rows are the real links they already are,
157
+ * and Tab is the document's own order, so the header button, both filters, every
158
+ * row and the footer link are each their own tab stop. An ARIA tab set or a
159
+ * `menu` would promise a composite widget with one tab stop and arrow-key
160
+ * traversal, which is the promise ADR-0010 took the switcher out of.
161
+ */
162
+ export declare function NotificationCenter({ items, viewAllHref, labels, linkComponent, }: NotificationCenterProps): import("react").JSX.Element;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The bell on the empty state, standing alone above "You're all caught up".
3
+ *
4
+ * Decorative: the two lines beneath it say the same thing in words, so a screen
5
+ * reader that announced it as well would say the state twice. It is sized in CSS
6
+ * (`--spacing-7`) rather than by width/height attributes, which is why it takes
7
+ * no size prop.
8
+ */
9
+ export declare function EmptyBell(): import("react").JSX.Element;
10
+ /**
11
+ * The tile a row draws when its notification names no product -- an account or
12
+ * security event, which is what the drawing's fifth row is.
13
+ *
14
+ * **Why this is not a `ProductMark` with an unknown slug.** It would draw the
15
+ * right thing by accident: ADR-0003's safe degrade gives an unrecognised slug
16
+ * the bare tile with no glyph, which is a product whose artwork has not landed.
17
+ * A notification with no product is not that. It is a row that correctly has no
18
+ * product, and drawing it as a gap says the mark failed to load. So the fallback
19
+ * is a bell -- the panel's own symbol, which is what the drawing puts there.
20
+ *
21
+ * It is still drawn ProductMark's way rather than as a styled `div`: a `rect` on
22
+ * a 40-unit grid, its corner taken from `--radius-product-tile`, so the two
23
+ * kinds of tile in the same column share one corner at every size instead of
24
+ * agreeing to a px value that only matches at 22px. The glyph is on the same
25
+ * 16-unit grid as `EmptyBell` above and the group transform insets it into the
26
+ * middle 20 of the 40, which is the inset ProductMark gives its four -- so the
27
+ * bell's ink lands at the weight the product glyphs do (1.3 on the 16 grid,
28
+ * scaled 1.25, is 0.89px of stroke at 22px, which is what Paper draws).
29
+ *
30
+ * Decorative like ProductMark is here: the row's text names the source.
31
+ */
32
+ export declare function SourceBellMark({ size }: {
33
+ size: number;
34
+ }): import("react").JSX.Element;
@@ -0,0 +1,112 @@
1
+ /** Every user-facing string the notification centre renders that is not row data. */
2
+ export interface NotificationCenterLabels {
3
+ /**
4
+ * The panel's heading, and through it the panel's accessible name. Default
5
+ * "Notifications".
6
+ *
7
+ * It is drawn as a real heading and the panel is pointed at it with
8
+ * `aria-labelledby`, rather than carrying a second copy of the same word in an
9
+ * `aria-label`. ADR-0010 says a disclosure panel carries no widget role and
10
+ * takes a name without one; it does not say the name has to be invisible, and
11
+ * a name that is already on the screen is one fewer string to translate and
12
+ * one fewer place for the two to disagree.
13
+ */
14
+ title: string;
15
+ /**
16
+ * The header's action, which clears every unread marker. Default
17
+ * "Mark all as read".
18
+ *
19
+ * Stage A renders the control and wires nothing to it -- the handler is Stage
20
+ * B's, with the rest of the interaction (CLAUDE.md, "Presentational-first").
21
+ */
22
+ markAllAsRead: string;
23
+ /** The first filter tab, showing every notification. Default "All". */
24
+ all: string;
25
+ /**
26
+ * The second filter tab, showing only the unread ones. Default "Unread".
27
+ *
28
+ * The count beside it is not a label: it is `items` counted, so it needs no
29
+ * translation and cannot disagree with the list under it.
30
+ */
31
+ unread: string;
32
+ /**
33
+ * The marker on an unread row, announced rather than drawn. Default "Unread".
34
+ *
35
+ * The drawing carries unread as a 6px orange dot and nothing else, which is a
36
+ * colour-only distinction and reaches nobody using a screen reader. This is
37
+ * the same state in words, in the row, visually hidden (CLAUDE.md,
38
+ * "Accessibility is part of done"). It repeats `unread` by default because it
39
+ * is the same English word, and it is a separate field because a locale is
40
+ * entitled to tag a row differently from how it names a filter.
41
+ */
42
+ unreadItem: string;
43
+ /**
44
+ * The second line of a row, around the host's source and time. Supplied whole
45
+ * so the consumer owns word order and the separator.
46
+ *
47
+ * Placeholders: `%source%`, `%time%`. Default (en) `%source% · %time%`.
48
+ *
49
+ * Both values are the host's, preformatted -- see `NotificationCenterItem`
50
+ * for why the time arrives as a string this package never computes.
51
+ */
52
+ meta: string;
53
+ /** The footer link out to the full list. Default "View all notifications". */
54
+ viewAll: string;
55
+ /** The empty state's headline. Default "You're all caught up". */
56
+ emptyTitle: string;
57
+ /**
58
+ * The line under the empty state's headline. Default
59
+ * "Nothing from any product needs your attention."
60
+ *
61
+ * **The empty state is not an edge case.** A host that has not wired a feed
62
+ * yet renders it, which makes it the state most consumers see first (B33-494's
63
+ * data decision). A locale that leaves it untranslated is a blank panel.
64
+ */
65
+ emptyBody: string;
66
+ }
67
+ /**
68
+ * The English defaults. A consumer that passes nothing renders the panel in
69
+ * English, which is what the specimen route shows and what a human will be
70
+ * looking at when they approve this component at its fidelity gate.
71
+ *
72
+ * Frozen, and frozen without a type annotation. It is a public export reaching
73
+ * four products on a version bump: `const` protects the binding and not the
74
+ * contents, so a consumer aliasing it -- `const mine = defaultNotificationCenterLabels;
75
+ * mine.title = 'Notifications'` -- would silently rename every panel in the
76
+ * process (B33-877), and an annotation here would widen the `Readonly` back off
77
+ * the emitted `.d.ts` and turn that into a runtime TypeError rather than a
78
+ * compile error (B33-916).
79
+ *
80
+ * The freeze is shallow. No field here is an object and none should become one
81
+ * without re-reading B33-916's note: a nested label defeats the guard silently.
82
+ */
83
+ export declare const defaultNotificationCenterLabels: Readonly<{
84
+ title: string;
85
+ markAllAsRead: string;
86
+ all: string;
87
+ unread: string;
88
+ unreadItem: string;
89
+ meta: string;
90
+ viewAll: string;
91
+ emptyTitle: string;
92
+ emptyBody: string;
93
+ }>;
94
+ /**
95
+ * The labels as the render consumes them: `meta` presented as a function of its
96
+ * two values, so the component writes `t.meta(…)` and never handles a template
97
+ * itself. Internal, and the interpolation has to happen somewhere.
98
+ */
99
+ export interface ResolvedNotificationCenterLabels extends Omit<NotificationCenterLabels, 'meta'> {
100
+ meta: (source: string, time: string) => string;
101
+ }
102
+ /**
103
+ * Merge a consumer's partial `labels` onto the English defaults, treating an
104
+ * explicitly-`undefined` field as unsupplied, and present `meta` as a function of
105
+ * its two values.
106
+ *
107
+ * `{ title: undefined }` is what one missing next-intl key looks like at the
108
+ * call site. It must fall back to English rather than blank the heading -- which
109
+ * here would also unname the panel, since the heading is what names it
110
+ * (B33-873).
111
+ */
112
+ export declare function resolveNotificationCenterLabels(labels?: Partial<NotificationCenterLabels>): ResolvedNotificationCenterLabels;
@@ -4,6 +4,21 @@
4
4
  * adding a slug without artwork is a compile error rather than a mark that
5
5
  * silently draws nothing.
6
6
  *
7
+ * **Module-internal since B33-1269, and it has to stay that way.** `src/index.ts`
8
+ * publishes `hasProductMark` below and neither this list nor `ProductSlug`. A
9
+ * `const` assertion over four strings made an artwork inventory part of the
10
+ * published type contract: a consumer could write `Record<ProductSlug, ...>` or
11
+ * switch exhaustively over it, and then artwork landing for a fifth product --
12
+ * the thing this package exists to ship -- would be a breaking change for them.
13
+ * Nothing published is typed `ProductSlug` anyway; every slug prop is `string` on
14
+ * purpose, so Identity's registry may run ahead of a mark release (ADR-0003, and
15
+ * `slug` below). So the union's only real job was answering "which slugs have
16
+ * artwork", and `hasProductMark` answers that without freezing the list.
17
+ *
18
+ * The tests and the specimen read both names by deep import, which is how
19
+ * everything in this repo reaches into `src/`. A consumer cannot: `package.json`
20
+ * exports `.`, `./style.css` and `./theme.css`, and nothing else.
21
+ *
7
22
  * **It is no longer the whole of Identity's registry, and the difference is
8
23
  * deliberate.** Identity serves five slugs: these four plus `brokers` (B33-1158,
9
24
  * "Boost Brokers"). This package has no `brokers` glyph, so that row draws the
@@ -66,10 +81,48 @@
66
81
  * gate. Not in that order optional: this list is what GLYPHS is typed against,
67
82
  * so adding the slug without the artwork stops the build, which is the one part
68
83
  * of this that cannot go wrong quietly.
84
+ *
85
+ * **And since B33-1269 that is an ordinary addition rather than a breaking
86
+ * change.** This list is not published, so widening it widens nothing a consumer
87
+ * has typed against: a fifth entry adds a drawing where there was a bare tile and
88
+ * flips `hasProductMark('brokers')` from false to true. Until then it was a
89
+ * major-bump-shaped change to ship a picture, which is the wrong price for the
90
+ * thing this package is for.
69
91
  */
70
92
  export declare const PRODUCT_SLUGS: readonly ["wallet", "boostscore", "boostvoice", "boostoutbound"];
71
- /** One of the slugs this package draws a mark for. Not every slug Identity serves -- see above. */
93
+ /**
94
+ * One of the slugs this package draws a mark for. Not every slug Identity serves
95
+ * -- see above -- and not published either, for the reason recorded there. What
96
+ * this type is for is typing GLYPHS, so the list and the artwork cannot part
97
+ * company.
98
+ */
72
99
  export type ProductSlug = (typeof PRODUCT_SLUGS)[number];
100
+ /**
101
+ * Does this package draw a mark for `slug`? The supported way to ask, and the
102
+ * only thing about the artwork inventory the package publishes (B33-1269).
103
+ *
104
+ * **Ask this rather than matching a slug against a union of known slugs.** The
105
+ * set of slugs with artwork is this package's inventory, not a vocabulary:
106
+ * Identity owns which products exist (ADR-0003) and this list grows whenever a
107
+ * drawing lands. `brokers` is the one slug Identity serves today that there is no
108
+ * glyph here for -- see "The fifth slug" above. A caller that pattern-matches a
109
+ * union is pinned to whichever inventory it compiled against; a caller that asks
110
+ * this gets the installed version's answer, and keeps working when the next mark
111
+ * ships.
112
+ *
113
+ * **A `false` is not an error and needs no guard around it.** `ProductMark`
114
+ * renders a slug it has no glyph for as the bare tile rather than throwing
115
+ * (ADR-0003's safe degrade), and in a switcher that row is still named, still
116
+ * linked and still clickable -- only the glyph is missing. So do not use this to
117
+ * filter a served registry, to hide a product's row, or to decide to substitute a
118
+ * drawing of your own: every one of those turns a missing glyph into a missing
119
+ * product, and the registry running ahead of the artwork is the supported state.
120
+ *
121
+ * It is here for a caller that needs to *know* rather than to branch on
122
+ * rendering -- a consumer test asserting its own hardcoded fixtures still draw
123
+ * something, or a report of which served slugs have artwork yet.
124
+ */
125
+ export declare function hasProductMark(slug: string): boolean;
73
126
  export interface ProductMarkProps {
74
127
  /**
75
128
  * Identity's product slug. An unknown one -- a product the served registry