@boost33/ui 0.8.0 → 0.8.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +83 -120
  2. package/dist/components/AccountMenu/AccountMenu.d.ts +42 -32
  3. package/dist/components/AccountMenu/AccountMenu.js +66 -48
  4. package/dist/components/AccountMenu/AccountMenu.js.map +1 -1
  5. package/dist/components/AccountMenu/AccountMenu.module.css.js +44 -38
  6. package/dist/components/AccountMenu/AccountMenu.module.css.js.map +1 -1
  7. package/dist/components/AccountMenu/StandardAccountMenu.d.ts +87 -0
  8. package/dist/components/AccountMenu/StandardAccountMenu.js +86 -0
  9. package/dist/components/AccountMenu/StandardAccountMenu.js.map +1 -0
  10. package/dist/components/AccountMenu/icons.d.ts +2 -2
  11. package/dist/components/AccountMenu/icons.js +21 -8
  12. package/dist/components/AccountMenu/icons.js.map +1 -1
  13. package/dist/components/AccountMenu/initials.d.ts +11 -0
  14. package/dist/components/AccountMenu/initials.js +8 -0
  15. package/dist/components/AccountMenu/initials.js.map +1 -0
  16. package/dist/components/AccountMenu/labels.d.ts +64 -3
  17. package/dist/components/AccountMenu/labels.js +33 -5
  18. package/dist/components/AccountMenu/labels.js.map +1 -1
  19. package/dist/components/AccountMenu/use-menu.d.ts +34 -0
  20. package/dist/components/AccountMenu/use-menu.js +102 -0
  21. package/dist/components/AccountMenu/use-menu.js.map +1 -0
  22. package/dist/components/TopBar/TopBar.d.ts +8 -1
  23. package/dist/components/TopBar/TopBar.js +19 -16
  24. package/dist/components/TopBar/TopBar.js.map +1 -1
  25. package/dist/index.d.ts +4 -2
  26. package/dist/index.js +40 -36
  27. package/dist/index.js.map +1 -1
  28. package/dist/style.css +1 -1
  29. package/package.json +1 -1
@@ -1,42 +1,48 @@
1
- const o = "_panel_4usug_54", s = "_person_4usug_72", r = "_personAvatar_4usug_85", e = "_personText_4usug_98", _ = "_personName_4usug_106", n = "_personEmail_4usug_122", t = "_org_4usug_147", u = "_orgIdentity_4usug_161", g = "_orgTile_4usug_178", a = "_orgName_4usug_194", c = "_orgRole_4usug_209", p = "_divider_4usug_143", i = "_rows_4usug_242", l = "_row_4usug_48", w = "_rowDescription_4usug_299", d = "_lane_4usug_303", m = "_rowText_4usug_347", T = "_rowLabel_4usug_365", x = {
2
- panel: o,
3
- person: s,
4
- personAvatar: r,
5
- personText: e,
6
- personName: _,
7
- personEmail: n,
8
- org: t,
9
- orgIdentity: u,
10
- orgTile: g,
11
- orgName: a,
12
- orgRole: c,
13
- divider: p,
14
- rows: i,
15
- row: l,
16
- rowDescription: w,
17
- lane: d,
18
- rowText: m,
19
- rowLabel: T
1
+ const t = "_root_trt1y_61", r = "_trigger_trt1y_71", o = "_avatarImage_trt1y_95", _ = "_panel_trt1y_102", e = "_person_trt1y_124", n = "_personAvatar_trt1y_138", s = "_personText_trt1y_156", a = "_personName_trt1y_164", c = "_personEmail_trt1y_180", y = "_org_trt1y_205", g = "_orgIdentity_trt1y_219", i = "_orgTile_trt1y_234", p = "_orgName_trt1y_250", l = "_orgRole_trt1y_265", w = "_divider_trt1y_201", m = "_rows_trt1y_298", d = "_row_trt1y_48", v = "_rowDescription_trt1y_359", T = "_lane_trt1y_364", x = "_rowLabel_trt1y_366", I = "_rowText_trt1y_411", N = {
2
+ root: t,
3
+ trigger: r,
4
+ avatarImage: o,
5
+ panel: _,
6
+ person: e,
7
+ personAvatar: n,
8
+ personText: s,
9
+ personName: a,
10
+ personEmail: c,
11
+ org: y,
12
+ orgIdentity: g,
13
+ orgTile: i,
14
+ orgName: p,
15
+ orgRole: l,
16
+ divider: w,
17
+ rows: m,
18
+ row: d,
19
+ rowDescription: v,
20
+ lane: T,
21
+ rowLabel: x,
22
+ rowText: I
20
23
  };
21
24
  export {
22
- x as default,
23
- p as divider,
24
- d as lane,
25
- t as org,
26
- u as orgIdentity,
27
- a as orgName,
28
- c as orgRole,
29
- g as orgTile,
30
- o as panel,
31
- s as person,
32
- r as personAvatar,
33
- n as personEmail,
34
- _ as personName,
35
- e as personText,
36
- l as row,
37
- w as rowDescription,
38
- T as rowLabel,
39
- m as rowText,
40
- i as rows
25
+ o as avatarImage,
26
+ N as default,
27
+ w as divider,
28
+ T as lane,
29
+ y as org,
30
+ g as orgIdentity,
31
+ p as orgName,
32
+ l as orgRole,
33
+ i as orgTile,
34
+ _ as panel,
35
+ e as person,
36
+ n as personAvatar,
37
+ c as personEmail,
38
+ a as personName,
39
+ s as personText,
40
+ t as root,
41
+ d as row,
42
+ v as rowDescription,
43
+ x as rowLabel,
44
+ I as rowText,
45
+ m as rows,
46
+ r as trigger
41
47
  };
42
48
  //# sourceMappingURL=AccountMenu.module.css.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AccountMenu.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"AccountMenu.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,87 @@
1
+ import { AccountMenuGroup, AccountMenuPerson } from './AccountMenu';
2
+ import { AccountMenuLabels, StandardAccountMenuLabels } from './labels';
3
+ /** The person's role in the organisation they are acting in, as Identity issues it. */
4
+ export type AccountMenuRole = 'admin' | 'member';
5
+ /**
6
+ * Where the standard rows go. Every href is absolute, built from the two base
7
+ * URLs, so the same rows work from any app -- the Hub included, where they are a
8
+ * full page load to the Hub's own settings rather than a client-side route.
9
+ */
10
+ export interface StandardAccountMenuRowsOptions {
11
+ /** The person's role. Picks Settings' line; `null` or omitted reads as a member's. */
12
+ role?: AccountMenuRole | null;
13
+ /**
14
+ * Identity's origin, e.g. `https://id.boost33.com`. Security, Switch account and
15
+ * Sign out go there: a passphrase is set against Identity's own session, only
16
+ * Identity can re-issue a token for another account, and a product cannot end a
17
+ * session (the Hub's reasoning, B33-1227).
18
+ */
19
+ identityUrl: string;
20
+ /**
21
+ * The Hub's origin. In production `https://app.boost33.com` (Identity's
22
+ * `HUB_URL`, docs/DEPLOY.md). Profile goes to `{hubUrl}/settings/profile` and
23
+ * Settings to `{hubUrl}/settings/org/general`, the Hub's settings pages.
24
+ */
25
+ hubUrl: string;
26
+ /**
27
+ * How many other accounts this person can switch to. Above 0, a Switch account
28
+ * row is drawn, described by `otherAccountNames` when every one is named, or by
29
+ * the count. 0 or omitted: no row.
30
+ */
31
+ otherAccountsCount?: number;
32
+ /** The other accounts' names, in order. Used only when there is one per account. */
33
+ otherAccountNames?: string[];
34
+ /** The rows' words. Each field defaults to the Hub's English. */
35
+ labels?: Partial<StandardAccountMenuLabels>;
36
+ }
37
+ /**
38
+ * The standard rows of the account menu, the Hub's, as plain data:
39
+ *
40
+ * Profile, Security, Settings, [Switch account]
41
+ * Feedback, Docs
42
+ * Sign out
43
+ *
44
+ * Pass the result to `AccountMenu` as `groups`, adding or reordering rows if an
45
+ * app ever has one the standard set cannot express -- or use
46
+ * `StandardAccountMenu`, which does this for you.
47
+ */
48
+ export declare function standardAccountMenuGroups(options: StandardAccountMenuRowsOptions): AccountMenuGroup[];
49
+ export interface StandardAccountMenuProps extends StandardAccountMenuRowsOptions {
50
+ /** The signed-in person: email, and a name and photo when Identity has them. */
51
+ person: AccountMenuPerson;
52
+ /**
53
+ * The organisation this person is acting in. Omit it and no strip is drawn. The
54
+ * role pill comes from `role`, in `labels.roleAdmin` / `labels.roleMember`.
55
+ */
56
+ org?: {
57
+ name: string;
58
+ initials?: string;
59
+ };
60
+ /** The menu's accessible names and the unnamed fallback. Each defaults to the Hub's English. */
61
+ menuLabels?: Partial<AccountMenuLabels>;
62
+ /** Draw the menu open on first render. For the specimen and screenshots. */
63
+ defaultOpen?: boolean;
64
+ }
65
+ /**
66
+ * The account menu every Boost33 app shows behind its avatar: the Hub's, with
67
+ * the standard rows. One element, plain data in, safe to render from a Server
68
+ * Component layout:
69
+ *
70
+ * ```tsx
71
+ * <AppShell
72
+ * topBar={{
73
+ * account: (
74
+ * <StandardAccountMenu
75
+ * person={{ name: claims.name, email: claims.email }}
76
+ * org={{ name: orgName }}
77
+ * role={role}
78
+ * identityUrl="https://id.boost33.com"
79
+ * hubUrl="https://app.boost33.com"
80
+ * otherAccountsCount={claims.accounts.length - 1}
81
+ * />
82
+ * ),
83
+ * }}
84
+ * />
85
+ * ```
86
+ */
87
+ export declare function StandardAccountMenu({ person, org, role, labels, menuLabels, defaultOpen, ...rows }: StandardAccountMenuProps): import("react").JSX.Element;
@@ -0,0 +1,86 @@
1
+ import { jsx as a } from "react/jsx-runtime";
2
+ import { fillTemplate as m } from "../../fill-template.js";
3
+ import { AccountMenu as f } from "./AccountMenu.js";
4
+ import { resolveStandardAccountMenuLabels as u } from "./labels.js";
5
+ const p = "mailto:support@boost33.com", b = "https://docs.boost33.com";
6
+ function n(t, e) {
7
+ return `${t.replace(/\/+$/, "")}${e}`;
8
+ }
9
+ function h(t) {
10
+ const e = u(t.labels), i = t.otherAccountsCount ?? 0, c = t.otherAccountNames ?? [], o = {
11
+ id: "account",
12
+ items: [
13
+ {
14
+ id: "profile",
15
+ label: e.profile,
16
+ description: e.profileDescription,
17
+ href: n(t.hubUrl, "/settings/profile"),
18
+ icon: "person"
19
+ },
20
+ {
21
+ id: "security",
22
+ label: e.security,
23
+ description: e.securityDescription,
24
+ href: n(t.identityUrl, "/account/security"),
25
+ icon: "security"
26
+ },
27
+ {
28
+ id: "settings",
29
+ label: e.settings,
30
+ description: t.role === "admin" ? e.settingsDescriptionAdmin : e.settingsDescriptionMember,
31
+ href: n(t.hubUrl, "/settings/org/general"),
32
+ icon: "settings"
33
+ }
34
+ ]
35
+ };
36
+ if (i > 0) {
37
+ const r = c.length === i ? c.join(" · ") : i === 1 ? e.otherAccountsOne : m(e.otherAccountsMany, { count: String(i) });
38
+ o.items.push({
39
+ id: "switch-account",
40
+ label: e.switchAccount,
41
+ description: r,
42
+ href: n(t.identityUrl, "/choose-account"),
43
+ icon: "switch"
44
+ });
45
+ }
46
+ return [
47
+ o,
48
+ {
49
+ id: "help",
50
+ items: [
51
+ { id: "feedback", label: e.feedback, description: e.feedbackDescription, href: p, icon: "feedback" },
52
+ { id: "docs", label: e.docs, description: e.docsDescription, href: b, icon: "docs" }
53
+ ]
54
+ },
55
+ {
56
+ id: "session",
57
+ items: [{ id: "sign-out", label: e.signOut, href: n(t.identityUrl, "/signout"), icon: "signOut" }]
58
+ }
59
+ ];
60
+ }
61
+ function M({
62
+ person: t,
63
+ org: e,
64
+ role: i,
65
+ labels: c,
66
+ menuLabels: o,
67
+ defaultOpen: r,
68
+ ...d
69
+ }) {
70
+ const s = u(c), l = i === "admin" ? s.roleAdmin : i === "member" ? s.roleMember : void 0;
71
+ return /* @__PURE__ */ a(
72
+ f,
73
+ {
74
+ person: t,
75
+ org: e === void 0 ? void 0 : { ...e, role: l },
76
+ groups: h({ ...d, role: i, labels: c }),
77
+ labels: o,
78
+ defaultOpen: r
79
+ }
80
+ );
81
+ }
82
+ export {
83
+ M as StandardAccountMenu,
84
+ h as standardAccountMenuGroups
85
+ };
86
+ //# sourceMappingURL=StandardAccountMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StandardAccountMenu.js","sources":["../../../src/components/AccountMenu/StandardAccountMenu.tsx"],"sourcesContent":["// No \"use client\": this builds plain data and renders the client AccountMenu with\n// it, so a Server Component layout can render it and pass no function across the\n// boundary. tests/guards.test.ts holds it to the server-component rules.\n\nimport { fillTemplate } from '../../fill-template'\nimport { AccountMenu, type AccountMenuGroup, type AccountMenuPerson } from './AccountMenu'\nimport {\n resolveStandardAccountMenuLabels,\n type AccountMenuLabels,\n type StandardAccountMenuLabels,\n} from './labels'\n\n/** The person's role in the organisation they are acting in, as Identity issues it. */\nexport type AccountMenuRole = 'admin' | 'member'\n\n/**\n * Where the standard rows go. Every href is absolute, built from the two base\n * URLs, so the same rows work from any app -- the Hub included, where they are a\n * full page load to the Hub's own settings rather than a client-side route.\n */\nexport interface StandardAccountMenuRowsOptions {\n /** The person's role. Picks Settings' line; `null` or omitted reads as a member's. */\n role?: AccountMenuRole | null\n /**\n * Identity's origin, e.g. `https://id.boost33.com`. Security, Switch account and\n * Sign out go there: a passphrase is set against Identity's own session, only\n * Identity can re-issue a token for another account, and a product cannot end a\n * session (the Hub's reasoning, B33-1227).\n */\n identityUrl: string\n /**\n * The Hub's origin. In production `https://app.boost33.com` (Identity's\n * `HUB_URL`, docs/DEPLOY.md). Profile goes to `{hubUrl}/settings/profile` and\n * Settings to `{hubUrl}/settings/org/general`, the Hub's settings pages.\n */\n hubUrl: string\n /**\n * How many other accounts this person can switch to. Above 0, a Switch account\n * row is drawn, described by `otherAccountNames` when every one is named, or by\n * the count. 0 or omitted: no row.\n */\n otherAccountsCount?: number\n /** The other accounts' names, in order. Used only when there is one per account. */\n otherAccountNames?: string[]\n /** The rows' words. Each field defaults to the Hub's English. */\n labels?: Partial<StandardAccountMenuLabels>\n}\n\nconst FEEDBACK_HREF = 'mailto:support@boost33.com'\nconst DOCS_HREF = 'https://docs.boost33.com'\n\n/** `https://x/` and `https://x` both join to `https://x/path`. */\nfunction join(base: string, path: string): string {\n return `${base.replace(/\\/+$/, '')}${path}`\n}\n\n/**\n * The standard rows of the account menu, the Hub's, as plain data:\n *\n * Profile, Security, Settings, [Switch account]\n * Feedback, Docs\n * Sign out\n *\n * Pass the result to `AccountMenu` as `groups`, adding or reordering rows if an\n * app ever has one the standard set cannot express -- or use\n * `StandardAccountMenu`, which does this for you.\n */\nexport function standardAccountMenuGroups(options: StandardAccountMenuRowsOptions): AccountMenuGroup[] {\n const t = resolveStandardAccountMenuLabels(options.labels)\n const count = options.otherAccountsCount ?? 0\n const names = options.otherAccountNames ?? []\n\n const account: AccountMenuGroup = {\n id: 'account',\n items: [\n {\n id: 'profile',\n label: t.profile,\n description: t.profileDescription,\n href: join(options.hubUrl, '/settings/profile'),\n icon: 'person',\n },\n {\n id: 'security',\n label: t.security,\n description: t.securityDescription,\n href: join(options.identityUrl, '/account/security'),\n icon: 'security',\n },\n {\n id: 'settings',\n label: t.settings,\n description: options.role === 'admin' ? t.settingsDescriptionAdmin : t.settingsDescriptionMember,\n href: join(options.hubUrl, '/settings/org/general'),\n icon: 'settings',\n },\n ],\n }\n\n if (count > 0) {\n // \"Nova Retail · Cortex Labs\" when every account is named; counted rather\n // than half-named otherwise (the Hub's describeOthers).\n const description =\n names.length === count\n ? names.join(' · ')\n : count === 1\n ? t.otherAccountsOne\n : fillTemplate(t.otherAccountsMany, { count: String(count) })\n account.items.push({\n id: 'switch-account',\n label: t.switchAccount,\n description,\n href: join(options.identityUrl, '/choose-account'),\n icon: 'switch',\n })\n }\n\n return [\n account,\n {\n id: 'help',\n items: [\n { id: 'feedback', label: t.feedback, description: t.feedbackDescription, href: FEEDBACK_HREF, icon: 'feedback' },\n { id: 'docs', label: t.docs, description: t.docsDescription, href: DOCS_HREF, icon: 'docs' },\n ],\n },\n {\n id: 'session',\n items: [{ id: 'sign-out', label: t.signOut, href: join(options.identityUrl, '/signout'), icon: 'signOut' }],\n },\n ]\n}\n\nexport interface StandardAccountMenuProps extends StandardAccountMenuRowsOptions {\n /** The signed-in person: email, and a name and photo when Identity has them. */\n person: AccountMenuPerson\n /**\n * The organisation this person is acting in. Omit it and no strip is drawn. The\n * role pill comes from `role`, in `labels.roleAdmin` / `labels.roleMember`.\n */\n org?: { name: string; initials?: string }\n /** The menu's accessible names and the unnamed fallback. Each defaults to the Hub's English. */\n menuLabels?: Partial<AccountMenuLabels>\n /** Draw the menu open on first render. For the specimen and screenshots. */\n defaultOpen?: boolean\n}\n\n/**\n * The account menu every Boost33 app shows behind its avatar: the Hub's, with\n * the standard rows. One element, plain data in, safe to render from a Server\n * Component layout:\n *\n * ```tsx\n * <AppShell\n * topBar={{\n * account: (\n * <StandardAccountMenu\n * person={{ name: claims.name, email: claims.email }}\n * org={{ name: orgName }}\n * role={role}\n * identityUrl=\"https://id.boost33.com\"\n * hubUrl=\"https://app.boost33.com\"\n * otherAccountsCount={claims.accounts.length - 1}\n * />\n * ),\n * }}\n * />\n * ```\n */\nexport function StandardAccountMenu({\n person,\n org,\n role,\n labels,\n menuLabels,\n defaultOpen,\n ...rows\n}: StandardAccountMenuProps) {\n const t = resolveStandardAccountMenuLabels(labels)\n const roleLabel = role === 'admin' ? t.roleAdmin : role === 'member' ? t.roleMember : undefined\n\n return (\n <AccountMenu\n person={person}\n org={org === undefined ? undefined : { ...org, role: roleLabel }}\n groups={standardAccountMenuGroups({ ...rows, role, labels })}\n labels={menuLabels}\n defaultOpen={defaultOpen}\n />\n )\n}\n"],"names":["FEEDBACK_HREF","DOCS_HREF","join","base","path","standardAccountMenuGroups","options","t","resolveStandardAccountMenuLabels","count","names","account","description","fillTemplate","StandardAccountMenu","person","org","role","labels","menuLabels","defaultOpen","rows","roleLabel","jsx","AccountMenu"],"mappings":";;;;AAgDA,MAAMA,IAAgB,8BAChBC,IAAY;AAGlB,SAASC,EAAKC,GAAcC,GAAsB;AAChD,SAAO,GAAGD,EAAK,QAAQ,QAAQ,EAAE,CAAC,GAAGC,CAAI;AAC3C;AAaO,SAASC,EAA0BC,GAA6D;AACrG,QAAMC,IAAIC,EAAiCF,EAAQ,MAAM,GACnDG,IAAQH,EAAQ,sBAAsB,GACtCI,IAAQJ,EAAQ,qBAAqB,CAAA,GAErCK,IAA4B;AAAA,IAChC,IAAI;AAAA,IACJ,OAAO;AAAA,MACL;AAAA,QACE,IAAI;AAAA,QACJ,OAAOJ,EAAE;AAAA,QACT,aAAaA,EAAE;AAAA,QACf,MAAML,EAAKI,EAAQ,QAAQ,mBAAmB;AAAA,QAC9C,MAAM;AAAA,MAAA;AAAA,MAER;AAAA,QACE,IAAI;AAAA,QACJ,OAAOC,EAAE;AAAA,QACT,aAAaA,EAAE;AAAA,QACf,MAAML,EAAKI,EAAQ,aAAa,mBAAmB;AAAA,QACnD,MAAM;AAAA,MAAA;AAAA,MAER;AAAA,QACE,IAAI;AAAA,QACJ,OAAOC,EAAE;AAAA,QACT,aAAaD,EAAQ,SAAS,UAAUC,EAAE,2BAA2BA,EAAE;AAAA,QACvE,MAAML,EAAKI,EAAQ,QAAQ,uBAAuB;AAAA,QAClD,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,EACF;AAGF,MAAIG,IAAQ,GAAG;AAGb,UAAMG,IACJF,EAAM,WAAWD,IACbC,EAAM,KAAK,KAAK,IAChBD,MAAU,IACRF,EAAE,mBACFM,EAAaN,EAAE,mBAAmB,EAAE,OAAO,OAAOE,CAAK,GAAG;AAClE,IAAAE,EAAQ,MAAM,KAAK;AAAA,MACjB,IAAI;AAAA,MACJ,OAAOJ,EAAE;AAAA,MACT,aAAAK;AAAA,MACA,MAAMV,EAAKI,EAAQ,aAAa,iBAAiB;AAAA,MACjD,MAAM;AAAA,IAAA,CACP;AAAA,EACH;AAEA,SAAO;AAAA,IACLK;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,QACL,EAAE,IAAI,YAAY,OAAOJ,EAAE,UAAU,aAAaA,EAAE,qBAAqB,MAAMP,GAAe,MAAM,WAAA;AAAA,QACpG,EAAE,IAAI,QAAQ,OAAOO,EAAE,MAAM,aAAaA,EAAE,iBAAiB,MAAMN,GAAW,MAAM,OAAA;AAAA,MAAO;AAAA,IAC7F;AAAA,IAEF;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,CAAC,EAAE,IAAI,YAAY,OAAOM,EAAE,SAAS,MAAML,EAAKI,EAAQ,aAAa,UAAU,GAAG,MAAM,WAAW;AAAA,IAAA;AAAA,EAC5G;AAEJ;AAsCO,SAASQ,EAAoB;AAAA,EAClC,QAAAC;AAAA,EACA,KAAAC;AAAA,EACA,MAAAC;AAAA,EACA,QAAAC;AAAA,EACA,YAAAC;AAAA,EACA,aAAAC;AAAA,EACA,GAAGC;AACL,GAA6B;AAC3B,QAAMd,IAAIC,EAAiCU,CAAM,GAC3CI,IAAYL,MAAS,UAAUV,EAAE,YAAYU,MAAS,WAAWV,EAAE,aAAa;AAEtF,SACE,gBAAAgB;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,QAAAT;AAAA,MACA,KAAKC,MAAQ,SAAY,SAAY,EAAE,GAAGA,GAAK,MAAMM,EAAA;AAAA,MACrD,QAAQjB,EAA0B,EAAE,GAAGgB,GAAM,MAAAJ,GAAM,QAAAC,GAAQ;AAAA,MAC3D,QAAQC;AAAA,MACR,aAAAC;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -1,11 +1,11 @@
1
1
  import { ReactElement } from 'react';
2
2
  /**
3
3
  * Which glyph a row carries. A closed union, unlike `ProductMark`'s `slug`:
4
- * these four are the package's own chrome rather than a registry Identity owns
4
+ * these are the package's own chrome rather than a registry Identity owns
5
5
  * and can run ahead of, so there is no served value to degrade from. A row that
6
6
  * wants no glyph omits the field and keeps the empty lane -- see `.lane` in
7
7
  * AccountMenu.module.css for why the lane stays.
8
8
  */
9
- export type AccountMenuIcon = 'person' | 'settings' | 'switch' | 'signOut';
9
+ export type AccountMenuIcon = 'person' | 'security' | 'settings' | 'switch' | 'feedback' | 'docs' | 'signOut';
10
10
  /** The glyph for each name, keyed so the row renders one lookup and no switch. */
11
11
  export declare const ACCOUNT_MENU_ICONS: Record<AccountMenuIcon, (className?: string) => ReactElement>;
@@ -1,5 +1,5 @@
1
- import { jsxs as h, jsx as a } from "react/jsx-runtime";
2
- const r = {
1
+ import { jsxs as t, jsx as a } from "react/jsx-runtime";
2
+ const d = {
3
3
  viewBox: "0 0 24 24",
4
4
  fill: "none",
5
5
  stroke: "currentColor",
@@ -7,26 +7,39 @@ const r = {
7
7
  strokeLinecap: "round",
8
8
  strokeLinejoin: "round",
9
9
  "aria-hidden": !0
10
- }, d = {
11
- person: (t) => /* @__PURE__ */ h("svg", { className: t, ...r, children: [
10
+ }, e = {
11
+ person: (h) => /* @__PURE__ */ t("svg", { className: h, ...d, children: [
12
12
  /* @__PURE__ */ a("path", { d: "M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" }),
13
13
  /* @__PURE__ */ a("path", { d: "M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" })
14
14
  ] }),
15
- settings: (t) => /* @__PURE__ */ h("svg", { className: t, ...r, children: [
15
+ settings: (h) => /* @__PURE__ */ t("svg", { className: h, ...d, children: [
16
16
  /* @__PURE__ */ a("path", { d: "M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065" }),
17
17
  /* @__PURE__ */ a("path", { d: "M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" })
18
18
  ] }),
19
- switch: (t) => /* @__PURE__ */ h("svg", { className: t, ...r, children: [
19
+ switch: (h) => /* @__PURE__ */ t("svg", { className: h, ...d, children: [
20
20
  /* @__PURE__ */ a("path", { d: "M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" }),
21
21
  /* @__PURE__ */ a("path", { d: "M9 8l6 4l-6 4z" })
22
22
  ] }),
23
- signOut: (t) => /* @__PURE__ */ h("svg", { className: t, ...r, children: [
23
+ security: (h) => /* @__PURE__ */ t("svg", { className: h, ...d, children: [
24
+ /* @__PURE__ */ a("path", { d: "M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3" }),
25
+ /* @__PURE__ */ a("path", { d: "M12 11m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" }),
26
+ /* @__PURE__ */ a("path", { d: "M12 12l0 2.5" })
27
+ ] }),
28
+ feedback: (h) => /* @__PURE__ */ t("svg", { className: h, ...d, children: [
29
+ /* @__PURE__ */ a("path", { d: "M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" }),
30
+ /* @__PURE__ */ a("path", { d: "M3 7l9 6l9 -6" })
31
+ ] }),
32
+ docs: (h) => /* @__PURE__ */ t("svg", { className: h, ...d, children: [
33
+ /* @__PURE__ */ a("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }),
34
+ /* @__PURE__ */ a("path", { d: "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" })
35
+ ] }),
36
+ signOut: (h) => /* @__PURE__ */ t("svg", { className: h, ...d, children: [
24
37
  /* @__PURE__ */ a("path", { d: "M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2" }),
25
38
  /* @__PURE__ */ a("path", { d: "M9 12h12l-3 -3" }),
26
39
  /* @__PURE__ */ a("path", { d: "M18 15l3 -3" })
27
40
  ] })
28
41
  };
29
42
  export {
30
- d as ACCOUNT_MENU_ICONS
43
+ e as ACCOUNT_MENU_ICONS
31
44
  };
32
45
  //# sourceMappingURL=icons.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"icons.js","sources":["../../../src/components/AccountMenu/icons.tsx"],"sourcesContent":["// The account menu's four row glyphs. Drawn in `currentColor` so the row's own\n// ink carries them -- which is also what keeps the no-hardcoded-hex guard\n// satisfied, and what lets the picked-out row darken its glyph with its text in\n// one rule rather than two. Same arrangement as AppSwitcher/icons.tsx.\n//\n// All four are decorative: every row has a visible name beside its glyph, so a\n// name here would only make a screen reader say the row twice. Hence\n// `aria-hidden` on each, with no `title` prop to pass -- the opposite of\n// ProductMark, which can stand alone.\n//\n// The path data is reproduced verbatim from the AccountMenu drawings on Paper's\n// 004 Components page, where all four are Tabler outline glyphs on a 24-unit\n// grid at stroke 2. The drawing on 103 Identity carries byte-identical data, so\n// there is nothing to reconcile between the two here. They are NOT settled\n// artwork: the Wallet has never rendered this menu, so no approved baseline\n// fixes them, and the person at the component's fidelity gate may replace any of\n// them (CLAUDE.md, \"The three components the Wallet never rendered\").\n//\n// One of them is worth flagging rather than quietly shipping: `switch` is a\n// rounded square with a play triangle in it, which is what the drawing uses for\n// \"Switch account\" and is not an obvious reading of that row. Reproduced as\n// drawn, raised at the gate, not silently swapped for a better guess.\n\nimport type { ReactElement } from 'react'\n\n/**\n * Which glyph a row carries. A closed union, unlike `ProductMark`'s `slug`:\n * these four are the package's own chrome rather than a registry Identity owns\n * and can run ahead of, so there is no served value to degrade from. A row that\n * wants no glyph omits the field and keeps the empty lane -- see `.lane` in\n * AccountMenu.module.css for why the lane stays.\n */\nexport type AccountMenuIcon = 'person' | 'settings' | 'switch' | 'signOut'\n\nconst COMMON = {\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: '2',\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n} as const\n\n/** The glyph for each name, keyed so the row renders one lookup and no switch. */\nexport const ACCOUNT_MENU_ICONS: Record<AccountMenuIcon, (className?: string) => ReactElement> = {\n person: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0\" />\n <path d=\"M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2\" />\n </svg>\n ),\n settings: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065\" />\n <path d=\"M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0\" />\n </svg>\n ),\n switch: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z\" />\n <path d=\"M9 8l6 4l-6 4z\" />\n </svg>\n ),\n signOut: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2\" />\n <path d=\"M9 12h12l-3 -3\" />\n <path d=\"M18 15l3 -3\" />\n </svg>\n ),\n}\n"],"names":["COMMON","ACCOUNT_MENU_ICONS","className","jsx"],"mappings":";AAkCA,MAAMA,IAAS;AAAA,EACb,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AACjB,GAGaC,IAAoF;AAAA,EAC/F,QAAQ,CAACC,wBACN,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,oCAAA,CAAoC;AAAA,IAC5C,gBAAAA,EAAC,QAAA,EAAK,GAAE,4CAAA,CAA4C;AAAA,EAAA,GACtD;AAAA,EAEF,UAAU,CAACD,wBACR,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,6gBAAA,CAA6gB;AAAA,IACrhB,gBAAAA,EAAC,QAAA,EAAK,GAAE,qCAAA,CAAqC;AAAA,EAAA,GAC/C;AAAA,EAEF,QAAQ,CAACD,wBACN,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,8EAAA,CAA8E;AAAA,IACtF,gBAAAA,EAAC,QAAA,EAAK,GAAE,iBAAA,CAAiB;AAAA,EAAA,GAC3B;AAAA,EAEF,SAAS,CAACD,wBACP,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,kFAAA,CAAkF;AAAA,IAC1F,gBAAAA,EAAC,QAAA,EAAK,GAAE,iBAAA,CAAiB;AAAA,IACzB,gBAAAA,EAAC,QAAA,EAAK,GAAE,cAAA,CAAc;AAAA,EAAA,EAAA,CACxB;AAEJ;"}
1
+ {"version":3,"file":"icons.js","sources":["../../../src/components/AccountMenu/icons.tsx"],"sourcesContent":["// The account menu's seven row glyphs. Drawn in `currentColor` so the row's own\n// ink carries them -- which is also what keeps the no-hardcoded-hex guard\n// satisfied, and what lets the picked-out row darken its glyph with its text in\n// one rule rather than two. Same arrangement as AppSwitcher/icons.tsx.\n//\n// All seven are decorative: every row has a visible name beside its glyph, so a\n// name here would only make a screen reader say the row twice. Hence\n// `aria-hidden` on each, with no `title` prop to pass -- the opposite of\n// ProductMark, which can stand alone.\n//\n// The path data is the Hub's working menu's (boost33-hub src/lib/icons.ts), the\n// referent Jamal named for Stage B on 5 Nov 2026: Tabler outline glyphs on a\n// 24-unit grid at stroke 2, copied from Paper 103 artboard 11. The original four\n// (person, settings, switch, signOut) are byte-identical to the ones Stage A\n// took from Paper 004; `security`, `feedback` and `docs` arrive with the Hub's\n// Security, Feedback and Docs rows. `switch` is still the rounded square with a\n// play triangle the drawing uses for \"Switch account\" -- the Hub ships it.\n\nimport type { ReactElement } from 'react'\n\n/**\n * Which glyph a row carries. A closed union, unlike `ProductMark`'s `slug`:\n * these are the package's own chrome rather than a registry Identity owns\n * and can run ahead of, so there is no served value to degrade from. A row that\n * wants no glyph omits the field and keeps the empty lane -- see `.lane` in\n * AccountMenu.module.css for why the lane stays.\n */\nexport type AccountMenuIcon = 'person' | 'security' | 'settings' | 'switch' | 'feedback' | 'docs' | 'signOut'\n\nconst COMMON = {\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: '2',\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n} as const\n\n/** The glyph for each name, keyed so the row renders one lookup and no switch. */\nexport const ACCOUNT_MENU_ICONS: Record<AccountMenuIcon, (className?: string) => ReactElement> = {\n person: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0\" />\n <path d=\"M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2\" />\n </svg>\n ),\n settings: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065\" />\n <path d=\"M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0\" />\n </svg>\n ),\n switch: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z\" />\n <path d=\"M9 8l6 4l-6 4z\" />\n </svg>\n ),\n security: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3\" />\n <path d=\"M12 11m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0\" />\n <path d=\"M12 12l0 2.5\" />\n </svg>\n ),\n feedback: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z\" />\n <path d=\"M3 7l9 6l9 -6\" />\n </svg>\n ),\n docs: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M14 3v4a1 1 0 0 0 1 1h4\" />\n <path d=\"M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z\" />\n </svg>\n ),\n signOut: (className) => (\n <svg className={className} {...COMMON}>\n <path d=\"M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2\" />\n <path d=\"M9 12h12l-3 -3\" />\n <path d=\"M18 15l3 -3\" />\n </svg>\n ),\n}\n"],"names":["COMMON","ACCOUNT_MENU_ICONS","className","jsx"],"mappings":";AA6BA,MAAMA,IAAS;AAAA,EACb,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AACjB,GAGaC,IAAoF;AAAA,EAC/F,QAAQ,CAACC,wBACN,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,oCAAA,CAAoC;AAAA,IAC5C,gBAAAA,EAAC,QAAA,EAAK,GAAE,4CAAA,CAA4C;AAAA,EAAA,GACtD;AAAA,EAEF,UAAU,CAACD,wBACR,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,6gBAAA,CAA6gB;AAAA,IACrhB,gBAAAA,EAAC,QAAA,EAAK,GAAE,qCAAA,CAAqC;AAAA,EAAA,GAC/C;AAAA,EAEF,QAAQ,CAACD,wBACN,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,8EAAA,CAA8E;AAAA,IACtF,gBAAAA,EAAC,QAAA,EAAK,GAAE,iBAAA,CAAiB;AAAA,EAAA,GAC3B;AAAA,EAEF,UAAU,CAACD,wBACR,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,sFAAA,CAAsF;AAAA,IAC9F,gBAAAA,EAAC,QAAA,EAAK,GAAE,2CAAA,CAA2C;AAAA,IACnD,gBAAAA,EAAC,QAAA,EAAK,GAAE,eAAA,CAAe;AAAA,EAAA,GACzB;AAAA,EAEF,UAAU,CAACD,wBACR,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,8EAAA,CAA8E;AAAA,IACtF,gBAAAA,EAAC,QAAA,EAAK,GAAE,gBAAA,CAAgB;AAAA,EAAA,GAC1B;AAAA,EAEF,MAAM,CAACD,wBACJ,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,0BAAA,CAA0B;AAAA,IAClC,gBAAAA,EAAC,QAAA,EAAK,GAAE,yEAAA,CAAyE;AAAA,EAAA,GACnF;AAAA,EAEF,SAAS,CAACD,wBACP,OAAA,EAAI,WAAAA,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAG,EAAC,QAAA,EAAK,GAAE,kFAAA,CAAkF;AAAA,IAC1F,gBAAAA,EAAC,QAAA,EAAK,GAAE,iBAAA,CAAiB;AAAA,IACzB,gBAAAA,EAAC,QAAA,EAAK,GAAE,cAAA,CAAc;AAAA,EAAA,EAAA,CACxB;AAEJ;"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Two letters from a name or an email, for where a photo would go: the trigger,
3
+ * the person block, and the organisation tile when the consumer gives none.
4
+ *
5
+ * The Hub's own rule (boost33-hub src/lib/initials.ts), copied rather than
6
+ * re-derived, because the Hub and every product draw the same person behind the
7
+ * same avatar and must never disagree about the letters -- one screen saying SM
8
+ * and the next saying SO for the same person reads as a bug in whichever one you
9
+ * look at second.
10
+ */
11
+ export declare function initials(value: string): string;
@@ -0,0 +1,8 @@
1
+ function r(e) {
2
+ const t = e.split("@")[0].trim().split(/[\s._-]+/).filter(Boolean);
3
+ return t.length === 0 ? "?" : t.length === 1 ? t[0].slice(0, 2).toUpperCase() : (t[0][0] + t[1][0]).toUpperCase();
4
+ }
5
+ export {
6
+ r as initials
7
+ };
8
+ //# sourceMappingURL=initials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initials.js","sources":["../../../src/components/AccountMenu/initials.ts"],"sourcesContent":["/**\n * Two letters from a name or an email, for where a photo would go: the trigger,\n * the person block, and the organisation tile when the consumer gives none.\n *\n * The Hub's own rule (boost33-hub src/lib/initials.ts), copied rather than\n * re-derived, because the Hub and every product draw the same person behind the\n * same avatar and must never disagree about the letters -- one screen saying SM\n * and the next saying SO for the same person reads as a bug in whichever one you\n * look at second.\n */\nexport function initials(value: string): string {\n const words = value.split('@')[0]!.trim().split(/[\\s._-]+/).filter(Boolean)\n if (words.length === 0) return '?'\n if (words.length === 1) return words[0]!.slice(0, 2).toUpperCase()\n return (words[0]![0]! + words[1]![0]!).toUpperCase()\n}\n"],"names":["initials","value","words"],"mappings":"AAUO,SAASA,EAASC,GAAuB;AAC9C,QAAMC,IAAQD,EAAM,MAAM,GAAG,EAAE,CAAC,EAAG,KAAA,EAAO,MAAM,UAAU,EAAE,OAAO,OAAO;AAC1E,SAAIC,EAAM,WAAW,IAAU,MAC3BA,EAAM,WAAW,IAAUA,EAAM,CAAC,EAAG,MAAM,GAAG,CAAC,EAAE,YAAA,KAC7CA,EAAM,CAAC,EAAG,CAAC,IAAKA,EAAM,CAAC,EAAG,CAAC,GAAI,YAAA;AACzC;"}
@@ -1,15 +1,52 @@
1
1
  /** Every user-facing string the account menu renders that is not row data. */
2
2
  export interface AccountMenuLabels {
3
3
  /**
4
- * The panel's accessible name. A disclosure panel carries no widget role, so
5
- * an `aria-label` is the only thing that names it (ADR-0010). Default
6
- * "Account".
4
+ * The menu's accessible name. Default "Your account", the Hub's.
7
5
  *
8
6
  * It names the control rather than the person in it: the person's name and
9
7
  * email are inside the panel and a screen reader reaches them on the way
10
8
  * through, so putting them in the name as well says them twice.
11
9
  */
12
10
  panel: string;
11
+ /**
12
+ * The avatar button's accessible name. Default "Your account", the Hub's. The
13
+ * button shows initials or a photo, neither of which says what it opens.
14
+ */
15
+ trigger: string;
16
+ /** The person block's name line when no name is known. Default "You", the Hub's. */
17
+ unnamed: string;
18
+ }
19
+ /**
20
+ * The standard rows' words (`standardAccountMenuGroups`, `StandardAccountMenu`).
21
+ * English defaults are the Hub's, copied from its working menu (boost33-hub
22
+ * src/app/account-menu.tsx), so that every app's menu reads as the Hub's does.
23
+ * Each field is overridable for a translated product; a field left out, or
24
+ * supplied as `undefined`, keeps its default.
25
+ */
26
+ export interface StandardAccountMenuLabels {
27
+ profile: string;
28
+ profileDescription: string;
29
+ security: string;
30
+ securityDescription: string;
31
+ settings: string;
32
+ /** Settings' line for an admin. */
33
+ settingsDescriptionAdmin: string;
34
+ /** Settings' line for a member, or when no role is known. */
35
+ settingsDescriptionMember: string;
36
+ switchAccount: string;
37
+ /** Switch account's line when the other accounts are counted, not named, and there is one. */
38
+ otherAccountsOne: string;
39
+ /** Switch account's line when the other accounts are counted, not named. `%count%` is the number. */
40
+ otherAccountsMany: string;
41
+ feedback: string;
42
+ feedbackDescription: string;
43
+ docs: string;
44
+ docsDescription: string;
45
+ signOut: string;
46
+ /** The role pill for an admin. */
47
+ roleAdmin: string;
48
+ /** The role pill for a member. */
49
+ roleMember: string;
13
50
  }
14
51
  /**
15
52
  * The English defaults. A consumer that passes nothing renders English.
@@ -24,6 +61,28 @@ export interface AccountMenuLabels {
24
61
  */
25
62
  export declare const defaultAccountMenuLabels: Readonly<{
26
63
  panel: string;
64
+ trigger: string;
65
+ unnamed: string;
66
+ }>;
67
+ /** The standard rows' English defaults, the Hub's words. Frozen for the reason above. */
68
+ export declare const defaultStandardAccountMenuLabels: Readonly<{
69
+ profile: string;
70
+ profileDescription: string;
71
+ security: string;
72
+ securityDescription: string;
73
+ settings: string;
74
+ settingsDescriptionAdmin: string;
75
+ settingsDescriptionMember: string;
76
+ switchAccount: string;
77
+ otherAccountsOne: string;
78
+ otherAccountsMany: string;
79
+ feedback: string;
80
+ feedbackDescription: string;
81
+ docs: string;
82
+ docsDescription: string;
83
+ signOut: string;
84
+ roleAdmin: string;
85
+ roleMember: string;
27
86
  }>;
28
87
  /**
29
88
  * Merge a consumer's partial `labels` onto the English defaults, treating an
@@ -37,3 +96,5 @@ export declare const defaultAccountMenuLabels: Readonly<{
37
96
  * skip does not (B33-873).
38
97
  */
39
98
  export declare function resolveAccountMenuLabels(labels?: Partial<AccountMenuLabels>): AccountMenuLabels;
99
+ /** As `resolveAccountMenuLabels`, for the standard rows' words. */
100
+ export declare function resolveStandardAccountMenuLabels(labels?: Partial<StandardAccountMenuLabels>): StandardAccountMenuLabels;
@@ -1,14 +1,42 @@
1
- const c = Object.freeze({
2
- panel: "Account"
1
+ const o = Object.freeze({
2
+ panel: "Your account",
3
+ trigger: "Your account",
4
+ unnamed: "You"
5
+ }), c = Object.freeze({
6
+ profile: "Profile",
7
+ profileDescription: "Your name, photo and email",
8
+ security: "Security",
9
+ securityDescription: "How you sign in, and your passphrase",
10
+ settings: "Settings",
11
+ settingsDescriptionAdmin: "Team, sign-in, billing and other accounts",
12
+ settingsDescriptionMember: "Notifications and other accounts",
13
+ switchAccount: "Switch account",
14
+ otherAccountsOne: "1 other account",
15
+ otherAccountsMany: "%count% other accounts",
16
+ feedback: "Feedback",
17
+ feedbackDescription: "Tell us what's working or not",
18
+ docs: "Docs",
19
+ docsDescription: "Guides for every Boost33 product",
20
+ signOut: "Sign out",
21
+ roleAdmin: "Admin",
22
+ roleMember: "Member"
3
23
  });
4
- function o(e) {
24
+ function i(e) {
25
+ const t = Object.fromEntries(
26
+ Object.entries(e ?? {}).filter(([, n]) => n !== void 0)
27
+ );
28
+ return { ...o, ...t };
29
+ }
30
+ function r(e) {
5
31
  const t = Object.fromEntries(
6
32
  Object.entries(e ?? {}).filter(([, n]) => n !== void 0)
7
33
  );
8
34
  return { ...c, ...t };
9
35
  }
10
36
  export {
11
- c as defaultAccountMenuLabels,
12
- o as resolveAccountMenuLabels
37
+ o as defaultAccountMenuLabels,
38
+ c as defaultStandardAccountMenuLabels,
39
+ i as resolveAccountMenuLabels,
40
+ r as resolveStandardAccountMenuLabels
13
41
  };
14
42
  //# sourceMappingURL=labels.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"labels.js","sources":["../../../src/components/AccountMenu/labels.ts"],"sourcesContent":["// The account menu's own user-facing words -- which is one word, and the\n// shortness is the point of the file rather than an argument against it.\n//\n// Everything else a person reads in this panel is row data the consumer supplies\n// and therefore translates by construction: the person's name and email, the\n// organisation's name, initials and role, and each row's label and description.\n// That is the same split AppSwitcherLabels draws (\"the product names ... are\n// Identity's data, supplied per row, not copy this package owns\"). What is left\n// is the panel's accessible name, which no row carries and which a disclosure\n// panel has no other way to get (ADR-0010).\n//\n// It takes the WalletChip's label contract rather than inventing a third\n// dialect, and it takes it in full on day one even for one field. B33-1007 is\n// the ticket for what happens otherwise: the switcher's module opens by claiming\n// parity with the chip's and had none, and two of the three divergences it\n// records were only expensive because they were retrofitted. The first --\n// `labels` being the whole interface rather than a `Partial` -- is a breaking\n// change to fix once a consumer has written the object out, which is exactly the\n// position a one-field interface invites. So:\n//\n// * `labels` is `Partial<AccountMenuLabels>`, so the second field costs no\n// consumer a typecheck (CLAUDE.md, \"Versioning\");\n// * `resolveAccountMenuLabels` filters explicitly-`undefined` fields before\n// merging, so a missed next-intl key falls back to English instead of\n// unnaming the panel (B33-873);\n// * `defaultAccountMenuLabels` is `Object.freeze({ ... } satisfies ...)` with\n// no type annotation, so the freeze survives into the emitted `.d.ts`\n// instead of being widened back to mutable (B33-877, B33-916).\n//\n// There are no value-carrying labels here, so none of the chip's\n// `%placeholder%` machinery is reproduced. If one ever arrives, it arrives in the\n// chip's shape -- a template delimited `%name%`, never `{name}`, because a brace\n// is ICU argument syntax and a consumer keeps these strings in an ICU catalogue\n// (B33-1277; the reasoning is in `src/fill-template.ts`).\n\n/** Every user-facing string the account menu renders that is not row data. */\nexport interface AccountMenuLabels {\n /**\n * The panel's accessible name. A disclosure panel carries no widget role, so\n * an `aria-label` is the only thing that names it (ADR-0010). Default\n * \"Account\".\n *\n * It names the control rather than the person in it: the person's name and\n * email are inside the panel and a screen reader reaches them on the way\n * through, so putting them in the name as well says them twice.\n */\n panel: string\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders English.\n *\n * Frozen because it is a public export reaching four products on a version bump:\n * `const` protects the binding and not the contents, so without the freeze a\n * consumer aliasing it -- `const mine = defaultAccountMenuLabels; mine.panel =\n * 'Compte'` -- renames every account menu in the process (B33-877). The freeze\n * carries no `: AccountMenuLabels` annotation on purpose; the annotation widens\n * `Readonly<...>` back to mutable in the emitted declaration file and makes the\n * freeze runtime-only (B33-916). `satisfies` checks the shape without widening.\n */\nexport const defaultAccountMenuLabels = Object.freeze({\n panel: 'Account',\n} satisfies AccountMenuLabels)\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` and `resolveAppSwitcherLabels` perform.\n *\n * A next-intl `t('...')` returns `undefined` on a missed key, so\n * `{ panel: undefined }` is what one missing translation looks like at the call\n * site. A plain object spread copies that `undefined` across and leaves the\n * panel with an empty `aria-label`, which is the one thing naming it; a per-field\n * skip does not (B33-873).\n */\nexport function resolveAccountMenuLabels(labels?: Partial<AccountMenuLabels>): AccountMenuLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<AccountMenuLabels>\n return { ...defaultAccountMenuLabels, ...supplied }\n}\n"],"names":["defaultAccountMenuLabels","resolveAccountMenuLabels","labels","supplied","value"],"mappings":"AA4DO,MAAMA,IAA2B,OAAO,OAAO;AAAA,EACpD,OAAO;AACT,CAA6B;AAatB,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;"}
1
+ {"version":3,"file":"labels.js","sources":["../../../src/components/AccountMenu/labels.ts"],"sourcesContent":["// The account menu's own user-facing words: its two accessible names and the\n// fallback name line, plus the standard rows' words (B33-493 Stage B).\n//\n// Everything else a person reads in this panel is row data the consumer supplies\n// and therefore translates by construction: the person's name and email, the\n// organisation's name, initials and role, and each row's label and description.\n// That is the same split AppSwitcherLabels draws (\"the product names ... are\n// Identity's data, supplied per row, not copy this package owns\"). What is left\n// is the panel's accessible name, which no row carries and which a disclosure\n// panel has no other way to get (ADR-0010).\n//\n// It takes the WalletChip's label contract rather than inventing a third\n// dialect, and it takes it in full on day one even for one field. B33-1007 is\n// the ticket for what happens otherwise: the switcher's module opens by claiming\n// parity with the chip's and had none, and two of the three divergences it\n// records were only expensive because they were retrofitted. The first --\n// `labels` being the whole interface rather than a `Partial` -- is a breaking\n// change to fix once a consumer has written the object out, which is exactly the\n// position a one-field interface invites. So:\n//\n// * `labels` is `Partial<AccountMenuLabels>`, so the second field costs no\n// consumer a typecheck (CLAUDE.md, \"Versioning\");\n// * `resolveAccountMenuLabels` filters explicitly-`undefined` fields before\n// merging, so a missed next-intl key falls back to English instead of\n// unnaming the panel (B33-873);\n// * `defaultAccountMenuLabels` is `Object.freeze({ ... } satisfies ...)` with\n// no type annotation, so the freeze survives into the emitted `.d.ts`\n// instead of being widened back to mutable (B33-877, B33-916).\n//\n// One value-carrying label, `otherAccountsMany`, in the chip's shape: a template\n// delimited `%count%`, never `{count}`, because a brace is ICU argument syntax and\n// a consumer keeps these strings in an ICU catalogue (B33-1277; the reasoning is\n// in `src/fill-template.ts`).\n\n/** Every user-facing string the account menu renders that is not row data. */\nexport interface AccountMenuLabels {\n /**\n * The menu's accessible name. Default \"Your account\", the Hub's.\n *\n * It names the control rather than the person in it: the person's name and\n * email are inside the panel and a screen reader reaches them on the way\n * through, so putting them in the name as well says them twice.\n */\n panel: string\n /**\n * The avatar button's accessible name. Default \"Your account\", the Hub's. The\n * button shows initials or a photo, neither of which says what it opens.\n */\n trigger: string\n /** The person block's name line when no name is known. Default \"You\", the Hub's. */\n unnamed: string\n}\n\n/**\n * The standard rows' words (`standardAccountMenuGroups`, `StandardAccountMenu`).\n * English defaults are the Hub's, copied from its working menu (boost33-hub\n * src/app/account-menu.tsx), so that every app's menu reads as the Hub's does.\n * Each field is overridable for a translated product; a field left out, or\n * supplied as `undefined`, keeps its default.\n */\nexport interface StandardAccountMenuLabels {\n profile: string\n profileDescription: string\n security: string\n securityDescription: string\n settings: string\n /** Settings' line for an admin. */\n settingsDescriptionAdmin: string\n /** Settings' line for a member, or when no role is known. */\n settingsDescriptionMember: string\n switchAccount: string\n /** Switch account's line when the other accounts are counted, not named, and there is one. */\n otherAccountsOne: string\n /** Switch account's line when the other accounts are counted, not named. `%count%` is the number. */\n otherAccountsMany: string\n feedback: string\n feedbackDescription: string\n docs: string\n docsDescription: string\n signOut: string\n /** The role pill for an admin. */\n roleAdmin: string\n /** The role pill for a member. */\n roleMember: string\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders English.\n *\n * Frozen because it is a public export reaching four products on a version bump:\n * `const` protects the binding and not the contents, so without the freeze a\n * consumer aliasing it -- `const mine = defaultAccountMenuLabels; mine.panel =\n * 'Compte'` -- renames every account menu in the process (B33-877). The freeze\n * carries no `: AccountMenuLabels` annotation on purpose; the annotation widens\n * `Readonly<...>` back to mutable in the emitted declaration file and makes the\n * freeze runtime-only (B33-916). `satisfies` checks the shape without widening.\n */\nexport const defaultAccountMenuLabels = Object.freeze({\n panel: 'Your account',\n trigger: 'Your account',\n unnamed: 'You',\n} satisfies AccountMenuLabels)\n\n/** The standard rows' English defaults, the Hub's words. Frozen for the reason above. */\nexport const defaultStandardAccountMenuLabels = Object.freeze({\n profile: 'Profile',\n profileDescription: 'Your name, photo and email',\n security: 'Security',\n securityDescription: 'How you sign in, and your passphrase',\n settings: 'Settings',\n settingsDescriptionAdmin: 'Team, sign-in, billing and other accounts',\n settingsDescriptionMember: 'Notifications and other accounts',\n switchAccount: 'Switch account',\n otherAccountsOne: '1 other account',\n otherAccountsMany: '%count% other accounts',\n feedback: 'Feedback',\n feedbackDescription: \"Tell us what's working or not\",\n docs: 'Docs',\n docsDescription: 'Guides for every Boost33 product',\n signOut: 'Sign out',\n roleAdmin: 'Admin',\n roleMember: 'Member',\n} satisfies StandardAccountMenuLabels)\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` and `resolveAppSwitcherLabels` perform.\n *\n * A next-intl `t('...')` returns `undefined` on a missed key, so\n * `{ panel: undefined }` is what one missing translation looks like at the call\n * site. A plain object spread copies that `undefined` across and leaves the\n * panel with an empty `aria-label`, which is the one thing naming it; a per-field\n * skip does not (B33-873).\n */\nexport function resolveAccountMenuLabels(labels?: Partial<AccountMenuLabels>): AccountMenuLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<AccountMenuLabels>\n return { ...defaultAccountMenuLabels, ...supplied }\n}\n\n/** As `resolveAccountMenuLabels`, for the standard rows' words. */\nexport function resolveStandardAccountMenuLabels(\n labels?: Partial<StandardAccountMenuLabels>,\n): StandardAccountMenuLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<StandardAccountMenuLabels>\n return { ...defaultStandardAccountMenuLabels, ...supplied }\n}\n"],"names":["defaultAccountMenuLabels","defaultStandardAccountMenuLabels","resolveAccountMenuLabels","labels","supplied","value","resolveStandardAccountMenuLabels"],"mappings":"AAiGO,MAAMA,IAA2B,OAAO,OAAO;AAAA,EACpD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,SAAS;AACX,CAA6B,GAGhBC,IAAmC,OAAO,OAAO;AAAA,EAC5D,SAAS;AAAA,EACT,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,UAAU;AAAA,EACV,0BAA0B;AAAA,EAC1B,2BAA2B;AAAA,EAC3B,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,WAAW;AAAA,EACX,YAAY;AACd,CAAqC;AAa9B,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,GAAGL,GAA0B,GAAGI,EAAA;AAC3C;AAGO,SAASE,EACdH,GAC2B;AAC3B,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA;AAExE,SAAO,EAAE,GAAGJ,GAAkC,GAAGG,EAAA;AACnD;"}
@@ -0,0 +1,34 @@
1
+ import { FocusEvent as ReactFocusEvent, KeyboardEvent as ReactKeyboardEvent } from 'react';
2
+ export type MenuFocus = 'first' | 'last';
3
+ /**
4
+ * Where a key press inside an open menu moves focus, as an item index, or null
5
+ * when the key is not a menu key. `current` is -1 when focus is on something
6
+ * that is not an item.
7
+ */
8
+ export declare function menuKeyTarget(key: string, current: number, count: number): number | null;
9
+ /** What a key press on the closed trigger does: open at one end, or nothing. */
10
+ export declare function triggerKeyOpens(key: string): MenuFocus | null;
11
+ export declare function useMenu({ defaultOpen }?: {
12
+ defaultOpen?: boolean;
13
+ }): {
14
+ open: boolean;
15
+ close: (returnFocus: boolean) => void;
16
+ rootProps: {
17
+ ref: import('react').RefObject<HTMLDivElement | null>;
18
+ onBlur: (event: ReactFocusEvent<HTMLDivElement>) => void;
19
+ };
20
+ triggerProps: {
21
+ ref: import('react').RefObject<HTMLButtonElement | null>;
22
+ 'aria-haspopup': "menu";
23
+ 'aria-expanded': boolean;
24
+ 'aria-controls': string | undefined;
25
+ onClick: () => void;
26
+ onKeyDown: (event: ReactKeyboardEvent<HTMLButtonElement>) => void;
27
+ };
28
+ menuProps: {
29
+ ref: import('react').RefObject<HTMLDivElement | null>;
30
+ id: string;
31
+ role: "menu";
32
+ onKeyDown: (event: ReactKeyboardEvent<HTMLDivElement>) => void;
33
+ };
34
+ };