@boost33/ui 0.8.0-next.5 → 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.
- package/CHANGELOG.md +262 -271
- package/dist/components/AccountMenu/AccountMenu.d.ts +42 -32
- package/dist/components/AccountMenu/AccountMenu.js +66 -48
- package/dist/components/AccountMenu/AccountMenu.js.map +1 -1
- package/dist/components/AccountMenu/AccountMenu.module.css.js +44 -38
- package/dist/components/AccountMenu/AccountMenu.module.css.js.map +1 -1
- package/dist/components/AccountMenu/StandardAccountMenu.d.ts +87 -0
- package/dist/components/AccountMenu/StandardAccountMenu.js +86 -0
- package/dist/components/AccountMenu/StandardAccountMenu.js.map +1 -0
- package/dist/components/AccountMenu/icons.d.ts +2 -2
- package/dist/components/AccountMenu/icons.js +21 -8
- package/dist/components/AccountMenu/icons.js.map +1 -1
- package/dist/components/AccountMenu/initials.d.ts +11 -0
- package/dist/components/AccountMenu/initials.js +8 -0
- package/dist/components/AccountMenu/initials.js.map +1 -0
- package/dist/components/AccountMenu/labels.d.ts +64 -3
- package/dist/components/AccountMenu/labels.js +33 -5
- package/dist/components/AccountMenu/labels.js.map +1 -1
- package/dist/components/AccountMenu/use-menu.d.ts +34 -0
- package/dist/components/AccountMenu/use-menu.js +102 -0
- package/dist/components/AccountMenu/use-menu.js.map +1 -0
- package/dist/components/TopBar/TopBar.d.ts +8 -1
- package/dist/components/TopBar/TopBar.js +19 -16
- package/dist/components/TopBar/TopBar.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +40 -36
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
2
|
-
panel: "
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { useState as K, useRef as a, useId as O, useCallback as m, useEffect as E } from "react";
|
|
2
|
+
function R(i, r, u) {
|
|
3
|
+
if (u === 0) return null;
|
|
4
|
+
switch (i) {
|
|
5
|
+
case "ArrowDown":
|
|
6
|
+
return r < 0 ? 0 : (r + 1) % u;
|
|
7
|
+
case "ArrowUp":
|
|
8
|
+
return r < 0 ? u - 1 : (r - 1 + u) % u;
|
|
9
|
+
case "Home":
|
|
10
|
+
return 0;
|
|
11
|
+
case "End":
|
|
12
|
+
return u - 1;
|
|
13
|
+
default:
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function v(i) {
|
|
18
|
+
return i === "ArrowDown" ? "first" : i === "ArrowUp" ? "last" : null;
|
|
19
|
+
}
|
|
20
|
+
const b = "[role='menuitem']:not([disabled])";
|
|
21
|
+
function C({ defaultOpen: i = !1 } = {}) {
|
|
22
|
+
const [r, u] = K(i), c = a(null), d = a(null), p = a(null), f = a(null), g = O(), l = m(
|
|
23
|
+
() => {
|
|
24
|
+
var e;
|
|
25
|
+
return Array.from(((e = p.current) == null ? void 0 : e.querySelectorAll(b)) ?? []);
|
|
26
|
+
},
|
|
27
|
+
[]
|
|
28
|
+
), w = m((e) => {
|
|
29
|
+
f.current = e, u(!0);
|
|
30
|
+
}, []), s = m((e) => {
|
|
31
|
+
var n;
|
|
32
|
+
u(!1), e && ((n = d.current) == null || n.focus());
|
|
33
|
+
}, []);
|
|
34
|
+
E(() => {
|
|
35
|
+
if (!r || !f.current) return;
|
|
36
|
+
const e = l(), n = f.current === "first" ? e[0] : e[e.length - 1];
|
|
37
|
+
f.current = null, n == null || n.focus();
|
|
38
|
+
}, [r, l]), E(() => {
|
|
39
|
+
if (!r) return;
|
|
40
|
+
function e(n) {
|
|
41
|
+
var t;
|
|
42
|
+
(t = c.current) != null && t.contains(n.target) || (s(!1), setTimeout(() => {
|
|
43
|
+
var y, D;
|
|
44
|
+
const o = document.activeElement;
|
|
45
|
+
(!o || o === document.body || (y = c.current) != null && y.contains(o)) && ((D = d.current) == null || D.focus());
|
|
46
|
+
}, 0));
|
|
47
|
+
}
|
|
48
|
+
return document.addEventListener("pointerdown", e), () => document.removeEventListener("pointerdown", e);
|
|
49
|
+
}, [r, s]);
|
|
50
|
+
function T() {
|
|
51
|
+
r ? s(!1) : w("first");
|
|
52
|
+
}
|
|
53
|
+
function h(e) {
|
|
54
|
+
var t;
|
|
55
|
+
const n = v(e.key);
|
|
56
|
+
if (n)
|
|
57
|
+
if (e.preventDefault(), r) {
|
|
58
|
+
const o = l();
|
|
59
|
+
(t = n === "first" ? o[0] : o[o.length - 1]) == null || t.focus();
|
|
60
|
+
} else
|
|
61
|
+
w(n);
|
|
62
|
+
}
|
|
63
|
+
function k(e) {
|
|
64
|
+
if (e.key === "Escape") {
|
|
65
|
+
e.preventDefault(), s(!0);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const n = l(), t = n.indexOf(document.activeElement);
|
|
69
|
+
if (e.key === "Tab") {
|
|
70
|
+
t >= 0 && s(!0);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const o = R(e.key, t, n.length);
|
|
74
|
+
o !== null && (e.preventDefault(), n[o].focus());
|
|
75
|
+
}
|
|
76
|
+
function A(e) {
|
|
77
|
+
var t;
|
|
78
|
+
if (!r) return;
|
|
79
|
+
const n = e.relatedTarget;
|
|
80
|
+
n && !((t = c.current) != null && t.contains(n)) && s(!1);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
open: r,
|
|
84
|
+
close: s,
|
|
85
|
+
rootProps: { ref: c, onBlur: A },
|
|
86
|
+
triggerProps: {
|
|
87
|
+
ref: d,
|
|
88
|
+
"aria-haspopup": "menu",
|
|
89
|
+
"aria-expanded": r,
|
|
90
|
+
"aria-controls": r ? g : void 0,
|
|
91
|
+
onClick: T,
|
|
92
|
+
onKeyDown: h
|
|
93
|
+
},
|
|
94
|
+
menuProps: { ref: p, id: g, role: "menu", onKeyDown: k }
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
R as menuKeyTarget,
|
|
99
|
+
v as triggerKeyOpens,
|
|
100
|
+
C as useMenu
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=use-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-menu.js","sources":["../../../src/components/AccountMenu/use-menu.ts"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type FocusEvent as ReactFocusEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n} from 'react'\n\n// Menu-button behaviour for AccountMenu (B33-493 Stage B), ported from the Hub's\n// working menu (boost33-hub src/app/use-menu.ts), which is the referent Jamal\n// named on 5 Nov 2026. The WAI-ARIA menu button pattern:\n//\n// - Opening moves focus to the first item (ArrowUp on the trigger: the last).\n// - ArrowDown/ArrowUp wrap through the items, Home/End jump to the ends.\n// - Items carry tabIndex -1, so Tab leaves the menu instead of walking it, and\n// leaving closes it.\n// - Escape closes and returns focus to the trigger. So does a click outside,\n// unless that click put focus somewhere else on purpose.\n//\n// This is a menu, not the disclosure AppSwitcher is (ADR-0010): the Hub's is a\n// menu, every app's avatar is to behave exactly as the Hub's does, and a person\n// moving between the Hub and a product must not meet two keyboard models behind\n// the same avatar. The two key decisions are split out as pure functions so they\n// test without a DOM, as the Hub's are.\n\nexport type MenuFocus = 'first' | 'last'\n\n/**\n * Where a key press inside an open menu moves focus, as an item index, or null\n * when the key is not a menu key. `current` is -1 when focus is on something\n * that is not an item.\n */\nexport function menuKeyTarget(key: string, current: number, count: number): number | null {\n if (count === 0) return null\n switch (key) {\n case 'ArrowDown':\n return current < 0 ? 0 : (current + 1) % count\n case 'ArrowUp':\n return current < 0 ? count - 1 : (current - 1 + count) % count\n case 'Home':\n return 0\n case 'End':\n return count - 1\n default:\n return null\n }\n}\n\n/** What a key press on the closed trigger does: open at one end, or nothing. */\nexport function triggerKeyOpens(key: string): MenuFocus | null {\n if (key === 'ArrowDown') return 'first'\n if (key === 'ArrowUp') return 'last'\n return null\n}\n\nconst ITEM_SELECTOR = \"[role='menuitem']:not([disabled])\"\n\nexport function useMenu({ defaultOpen = false }: { defaultOpen?: boolean } = {}) {\n const [open, setOpen] = useState(defaultOpen)\n const rootRef = useRef<HTMLDivElement>(null)\n const triggerRef = useRef<HTMLButtonElement>(null)\n const menuRef = useRef<HTMLDivElement>(null)\n const focusOnOpen = useRef<MenuFocus | null>(null)\n const menuId = useId()\n\n const items = useCallback(\n () => Array.from(menuRef.current?.querySelectorAll<HTMLElement>(ITEM_SELECTOR) ?? []),\n [],\n )\n\n const show = useCallback((at: MenuFocus) => {\n focusOnOpen.current = at\n setOpen(true)\n }, [])\n\n const close = useCallback((returnFocus: boolean) => {\n setOpen(false)\n if (returnFocus) triggerRef.current?.focus()\n }, [])\n\n // Focus lands on an item once the menu is in the DOM. A menu drawn open by\n // `defaultOpen` takes no focus: nobody opened it, so nothing should move.\n useEffect(() => {\n if (!open || !focusOnOpen.current) return\n const list = items()\n const target = focusOnOpen.current === 'first' ? list[0] : list[list.length - 1]\n focusOnOpen.current = null\n target?.focus()\n }, [open, items])\n\n // `pointerdown` rather than `click`: a press that starts inside the menu and\n // ends outside it should not count as leaving.\n useEffect(() => {\n if (!open) return\n function onPointerDown(event: PointerEvent) {\n if (rootRef.current?.contains(event.target as Node)) return\n close(false)\n // A click on blank page leaves focus nowhere; put it back on the\n // trigger. A click on another control keeps the focus it just took.\n setTimeout(() => {\n const active = document.activeElement\n if (!active || active === document.body || rootRef.current?.contains(active)) {\n triggerRef.current?.focus()\n }\n }, 0)\n }\n document.addEventListener('pointerdown', onPointerDown)\n return () => document.removeEventListener('pointerdown', onPointerDown)\n }, [open, close])\n\n function onTriggerClick() {\n if (open) close(false)\n else show('first')\n }\n\n function onTriggerKeyDown(event: ReactKeyboardEvent<HTMLButtonElement>) {\n const at = triggerKeyOpens(event.key)\n if (!at) return\n event.preventDefault()\n if (open) {\n const list = items()\n ;(at === 'first' ? list[0] : list[list.length - 1])?.focus()\n } else {\n show(at)\n }\n }\n\n function onMenuKeyDown(event: ReactKeyboardEvent<HTMLDivElement>) {\n if (event.key === 'Escape') {\n event.preventDefault()\n close(true)\n return\n }\n const list = items()\n const current = list.indexOf(document.activeElement as HTMLElement)\n if (event.key === 'Tab') {\n // Focus goes to the trigger first so the browser's own Tab moves on from\n // there, to the next thing on the page.\n if (current >= 0) close(true)\n return\n }\n const next = menuKeyTarget(event.key, current, list.length)\n if (next === null) return\n event.preventDefault()\n list[next]!.focus()\n }\n\n /** `focusout` on the whole widget: focus leaving it for somewhere else closes it. */\n function onRootBlur(event: ReactFocusEvent<HTMLDivElement>) {\n if (!open) return\n const to = event.relatedTarget as Node | null\n // Null is focus going nowhere (a blank-page click, the window losing focus,\n // the focused item unmounting); the pointerdown handler covers the click,\n // and the others should not close anything.\n if (to && !rootRef.current?.contains(to)) close(false)\n }\n\n return {\n open,\n close,\n rootProps: { ref: rootRef, onBlur: onRootBlur },\n triggerProps: {\n ref: triggerRef,\n 'aria-haspopup': 'menu' as const,\n 'aria-expanded': open,\n 'aria-controls': open ? menuId : undefined,\n onClick: onTriggerClick,\n onKeyDown: onTriggerKeyDown,\n },\n menuProps: { ref: menuRef, id: menuId, role: 'menu' as const, onKeyDown: onMenuKeyDown },\n }\n}\n"],"names":["menuKeyTarget","key","current","count","triggerKeyOpens","ITEM_SELECTOR","useMenu","defaultOpen","open","setOpen","useState","rootRef","useRef","triggerRef","menuRef","focusOnOpen","menuId","useId","items","useCallback","_a","show","at","close","returnFocus","useEffect","list","target","onPointerDown","event","active","_b","onTriggerClick","onTriggerKeyDown","onMenuKeyDown","next","onRootBlur","to"],"mappings":"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"}
|
|
@@ -43,6 +43,13 @@ export interface TopBarProps {
|
|
|
43
43
|
* open from it is left out of 0.8.0 with no trigger and no shortcut.
|
|
44
44
|
*/
|
|
45
45
|
actions?: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* The account menu, at the far right of the bar after `actions` (B33-493). A
|
|
48
|
+
* slot: pass `StandardAccountMenu`, or `AccountMenu` with rows of your own. Its
|
|
49
|
+
* panel opens under the avatar, aligned to the bar's right edge. Every app
|
|
50
|
+
* puts the same menu here, so a person meets one account menu across Boost33.
|
|
51
|
+
*/
|
|
52
|
+
account?: ReactNode;
|
|
46
53
|
/** The centre group's two labels. Each field defaults to English. */
|
|
47
54
|
labels?: Partial<TopBarLabels>;
|
|
48
55
|
/**
|
|
@@ -56,4 +63,4 @@ export interface TopBarProps {
|
|
|
56
63
|
*/
|
|
57
64
|
breadcrumb?: ReactNode;
|
|
58
65
|
}
|
|
59
|
-
export declare function TopBar({ sidebarToggle, start, switcher, tokens, actions, labels, breadcrumb }: TopBarProps): import("react").JSX.Element;
|
|
66
|
+
export declare function TopBar({ sidebarToggle, start, switcher, tokens, actions, account, labels, breadcrumb }: TopBarProps): import("react").JSX.Element;
|
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { warnDeprecatedProp as
|
|
3
|
-
import { resolveTopBarLabels as
|
|
4
|
-
import
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
return /* @__PURE__ */
|
|
9
|
-
/* @__PURE__ */
|
|
1
|
+
import { jsxs as e, Fragment as s, jsx as c } from "react/jsx-runtime";
|
|
2
|
+
import { warnDeprecatedProp as u } from "../../deprecated-prop-warning.js";
|
|
3
|
+
import { resolveTopBarLabels as f } from "./labels.js";
|
|
4
|
+
import l from "./TopBar.module.css.js";
|
|
5
|
+
function x({ sidebarToggle: o, start: d, switcher: r, tokens: a, actions: i, account: t, labels: m, breadcrumb: p }) {
|
|
6
|
+
p != null && u("TopBar.breadcrumb");
|
|
7
|
+
const n = f(m), h = r != null || a != null;
|
|
8
|
+
return /* @__PURE__ */ e("header", { className: l.bar, children: [
|
|
9
|
+
/* @__PURE__ */ e("div", { className: l.start, children: [
|
|
10
10
|
o,
|
|
11
11
|
d
|
|
12
12
|
] }),
|
|
13
|
-
|
|
14
|
-
r != null && /* @__PURE__ */
|
|
15
|
-
/* @__PURE__ */
|
|
13
|
+
h && /* @__PURE__ */ e("div", { className: l.centre, children: [
|
|
14
|
+
r != null && /* @__PURE__ */ e(s, { children: [
|
|
15
|
+
/* @__PURE__ */ c("span", { className: l.label, children: n.switchTools }),
|
|
16
16
|
r
|
|
17
17
|
] }),
|
|
18
|
-
a != null && /* @__PURE__ */
|
|
19
|
-
/* @__PURE__ */
|
|
18
|
+
a != null && /* @__PURE__ */ e(s, { children: [
|
|
19
|
+
/* @__PURE__ */ c("span", { className: l.label, "data-after-switcher": r != null ? "true" : void 0, children: n.checkTokens }),
|
|
20
20
|
a
|
|
21
21
|
] })
|
|
22
22
|
] }),
|
|
23
|
-
/* @__PURE__ */
|
|
23
|
+
/* @__PURE__ */ e("div", { className: l.end, children: [
|
|
24
|
+
i,
|
|
25
|
+
t
|
|
26
|
+
] })
|
|
24
27
|
] });
|
|
25
28
|
}
|
|
26
29
|
export {
|
|
27
|
-
|
|
30
|
+
x as TopBar
|
|
28
31
|
};
|
|
29
32
|
//# sourceMappingURL=TopBar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TopBar.js","sources":["../../../src/components/TopBar/TopBar.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { warnDeprecatedProp } from '../../deprecated-prop-warning'\nimport { resolveTopBarLabels, type TopBarLabels } from './labels'\nimport styles from './TopBar.module.css'\n\nexport interface TopBarProps {\n /**\n * The control that collapses the sidebar to its 56px rail and back, at the\n * far left of the bar, before the mark (B33-1712). A slot: the product's own\n * button, which owns the collapsed state.\n *\n * It has a slot of its own because the bar stopped drawing `breadcrumb` in\n * 0.8.0, and a consumer that kept its toggle inside the breadcrumb -- the\n * Wallet did -- would otherwise lose its only way into and out of the rail on\n * upgrade, silently: the pixel diff passed the bar at 0.19% with the toggle\n * gone. No board draws it; the rail keeps working with no new design.\n */\n sidebarToggle?: ReactNode\n /**\n * The left of the bar: the Boost33 mark and the organisation's pill, as the 3\n * Nov 2026 redesign draws it (B33-1712, board 20). A slot. Inside `AppShell`\n * the shell fills it from its own `mark`, `hubHref` and `org` props, so a\n * consumer of the shell does not set this.\n */\n start?: ReactNode\n /**\n * The app switcher, at the centre of the bar after its \"Switch Tools →\"\n * label. A slot: pass `AppSwitcher`. Omit it and neither it nor its label is\n * drawn. Inside `AppShell`, the shell's `switcher` prop lands here.\n */\n switcher?: ReactNode\n /**\n * The token chip, after the switcher and its \"Check Your Tokens →\" label. A\n * slot: pass `WalletChip` with `placement=\"topbar\"`. Omit it and neither it\n * nor its label is drawn. Inside `AppShell`, the shell's `wallet` prop lands\n * here.\n */\n tokens?: ReactNode\n /**\n * The right of the bar: the notification button and the account avatar\n * (board 20). A slot -- the product's own, or the package's\n * `NotificationButton` beside the consumer's avatar.\n *\n * Search is not here and nothing replaces it: Jamal removed it on 3 Nov 2026\n * until the feature is built (B33-1710), and the CommandPalette that would\n * open from it is left out of 0.8.0 with no trigger and no shortcut.\n */\n actions?: ReactNode\n /** The centre group's two labels. Each field defaults to English. */\n labels?: Partial<TopBarLabels>\n /**\n * Was the breadcrumb on the left of the bar. **Not drawn since 0.8.0**: the\n * redesign puts the mark and the organisation there, and the page's own\n * `PageHeader` names where you are. Optional so an existing call site still\n * compiles.\n *\n * @deprecated Rendered nowhere since 0.8.0 (B33-1712). Stop passing it; in\n * development, passing it logs a console warning once.\n */\n breadcrumb?: ReactNode\n}\n\n// The bar across the top of every screen (B33-1712). Three regions: the mark\n// and organisation on the left, the switcher and token chip in a centred group,\n// and the product's actions on the right. Left and right take equal shares of\n// what the centre leaves, which is what holds the centre group in the middle of\n// the bar whatever either side holds.\n//\n// A Server Component: no state, no handlers. The two controls in the centre are\n// client components passed in as slots.\nexport function TopBar({ sidebarToggle, start, switcher, tokens, actions, labels, breadcrumb }: TopBarProps) {\n if (breadcrumb != null) warnDeprecatedProp('TopBar.breadcrumb')\n const t = resolveTopBarLabels(labels)\n const hasCentre = switcher != null || tokens != null\n\n // A <header>, so the bar is the page's banner landmark and a screen-reader\n // user can jump to it, and an end-to-end test can scope to it (score-main,\n // 0.8.0-next.2). Inside AppShell it is never within <main> or an <article>,\n // which is what makes a <header> a banner. `display: grid` is set on the\n // class, so the element swap changes no box.\n return (\n <header className={styles.bar}>\n <div className={styles.start}>\n {sidebarToggle}\n {start}\n </div>\n {hasCentre && (\n <div className={styles.centre}>\n {switcher != null && (\n <>\n <span className={styles.label}>{t.switchTools}</span>\n {switcher}\n </>\n )}\n {tokens != null && (\n <>\n {/* The second label keeps 32px clear of the switcher, so each\n label reads as belonging to the control after it, not the one\n before it (board 20). With no switcher there is nothing to\n keep clear of, and the padding goes. */}\n <span className={styles.label} data-after-switcher={switcher != null ? 'true' : undefined}>\n {t.checkTokens}\n </span>\n {tokens}\n </>\n )}\n </div>\n )}\n <div className={styles.end}
|
|
1
|
+
{"version":3,"file":"TopBar.js","sources":["../../../src/components/TopBar/TopBar.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport { warnDeprecatedProp } from '../../deprecated-prop-warning'\nimport { resolveTopBarLabels, type TopBarLabels } from './labels'\nimport styles from './TopBar.module.css'\n\nexport interface TopBarProps {\n /**\n * The control that collapses the sidebar to its 56px rail and back, at the\n * far left of the bar, before the mark (B33-1712). A slot: the product's own\n * button, which owns the collapsed state.\n *\n * It has a slot of its own because the bar stopped drawing `breadcrumb` in\n * 0.8.0, and a consumer that kept its toggle inside the breadcrumb -- the\n * Wallet did -- would otherwise lose its only way into and out of the rail on\n * upgrade, silently: the pixel diff passed the bar at 0.19% with the toggle\n * gone. No board draws it; the rail keeps working with no new design.\n */\n sidebarToggle?: ReactNode\n /**\n * The left of the bar: the Boost33 mark and the organisation's pill, as the 3\n * Nov 2026 redesign draws it (B33-1712, board 20). A slot. Inside `AppShell`\n * the shell fills it from its own `mark`, `hubHref` and `org` props, so a\n * consumer of the shell does not set this.\n */\n start?: ReactNode\n /**\n * The app switcher, at the centre of the bar after its \"Switch Tools →\"\n * label. A slot: pass `AppSwitcher`. Omit it and neither it nor its label is\n * drawn. Inside `AppShell`, the shell's `switcher` prop lands here.\n */\n switcher?: ReactNode\n /**\n * The token chip, after the switcher and its \"Check Your Tokens →\" label. A\n * slot: pass `WalletChip` with `placement=\"topbar\"`. Omit it and neither it\n * nor its label is drawn. Inside `AppShell`, the shell's `wallet` prop lands\n * here.\n */\n tokens?: ReactNode\n /**\n * The right of the bar: the notification button and the account avatar\n * (board 20). A slot -- the product's own, or the package's\n * `NotificationButton` beside the consumer's avatar.\n *\n * Search is not here and nothing replaces it: Jamal removed it on 3 Nov 2026\n * until the feature is built (B33-1710), and the CommandPalette that would\n * open from it is left out of 0.8.0 with no trigger and no shortcut.\n */\n actions?: ReactNode\n /**\n * The account menu, at the far right of the bar after `actions` (B33-493). A\n * slot: pass `StandardAccountMenu`, or `AccountMenu` with rows of your own. Its\n * panel opens under the avatar, aligned to the bar's right edge. Every app\n * puts the same menu here, so a person meets one account menu across Boost33.\n */\n account?: ReactNode\n /** The centre group's two labels. Each field defaults to English. */\n labels?: Partial<TopBarLabels>\n /**\n * Was the breadcrumb on the left of the bar. **Not drawn since 0.8.0**: the\n * redesign puts the mark and the organisation there, and the page's own\n * `PageHeader` names where you are. Optional so an existing call site still\n * compiles.\n *\n * @deprecated Rendered nowhere since 0.8.0 (B33-1712). Stop passing it; in\n * development, passing it logs a console warning once.\n */\n breadcrumb?: ReactNode\n}\n\n// The bar across the top of every screen (B33-1712). Three regions: the mark\n// and organisation on the left, the switcher and token chip in a centred group,\n// and the product's actions on the right. Left and right take equal shares of\n// what the centre leaves, which is what holds the centre group in the middle of\n// the bar whatever either side holds.\n//\n// A Server Component: no state, no handlers. The two controls in the centre are\n// client components passed in as slots.\nexport function TopBar({ sidebarToggle, start, switcher, tokens, actions, account, labels, breadcrumb }: TopBarProps) {\n if (breadcrumb != null) warnDeprecatedProp('TopBar.breadcrumb')\n const t = resolveTopBarLabels(labels)\n const hasCentre = switcher != null || tokens != null\n\n // A <header>, so the bar is the page's banner landmark and a screen-reader\n // user can jump to it, and an end-to-end test can scope to it (score-main,\n // 0.8.0-next.2). Inside AppShell it is never within <main> or an <article>,\n // which is what makes a <header> a banner. `display: grid` is set on the\n // class, so the element swap changes no box.\n return (\n <header className={styles.bar}>\n <div className={styles.start}>\n {sidebarToggle}\n {start}\n </div>\n {hasCentre && (\n <div className={styles.centre}>\n {switcher != null && (\n <>\n <span className={styles.label}>{t.switchTools}</span>\n {switcher}\n </>\n )}\n {tokens != null && (\n <>\n {/* The second label keeps 32px clear of the switcher, so each\n label reads as belonging to the control after it, not the one\n before it (board 20). With no switcher there is nothing to\n keep clear of, and the padding goes. */}\n <span className={styles.label} data-after-switcher={switcher != null ? 'true' : undefined}>\n {t.checkTokens}\n </span>\n {tokens}\n </>\n )}\n </div>\n )}\n <div className={styles.end}>\n {actions}\n {account}\n </div>\n </header>\n )\n}\n"],"names":["TopBar","sidebarToggle","start","switcher","tokens","actions","account","labels","breadcrumb","warnDeprecatedProp","t","resolveTopBarLabels","hasCentre","jsxs","styles","Fragment","jsx"],"mappings":";;;;AA6EO,SAASA,EAAO,EAAE,eAAAC,GAAe,OAAAC,GAAO,UAAAC,GAAU,QAAAC,GAAQ,SAAAC,GAAS,SAAAC,GAAS,QAAAC,GAAQ,YAAAC,KAA2B;AACpH,EAAIA,KAAc,QAAMC,EAAmB,mBAAmB;AAC9D,QAAMC,IAAIC,EAAoBJ,CAAM,GAC9BK,IAAYT,KAAY,QAAQC,KAAU;AAOhD,SACE,gBAAAS,EAAC,UAAA,EAAO,WAAWC,EAAO,KACxB,UAAA;AAAA,IAAA,gBAAAD,EAAC,OAAA,EAAI,WAAWC,EAAO,OACpB,UAAA;AAAA,MAAAb;AAAA,MACAC;AAAA,IAAA,GACH;AAAA,IACCU,KACC,gBAAAC,EAAC,OAAA,EAAI,WAAWC,EAAO,QACpB,UAAA;AAAA,MAAAX,KAAY,QACX,gBAAAU,EAAAE,GAAA,EACE,UAAA;AAAA,QAAA,gBAAAC,EAAC,QAAA,EAAK,WAAWF,EAAO,OAAQ,YAAE,aAAY;AAAA,QAC7CX;AAAA,MAAA,GACH;AAAA,MAEDC,KAAU,QACT,gBAAAS,EAAAE,GAAA,EAKE,UAAA;AAAA,QAAA,gBAAAC,EAAC,QAAA,EAAK,WAAWF,EAAO,OAAO,uBAAqBX,KAAY,OAAO,SAAS,QAC7E,UAAAO,EAAE,YAAA,CACL;AAAA,QACCN;AAAA,MAAA,EAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IAEF,gBAAAS,EAAC,OAAA,EAAI,WAAWC,EAAO,KACpB,UAAA;AAAA,MAAAT;AAAA,MACAC;AAAA,IAAA,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -35,9 +35,11 @@ export type { NotificationCenterProps, NotificationCenterItem, } from './compone
|
|
|
35
35
|
export { defaultNotificationCenterLabels } from './components/NotificationCenter/labels';
|
|
36
36
|
export type { NotificationCenterLabels } from './components/NotificationCenter/labels';
|
|
37
37
|
export { AccountMenu } from './components/AccountMenu/AccountMenu';
|
|
38
|
+
export { StandardAccountMenu, standardAccountMenuGroups } from './components/AccountMenu/StandardAccountMenu';
|
|
39
|
+
export type { AccountMenuRole, StandardAccountMenuProps, StandardAccountMenuRowsOptions, } from './components/AccountMenu/StandardAccountMenu';
|
|
38
40
|
export type { AccountMenuProps, AccountMenuPerson, AccountMenuOrg, AccountMenuGroup, AccountMenuItem, } from './components/AccountMenu/AccountMenu';
|
|
39
|
-
export { defaultAccountMenuLabels } from './components/AccountMenu/labels';
|
|
40
|
-
export type { AccountMenuLabels } from './components/AccountMenu/labels';
|
|
41
|
+
export { defaultAccountMenuLabels, defaultStandardAccountMenuLabels } from './components/AccountMenu/labels';
|
|
42
|
+
export type { AccountMenuLabels, StandardAccountMenuLabels } from './components/AccountMenu/labels';
|
|
41
43
|
export type { AccountMenuIcon } from './components/AccountMenu/icons';
|
|
42
44
|
export { CommandPalette } from './components/CommandPalette/CommandPalette';
|
|
43
45
|
export type { CommandPaletteProps, CommandPaletteGroup, CommandPaletteResult, } from './components/CommandPalette/CommandPalette';
|
package/dist/index.js
CHANGED
|
@@ -2,63 +2,67 @@
|
|
|
2
2
|
import { Sidebar as e } from "./components/Sidebar/Sidebar.js";
|
|
3
3
|
import { NavSection as p } from "./components/NavSection/NavSection.js";
|
|
4
4
|
import { Wordmark as m } from "./components/Wordmark/Wordmark.js";
|
|
5
|
-
import { BOOST33_MARK_MIN_LEGIBLE_PX as
|
|
6
|
-
import { ProductMark as
|
|
5
|
+
import { BOOST33_MARK_MIN_LEGIBLE_PX as x, Boost33Mark as u } from "./components/Boost33Mark/Boost33Mark.js";
|
|
6
|
+
import { ProductMark as l, hasProductMark as i } from "./components/ProductMark/ProductMark.js";
|
|
7
7
|
import { NavItem as s } from "./components/NavItem/NavItem.js";
|
|
8
8
|
import { WalletChip as h } from "./components/WalletChip/WalletChip.js";
|
|
9
9
|
import { defaultWalletChipLabels as b } from "./components/WalletChip/labels.js";
|
|
10
|
-
import { TopBar as
|
|
11
|
-
import { defaultTopBarLabels as
|
|
12
|
-
import { NotificationButton as
|
|
10
|
+
import { TopBar as S } from "./components/TopBar/TopBar.js";
|
|
11
|
+
import { defaultTopBarLabels as C } from "./components/TopBar/labels.js";
|
|
12
|
+
import { NotificationButton as g } from "./components/NotificationButton/NotificationButton.js";
|
|
13
13
|
import { defaultNotificationButtonLabels as P } from "./components/NotificationButton/labels.js";
|
|
14
14
|
import { PageHeader as _ } from "./components/PageHeader/PageHeader.js";
|
|
15
15
|
import { AppShell as F } from "./components/AppShell/AppShell.js";
|
|
16
16
|
import { AppSwitcher as I } from "./components/AppSwitcher/AppSwitcher.js";
|
|
17
17
|
import { defaultAppSwitcherLabels as W } from "./components/AppSwitcher/labels.js";
|
|
18
|
-
import { NotificationCenter as
|
|
19
|
-
import { defaultNotificationCenterLabels as
|
|
18
|
+
import { NotificationCenter as G } from "./components/NotificationCenter/NotificationCenter.js";
|
|
19
|
+
import { defaultNotificationCenterLabels as D } from "./components/NotificationCenter/labels.js";
|
|
20
20
|
import { AccountMenu as R } from "./components/AccountMenu/AccountMenu.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
21
|
+
import { StandardAccountMenu as X, standardAccountMenuGroups as j } from "./components/AccountMenu/StandardAccountMenu.js";
|
|
22
|
+
import { defaultAccountMenuLabels as y, defaultStandardAccountMenuLabels as z } from "./components/AccountMenu/labels.js";
|
|
23
|
+
import { CommandPalette as Q } from "./components/CommandPalette/CommandPalette.js";
|
|
24
|
+
import { defaultCommandPaletteLabels as Y } from "./components/CommandPalette/labels.js";
|
|
25
|
+
import { DotMatrixLoader as $ } from "./components/DotMatrixLoader/DotMatrixLoader.js";
|
|
26
|
+
import { LaunchLoader as to } from "./components/LaunchLoader/LaunchLoader.js";
|
|
27
|
+
import { launchBackdropCss as eo } from "./launch-gradient.js";
|
|
28
|
+
import { SettingsFrame as po } from "./components/SettingsFrame/SettingsFrame.js";
|
|
29
|
+
import { defaultSettingsFrameLabels as mo } from "./components/SettingsFrame/labels.js";
|
|
30
|
+
import { checkNavHeightBudget as xo, navBudgetForViewport as uo, navContentHeight as lo } from "./nav-height-budget.js";
|
|
30
31
|
export {
|
|
31
32
|
R as AccountMenu,
|
|
32
33
|
F as AppShell,
|
|
33
34
|
I as AppSwitcher,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
x as BOOST33_MARK_MIN_LEGIBLE_PX,
|
|
36
|
+
u as Boost33Mark,
|
|
37
|
+
Q as CommandPalette,
|
|
38
|
+
$ as DotMatrixLoader,
|
|
39
|
+
to as LaunchLoader,
|
|
39
40
|
s as NavItem,
|
|
40
41
|
p as NavSection,
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
g as NotificationButton,
|
|
43
|
+
G as NotificationCenter,
|
|
43
44
|
_ as PageHeader,
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
l as ProductMark,
|
|
46
|
+
po as SettingsFrame,
|
|
46
47
|
e as Sidebar,
|
|
47
|
-
|
|
48
|
+
X as StandardAccountMenu,
|
|
49
|
+
S as TopBar,
|
|
48
50
|
h as WalletChip,
|
|
49
51
|
m as Wordmark,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
xo as checkNavHeightBudget,
|
|
53
|
+
y as defaultAccountMenuLabels,
|
|
52
54
|
W as defaultAppSwitcherLabels,
|
|
53
|
-
|
|
55
|
+
Y as defaultCommandPaletteLabels,
|
|
54
56
|
P as defaultNotificationButtonLabels,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
D as defaultNotificationCenterLabels,
|
|
58
|
+
mo as defaultSettingsFrameLabels,
|
|
59
|
+
z as defaultStandardAccountMenuLabels,
|
|
60
|
+
C as defaultTopBarLabels,
|
|
58
61
|
b as defaultWalletChipLabels,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
i as hasProductMark,
|
|
63
|
+
eo as launchBackdropCss,
|
|
64
|
+
uo as navBudgetForViewport,
|
|
65
|
+
lo as navContentHeight,
|
|
66
|
+
j as standardAccountMenuGroups
|
|
63
67
|
};
|
|
64
68
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|