@boost33/ui 0.7.1 → 0.8.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +183 -0
  2. package/dist/buttons.module.css.js +10 -0
  3. package/dist/buttons.module.css.js.map +1 -0
  4. package/dist/components/AppShell/AppShell.d.ts +85 -17
  5. package/dist/components/AppShell/AppShell.js +75 -50
  6. package/dist/components/AppShell/AppShell.js.map +1 -1
  7. package/dist/components/AppShell/AppShell.module.css.js +22 -16
  8. package/dist/components/AppShell/AppShell.module.css.js.map +1 -1
  9. package/dist/components/AppSwitcher/AppSwitcher.d.ts +90 -45
  10. package/dist/components/AppSwitcher/AppSwitcher.js +71 -66
  11. package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
  12. package/dist/components/AppSwitcher/AppSwitcher.module.css.js +42 -36
  13. package/dist/components/AppSwitcher/AppSwitcher.module.css.js.map +1 -1
  14. package/dist/components/AppSwitcher/icons.js +10 -10
  15. package/dist/components/AppSwitcher/icons.js.map +1 -1
  16. package/dist/components/AppSwitcher/labels.d.ts +11 -22
  17. package/dist/components/AppSwitcher/labels.js.map +1 -1
  18. package/dist/components/CommandPalette/CommandPalette.d.ts +8 -2
  19. package/dist/components/CommandPalette/CommandPalette.js.map +1 -1
  20. package/dist/components/NavItem/NavItem.d.ts +15 -2
  21. package/dist/components/NavItem/NavItem.js +14 -11
  22. package/dist/components/NavItem/NavItem.js.map +1 -1
  23. package/dist/components/NavItem/NavItem.module.css.js +6 -2
  24. package/dist/components/NavItem/NavItem.module.css.js.map +1 -1
  25. package/dist/components/NavSection/NavSection.module.css.js +5 -5
  26. package/dist/components/NotificationButton/NotificationButton.d.ts +29 -0
  27. package/dist/components/NotificationButton/NotificationButton.js +23 -0
  28. package/dist/components/NotificationButton/NotificationButton.js.map +1 -0
  29. package/dist/components/NotificationButton/NotificationButton.module.css.js +14 -0
  30. package/dist/components/NotificationButton/NotificationButton.module.css.js.map +1 -0
  31. package/dist/components/NotificationButton/labels.d.ts +12 -0
  32. package/dist/components/NotificationButton/labels.js +15 -0
  33. package/dist/components/NotificationButton/labels.js.map +1 -0
  34. package/dist/components/NotificationCenter/NotificationCenter.js +37 -36
  35. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
  36. package/dist/components/NotificationCenter/NotificationCenter.module.css.js +43 -43
  37. package/dist/components/Sidebar/NavHeightBudgetWarning.js +2 -2
  38. package/dist/components/Sidebar/NavHeightBudgetWarning.js.map +1 -1
  39. package/dist/components/Sidebar/Sidebar.module.css.js +1 -1
  40. package/dist/components/TopBar/TopBar.d.ts +54 -4
  41. package/dist/components/TopBar/TopBar.js +22 -7
  42. package/dist/components/TopBar/TopBar.js.map +1 -1
  43. package/dist/components/TopBar/TopBar.module.css.js +12 -8
  44. package/dist/components/TopBar/TopBar.module.css.js.map +1 -1
  45. package/dist/components/TopBar/labels.d.ts +20 -0
  46. package/dist/components/TopBar/labels.js +15 -0
  47. package/dist/components/TopBar/labels.js.map +1 -0
  48. package/dist/components/WalletChip/WalletChip.d.ts +77 -2
  49. package/dist/components/WalletChip/WalletChip.js +142 -50
  50. package/dist/components/WalletChip/WalletChip.js.map +1 -1
  51. package/dist/components/WalletChip/WalletChip.module.css.js +93 -47
  52. package/dist/components/WalletChip/WalletChip.module.css.js.map +1 -1
  53. package/dist/components/WalletChip/labels.d.ts +63 -1
  54. package/dist/components/WalletChip/labels.js +21 -6
  55. package/dist/components/WalletChip/labels.js.map +1 -1
  56. package/dist/index.d.ts +9 -3
  57. package/dist/index.js +55 -49
  58. package/dist/index.js.map +1 -1
  59. package/dist/nav-height-budget.d.ts +12 -9
  60. package/dist/nav-height-budget.js +3 -3
  61. package/dist/nav-height-budget.js.map +1 -1
  62. package/dist/style.css +1 -1
  63. package/dist/theme.css +1 -1
  64. package/package.json +1 -1
@@ -3,9 +3,13 @@ import { AppSwitcherLabels } from './labels';
3
3
  /**
4
4
  * One product the switcher can jump to. It is Identity's registry row, supplied
5
5
  * by the consuming app (ADR-0003) -- the switcher fetches nothing and hardcodes
6
- * no list. Every product is rendered, entitled or not and built or not: hiding
7
- * an unowned one means nobody learns the family exists (B33-479), and hiding an
8
- * unbuilt one means nobody learns it is coming.
6
+ * no list. Every product that exists is rendered, entitled or not: hiding an
7
+ * unowned one means nobody learns the family exists (B33-479), and it tells a
8
+ * client who could buy a product that the product is not there.
9
+ *
10
+ * A product that does not exist yet is not rendered (B33-1711). Until 0.8 it
11
+ * drew with a "Soon" tag; the navigation redesign of 3 Nov 2026 took unbuilt
12
+ * products off the switcher, the same way they came off the hub. See `live`.
9
13
  */
10
14
  export interface AppSwitcherProduct {
11
15
  /** Identity's product slug, which selects the ProductMark artwork. */
@@ -22,8 +26,23 @@ export interface AppSwitcherProduct {
22
26
  */
23
27
  entitled?: boolean;
24
28
  /**
25
- * Whether the product exists yet. Defaults to `true`. `false` dims the mark and
26
- * shows the "Soon" tag in place of "Not on plan".
29
+ * Whether the product exists yet. Defaults to `true`. `false` leaves the row
30
+ * out of the panel entirely (B33-1711), unless it is the product this shell is
31
+ * rendering for -- the current product is always listed, since you are in it.
32
+ *
33
+ * **Only this hides a row. `entitled: false` never does.** The two answer
34
+ * different questions, and the difference is commercial: a product the account
35
+ * could buy is real, and leaving it out tells a paying client it does not
36
+ * exist. That rule is the hub's as well (boost33-hub CLAUDE.md, `cardState`),
37
+ * and the switcher follows it.
38
+ *
39
+ * Until 0.8 an unbuilt row was drawn with a "Soon" tag. The 3 Nov 2026
40
+ * navigation redesign (B33-1710) removed unbuilt products from the switcher,
41
+ * which overrules the Vocabulary document's "the switcher shows every product
42
+ * regardless of state"; that document is amended alongside this change.
43
+ *
44
+ * What follows is why `live` is a second field rather than a third value of
45
+ * `entitled`, which still holds.
27
46
  *
28
47
  * **A different question from `entitled`, which is why it is a second field
29
48
  * and not a third value of the first.** `live` is global: the same answer for
@@ -38,14 +57,50 @@ export interface AppSwitcherProduct {
38
57
  * told a client they had not bought Boost Voice, when nobody has: the row read
39
58
  * "Not on plan" and pointed at a product with no code behind it.
40
59
  *
41
- * It stays a rendering hint like `entitled`. An unbuilt product's row is still
42
- * an ordinary focusable link -- the switcher does not decide what anyone may
43
- * open (ADR-0010), and `url` is served for every slug whether or not anything
44
- * answers on it, because `?app=` has to resolve for a campaign link.
60
+ * Hiding the row is a rendering decision, not an access one. The switcher
61
+ * still does not decide what anyone may open (ADR-0010), and `url` is still
62
+ * served for every slug, because `?app=` has to resolve for a campaign link.
45
63
  */
46
64
  live?: boolean;
65
+ /**
66
+ * The heading of the section this row sits under, in the consumer's language
67
+ * -- "Your tools", "Included with every account" (B33-1711, board 21). Rows
68
+ * are grouped by this label in the order the labels first appear, and each
69
+ * group gets a header. Leave it off every row and the panel is one plain list
70
+ * with no headers; rows without one, in a panel where others have one, form a
71
+ * headerless group of their own.
72
+ *
73
+ * Data the consumer is served, never decided here: which products are "your
74
+ * tools" is the account's grant and Identity's registry, and the package
75
+ * knows neither (CLAUDE.md -- no component knows what a product means).
76
+ */
77
+ group?: string;
78
+ /**
79
+ * One thing at the right of the row (B33-1711, board 21) -- a figure such as
80
+ * the Wallet's balance, or a dot saying there is something new. The consumer
81
+ * supplies it already formatted; the switcher fetches nothing and formats
82
+ * nothing. The current product's check takes the same slot, so a trailing on
83
+ * the current row is not drawn.
84
+ */
85
+ trailing?: AppSwitcherTrailing;
47
86
  }
48
- /** The "all products" row above the divider. Its blurb and URL are the consumer's. */
87
+ /**
88
+ * The right-hand slot of a product row, in its two shapes.
89
+ *
90
+ * - `value` -- a short preformatted figure, drawn muted in the mono face:
91
+ * `{ kind: 'value', value: '8,940' }`.
92
+ * - `dot` -- a small primary dot meaning "something new here". A dot is a
93
+ * colour and nothing else, so `label` is required: it is what a screen reader
94
+ * hears instead ("2 new"), in the consumer's language.
95
+ */
96
+ export type AppSwitcherTrailing = {
97
+ kind: 'value';
98
+ value: string;
99
+ } | {
100
+ kind: 'dot';
101
+ label: string;
102
+ };
103
+ /** The "all products" row at the top of the panel. Its blurb and URL are the consumer's. */
49
104
  export interface AppSwitcherHub {
50
105
  /** The row's name, e.g. "Hub". */
51
106
  name: string;
@@ -55,9 +110,14 @@ export interface AppSwitcherHub {
55
110
  href: string;
56
111
  }
57
112
  /**
58
- * The account/workspace row at the foot of the panel. A row that opens a second
59
- * panel (a later ticket owns that panel), so it is a button rather than a link;
60
- * this ticket renders the row and calls `onSelect` when it is chosen.
113
+ * The account/workspace row that sat at the foot of the panel until 0.8.0.
114
+ *
115
+ * **Not drawn since 0.8.0.** The 3 Nov 2026 redesign (B33-1710, board 21) took
116
+ * the account row off the switcher: the organisation now lives in the top bar's
117
+ * left-hand slot, as the consumer's own pill (B33-1712). The type stays so a
118
+ * consumer's existing `account` object still compiles; nothing reads it.
119
+ *
120
+ * @deprecated Rendered nowhere since 0.8.0 (B33-1711). Stop passing `account`.
61
121
  */
62
122
  export interface AppSwitcherAccount {
63
123
  /** The organisation's name, e.g. "Acme Coaching". */
@@ -66,35 +126,15 @@ export interface AppSwitcherAccount {
66
126
  initials: string;
67
127
  /** The line beneath the name, preformatted, e.g. "Admin · 2 workspaces". */
68
128
  description: string;
69
- /**
70
- * Chosen -- opens the workspace panel.
71
- *
72
- * **It is also what makes the row a control.** Supplied, the row is the
73
- * `<button>` it has always been, with the chevron pointing at the panel it
74
- * opens. Omitted, the row still draws -- the organisation and its workspace
75
- * count are facts about the account whether or not anything opens -- but as a
76
- * plain `<div>`: no chevron, no hover step, no tab stop (B33-1270).
77
- *
78
- * Both consumers ship it unarmed today, because the panel it would open is
79
- * B33-493's and unbuilt: see the comment on `account` in
80
- * `boostscore/components/app-shell/product-switcher.tsx` ("the Wallet's
81
- * migration left the same row inert for the same reason"). Before this gate
82
- * existed they got a button that swallowed a click and did nothing, and no way
83
- * to reach it -- `.accountRow`'s class name is CSS-Modules-hashed.
84
- *
85
- * The whole row is not removed, and that is deliberate. ADR-0010's "Not
86
- * entitled is not disabled" is the nearest settled reasoning: a row that
87
- * carries information is not the component's to withhold, and `aria-disabled`
88
- * does not belong on something nobody can act on anyway. So the information
89
- * stays and the affordances go. Nor is the button `disabled` -- that is still
90
- * a control a consumer cannot remove.
91
- */
129
+ /** Was: chosen -- opens the workspace panel. Never called since 0.8.0. */
92
130
  onSelect?: () => void;
93
131
  }
94
132
  export interface AppSwitcherProps {
95
133
  /**
96
- * The products, in the order Identity serves them. Rendered whole -- entitled
97
- * or not, built or not.
134
+ * The products, in the order Identity serves them. Pass the registry whole:
135
+ * the switcher leaves out the rows with `live: false` itself (B33-1711), so a
136
+ * consumer does not filter, and every product that exists is drawn whether
137
+ * the account holds it or not.
98
138
  */
99
139
  products: AppSwitcherProduct[];
100
140
  /**
@@ -109,8 +149,14 @@ export interface AppSwitcherProps {
109
149
  currentSlug: string;
110
150
  /** The "all products" row above the products. */
111
151
  hub: AppSwitcherHub;
112
- /** The account/workspace row below the products. */
113
- account: AppSwitcherAccount;
152
+ /**
153
+ * Was the account/workspace row below the products. Optional and unread since
154
+ * 0.8.0, when the redesign took that row off the panel (B33-1711).
155
+ *
156
+ * @deprecated Rendered nowhere since 0.8.0. The organisation goes in the top
157
+ * bar's slot instead (B33-1712).
158
+ */
159
+ account?: AppSwitcherAccount;
114
160
  /**
115
161
  * Every user-facing string that is not row data. Each field defaults to
116
162
  * English, and a field left out -- or supplied as `undefined`, which is what
@@ -120,8 +166,7 @@ export interface AppSwitcherProps {
120
166
  /**
121
167
  * The element the product and hub rows render through. Defaults to a plain
122
168
  * `a`; a router-owning consumer passes its own so the rows route client-side
123
- * with the locale prefix intact (ADR-0011, B33-847). The account row stays a
124
- * button -- it opens a panel, it does not navigate.
169
+ * with the locale prefix intact (ADR-0011, B33-847).
125
170
  */
126
171
  linkComponent?: ElementType;
127
172
  /** The 56px rail. The trigger collapses to the current product's mark alone. */
@@ -132,8 +177,8 @@ export interface AppSwitcherProps {
132
177
  onOpenChange?: (open: boolean) => void;
133
178
  }
134
179
  /**
135
- * The product switcher under the wordmark. It opens, lists every Boost33 product
136
- * with the current one checked, and takes you straight into the one you pick --
180
+ * The product switcher. It opens, lists every Boost33 product that exists with
181
+ * the current one checked, and takes you straight into the one you pick --
137
182
  * the real switcher the Wallet's chevron-that-opens-nothing never was (B33-479).
138
183
  *
139
184
  * A disclosure, not a menu (ADR-0010): the panel carries no widget role, the
@@ -142,4 +187,4 @@ export interface AppSwitcherProps {
142
187
  * panel on the first row. Dismissal, focus restoration and trigger ARIA are the
143
188
  * shared anchored-overlay contract's (ADR-0009); it never becomes a modal.
144
189
  */
145
- export declare function AppSwitcher({ products, currentSlug, hub, account, labels, linkComponent, collapsed, defaultOpen, onOpenChange, }: AppSwitcherProps): import("react").JSX.Element;
190
+ export declare function AppSwitcher({ products, currentSlug, hub, labels, linkComponent, collapsed, defaultOpen, onOpenChange, }: AppSwitcherProps): import("react").JSX.Element;
@@ -1,88 +1,93 @@
1
1
  "use client";
2
- import { jsxs as s, jsx as a, Fragment as f } from "react/jsx-runtime";
3
- import { useAnchoredOverlay as y } from "../../anchored-overlay.js";
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";
2
+ import { jsxs as n, jsx as a, Fragment as w } from "react/jsx-runtime";
3
+ import { useId as M } from "react";
4
+ import { useAnchoredOverlay as O } from "../../anchored-overlay.js";
5
+ import { ProductMark as k } from "../ProductMark/ProductMark.js";
6
+ import { Chevron as x, HubMark as I, ChevronRight as L, Check as T } from "./icons.js";
7
+ import { resolveAppSwitcherLabels as j } from "./labels.js";
7
8
  import e from "./AppSwitcher.module.css.js";
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 })
9
+ const E = 28;
10
+ function _(l) {
11
+ const s = [];
12
+ for (const i of l) {
13
+ const c = s[s.length - 1];
14
+ c !== void 0 && c.label === i.group ? c.products.push(i) : s.push({ label: i.group, products: [i] });
15
+ }
16
+ return s;
17
+ }
18
+ function $({ trailing: l }) {
19
+ return l === void 0 ? null : l.kind === "value" ? /* @__PURE__ */ a("span", { className: e.trailingValue, children: l.value }) : /* @__PURE__ */ n(w, { children: [
20
+ /* @__PURE__ */ a("span", { className: e.trailingDot, "aria-hidden": "true" }),
21
+ /* @__PURE__ */ a("span", { className: e.srOnly, children: l.label })
19
22
  ] });
20
23
  }
21
- function q({
22
- products: r,
23
- currentSlug: l,
24
- hub: o,
25
- account: w,
26
- labels: b,
27
- linkComponent: k,
28
- collapsed: c = !1,
29
- defaultOpen: C,
24
+ function W({
25
+ products: l,
26
+ currentSlug: s,
27
+ hub: i,
28
+ labels: c,
29
+ linkComponent: C,
30
+ collapsed: d = !1,
31
+ defaultOpen: y,
30
32
  onOpenChange: R
31
33
  }) {
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(
34
+ const t = O({ defaultOpen: y, onOpenChange: R }), P = M(), m = j(c), g = C ?? "a", h = l.find((r) => r.slug === s), A = l.filter((r) => r.live !== !1 || r.slug === s), N = (h == null ? void 0 : h.name) ?? m.switchProduct;
35
+ return /* @__PURE__ */ n("div", { className: e.root, children: [
36
+ /* @__PURE__ */ n(
35
37
  "button",
36
38
  {
37
39
  type: "button",
38
- className: c ? e.railTrigger : e.trigger,
39
- onClick: i.toggle,
40
- ...i.triggerProps,
40
+ className: d ? e.railTrigger : e.trigger,
41
+ onClick: t.toggle,
42
+ ...t.triggerProps,
41
43
  children: [
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 }),
44
+ /* @__PURE__ */ a(k, { slug: s, title: d ? N : void 0 }),
45
+ !d && /* @__PURE__ */ n(w, { children: [
46
+ /* @__PURE__ */ a("span", { className: e.triggerName, children: N }),
45
47
  /* @__PURE__ */ a(x, { className: e.chevron })
46
48
  ] })
47
49
  ]
48
50
  }
49
51
  ),
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: [
54
- /* @__PURE__ */ a("span", { className: e.rowName, children: o.name }),
55
- /* @__PURE__ */ a("span", { className: e.rowSub, children: o.description })
56
- ] })
52
+ t.open && /* @__PURE__ */ n("div", { ...t.panelProps, className: e.panel, "aria-label": m.panel, children: [
53
+ /* @__PURE__ */ n(g, { className: e.hubRow, href: i.href, onClick: t.close, children: [
54
+ /* @__PURE__ */ a(I, { className: e.hubMark }),
55
+ /* @__PURE__ */ n("span", { className: e.rowText, children: [
56
+ /* @__PURE__ */ a("span", { className: e.rowName, children: i.name }),
57
+ /* @__PURE__ */ a("span", { className: e.rowSub, children: i.description })
58
+ ] }),
59
+ /* @__PURE__ */ a(L, { className: e.hubChevron })
57
60
  ] }),
58
- /* @__PURE__ */ a("hr", { className: e.divider }),
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,
63
- {
64
- className: e.productRow,
65
- href: n.href,
66
- "aria-current": d ? "true" : void 0,
67
- "data-current": d ? "true" : void 0,
68
- "data-entitled": h ? "false" : void 0,
69
- "data-live": N ? "false" : void 0,
70
- onClick: i.close,
71
- children: [
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 }),
75
- d && /* @__PURE__ */ a(A, { className: e.check })
76
- ]
77
- }
78
- ) }, n.slug);
79
- }) }),
80
- /* @__PURE__ */ a("hr", { className: e.divider }),
81
- /* @__PURE__ */ a(M, { account: w })
61
+ _(A).map((r, f) => {
62
+ const v = `${P}-group-${f}`;
63
+ return /* @__PURE__ */ n("div", { className: e.group, children: [
64
+ r.label !== void 0 && /* @__PURE__ */ a("p", { id: v, className: e.groupLabel, children: r.label }),
65
+ /* @__PURE__ */ a("ul", { className: e.products, "aria-labelledby": r.label !== void 0 ? v : void 0, children: r.products.map((o) => {
66
+ const u = o.slug === s, p = o.entitled === !1, b = p ? m.notOnPlan : null;
67
+ return /* @__PURE__ */ a("li", { children: /* @__PURE__ */ n(
68
+ g,
69
+ {
70
+ className: e.productRow,
71
+ href: o.href,
72
+ "aria-current": u ? "true" : void 0,
73
+ "data-current": u ? "true" : void 0,
74
+ "data-entitled": p ? "false" : void 0,
75
+ onClick: t.close,
76
+ children: [
77
+ /* @__PURE__ */ a(k, { slug: o.slug, size: E, muted: p }),
78
+ /* @__PURE__ */ a("span", { className: e.rowName, children: o.name }),
79
+ b !== null && /* @__PURE__ */ a("span", { className: e.tag, children: b }),
80
+ /* @__PURE__ */ a("span", { className: e.trailing, children: u ? /* @__PURE__ */ a(T, { className: e.check }) : /* @__PURE__ */ a($, { trailing: o.trailing }) })
81
+ ]
82
+ }
83
+ ) }, o.slug);
84
+ }) })
85
+ ] }, f);
86
+ })
82
87
  ] })
83
88
  ] });
84
89
  }
85
90
  export {
86
- q as AppSwitcher
91
+ W as AppSwitcher
87
92
  };
88
93
  //# 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 /**\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
+ {"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 { useId, 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 that exists is rendered, entitled or not: hiding an\n * unowned one means nobody learns the family exists (B33-479), and it tells a\n * client who could buy a product that the product is not there.\n *\n * A product that does not exist yet is not rendered (B33-1711). Until 0.8 it\n * drew with a \"Soon\" tag; the navigation redesign of 3 Nov 2026 took unbuilt\n * products off the switcher, the same way they came off the hub. See `live`.\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` leaves the row\n * out of the panel entirely (B33-1711), unless it is the product this shell is\n * rendering for -- the current product is always listed, since you are in it.\n *\n * **Only this hides a row. `entitled: false` never does.** The two answer\n * different questions, and the difference is commercial: a product the account\n * could buy is real, and leaving it out tells a paying client it does not\n * exist. That rule is the hub's as well (boost33-hub CLAUDE.md, `cardState`),\n * and the switcher follows it.\n *\n * Until 0.8 an unbuilt row was drawn with a \"Soon\" tag. The 3 Nov 2026\n * navigation redesign (B33-1710) removed unbuilt products from the switcher,\n * which overrules the Vocabulary document's \"the switcher shows every product\n * regardless of state\"; that document is amended alongside this change.\n *\n * What follows is why `live` is a second field rather than a third value of\n * `entitled`, which still holds.\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 * Hiding the row is a rendering decision, not an access one. The switcher\n * still does not decide what anyone may open (ADR-0010), and `url` is still\n * served for every slug, because `?app=` has to resolve for a campaign link.\n */\n live?: boolean\n /**\n * The heading of the section this row sits under, in the consumer's language\n * -- \"Your tools\", \"Included with every account\" (B33-1711, board 21). Rows\n * are grouped by this label in the order the labels first appear, and each\n * group gets a header. Leave it off every row and the panel is one plain list\n * with no headers; rows without one, in a panel where others have one, form a\n * headerless group of their own.\n *\n * Data the consumer is served, never decided here: which products are \"your\n * tools\" is the account's grant and Identity's registry, and the package\n * knows neither (CLAUDE.md -- no component knows what a product means).\n */\n group?: string\n /**\n * One thing at the right of the row (B33-1711, board 21) -- a figure such as\n * the Wallet's balance, or a dot saying there is something new. The consumer\n * supplies it already formatted; the switcher fetches nothing and formats\n * nothing. The current product's check takes the same slot, so a trailing on\n * the current row is not drawn.\n */\n trailing?: AppSwitcherTrailing\n}\n\n/**\n * The right-hand slot of a product row, in its two shapes.\n *\n * - `value` -- a short preformatted figure, drawn muted in the mono face:\n * `{ kind: 'value', value: '8,940' }`.\n * - `dot` -- a small primary dot meaning \"something new here\". A dot is a\n * colour and nothing else, so `label` is required: it is what a screen reader\n * hears instead (\"2 new\"), in the consumer's language.\n */\nexport type AppSwitcherTrailing = { kind: 'value'; value: string } | { kind: 'dot'; label: string }\n\n/** The \"all products\" row at the top of the panel. 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 that sat at the foot of the panel until 0.8.0.\n *\n * **Not drawn since 0.8.0.** The 3 Nov 2026 redesign (B33-1710, board 21) took\n * the account row off the switcher: the organisation now lives in the top bar's\n * left-hand slot, as the consumer's own pill (B33-1712). The type stays so a\n * consumer's existing `account` object still compiles; nothing reads it.\n *\n * @deprecated Rendered nowhere since 0.8.0 (B33-1711). Stop passing `account`.\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 /** Was: chosen -- opens the workspace panel. Never called since 0.8.0. */\n onSelect?: () => void\n}\n\nexport interface AppSwitcherProps {\n /**\n * The products, in the order Identity serves them. Pass the registry whole:\n * the switcher leaves out the rows with `live: false` itself (B33-1711), so a\n * consumer does not filter, and every product that exists is drawn whether\n * the account holds it 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 /**\n * Was the account/workspace row below the products. Optional and unread since\n * 0.8.0, when the redesign took that row off the panel (B33-1711).\n *\n * @deprecated Rendered nowhere since 0.8.0. The organisation goes in the top\n * bar's slot instead (B33-1712).\n */\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).\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/** The panel's product tile, in px: board 21's 28px row tile. */\nconst ROW_MARK_SIZE = 28\n\n/**\n * Consecutive rows that share a `group`, in the order the rows arrive. A run\n * whose rows carry no group has no label and draws no header, so a list with no\n * groups at all is one headerless run -- the plain list.\n *\n * Runs, not buckets: two rows with the same label separated by a different one\n * draw two sections with the same header. The order is Identity's, and\n * regrouping would silently reorder what the consumer was served.\n */\nfunction groupRuns(products: AppSwitcherProduct[]): { label: string | undefined; products: AppSwitcherProduct[] }[] {\n const runs: { label: string | undefined; products: AppSwitcherProduct[] }[] = []\n for (const product of products) {\n const last = runs[runs.length - 1]\n if (last !== undefined && last.label === product.group) last.products.push(product)\n else runs.push({ label: product.group, products: [product] })\n }\n return runs\n}\n\n/** A row's right-hand figure or dot. A dot carries its words for a screen reader. */\nfunction Trailing({ trailing }: { trailing: AppSwitcherTrailing | undefined }) {\n if (trailing === undefined) return null\n if (trailing.kind === 'value') return <span className={styles.trailingValue}>{trailing.value}</span>\n return (\n <>\n <span className={styles.trailingDot} aria-hidden=\"true\" />\n <span className={styles.srOnly}>{trailing.label}</span>\n </>\n )\n}\n\n/**\n * The product switcher. It opens, lists every Boost33 product that exists with\n * 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 labels,\n linkComponent,\n collapsed = false,\n defaultOpen,\n onOpenChange,\n}: AppSwitcherProps) {\n const overlay = useAnchoredOverlay({ defaultOpen, onOpenChange })\n const panelId = useId()\n const t = resolveAppSwitcherLabels(labels)\n const Link: ElementType = linkComponent ?? 'a'\n const current = products.find((p) => p.slug === currentSlug)\n // Unbuilt products are not drawn (B33-1711) -- except the one you are in.\n // A shell rendering for a product Identity still marks `live: false` is a\n // product in a preview or a soft launch, and a switcher that left out the\n // place you are standing would have nothing to put its check on.\n const listed = products.filter((p) => p.live !== false || 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 {/* The way out to every tool, drawn as the panel's one dark row\n (board 21). Its chevron says it leaves this product. */}\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 <ChevronRight className={styles.hubChevron} />\n </Link>\n\n {groupRuns(listed).map((run, runIndex) => {\n const headerId = `${panelId}-group-${runIndex}`\n return (\n <div key={runIndex} className={styles.group}>\n {run.label !== undefined && (\n <p id={headerId} className={styles.groupLabel}>\n {run.label}\n </p>\n )}\n <ul className={styles.products} aria-labelledby={run.label !== undefined ? headerId : undefined}>\n {run.products.map((product) => {\n const isCurrent = product.slug === currentSlug\n const notEntitled = product.entitled === false\n // A product that has not shipped never reaches here (see\n // `listed`), so the one tag left is entitlement's. B33-561's\n // problem -- an unbuilt product reading \"Not on plan\" --\n // cannot come back: Identity serves every unbuilt row\n // `entitled: false`, and none of them is drawn.\n const tag = 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 onClick={overlay.close}\n >\n {/* 28px in the panel (board 21) -- the tile the row is\n built around. The trigger keeps the 20px default. */}\n <ProductMark slug={product.slug} size={ROW_MARK_SIZE} muted={notEntitled} />\n <span className={styles.rowName}>{product.name}</span>\n {tag !== null && <span className={styles.tag}>{tag}</span>}\n <span className={styles.trailing}>\n {isCurrent ? <Check className={styles.check} /> : <Trailing trailing={product.trailing} />}\n </span>\n </Link>\n </li>\n )\n })}\n </ul>\n </div>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],"names":["ROW_MARK_SIZE","groupRuns","products","runs","product","last","Trailing","trailing","jsx","styles","jsxs","Fragment","AppSwitcher","currentSlug","hub","labels","linkComponent","collapsed","defaultOpen","onOpenChange","overlay","useAnchoredOverlay","panelId","useId","t","resolveAppSwitcherLabels","Link","current","p","listed","triggerName","ProductMark","Chevron","HubMark","ChevronRight","run","runIndex","headerId","isCurrent","notEntitled","tag","Check"],"mappings":"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"}
@@ -1,44 +1,50 @@
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,
1
+ const r = "_root_18wu0_8", o = "_trigger_18wu0_17", _ = "_railTrigger_18wu0_52", t = "_triggerName_18wu0_62", u = "_chevron_18wu0_79", n = "_panel_18wu0_127", e = "_hubRow_18wu0_147", c = "_productRow_18wu0_148", w = "_rowText_18wu0_179", g = "_rowName_18wu0_184", a = "_rowSub_18wu0_191", s = "_hubMark_18wu0_197", i = "_hubChevron_18wu0_204", l = "_group_18wu0_250", h = "_groupLabel_18wu0_258", p = "_products_18wu0_269", b = "_tag_18wu0_305", d = "_trailing_18wu0_317", k = "_check_18wu0_325", m = "_trailingValue_18wu0_333", v = "_trailingDot_18wu0_343", N = "_srOnly_18wu0_342", R = {
2
+ root: r,
3
+ trigger: o,
4
+ railTrigger: _,
5
5
  triggerName: t,
6
- chevron: c,
7
- panel: e,
8
- hubRow: a,
9
- productRow: d,
10
- accountRow: n,
11
- products: s,
6
+ chevron: u,
7
+ panel: n,
8
+ hubRow: e,
9
+ productRow: c,
10
+ rowText: w,
12
11
  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
12
+ rowSub: a,
13
+ hubMark: s,
14
+ hubChevron: i,
15
+ group: l,
16
+ groupLabel: h,
17
+ products: p,
18
+ tag: b,
19
+ trailing: d,
20
+ check: k,
21
+ trailingValue: m,
22
+ trailingDot: v,
23
+ srOnly: N
21
24
  };
22
25
  export {
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
- a as hubRow,
32
- e as panel,
33
- d as productRow,
34
- s as products,
35
- r as railTrigger,
36
- o as root,
26
+ k as check,
27
+ u as chevron,
28
+ R as default,
29
+ l as group,
30
+ h as groupLabel,
31
+ i as hubChevron,
32
+ s as hubMark,
33
+ e as hubRow,
34
+ n as panel,
35
+ c as productRow,
36
+ p as products,
37
+ _ as railTrigger,
38
+ r as root,
37
39
  g as rowName,
38
- i as rowSub,
39
- f as rowText,
40
- h as tag,
41
- _ as trigger,
40
+ a as rowSub,
41
+ w as rowText,
42
+ N as srOnly,
43
+ b as tag,
44
+ d as trailing,
45
+ v as trailingDot,
46
+ m as trailingValue,
47
+ o as trigger,
42
48
  t as triggerName
43
49
  };
44
50
  //# sourceMappingURL=AppSwitcher.module.css.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AppSwitcher.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"AppSwitcher.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,13 +1,13 @@
1
- import { jsx as r, jsxs as t } from "react/jsx-runtime";
2
- function o({ className: e }) {
3
- return /* @__PURE__ */ t("svg", { className: e, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round", "aria-hidden": "true", children: [
4
- /* @__PURE__ */ r("rect", { x: "3", y: "3", width: "7", height: "7", rx: "2" }),
5
- /* @__PURE__ */ r("rect", { x: "14", y: "3", width: "7", height: "7", rx: "2" }),
6
- /* @__PURE__ */ r("rect", { x: "3", y: "14", width: "7", height: "7", rx: "2" }),
7
- /* @__PURE__ */ r("rect", { x: "14", y: "14", width: "7", height: "7", rx: "2" })
1
+ import { jsx as r, jsxs as o } from "react/jsx-runtime";
2
+ function i({ className: e }) {
3
+ return /* @__PURE__ */ o("svg", { className: e, viewBox: "0 0 24 24", "aria-hidden": "true", children: [
4
+ /* @__PURE__ */ r("rect", { x: "3", y: "3", width: "8", height: "8", rx: "2", fill: "currentColor" }),
5
+ /* @__PURE__ */ r("rect", { x: "13", y: "3", width: "8", height: "8", rx: "2", fill: "currentColor" }),
6
+ /* @__PURE__ */ r("rect", { x: "3", y: "13", width: "8", height: "8", rx: "2", fill: "currentColor" }),
7
+ /* @__PURE__ */ r("rect", { x: "13", y: "13", width: "8", height: "8", rx: "2", fill: "var(--color-primary)" })
8
8
  ] });
9
9
  }
10
- function i({ className: e }) {
10
+ function n({ className: e }) {
11
11
  return /* @__PURE__ */ r("svg", { className: e, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ r("path", { d: "M6 9l6 6 6-6" }) });
12
12
  }
13
13
  function h({ className: e }) {
@@ -18,8 +18,8 @@ function d({ className: e }) {
18
18
  }
19
19
  export {
20
20
  d as Check,
21
- i as Chevron,
21
+ n as Chevron,
22
22
  h as ChevronRight,
23
- o as HubMark
23
+ i as HubMark
24
24
  };
25
25
  //# sourceMappingURL=icons.js.map