@boost33/ui 0.4.1 → 0.5.1
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 +26 -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 +11 -11
- 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 +36 -36
- 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 +54 -0
- package/dist/components/LaunchLoader/LaunchLoader.js +57 -0
- package/dist/components/LaunchLoader/LaunchLoader.js.map +1 -0
- package/dist/components/LaunchLoader/LaunchLoader.module.css.js +24 -0
- package/dist/components/LaunchLoader/LaunchLoader.module.css.js.map +1 -0
- 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/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 +36 -36
- 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 +36 -39
- package/dist/index.js.map +1 -1
- package/dist/launch-gradient.d.ts +36 -0
- package/dist/launch-gradient.js +8 -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
|
@@ -1,82 +1,88 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs as
|
|
2
|
+
import { jsxs as s, jsx as a, Fragment as f } from "react/jsx-runtime";
|
|
3
3
|
import { useAnchoredOverlay as y } from "../../anchored-overlay.js";
|
|
4
|
-
import { ProductMark as
|
|
5
|
-
import { Chevron as
|
|
6
|
-
import { resolveAppSwitcherLabels as
|
|
4
|
+
import { ProductMark as g } from "../ProductMark/ProductMark.js";
|
|
5
|
+
import { Chevron as x, HubMark as P, Check as A, ChevronRight as S } from "./icons.js";
|
|
6
|
+
import { resolveAppSwitcherLabels as L } from "./labels.js";
|
|
7
7
|
import e from "./AppSwitcher.module.css.js";
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
function M({ account: r }) {
|
|
9
|
+
const l = /* @__PURE__ */ s(f, { children: [
|
|
10
|
+
/* @__PURE__ */ a("span", { className: e.avatar, "aria-hidden": "true", children: r.initials }),
|
|
11
|
+
/* @__PURE__ */ s("span", { className: e.rowText, children: [
|
|
12
|
+
/* @__PURE__ */ a("span", { className: e.rowName, children: r.name }),
|
|
13
|
+
/* @__PURE__ */ a("span", { className: e.rowSub, children: r.description })
|
|
14
|
+
] })
|
|
15
|
+
] });
|
|
16
|
+
return r.onSelect === void 0 ? /* @__PURE__ */ a("div", { className: e.accountRow, "data-actionable": "false", children: l }) : /* @__PURE__ */ s("button", { type: "button", className: e.accountRow, onClick: r.onSelect, children: [
|
|
17
|
+
l,
|
|
18
|
+
/* @__PURE__ */ a(S, { className: e.chevronRight })
|
|
19
|
+
] });
|
|
20
|
+
}
|
|
21
|
+
function q({
|
|
22
|
+
products: r,
|
|
23
|
+
currentSlug: l,
|
|
11
24
|
hub: o,
|
|
12
|
-
account:
|
|
13
|
-
labels:
|
|
25
|
+
account: w,
|
|
26
|
+
labels: b,
|
|
14
27
|
linkComponent: k,
|
|
15
28
|
collapsed: c = !1,
|
|
16
|
-
defaultOpen:
|
|
17
|
-
onOpenChange:
|
|
29
|
+
defaultOpen: C,
|
|
30
|
+
onOpenChange: R
|
|
18
31
|
}) {
|
|
19
|
-
const
|
|
20
|
-
return /* @__PURE__ */
|
|
21
|
-
/* @__PURE__ */
|
|
32
|
+
const i = y({ defaultOpen: C, onOpenChange: R }), t = L(b), p = k ?? "a", m = r.find((n) => n.slug === l), u = (m == null ? void 0 : m.name) ?? t.switchProduct;
|
|
33
|
+
return /* @__PURE__ */ s("div", { className: e.root, children: [
|
|
34
|
+
/* @__PURE__ */ s(
|
|
22
35
|
"button",
|
|
23
36
|
{
|
|
24
37
|
type: "button",
|
|
25
38
|
className: c ? e.railTrigger : e.trigger,
|
|
26
|
-
onClick:
|
|
27
|
-
...
|
|
39
|
+
onClick: i.toggle,
|
|
40
|
+
...i.triggerProps,
|
|
28
41
|
children: [
|
|
29
|
-
/* @__PURE__ */ a(
|
|
30
|
-
!c && /* @__PURE__ */
|
|
31
|
-
/* @__PURE__ */ a("span", { className: e.triggerName, children:
|
|
32
|
-
/* @__PURE__ */ a(
|
|
42
|
+
/* @__PURE__ */ a(g, { slug: l, title: c ? u : void 0 }),
|
|
43
|
+
!c && /* @__PURE__ */ s(f, { children: [
|
|
44
|
+
/* @__PURE__ */ a("span", { className: e.triggerName, children: u }),
|
|
45
|
+
/* @__PURE__ */ a(x, { className: e.chevron })
|
|
33
46
|
] })
|
|
34
47
|
]
|
|
35
48
|
}
|
|
36
49
|
),
|
|
37
|
-
|
|
38
|
-
/* @__PURE__ */
|
|
39
|
-
/* @__PURE__ */ a(
|
|
40
|
-
/* @__PURE__ */
|
|
50
|
+
i.open && /* @__PURE__ */ s("div", { ...i.panelProps, className: e.panel, "aria-label": t.panel, children: [
|
|
51
|
+
/* @__PURE__ */ s(p, { className: e.hubRow, href: o.href, onClick: i.close, children: [
|
|
52
|
+
/* @__PURE__ */ a(P, { className: e.hubMark }),
|
|
53
|
+
/* @__PURE__ */ s("span", { className: e.rowText, children: [
|
|
41
54
|
/* @__PURE__ */ a("span", { className: e.rowName, children: o.name }),
|
|
42
55
|
/* @__PURE__ */ a("span", { className: e.rowSub, children: o.description })
|
|
43
56
|
] })
|
|
44
57
|
] }),
|
|
45
58
|
/* @__PURE__ */ a("hr", { className: e.divider }),
|
|
46
|
-
/* @__PURE__ */ a("ul", { className: e.products, children:
|
|
47
|
-
const d =
|
|
48
|
-
return /* @__PURE__ */ a("li", { children: /* @__PURE__ */
|
|
49
|
-
|
|
59
|
+
/* @__PURE__ */ a("ul", { className: e.products, children: r.map((n) => {
|
|
60
|
+
const d = n.slug === l, h = n.entitled === !1, N = n.live === !1, v = N ? t.soon : h ? t.notOnPlan : null;
|
|
61
|
+
return /* @__PURE__ */ a("li", { children: /* @__PURE__ */ s(
|
|
62
|
+
p,
|
|
50
63
|
{
|
|
51
64
|
className: e.productRow,
|
|
52
|
-
href:
|
|
65
|
+
href: n.href,
|
|
53
66
|
"aria-current": d ? "true" : void 0,
|
|
54
67
|
"data-current": d ? "true" : void 0,
|
|
55
68
|
"data-entitled": h ? "false" : void 0,
|
|
56
69
|
"data-live": N ? "false" : void 0,
|
|
57
|
-
onClick:
|
|
70
|
+
onClick: i.close,
|
|
58
71
|
children: [
|
|
59
|
-
/* @__PURE__ */ a(
|
|
60
|
-
/* @__PURE__ */ a("span", { className: e.rowName, children:
|
|
61
|
-
|
|
72
|
+
/* @__PURE__ */ a(g, { slug: n.slug, muted: h || N }),
|
|
73
|
+
/* @__PURE__ */ a("span", { className: e.rowName, children: n.name }),
|
|
74
|
+
v !== null && /* @__PURE__ */ a("span", { className: e.tag, children: v }),
|
|
62
75
|
d && /* @__PURE__ */ a(A, { className: e.check })
|
|
63
76
|
]
|
|
64
77
|
}
|
|
65
|
-
) },
|
|
78
|
+
) }, n.slug);
|
|
66
79
|
}) }),
|
|
67
80
|
/* @__PURE__ */ a("hr", { className: e.divider }),
|
|
68
|
-
/* @__PURE__ */
|
|
69
|
-
/* @__PURE__ */ a("span", { className: e.avatar, "aria-hidden": "true", children: n.initials }),
|
|
70
|
-
/* @__PURE__ */ r("span", { className: e.rowText, children: [
|
|
71
|
-
/* @__PURE__ */ a("span", { className: e.rowName, children: n.name }),
|
|
72
|
-
/* @__PURE__ */ a("span", { className: e.rowSub, children: n.description })
|
|
73
|
-
] }),
|
|
74
|
-
/* @__PURE__ */ a(L, { className: e.chevronRight })
|
|
75
|
-
] })
|
|
81
|
+
/* @__PURE__ */ a(M, { account: w })
|
|
76
82
|
] })
|
|
77
83
|
] });
|
|
78
84
|
}
|
|
79
85
|
export {
|
|
80
|
-
|
|
86
|
+
q as AppSwitcher
|
|
81
87
|
};
|
|
82
88
|
//# sourceMappingURL=AppSwitcher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppSwitcher.js","sources":["../../../src/components/AppSwitcher/AppSwitcher.tsx"],"sourcesContent":["'use client'\n\n// A client module: the switcher holds open/closed state and wires clicks, focus\n// and Escape through useAnchoredOverlay, none of which a Server Component can\n// carry. The rest of the shell stays on the server -- see WalletChip, Sidebar\n// and B33-846. AppShell renders this as a slot (`switcher`), so it imports no\n// client code by taking it.\n\nimport type { ElementType } from 'react'\nimport { useAnchoredOverlay } from '../../anchored-overlay'\nimport { ProductMark } from '../ProductMark/ProductMark'\nimport { Check, Chevron, ChevronRight, HubMark } from './icons'\nimport { resolveAppSwitcherLabels, type AppSwitcherLabels } from './labels'\nimport styles from './AppSwitcher.module.css'\n\n/**\n * One product the switcher can jump to. It is Identity's registry row, supplied\n * by the consuming app (ADR-0003) -- the switcher fetches nothing and hardcodes\n * no list. Every product is rendered, entitled or not and built or not: hiding\n * an unowned one means nobody learns the family exists (B33-479), and hiding an\n * unbuilt one means nobody learns it is coming.\n */\nexport interface AppSwitcherProduct {\n /** Identity's product slug, which selects the ProductMark artwork. */\n slug: string\n /** The product's display name, e.g. \"Boost Score\". Identity's, not the slug. */\n name: string\n /** Where selecting this product goes -- the product's own URL, never the hub. */\n href: string\n /**\n * Whether the account holds this product. Defaults to `true`. `false` dims the\n * mark and shows the \"Not on plan\" tag, but the row stays an ordinary focusable\n * link: entitlement is a rendering hint, and the switcher is not the thing\n * deciding what someone may open (ADR-0010, \"Not entitled is not disabled\").\n */\n entitled?: boolean\n /**\n * Whether the product exists yet. Defaults to `true`. `false` dims the mark and\n * shows the \"Soon\" tag in place of \"Not on plan\".\n *\n * **A different question from `entitled`, which is why it is a second field\n * and not a third value of the first.** `live` is global: the same answer for\n * every account, because either the product has shipped or it has not.\n * `entitled` is per account, read from the grant. Identity serves both for\n * exactly this reason (B33-561, and the `live` note in\n * `boost33-identity/src/lib/productRegistry.ts`).\n *\n * Until 0.4 this interface had nowhere to put it, so both consumers read the\n * field off the registry and dropped it on the floor -- each with a comment\n * saying so and calling it worth a package ticket. The cost was a row that\n * told a client they had not bought Boost Voice, when nobody has: the row read\n * \"Not on plan\" and pointed at a product with no code behind it.\n *\n * It stays a rendering hint like `entitled`. An unbuilt product's row is still\n * an ordinary focusable link -- the switcher does not decide what anyone may\n * open (ADR-0010), and `url` is served for every slug whether or not anything\n * answers on it, because `?app=` has to resolve for a campaign link.\n */\n live?: boolean\n}\n\n/** The \"all products\" row above the divider. Its blurb and URL are the consumer's. */\nexport interface AppSwitcherHub {\n /** The row's name, e.g. \"Hub\". */\n name: string\n /** The line beneath it, e.g. \"All Boost33 products\". */\n description: string\n /** The hub URL. */\n href: string\n}\n\n/**\n * The account/workspace row at the foot of the panel. A row that opens a second\n * panel (a later ticket owns that panel), so it is a button rather than a link;\n * this ticket renders the row and calls `onSelect` when it is chosen.\n */\nexport interface AppSwitcherAccount {\n /** The organisation's name, e.g. \"Acme Coaching\". */\n name: string\n /** The avatar's initials, e.g. \"AC\". */\n initials: string\n /** The line beneath the name, preformatted, e.g. \"Admin · 2 workspaces\". */\n description: string\n /** Chosen -- opens the workspace panel. */\n onSelect?: () => void\n}\n\nexport interface AppSwitcherProps {\n /**\n * The products, in the order Identity serves them. Rendered whole -- entitled\n * or not, built or not.\n */\n products: AppSwitcherProduct[]\n /**\n * The slug of the product this shell is rendering for. It names the trigger\n * and is the row that carries the check -- the product you are in.\n *\n * A slug matching nothing in `products` is a supported state, not a caller\n * error: the registry is Identity's and can run ahead of this package\n * (ADR-0003). The mark degrades to a bare tile and the trigger falls back to\n * `labels.switchProduct` for its name (B33-919).\n */\n currentSlug: string\n /** The \"all products\" row above the products. */\n hub: AppSwitcherHub\n /** The account/workspace row below the products. */\n account: AppSwitcherAccount\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\n * a missed next-intl key looks like -- keeps its default (B33-873).\n */\n labels?: Partial<AppSwitcherLabels>\n /**\n * The element the product and hub rows render through. Defaults to a plain\n * `a`; a router-owning consumer passes its own so the rows route client-side\n * with the locale prefix intact (ADR-0011, B33-847). The account row stays a\n * button -- it opens a panel, it does not navigate.\n */\n linkComponent?: ElementType\n /** The 56px rail. The trigger collapses to the current product's mark alone. */\n collapsed?: boolean\n /** Render the panel open on first paint -- the specimen's Stage-A open state. */\n defaultOpen?: boolean\n /** Every open and close, so a consumer can un-clip an ancestor's overflow while the panel overhangs. */\n onOpenChange?: (open: boolean) => void\n}\n\n/**\n * The product switcher under the wordmark. It opens, lists every Boost33 product\n * with the current one checked, and takes you straight into the one you pick --\n * the real switcher the Wallet's chevron-that-opens-nothing never was (B33-479).\n *\n * A disclosure, not a menu (ADR-0010): the panel carries no widget role, the\n * rows are the real links and buttons they already are, and Tab is the\n * document's own tab order -- so tabbing through the products does not close the\n * panel on the first row. Dismissal, focus restoration and trigger ARIA are the\n * shared anchored-overlay contract's (ADR-0009); it never becomes a modal.\n */\nexport function AppSwitcher({\n products,\n currentSlug,\n hub,\n account,\n labels,\n linkComponent,\n collapsed = false,\n defaultOpen,\n onOpenChange,\n}: AppSwitcherProps) {\n const overlay = useAnchoredOverlay({ defaultOpen, onOpenChange })\n const t = resolveAppSwitcherLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const current = products.find((p) => p.slug === currentSlug)\n\n // The name the trigger carries, in both its shapes. `current` is `undefined`\n // whenever `currentSlug` matches nothing in the served registry -- a fifth\n // Identity product, or a slug left stale by a rename -- which ADR-0003\n // contemplates and `AppSwitcherProduct.slug` being `string` rather than\n // `ProductSlug` deliberately allows.\n //\n // Degrading the artwork to a bare tile there is right. Degrading the name is\n // not, and that is what happened: the mark is `aria-hidden` unless it is\n // given a title, so a collapsed trigger handed `title={undefined}` had no\n // accessible name at all. A screen reader reached the only control in the\n // 56px rail and announced \"button\" -- no product, no purpose, nothing to act\n // on. Expanded it was the same absence with a visible face: an empty\n // `.triggerName` between a blank tile and a chevron. CLAUDE.md puts labels in\n // the definition of done, so the fallback is a label prop rather than a\n // literal (B33-919, B33-849).\n const triggerName = current?.name ?? t.switchProduct\n\n return (\n <div className={styles.root}>\n <button\n type=\"button\"\n className={collapsed ? styles.railTrigger : styles.trigger}\n onClick={overlay.toggle}\n {...overlay.triggerProps}\n >\n {/* On the rail the mark is the button's only content, so it carries the\n accessible name (title); expanded, the name sits beside it and the\n mark is decorative, or a screen reader says the product twice.\n\n No `size`. ProductMark's default is 20px -- the Wallet's own size at\n its call sites, and the size all three appswitcher-* baselines were\n captured at. This passed 28 expanded and 32 collapsed until B33-479's\n fidelity pass: a mark 8px over budget in every row pushed the account\n row out of the open panel's frame entirely and truncated \"Boost\n Outbound\" to \"Boos...\". The rail is not a larger mark; it is the same\n mark with the label and chevron gone. */}\n <ProductMark slug={currentSlug} title={collapsed ? triggerName : undefined} />\n {!collapsed && (\n <>\n <span className={styles.triggerName}>{triggerName}</span>\n <Chevron className={styles.chevron} />\n </>\n )}\n </button>\n\n {overlay.open && (\n <div {...overlay.panelProps} className={styles.panel} aria-label={t.panel}>\n <Link className={styles.hubRow} href={hub.href} onClick={overlay.close}>\n <HubMark className={styles.hubMark} />\n <span className={styles.rowText}>\n <span className={styles.rowName}>{hub.name}</span>\n <span className={styles.rowSub}>{hub.description}</span>\n </span>\n </Link>\n\n <hr className={styles.divider} />\n\n <ul className={styles.products}>\n {products.map((product) => {\n const isCurrent = product.slug === currentSlug\n const notEntitled = product.entitled === false\n const notLive = product.live === false\n\n // One tag slot, and the unbuilt word wins it.\n //\n // A product that has not shipped is held by nobody, so Identity\n // serves every `live: false` row as `entitled: false` too and both\n // conditions are true at once on the same row. Rendering both tags\n // would stack two pills on a row with one tag's worth of space --\n // and the second would be answering a question the first has\n // already closed: \"not on plan\" is trivially true of a product\n // that does not exist, and saying it sends the reader off to ask\n // about buying something unbuyable (B33-561).\n //\n // The dim is deliberately shared rather than split. `muted` has\n // one meaning on the mark -- this row is not somewhere you can go\n // today -- and that is equally true either way; the tag is the\n // channel carrying *which* reason, because it is the one a screen\n // reader reaches and the mark is decorative here (ADR-0012).\n const tag = notLive ? t.soon : notEntitled ? t.notOnPlan : null\n\n return (\n <li key={product.slug}>\n <Link\n className={styles.productRow}\n href={product.href}\n aria-current={isCurrent ? 'true' : undefined}\n data-current={isCurrent ? 'true' : undefined}\n data-entitled={notEntitled ? 'false' : undefined}\n data-live={notLive ? 'false' : undefined}\n onClick={overlay.close}\n >\n {/* Default 20px, as in the trigger above -- see the note there. */}\n <ProductMark slug={product.slug} muted={notEntitled || notLive} />\n <span className={styles.rowName}>{product.name}</span>\n {tag !== null && <span className={styles.tag}>{tag}</span>}\n {isCurrent && <Check className={styles.check} />}\n </Link>\n </li>\n )\n })}\n </ul>\n\n <hr className={styles.divider} />\n\n <button type=\"button\" className={styles.accountRow} onClick={account.onSelect}>\n <span className={styles.avatar} aria-hidden=\"true\">\n {account.initials}\n </span>\n <span className={styles.rowText}>\n <span className={styles.rowName}>{account.name}</span>\n <span className={styles.rowSub}>{account.description}</span>\n </span>\n <ChevronRight className={styles.chevronRight} />\n </button>\n </div>\n )}\n </div>\n )\n}\n"],"names":["AppSwitcher","products","currentSlug","hub","account","labels","linkComponent","collapsed","defaultOpen","onOpenChange","overlay","useAnchoredOverlay","t","resolveAppSwitcherLabels","Link","current","p","triggerName","jsxs","styles","jsx","ProductMark","Fragment","Chevron","HubMark","product","isCurrent","notEntitled","notLive","tag","Check","ChevronRight"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppSwitcher.js","sources":["../../../src/components/AppSwitcher/AppSwitcher.tsx"],"sourcesContent":["'use client'\n\n// A client module: the switcher holds open/closed state and wires clicks, focus\n// and Escape through useAnchoredOverlay, none of which a Server Component can\n// carry. The rest of the shell stays on the server -- see WalletChip, Sidebar\n// and B33-846. AppShell renders this as a slot (`switcher`), so it imports no\n// client code by taking it.\n\nimport type { ElementType } from 'react'\nimport { useAnchoredOverlay } from '../../anchored-overlay'\nimport { ProductMark } from '../ProductMark/ProductMark'\nimport { Check, Chevron, ChevronRight, HubMark } from './icons'\nimport { resolveAppSwitcherLabels, type AppSwitcherLabels } from './labels'\nimport styles from './AppSwitcher.module.css'\n\n/**\n * One product the switcher can jump to. It is Identity's registry row, supplied\n * by the consuming app (ADR-0003) -- the switcher fetches nothing and hardcodes\n * no list. Every product is rendered, entitled or not and built or not: hiding\n * an unowned one means nobody learns the family exists (B33-479), and hiding an\n * unbuilt one means nobody learns it is coming.\n */\nexport interface AppSwitcherProduct {\n /** Identity's product slug, which selects the ProductMark artwork. */\n slug: string\n /** The product's display name, e.g. \"Boost Score\". Identity's, not the slug. */\n name: string\n /** Where selecting this product goes -- the product's own URL, never the hub. */\n href: string\n /**\n * Whether the account holds this product. Defaults to `true`. `false` dims the\n * mark and shows the \"Not on plan\" tag, but the row stays an ordinary focusable\n * link: entitlement is a rendering hint, and the switcher is not the thing\n * deciding what someone may open (ADR-0010, \"Not entitled is not disabled\").\n */\n entitled?: boolean\n /**\n * Whether the product exists yet. Defaults to `true`. `false` dims the mark and\n * shows the \"Soon\" tag in place of \"Not on plan\".\n *\n * **A different question from `entitled`, which is why it is a second field\n * and not a third value of the first.** `live` is global: the same answer for\n * every account, because either the product has shipped or it has not.\n * `entitled` is per account, read from the grant. Identity serves both for\n * exactly this reason (B33-561, and the `live` note in\n * `boost33-identity/src/lib/productRegistry.ts`).\n *\n * Until 0.4 this interface had nowhere to put it, so both consumers read the\n * field off the registry and dropped it on the floor -- each with a comment\n * saying so and calling it worth a package ticket. The cost was a row that\n * told a client they had not bought Boost Voice, when nobody has: the row read\n * \"Not on plan\" and pointed at a product with no code behind it.\n *\n * It stays a rendering hint like `entitled`. An unbuilt product's row is still\n * an ordinary focusable link -- the switcher does not decide what anyone may\n * open (ADR-0010), and `url` is served for every slug whether or not anything\n * answers on it, because `?app=` has to resolve for a campaign link.\n */\n live?: boolean\n}\n\n/** The \"all products\" row above the divider. Its blurb and URL are the consumer's. */\nexport interface AppSwitcherHub {\n /** The row's name, e.g. \"Hub\". */\n name: string\n /** The line beneath it, e.g. \"All Boost33 products\". */\n description: string\n /** The hub URL. */\n href: string\n}\n\n/**\n * The account/workspace row at the foot of the panel. A row that opens a second\n * panel (a later ticket owns that panel), so it is a button rather than a link;\n * this ticket renders the row and calls `onSelect` when it is chosen.\n */\nexport interface AppSwitcherAccount {\n /** The organisation's name, e.g. \"Acme Coaching\". */\n name: string\n /** The avatar's initials, e.g. \"AC\". */\n initials: string\n /** The line beneath the name, preformatted, e.g. \"Admin · 2 workspaces\". */\n description: string\n /**\n * Chosen -- opens the workspace panel.\n *\n * **It is also what makes the row a control.** Supplied, the row is the\n * `<button>` it has always been, with the chevron pointing at the panel it\n * opens. Omitted, the row still draws -- the organisation and its workspace\n * count are facts about the account whether or not anything opens -- but as a\n * plain `<div>`: no chevron, no hover step, no tab stop (B33-1270).\n *\n * Both consumers ship it unarmed today, because the panel it would open is\n * B33-493's and unbuilt: see the comment on `account` in\n * `boostscore/components/app-shell/product-switcher.tsx` (\"the Wallet's\n * migration left the same row inert for the same reason\"). Before this gate\n * existed they got a button that swallowed a click and did nothing, and no way\n * to reach it -- `.accountRow`'s class name is CSS-Modules-hashed.\n *\n * The whole row is not removed, and that is deliberate. ADR-0010's \"Not\n * entitled is not disabled\" is the nearest settled reasoning: a row that\n * carries information is not the component's to withhold, and `aria-disabled`\n * does not belong on something nobody can act on anyway. So the information\n * stays and the affordances go. Nor is the button `disabled` -- that is still\n * a control a consumer cannot remove.\n */\n onSelect?: () => void\n}\n\nexport interface AppSwitcherProps {\n /**\n * The products, in the order Identity serves them. Rendered whole -- entitled\n * or not, built or not.\n */\n products: AppSwitcherProduct[]\n /**\n * The slug of the product this shell is rendering for. It names the trigger\n * and is the row that carries the check -- the product you are in.\n *\n * A slug matching nothing in `products` is a supported state, not a caller\n * error: the registry is Identity's and can run ahead of this package\n * (ADR-0003). The mark degrades to a bare tile and the trigger falls back to\n * `labels.switchProduct` for its name (B33-919).\n */\n currentSlug: string\n /** The \"all products\" row above the products. */\n hub: AppSwitcherHub\n /** The account/workspace row below the products. */\n account: AppSwitcherAccount\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\n * a missed next-intl key looks like -- keeps its default (B33-873).\n */\n labels?: Partial<AppSwitcherLabels>\n /**\n * The element the product and hub rows render through. Defaults to a plain\n * `a`; a router-owning consumer passes its own so the rows route client-side\n * with the locale prefix intact (ADR-0011, B33-847). The account row stays a\n * button -- it opens a panel, it does not navigate.\n */\n linkComponent?: ElementType\n /** The 56px rail. The trigger collapses to the current product's mark alone. */\n collapsed?: boolean\n /** Render the panel open on first paint -- the specimen's Stage-A open state. */\n defaultOpen?: boolean\n /** Every open and close, so a consumer can un-clip an ancestor's overflow while the panel overhangs. */\n onOpenChange?: (open: boolean) => void\n}\n\n/**\n * The panel's foot, in its two shapes. The information is identical in both and\n * the affordances are not: a row with an `onSelect` is the button it always was,\n * and a row without one keeps the organisation and its workspace count while\n * losing everything that promises a second panel (B33-1270).\n *\n * The chevron goes with the handler rather than with the row, because it is the\n * part of the row that makes a promise: it points at the workspace panel, and\n * there is nothing to point at when nobody is listening. The avatar stays\n * `aria-hidden` either way -- the initials restate the name beside them.\n *\n * In the unarmed shape the accessible name is nothing at all, which is correct:\n * a `<div>` is not a control, so it has no name to get wrong, and a screen\n * reader reads its two lines of text in document order like any other text in\n * the panel. The armed shape's name is unchanged -- the button's content, which\n * is the organisation and its description.\n */\nfunction AccountRow({ account }: { account: AppSwitcherAccount }) {\n const body = (\n <>\n <span className={styles.avatar} aria-hidden=\"true\">\n {account.initials}\n </span>\n <span className={styles.rowText}>\n <span className={styles.rowName}>{account.name}</span>\n <span className={styles.rowSub}>{account.description}</span>\n </span>\n </>\n )\n\n // Tested against `undefined` rather than for truthiness, so a consumer's own\n // absent value -- `onSelect: undefined`, which is what an unwired branch\n // produces -- reads the same as leaving the field out.\n if (account.onSelect === undefined) {\n return (\n <div className={styles.accountRow} data-actionable=\"false\">\n {body}\n </div>\n )\n }\n\n return (\n <button type=\"button\" className={styles.accountRow} onClick={account.onSelect}>\n {body}\n <ChevronRight className={styles.chevronRight} />\n </button>\n )\n}\n\n/**\n * The product switcher under the wordmark. It opens, lists every Boost33 product\n * with the current one checked, and takes you straight into the one you pick --\n * the real switcher the Wallet's chevron-that-opens-nothing never was (B33-479).\n *\n * A disclosure, not a menu (ADR-0010): the panel carries no widget role, the\n * rows are the real links and buttons they already are, and Tab is the\n * document's own tab order -- so tabbing through the products does not close the\n * panel on the first row. Dismissal, focus restoration and trigger ARIA are the\n * shared anchored-overlay contract's (ADR-0009); it never becomes a modal.\n */\nexport function AppSwitcher({\n products,\n currentSlug,\n hub,\n account,\n labels,\n linkComponent,\n collapsed = false,\n defaultOpen,\n onOpenChange,\n}: AppSwitcherProps) {\n const overlay = useAnchoredOverlay({ defaultOpen, onOpenChange })\n const t = resolveAppSwitcherLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const current = products.find((p) => p.slug === currentSlug)\n\n // The name the trigger carries, in both its shapes. `current` is `undefined`\n // whenever `currentSlug` matches nothing in the served registry -- a fifth\n // Identity product, or a slug left stale by a rename -- which ADR-0003\n // contemplates and `AppSwitcherProduct.slug` being `string` rather than\n // `ProductSlug` deliberately allows.\n //\n // Degrading the artwork to a bare tile there is right. Degrading the name is\n // not, and that is what happened: the mark is `aria-hidden` unless it is\n // given a title, so a collapsed trigger handed `title={undefined}` had no\n // accessible name at all. A screen reader reached the only control in the\n // 56px rail and announced \"button\" -- no product, no purpose, nothing to act\n // on. Expanded it was the same absence with a visible face: an empty\n // `.triggerName` between a blank tile and a chevron. CLAUDE.md puts labels in\n // the definition of done, so the fallback is a label prop rather than a\n // literal (B33-919, B33-849).\n const triggerName = current?.name ?? t.switchProduct\n\n return (\n <div className={styles.root}>\n <button\n type=\"button\"\n className={collapsed ? styles.railTrigger : styles.trigger}\n onClick={overlay.toggle}\n {...overlay.triggerProps}\n >\n {/* On the rail the mark is the button's only content, so it carries the\n accessible name (title); expanded, the name sits beside it and the\n mark is decorative, or a screen reader says the product twice.\n\n No `size`. ProductMark's default is 20px -- the Wallet's own size at\n its call sites, and the size all three appswitcher-* baselines were\n captured at. This passed 28 expanded and 32 collapsed until B33-479's\n fidelity pass: a mark 8px over budget in every row pushed the account\n row out of the open panel's frame entirely and truncated \"Boost\n Outbound\" to \"Boos...\". The rail is not a larger mark; it is the same\n mark with the label and chevron gone. */}\n <ProductMark slug={currentSlug} title={collapsed ? triggerName : undefined} />\n {!collapsed && (\n <>\n <span className={styles.triggerName}>{triggerName}</span>\n <Chevron className={styles.chevron} />\n </>\n )}\n </button>\n\n {overlay.open && (\n <div {...overlay.panelProps} className={styles.panel} aria-label={t.panel}>\n <Link className={styles.hubRow} href={hub.href} onClick={overlay.close}>\n <HubMark className={styles.hubMark} />\n <span className={styles.rowText}>\n <span className={styles.rowName}>{hub.name}</span>\n <span className={styles.rowSub}>{hub.description}</span>\n </span>\n </Link>\n\n <hr className={styles.divider} />\n\n <ul className={styles.products}>\n {products.map((product) => {\n const isCurrent = product.slug === currentSlug\n const notEntitled = product.entitled === false\n const notLive = product.live === false\n\n // One tag slot, and the unbuilt word wins it.\n //\n // A product that has not shipped is held by nobody, so Identity\n // serves every `live: false` row as `entitled: false` too and both\n // conditions are true at once on the same row. Rendering both tags\n // would stack two pills on a row with one tag's worth of space --\n // and the second would be answering a question the first has\n // already closed: \"not on plan\" is trivially true of a product\n // that does not exist, and saying it sends the reader off to ask\n // about buying something unbuyable (B33-561).\n //\n // The dim is deliberately shared rather than split. `muted` has\n // one meaning on the mark -- this row is not somewhere you can go\n // today -- and that is equally true either way; the tag is the\n // channel carrying *which* reason, because it is the one a screen\n // reader reaches and the mark is decorative here (ADR-0012).\n const tag = notLive ? t.soon : notEntitled ? t.notOnPlan : null\n\n return (\n <li key={product.slug}>\n <Link\n className={styles.productRow}\n href={product.href}\n aria-current={isCurrent ? 'true' : undefined}\n data-current={isCurrent ? 'true' : undefined}\n data-entitled={notEntitled ? 'false' : undefined}\n data-live={notLive ? 'false' : undefined}\n onClick={overlay.close}\n >\n {/* Default 20px, as in the trigger above -- see the note there. */}\n <ProductMark slug={product.slug} muted={notEntitled || notLive} />\n <span className={styles.rowName}>{product.name}</span>\n {tag !== null && <span className={styles.tag}>{tag}</span>}\n {isCurrent && <Check className={styles.check} />}\n </Link>\n </li>\n )\n })}\n </ul>\n\n <hr className={styles.divider} />\n\n <AccountRow account={account} />\n </div>\n )}\n </div>\n )\n}\n"],"names":["AccountRow","account","body","jsxs","Fragment","jsx","styles","ChevronRight","AppSwitcher","products","currentSlug","hub","labels","linkComponent","collapsed","defaultOpen","onOpenChange","overlay","useAnchoredOverlay","resolveAppSwitcherLabels","Link","current","p","triggerName","ProductMark","Chevron","HubMark","product","isCurrent","notEntitled","notLive","tag","Check"],"mappings":"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"}
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
const
|
|
2
|
-
root:
|
|
3
|
-
trigger:
|
|
4
|
-
railTrigger:
|
|
5
|
-
triggerName:
|
|
6
|
-
chevron:
|
|
1
|
+
const o = "_root_15f7d_8", _ = "_trigger_15f7d_17", r = "_railTrigger_15f7d_46", t = "_triggerName_15f7d_56", c = "_chevron_15f7d_67", e = "_panel_15f7d_109", a = "_hubRow_15f7d_135", d = "_productRow_15f7d_136", n = "_accountRow_15f7d_137", s = "_products_15f7d_144", g = "_rowName_15f7d_193", i = "_rowSub_15f7d_213", f = "_rowText_15f7d_238", u = "_hubMark_15f7d_281", h = "_tag_15f7d_297", w = "_check_15f7d_308", v = "_avatar_15f7d_321", R = "_chevronRight_15f7d_334", p = "_divider_15f7d_355", b = {
|
|
2
|
+
root: o,
|
|
3
|
+
trigger: _,
|
|
4
|
+
railTrigger: r,
|
|
5
|
+
triggerName: t,
|
|
6
|
+
chevron: c,
|
|
7
7
|
panel: e,
|
|
8
8
|
hubRow: a,
|
|
9
|
-
productRow:
|
|
10
|
-
accountRow:
|
|
11
|
-
products:
|
|
12
|
-
rowName:
|
|
13
|
-
rowSub:
|
|
14
|
-
rowText:
|
|
15
|
-
hubMark:
|
|
16
|
-
tag:
|
|
17
|
-
check:
|
|
18
|
-
avatar:
|
|
19
|
-
chevronRight:
|
|
20
|
-
divider:
|
|
9
|
+
productRow: d,
|
|
10
|
+
accountRow: n,
|
|
11
|
+
products: s,
|
|
12
|
+
rowName: g,
|
|
13
|
+
rowSub: i,
|
|
14
|
+
rowText: f,
|
|
15
|
+
hubMark: u,
|
|
16
|
+
tag: h,
|
|
17
|
+
check: w,
|
|
18
|
+
avatar: v,
|
|
19
|
+
chevronRight: R,
|
|
20
|
+
divider: p
|
|
21
21
|
};
|
|
22
22
|
export {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
n as accountRow,
|
|
24
|
+
v as avatar,
|
|
25
|
+
w as check,
|
|
26
|
+
c as chevron,
|
|
27
|
+
R as chevronRight,
|
|
28
|
+
b as default,
|
|
29
|
+
p as divider,
|
|
30
|
+
u as hubMark,
|
|
31
31
|
a as hubRow,
|
|
32
32
|
e as panel,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
d as productRow,
|
|
34
|
+
s as products,
|
|
35
|
+
r as railTrigger,
|
|
36
|
+
o as root,
|
|
37
|
+
g as rowName,
|
|
38
|
+
i as rowSub,
|
|
39
|
+
f as rowText,
|
|
40
|
+
h as tag,
|
|
41
|
+
_ as trigger,
|
|
42
|
+
t as triggerName
|
|
43
43
|
};
|
|
44
44
|
//# sourceMappingURL=AppSwitcher.module.css.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labels.js","sources":["../../../src/components/AppSwitcher/labels.ts"],"sourcesContent":["// The switcher's own user-facing words, each a prop with an English default --\n// the same pattern WalletChipLabels follows, and for the same reason: the first\n// two consumers are French-default, and the package renders what it is given\n// rather than taking an i18n dependency (CLAUDE.md; B33-849). The product names,\n// the Hub's name and blurb, and the account row's name and subtitle are not here\n// -- they are Identity's data, supplied per row on `products`, `hub` and\n// `account`, not copy this package owns.\n\n/** Every user-facing string the switcher renders that is not row data. */\nexport interface AppSwitcherLabels {\n /**\n * The panel's accessible name. A disclosure panel carries no widget role, so\n * it takes an `aria-label` to be named at all (ADR-0010). Default\n * \"Boost33 products\".\n */\n panel: string\n /**\n * The tag on a product the account is not entitled to. Not a gate -- the row\n * stays an ordinary link -- only a hint (ADR-0010: \"Not entitled is not\n * disabled\"). Default \"Not on plan\".\n */\n notOnPlan: string\n /**\n * The tag on a product that does not exist yet -- `live: false` on the row.\n * Default \"Soon\".\n *\n * It takes the same slot as `notOnPlan` and never sits beside it, because the\n * two answer different questions and only one of them is worth a person's\n * attention at a time. `live` is whether the product exists at all, globally;\n * `notOnPlan` is whether this account holds it. A product that has not shipped\n * is held by nobody, so an unbuilt row is *always* also an unentitled one --\n * which is why the unbuilt word wins the slot rather than stacking with it.\n *\n * Collapsing the two is the confusion Identity added the `live` field to\n * prevent (B33-561). With only `notOnPlan`, a product nobody can buy yet\n * renders as one this client has not bought, and the row invites them to go\n * and ask about something that does not exist.\n *\n * Like every other label here it is a prop with an English default, so the two\n * French-default consumers translate it rather than reading \"Soon\".\n */\n soon: string\n /**\n * The trigger's name when `currentSlug` matches no product in the supplied\n * list, so there is no product name to put there. Default \"Switch product\".\n *\n * That case is reachable by design: `AppSwitcherProduct.slug` is typed\n * `string` rather than `ProductSlug` so an unknown slug degrades to a bare\n * tile instead of failing (ADR-0003 -- Identity owns the registry and this\n * package does not know which slugs exist). A fifth Identity product, or a\n * `currentSlug` left stale by a rename, lands there. Before B33-919 the\n * degradation took the button's accessible name with it and the only control\n * in the collapsed shell announced nothing at all.\n *\n * It names the control rather than describing the missing product, because\n * the product is precisely what this package does not know. \"Switch product\"\n * is true in every state; a guess at the product's name would not be.\n */\n switchProduct: string\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders exactly what the\n * appswitcher-* baselines were captured showing, so those baselines do not move.\n * `switchProduct` is no exception: it only ever reaches the screen when\n * `currentSlug` matches nothing, and all three baselines were captured with the\n * Wallet current.\n *\n * `soon` is no exception either, for the same shape of reason: it reaches the\n * screen only on a row carrying `live: false`, and `live` defaults to true, so\n * the four rows the baselines were captured from still draw exactly the tag they\n * drew before the field existed. The Wallet never rendered a \"Soon\" tag at all\n * -- it had no registry to read `live` from -- so there is no captured baseline\n * for this state and `scripts/screenshot-states.ts` records the specimen row\n * that shows it as ungated, pending a human capture (CLAUDE.md).\n */\nexport const defaultAppSwitcherLabels: AppSwitcherLabels = {\n panel: 'Boost33 products',\n notOnPlan: 'Not on plan',\n soon: 'Soon',\n switchProduct: 'Switch product',\n}\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied -- the same resolution\n * `resolveWalletChipLabels` performs, for the same two reasons.\n *\n * The first is forward compatibility, and it is why this exists now. `labels`\n * used to be a whole `AppSwitcherLabels`, so a consumer wrote the object out in\n * full; adding `switchProduct` to a required-field interface would have failed\n * the typecheck of every app that had done so, turning an accessibility fix\n * into a breaking change across four products on a patch bump (CLAUDE.md,\n * \"Versioning\"). Taking a `Partial` means a consumer supplies the fields it\n * wants to translate and inherits the rest, today and at the next field.\n *\n * The second is that a missed translation key must fall back to English rather\n * than blank the string. A next-intl `t('...')` returns `undefined` on a miss,\n * so `{ panel: undefined }` is what one missing key looks like at the call\n * site. A plain object spread copies that `undefined` across and unnames the\n * panel; a per-field skip does not (B33-873).\n */\nexport function resolveAppSwitcherLabels(labels?: Partial<AppSwitcherLabels>): AppSwitcherLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<AppSwitcherLabels>\n return { ...defaultAppSwitcherLabels, ...supplied }\n}\n"],"names":["defaultAppSwitcherLabels","resolveAppSwitcherLabels","labels","supplied","value"],"mappings":"
|
|
1
|
+
{"version":3,"file":"labels.js","sources":["../../../src/components/AppSwitcher/labels.ts"],"sourcesContent":["// The switcher's own user-facing words, each a prop with an English default --\n// the same pattern WalletChipLabels follows, and for the same reason: the first\n// two consumers are French-default, and the package renders what it is given\n// rather than taking an i18n dependency (CLAUDE.md; B33-849). The product names,\n// the Hub's name and blurb, and the account row's name and subtitle are not here\n// -- they are Identity's data, supplied per row on `products`, `hub` and\n// `account`, not copy this package owns.\n//\n// No label here carries a value, so this module names no placeholder. The one a\n// future field would use is `%name%` and not `{name}`: a brace is ICU\n// MessageFormat argument syntax, and a consumer keeps these strings in an ICU\n// catalogue (B33-1277; the reasoning is in `src/fill-template.ts`). Written down\n// because B33-1007 is open about this module quietly diverging from the chip's\n// contract, and a delimiter is the easiest thing to re-decide wrongly.\n\n/** Every user-facing string the switcher renders that is not row data. */\nexport interface AppSwitcherLabels {\n /**\n * The panel's accessible name. A disclosure panel carries no widget role, so\n * it takes an `aria-label` to be named at all (ADR-0010). Default\n * \"Boost33 products\".\n */\n panel: string\n /**\n * The tag on a product the account is not entitled to. Not a gate -- the row\n * stays an ordinary link -- only a hint (ADR-0010: \"Not entitled is not\n * disabled\"). Default \"Not on plan\".\n */\n notOnPlan: string\n /**\n * The tag on a product that does not exist yet -- `live: false` on the row.\n * Default \"Soon\".\n *\n * It takes the same slot as `notOnPlan` and never sits beside it, because the\n * two answer different questions and only one of them is worth a person's\n * attention at a time. `live` is whether the product exists at all, globally;\n * `notOnPlan` is whether this account holds it. A product that has not shipped\n * is held by nobody, so an unbuilt row is *always* also an unentitled one --\n * which is why the unbuilt word wins the slot rather than stacking with it.\n *\n * Collapsing the two is the confusion Identity added the `live` field to\n * prevent (B33-561). With only `notOnPlan`, a product nobody can buy yet\n * renders as one this client has not bought, and the row invites them to go\n * and ask about something that does not exist.\n *\n * Like every other label here it is a prop with an English default, so the two\n * French-default consumers translate it rather than reading \"Soon\".\n */\n soon: string\n /**\n * The trigger's name when `currentSlug` matches no product in the supplied\n * list, so there is no product name to put there. Default \"Switch product\".\n *\n * That case is reachable by design: `AppSwitcherProduct.slug` is typed\n * `string` rather than `ProductSlug` so an unknown slug degrades to a bare\n * tile instead of failing (ADR-0003 -- Identity owns the registry and this\n * package does not know which slugs exist). A fifth Identity product, or a\n * `currentSlug` left stale by a rename, lands there. Before B33-919 the\n * degradation took the button's accessible name with it and the only control\n * in the collapsed shell announced nothing at all.\n *\n * It names the control rather than describing the missing product, because\n * the product is precisely what this package does not know. \"Switch product\"\n * is true in every state; a guess at the product's name would not be.\n */\n switchProduct: string\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders exactly what the\n * appswitcher-* baselines were captured showing, so those baselines do not move.\n * `switchProduct` is no exception: it only ever reaches the screen when\n * `currentSlug` matches nothing, and all three baselines were captured with the\n * Wallet current.\n *\n * `soon` is no exception either, for the same shape of reason: it reaches the\n * screen only on a row carrying `live: false`, and `live` defaults to true, so\n * the four rows the baselines were captured from still draw exactly the tag they\n * drew before the field existed. The Wallet never rendered a \"Soon\" tag at all\n * -- it had no registry to read `live` from -- so there is no captured baseline\n * for this state and `scripts/screenshot-states.ts` records the specimen row\n * that shows it as ungated, pending a human capture (CLAUDE.md).\n */\nexport const defaultAppSwitcherLabels: AppSwitcherLabels = {\n panel: 'Boost33 products',\n notOnPlan: 'Not on plan',\n soon: 'Soon',\n switchProduct: 'Switch product',\n}\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied -- the same resolution\n * `resolveWalletChipLabels` performs, for the same two reasons.\n *\n * The first is forward compatibility, and it is why this exists now. `labels`\n * used to be a whole `AppSwitcherLabels`, so a consumer wrote the object out in\n * full; adding `switchProduct` to a required-field interface would have failed\n * the typecheck of every app that had done so, turning an accessibility fix\n * into a breaking change across four products on a patch bump (CLAUDE.md,\n * \"Versioning\"). Taking a `Partial` means a consumer supplies the fields it\n * wants to translate and inherits the rest, today and at the next field.\n *\n * The second is that a missed translation key must fall back to English rather\n * than blank the string. A next-intl `t('...')` returns `undefined` on a miss,\n * so `{ panel: undefined }` is what one missing key looks like at the call\n * site. A plain object spread copies that `undefined` across and unnames the\n * panel; a per-field skip does not (B33-873).\n */\nexport function resolveAppSwitcherLabels(labels?: Partial<AppSwitcherLabels>): AppSwitcherLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<AppSwitcherLabels>\n return { ...defaultAppSwitcherLabels, ...supplied }\n}\n"],"names":["defaultAppSwitcherLabels","resolveAppSwitcherLabels","labels","supplied","value"],"mappings":"AAmFO,MAAMA,IAA8C;AAAA,EACzD,OAAO;AAAA,EACP,WAAW;AAAA,EACX,MAAM;AAAA,EACN,eAAe;AACjB;AAqBO,SAASC,EAAyBC,GAAwD;AAC/F,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA;AAExE,SAAO,EAAE,GAAGJ,GAA0B,GAAGG,EAAA;AAC3C;"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ElementType, ReactNode } from 'react';
|
|
2
|
+
import { CommandPaletteLabels } from './labels';
|
|
3
|
+
/**
|
|
4
|
+
* One row in the palette. The host owns everything about it: its words, where it
|
|
5
|
+
* goes, and the glyph in its leading slot.
|
|
6
|
+
*
|
|
7
|
+
* There is no `kind` field and there will not be one. The artboard draws three
|
|
8
|
+
* kinds of row -- a page, a product, an action -- but the groups they sit in are
|
|
9
|
+
* the host's and arbitrary, so a `kind` would be this package deciding what a
|
|
10
|
+
* host's group *means*, which is the product meaning CLAUDE.md keeps out. A row
|
|
11
|
+
* that draws a product tile draws it because the host passed
|
|
12
|
+
* `icon={<ProductMark slug="boostscore" />}`, not because the palette recognised
|
|
13
|
+
* anything.
|
|
14
|
+
*/
|
|
15
|
+
export interface CommandPaletteResult {
|
|
16
|
+
/**
|
|
17
|
+
* Stable within one palette. It is the row's React key and, composed with the
|
|
18
|
+
* palette's own `useId` base, the DOM id `aria-activedescendant` points at --
|
|
19
|
+
* so two results sharing an id would leave the highlight ambiguous.
|
|
20
|
+
*/
|
|
21
|
+
id: string;
|
|
22
|
+
/** The row's visible text, e.g. "Buy tokens". The host's, never defaulted. */
|
|
23
|
+
label: string;
|
|
24
|
+
/**
|
|
25
|
+
* Where choosing this row goes. A result with an `href` navigates and renders
|
|
26
|
+
* through `linkComponent`; one without is an action and renders as a button.
|
|
27
|
+
*
|
|
28
|
+
* The button has no handler in Stage A. Activation -- Enter in the input, or a
|
|
29
|
+
* click -- is interaction, and interaction is Stage B.
|
|
30
|
+
*/
|
|
31
|
+
href?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The glyph in the row's 22px leading slot. Supplied by the host, because what
|
|
34
|
+
* a row *is* is the host's business (see the note on this interface).
|
|
35
|
+
*
|
|
36
|
+
* The slot is drawn whether or not a row has one, so labels line up in a
|
|
37
|
+
* single lane down the panel -- the same reason NavItem's trailing slot is
|
|
38
|
+
* fixed-width (CONTEXT.md, "Trailing slot").
|
|
39
|
+
*/
|
|
40
|
+
icon?: ReactNode;
|
|
41
|
+
}
|
|
42
|
+
/** One titled run of results -- the artboard's Pages, Products and Actions. */
|
|
43
|
+
export interface CommandPaletteGroup {
|
|
44
|
+
/** Stable within one palette; the group's React key and its heading's DOM id. */
|
|
45
|
+
id: string;
|
|
46
|
+
/**
|
|
47
|
+
* The group's uppercase label, e.g. "Pages". The host's words, not this
|
|
48
|
+
* package's: the palette owns the grouping chrome and not the grouping.
|
|
49
|
+
*
|
|
50
|
+
* **Omit it and the group draws its rows with no label at all.** That is the
|
|
51
|
+
* state the artboard's annotation describes -- "before a search starts, the
|
|
52
|
+
* same list shows Recent pages instead of grouped results, same shell, no
|
|
53
|
+
* group labels needed" -- expressed as the absence of a heading rather than as
|
|
54
|
+
* a second mode. One prop, not a variant (CLAUDE.md, "build-once components").
|
|
55
|
+
*/
|
|
56
|
+
heading?: string;
|
|
57
|
+
/** The rows, in the order the host wants them read. An empty group is skipped. */
|
|
58
|
+
results: CommandPaletteResult[];
|
|
59
|
+
}
|
|
60
|
+
export interface CommandPaletteProps {
|
|
61
|
+
/**
|
|
62
|
+
* The grouped results, in the order they should appear.
|
|
63
|
+
*
|
|
64
|
+
* **The palette takes these as props and owns no index.** That is the decision
|
|
65
|
+
* recorded on B33-495 on 2026-10-06: Pages, Products and Actions are all the
|
|
66
|
+
* host's, there is no cross-suite search index in any repo, and this component
|
|
67
|
+
* must not imply one. It is scope-agnostic by construction -- four products'
|
|
68
|
+
* worth of results render exactly as one product's do -- so a real index later
|
|
69
|
+
* changes what is passed in and not what is here.
|
|
70
|
+
*/
|
|
71
|
+
groups: CommandPaletteGroup[];
|
|
72
|
+
/**
|
|
73
|
+
* What is in the search field. Rendered as the input's initial value.
|
|
74
|
+
*
|
|
75
|
+
* Uncontrolled on purpose for now: a `value` with no `onChange` is a React
|
|
76
|
+
* warning and a field nobody can type in, and the handler is Stage B. The
|
|
77
|
+
* element and its markup do not change when the wiring arrives.
|
|
78
|
+
*/
|
|
79
|
+
query?: string;
|
|
80
|
+
/**
|
|
81
|
+
* The `id` of the highlighted result, if any.
|
|
82
|
+
*
|
|
83
|
+
* Settable from day one for the same reason `useAnchoredOverlay` takes `open`:
|
|
84
|
+
* it is what lets a specimen route draw the highlighted row statically, which
|
|
85
|
+
* is Stage A rather than wiring (ADR-0009). What *moves* the highlight is
|
|
86
|
+
* Stage B. That the palette has a highlight at all, rather than moving DOM
|
|
87
|
+
* focus between rows, is settled -- ADR-0010 puts it on the combobox pattern
|
|
88
|
+
* with `aria-activedescendant`, and B33-693 is closed.
|
|
89
|
+
*
|
|
90
|
+
* An id matching no result is a supported state and simply draws no highlight.
|
|
91
|
+
*/
|
|
92
|
+
activeResultId?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Every user-facing string that is not result data. Each field defaults to
|
|
95
|
+
* English, and a field left out -- or supplied as `undefined`, which is what a
|
|
96
|
+
* missed next-intl key looks like -- keeps its default (B33-873).
|
|
97
|
+
*/
|
|
98
|
+
labels?: Partial<CommandPaletteLabels>;
|
|
99
|
+
/**
|
|
100
|
+
* The element a navigating result renders through. Defaults to a plain `a`; a
|
|
101
|
+
* router-owning consumer passes its own so rows route client-side with the
|
|
102
|
+
* locale prefix intact (ADR-0011, B33-847).
|
|
103
|
+
*
|
|
104
|
+
* Consulted only for the linked form, as ADR-0011 requires: a result with no
|
|
105
|
+
* `href` is an action and stays a `<button>`, and `linkComponent` is ignored
|
|
106
|
+
* on it rather than rendered.
|
|
107
|
+
*/
|
|
108
|
+
linkComponent?: ElementType;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The command palette: a search field over grouped results, opened from the top
|
|
112
|
+
* bar's search button or `⌘K` (B33-495).
|
|
113
|
+
*
|
|
114
|
+
* **Stage A. Static, and not yet through its fidelity gate.** It holds no state,
|
|
115
|
+
* wires no handler and registers no shortcut. It is not the modal variant yet
|
|
116
|
+
* either: ADR-0009 folds this component into the one dismissal contract behind a
|
|
117
|
+
* `modal` flag and it is the only one of the four anchored overlays that sets
|
|
118
|
+
* it, but modality *is* interaction -- the focus trap, the `inert` background
|
|
119
|
+
* and backdrop dismissal all arrive with the wiring.
|
|
120
|
+
*
|
|
121
|
+
* A combobox over a listbox, not a disclosure (ADR-0010): the input carries
|
|
122
|
+
* `role="combobox"` and names the highlighted row through
|
|
123
|
+
* `aria-activedescendant`, the list is a `listbox`, each result an `option` and
|
|
124
|
+
* each group a `group` with its own label. That markup is what the ADR settles;
|
|
125
|
+
* what the arrow keys do inside it is Stage B's to implement.
|
|
126
|
+
*/
|
|
127
|
+
export declare function CommandPalette({ groups, query, activeResultId, labels, linkComponent, }: CommandPaletteProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The magnifier at the head of the search row. 16px, a 5px-radius lens with a
|
|
3
|
+
* handle -- read off the SearchRow's own SVG on the artboard rather than lifted
|
|
4
|
+
* from an icon set, so it matches the field it sits in.
|
|
5
|
+
*/
|
|
6
|
+
export declare function SearchGlyph({ className }: {
|
|
7
|
+
className?: string;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Every user-facing string the palette renders that is not result data. */
|
|
2
|
+
export interface CommandPaletteLabels {
|
|
3
|
+
/**
|
|
4
|
+
* The search input's accessible name. The artboard draws no visible label --
|
|
5
|
+
* the magnifier beside the field is decorative -- so this is the only thing
|
|
6
|
+
* naming the only control in the panel. Default "Search".
|
|
7
|
+
*/
|
|
8
|
+
search: string;
|
|
9
|
+
/**
|
|
10
|
+
* The search input's placeholder, shown before anything is typed. Default
|
|
11
|
+
* "Search pages, products and actions".
|
|
12
|
+
*
|
|
13
|
+
* Deliberately not "search everything" or "search the suite". The palette
|
|
14
|
+
* renders whatever groups the host passes, and today that is one product's
|
|
15
|
+
* results rather than four (B33-495, the 2026-10-06 decision). A placeholder
|
|
16
|
+
* promising a cross-suite index would be the component claiming a scope it
|
|
17
|
+
* does not have.
|
|
18
|
+
*/
|
|
19
|
+
placeholder: string;
|
|
20
|
+
/**
|
|
21
|
+
* The key badge at the right of the search row, which the artboard draws
|
|
22
|
+
* reading "esc". Default "esc".
|
|
23
|
+
*
|
|
24
|
+
* A label rather than a literal because it is a word on screen, and locales do
|
|
25
|
+
* not all legend that key the same way -- a French keyboard reads "échap".
|
|
26
|
+
* What it does is Stage B's; this is only what it says.
|
|
27
|
+
*/
|
|
28
|
+
dismissKey: string;
|
|
29
|
+
/**
|
|
30
|
+
* The results list's accessible name. The list is a `listbox` (ADR-0010) and
|
|
31
|
+
* takes a name like any other widget. Default "Results".
|
|
32
|
+
*/
|
|
33
|
+
results: string;
|
|
34
|
+
/**
|
|
35
|
+
* The line shown in place of the list when no group holds a result. Default
|
|
36
|
+
* "No results".
|
|
37
|
+
*
|
|
38
|
+
* **The artboard does not draw this state**, which is worth saying plainly.
|
|
39
|
+
* Its empty-state annotation describes the *pre-search* case -- "before a
|
|
40
|
+
* search starts, the same list shows Recent pages instead of grouped results"
|
|
41
|
+
* -- and that case is already expressible without new copy: the host passes one
|
|
42
|
+
* group with no heading. A list that genuinely matches nothing is a different
|
|
43
|
+
* state, it is reachable the moment anyone types, and a panel that answers it
|
|
44
|
+
* with blank space is not finished. So this exists, and the person at the
|
|
45
|
+
* fidelity gate owns whether the words and the treatment are right.
|
|
46
|
+
*/
|
|
47
|
+
empty: string;
|
|
48
|
+
/**
|
|
49
|
+
* The footer's keyboard legend, supplied whole. Default
|
|
50
|
+
* "↑↓ Navigate ↵ Select esc Close".
|
|
51
|
+
*
|
|
52
|
+
* One field rather than six (three key glyphs and three verbs) for the reason
|
|
53
|
+
* `WalletChipLabels` gives for its templates: word order is not a property of
|
|
54
|
+
* English, and a legend reassembled from fragments on the consumer's side
|
|
55
|
+
* cannot be translated. A locale is entitled to put the glyph after the verb,
|
|
56
|
+
* to separate the pairs differently, or to drop a hint it has no key for. The
|
|
57
|
+
* artboard draws it as a single text run, so one string is also what it is.
|
|
58
|
+
*/
|
|
59
|
+
footerHints: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The English defaults.
|
|
63
|
+
*
|
|
64
|
+
* Frozen, and with no type annotation. `const` protects the binding and not the
|
|
65
|
+
* contents, and this is a public export reaching four products on a version
|
|
66
|
+
* bump: without the freeze a consumer aliasing it -- `const mine =
|
|
67
|
+
* defaultCommandPaletteLabels; mine.empty = 'Aucun résultat'` -- renames every
|
|
68
|
+
* palette in the process (B33-877). The annotation is left off on purpose too:
|
|
69
|
+
* `: CommandPaletteLabels` on a frozen object widens `Readonly` back to mutable
|
|
70
|
+
* in the emitted `.d.ts`, so `satisfies` carries the checking instead (B33-916,
|
|
71
|
+
* and B33-1007 asking that the two exports not drift again).
|
|
72
|
+
*/
|
|
73
|
+
export declare const defaultCommandPaletteLabels: Readonly<{
|
|
74
|
+
search: string;
|
|
75
|
+
placeholder: string;
|
|
76
|
+
dismissKey: string;
|
|
77
|
+
results: string;
|
|
78
|
+
empty: string;
|
|
79
|
+
footerHints: string;
|
|
80
|
+
}>;
|
|
81
|
+
/**
|
|
82
|
+
* Merge a consumer's partial `labels` onto the English defaults, treating an
|
|
83
|
+
* explicitly-`undefined` field as unsupplied.
|
|
84
|
+
*
|
|
85
|
+
* The same resolution `resolveWalletChipLabels` performs, for the same two
|
|
86
|
+
* reasons. A `Partial` means a consumer translates the fields it cares about and
|
|
87
|
+
* inherits the rest, today and at the next field added -- so a new label is not
|
|
88
|
+
* a breaking change across four products on a patch bump. And a next-intl
|
|
89
|
+
* `t('...')` returns `undefined` on a missed key, so `{ search: undefined }` is
|
|
90
|
+
* what one missing translation looks like at the call site: a plain spread
|
|
91
|
+
* copies that `undefined` across and leaves the palette's only control with no
|
|
92
|
+
* accessible name at all, which is B33-873 exactly. A per-field skip does not.
|
|
93
|
+
*/
|
|
94
|
+
export declare function resolveCommandPaletteLabels(labels?: Partial<CommandPaletteLabels>): CommandPaletteLabels;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface DotMatrixLoaderProps {
|
|
2
|
+
/**
|
|
3
|
+
* The loader's accessible name. Defaults to `"Loading"`, which keeps a loader
|
|
4
|
+
* standing on its own correct: it is announced as an indeterminate
|
|
5
|
+
* progressbar with that name.
|
|
6
|
+
*
|
|
7
|
+
* Pass `null` when visible words beside it already say what is happening.
|
|
8
|
+
* The grid then renders `aria-hidden="true"` with no role, no name and no
|
|
9
|
+
* hidden duplicate text -- the dots say nothing the words do not, and a name
|
|
10
|
+
* on both is the same thing read twice. LaunchLoader passes `null`.
|
|
11
|
+
*/
|
|
12
|
+
label?: string | null;
|
|
13
|
+
/** Sets `color`, which every dot draws in. Omit to inherit it from an ancestor. */
|
|
14
|
+
color?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A 3x3 grid of dots pulsing in a diagonal wave. Pure CSS: no state, no
|
|
19
|
+
* effect, no animation runtime, so it renders and animates from server HTML.
|
|
20
|
+
*/
|
|
21
|
+
export declare function DotMatrixLoader({ label, color, className }: DotMatrixLoaderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as t } from "react/jsx-runtime";
|
|
2
|
+
import a from "./DotMatrixLoader.module.css.js";
|
|
3
|
+
const n = Array.from({ length: 9 }, (o, r) => r);
|
|
4
|
+
function m({ label: o = "Loading", color: r, className: s }) {
|
|
5
|
+
return /* @__PURE__ */ t(
|
|
6
|
+
"span",
|
|
7
|
+
{
|
|
8
|
+
...o === null ? { "aria-hidden": !0 } : { role: "progressbar", "aria-label": o },
|
|
9
|
+
className: s ? `${a.root} ${s}` : a.root,
|
|
10
|
+
style: r ? { color: r } : void 0,
|
|
11
|
+
children: /* @__PURE__ */ t("span", { className: a.grid, children: n.map((e) => /* @__PURE__ */ t("span", { className: a.dot }, e)) })
|
|
12
|
+
}
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
m as DotMatrixLoader
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=DotMatrixLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DotMatrixLoader.js","sources":["../../../src/components/DotMatrixLoader/DotMatrixLoader.tsx"],"sourcesContent":["import styles from './DotMatrixLoader.module.css'\n\nexport interface DotMatrixLoaderProps {\n /**\n * The loader's accessible name. Defaults to `\"Loading\"`, which keeps a loader\n * standing on its own correct: it is announced as an indeterminate\n * progressbar with that name.\n *\n * Pass `null` when visible words beside it already say what is happening.\n * The grid then renders `aria-hidden=\"true\"` with no role, no name and no\n * hidden duplicate text -- the dots say nothing the words do not, and a name\n * on both is the same thing read twice. LaunchLoader passes `null`.\n */\n label?: string | null\n /** Sets `color`, which every dot draws in. Omit to inherit it from an ancestor. */\n color?: string\n className?: string\n}\n\nconst DOTS = Array.from({ length: 9 }, (_, i) => i)\n\n/**\n * A 3x3 grid of dots pulsing in a diagonal wave. Pure CSS: no state, no\n * effect, no animation runtime, so it renders and animates from server HTML.\n */\nexport function DotMatrixLoader({ label = 'Loading', color, className }: DotMatrixLoaderProps) {\n // role=\"progressbar\" with no aria-valuenow is the indeterminate form. It\n // takes the name from aria-label alone; there is no visually-hidden text node\n // as well, which is how the beUI source came to announce \"Loading\" twice.\n const a11y =\n label === null\n ? ({ 'aria-hidden': true } as const)\n : ({ role: 'progressbar', 'aria-label': label } as const)\n\n return (\n <span\n {...a11y}\n className={className ? `${styles.root} ${className}` : styles.root}\n style={color ? { color } : undefined}\n >\n <span className={styles.grid}>\n {DOTS.map((i) => (\n <span key={i} className={styles.dot} />\n ))}\n </span>\n </span>\n )\n}\n"],"names":["DOTS","_","i","DotMatrixLoader","label","color","className","jsx","styles"],"mappings":";;AAmBA,MAAMA,IAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,CAACC,GAAGC,MAAMA,CAAC;AAM3C,SAASC,EAAgB,EAAE,OAAAC,IAAQ,WAAW,OAAAC,GAAO,WAAAC,KAAmC;AAS7F,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GANHH,MAAU,OACL,EAAE,eAAe,GAAA,IACjB,EAAE,MAAM,eAAe,cAAcA,EAAA;AAAA,MAKxC,WAAWE,IAAY,GAAGE,EAAO,IAAI,IAAIF,CAAS,KAAKE,EAAO;AAAA,MAC9D,OAAOH,IAAQ,EAAE,OAAAA,EAAA,IAAU;AAAA,MAE3B,4BAAC,QAAA,EAAK,WAAWG,EAAO,MACrB,YAAK,IAAI,CAACN,MACT,gBAAAK,EAAC,UAAa,WAAWC,EAAO,IAAA,GAArBN,CAA0B,CACtC,EAAA,CACH;AAAA,IAAA;AAAA,EAAA;AAGN;"}
|