@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.
- package/README.md +27 -1
- package/dist/components/AccountMenu/AccountMenu.d.ts +131 -0
- package/dist/components/AccountMenu/icons.d.ts +11 -0
- package/dist/components/AccountMenu/labels.d.ts +39 -0
- package/dist/components/AppShell/AppShell.d.ts +71 -18
- package/dist/components/AppShell/AppShell.js +33 -35
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/AppShell/AppShell.module.css.js +16 -16
- package/dist/components/AppSwitcher/AppSwitcher.d.ts +23 -1
- package/dist/components/AppSwitcher/AppSwitcher.js +49 -43
- package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
- package/dist/components/AppSwitcher/AppSwitcher.module.css.js +28 -28
- package/dist/components/AppSwitcher/labels.js.map +1 -1
- package/dist/components/CommandPalette/CommandPalette.d.ts +127 -0
- package/dist/components/CommandPalette/icons.d.ts +8 -0
- package/dist/components/CommandPalette/labels.d.ts +94 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.d.ts +21 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.js +18 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.js.map +1 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js +12 -0
- package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js.map +1 -0
- package/dist/components/LaunchLoader/LaunchLoader.d.ts +50 -0
- package/dist/components/LaunchLoader/LaunchLoader.js +70 -0
- package/dist/components/LaunchLoader/LaunchLoader.js.map +1 -0
- package/dist/components/LaunchLoader/LaunchLoader.module.css.js +26 -0
- package/dist/components/LaunchLoader/LaunchLoader.module.css.js.map +1 -0
- package/dist/components/NavItem/NavItem.module.css.js +4 -4
- package/dist/components/NotificationCenter/NotificationCenter.d.ts +162 -0
- package/dist/components/NotificationCenter/icons.d.ts +34 -0
- package/dist/components/NotificationCenter/labels.d.ts +112 -0
- package/dist/components/ProductMark/ProductMark.d.ts +54 -1
- package/dist/components/ProductMark/ProductMark.js +23 -19
- package/dist/components/ProductMark/ProductMark.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.d.ts +17 -3
- package/dist/components/Sidebar/Sidebar.js +5 -5
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/TopBar/TopBar.module.css.js +4 -4
- package/dist/components/WalletChip/WalletChip.d.ts +150 -27
- package/dist/components/WalletChip/WalletChip.js +38 -35
- package/dist/components/WalletChip/WalletChip.js.map +1 -1
- package/dist/components/WalletChip/WalletChip.module.css.js +31 -31
- package/dist/components/WalletChip/labels.d.ts +65 -47
- package/dist/components/WalletChip/labels.js +15 -20
- package/dist/components/WalletChip/labels.js.map +1 -1
- package/dist/fill-template.d.ts +10 -0
- package/dist/fill-template.js +10 -0
- package/dist/fill-template.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.js +41 -41
- package/dist/index.js.map +1 -1
- package/dist/launch-gradient.d.ts +112 -0
- package/dist/launch-gradient.js +60 -0
- package/dist/launch-gradient.js.map +1 -0
- package/dist/nav-height-budget.d.ts +4 -0
- package/dist/nav-height-budget.js +18 -19
- package/dist/nav-height-budget.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/theme.css +40 -14
- 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 = "
|
|
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:
|
|
7
|
+
countUrgent: l
|
|
8
8
|
};
|
|
9
9
|
export {
|
|
10
10
|
c as count,
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
/**
|
|
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
|