@boost33/ui 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +214 -123
- package/dist/backdrop-icons.d.ts +53 -0
- package/dist/backdrop-icons.js +62 -0
- package/dist/backdrop-icons.js.map +1 -0
- package/dist/backdrop.css +1 -0
- package/dist/backdrop.d.ts +46 -0
- package/dist/backdrop.js +127 -0
- package/dist/backdrop.js.map +1 -0
- 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/Backdrop/BackdropAppMenu.d.ts +55 -0
- package/dist/components/Backdrop/BackdropAppMenu.js +79 -0
- package/dist/components/Backdrop/BackdropAppMenu.js.map +1 -0
- package/dist/components/Backdrop/BackdropAppMenu.module.css.js +48 -0
- package/dist/components/Backdrop/BackdropAppMenu.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropBasics.d.ts +129 -0
- package/dist/components/Backdrop/BackdropBasics.js +130 -0
- package/dist/components/Backdrop/BackdropBasics.js.map +1 -0
- package/dist/components/Backdrop/BackdropBasics.module.css.js +71 -0
- package/dist/components/Backdrop/BackdropBasics.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropButton.d.ts +63 -0
- package/dist/components/Backdrop/BackdropButton.js +59 -0
- package/dist/components/Backdrop/BackdropButton.js.map +1 -0
- package/dist/components/Backdrop/BackdropButton.module.css.js +41 -0
- package/dist/components/Backdrop/BackdropButton.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropCharts.d.ts +158 -0
- package/dist/components/Backdrop/BackdropCharts.js +222 -0
- package/dist/components/Backdrop/BackdropCharts.js.map +1 -0
- package/dist/components/Backdrop/BackdropCharts.module.css.js +109 -0
- package/dist/components/Backdrop/BackdropCharts.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropChrome.d.ts +26 -0
- package/dist/components/Backdrop/BackdropChrome.js +35 -0
- package/dist/components/Backdrop/BackdropChrome.js.map +1 -0
- package/dist/components/Backdrop/BackdropConversation.d.ts +67 -0
- package/dist/components/Backdrop/BackdropConversation.js +76 -0
- package/dist/components/Backdrop/BackdropConversation.js.map +1 -0
- package/dist/components/Backdrop/BackdropConversation.module.css.js +44 -0
- package/dist/components/Backdrop/BackdropConversation.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropData.d.ts +195 -0
- package/dist/components/Backdrop/BackdropData.js +204 -0
- package/dist/components/Backdrop/BackdropData.js.map +1 -0
- package/dist/components/Backdrop/BackdropData.module.css.js +135 -0
- package/dist/components/Backdrop/BackdropData.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropDisplay.d.ts +122 -0
- package/dist/components/Backdrop/BackdropDisplay.js +80 -0
- package/dist/components/Backdrop/BackdropDisplay.js.map +1 -0
- package/dist/components/Backdrop/BackdropDisplay.module.css.js +80 -0
- package/dist/components/Backdrop/BackdropDisplay.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropFields.d.ts +122 -0
- package/dist/components/Backdrop/BackdropFields.js +196 -0
- package/dist/components/Backdrop/BackdropFields.js.map +1 -0
- package/dist/components/Backdrop/BackdropFields.module.css.js +104 -0
- package/dist/components/Backdrop/BackdropFields.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropFlows.d.ts +128 -0
- package/dist/components/Backdrop/BackdropFlows.js +156 -0
- package/dist/components/Backdrop/BackdropFlows.js.map +1 -0
- package/dist/components/Backdrop/BackdropFlows.module.css.js +80 -0
- package/dist/components/Backdrop/BackdropFlows.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropFrame.d.ts +36 -0
- package/dist/components/Backdrop/BackdropFrame.js +15 -0
- package/dist/components/Backdrop/BackdropFrame.js.map +1 -0
- package/dist/components/Backdrop/BackdropFrame.module.css.js +14 -0
- package/dist/components/Backdrop/BackdropFrame.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.d.ts +272 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.js +264 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.js.map +1 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.module.css.js +201 -0
- package/dist/components/Backdrop/BackdropGlobalPanels.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropHub.d.ts +80 -0
- package/dist/components/Backdrop/BackdropHub.js +92 -0
- package/dist/components/Backdrop/BackdropHub.js.map +1 -0
- package/dist/components/Backdrop/BackdropHub.module.css.js +40 -0
- package/dist/components/Backdrop/BackdropHub.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropLists.d.ts +165 -0
- package/dist/components/Backdrop/BackdropLists.js +162 -0
- package/dist/components/Backdrop/BackdropLists.js.map +1 -0
- package/dist/components/Backdrop/BackdropLists.module.css.js +114 -0
- package/dist/components/Backdrop/BackdropLists.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropOverlays.d.ts +175 -0
- package/dist/components/Backdrop/BackdropOverlays.js +252 -0
- package/dist/components/Backdrop/BackdropOverlays.js.map +1 -0
- package/dist/components/Backdrop/BackdropOverlays.module.css.js +104 -0
- package/dist/components/Backdrop/BackdropOverlays.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropPage.d.ts +73 -0
- package/dist/components/Backdrop/BackdropPage.js +65 -0
- package/dist/components/Backdrop/BackdropPage.js.map +1 -0
- package/dist/components/Backdrop/BackdropPage.module.css.js +46 -0
- package/dist/components/Backdrop/BackdropPage.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropPatterns.d.ts +224 -0
- package/dist/components/Backdrop/BackdropPatterns.js +202 -0
- package/dist/components/Backdrop/BackdropPatterns.js.map +1 -0
- package/dist/components/Backdrop/BackdropPatterns.module.css.js +114 -0
- package/dist/components/Backdrop/BackdropPatterns.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropRail.d.ts +63 -0
- package/dist/components/Backdrop/BackdropRail.js +98 -0
- package/dist/components/Backdrop/BackdropRail.js.map +1 -0
- package/dist/components/Backdrop/BackdropRail.module.css.js +42 -0
- package/dist/components/Backdrop/BackdropRail.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropSheets.d.ts +68 -0
- package/dist/components/Backdrop/BackdropSheets.js +75 -0
- package/dist/components/Backdrop/BackdropSheets.js.map +1 -0
- package/dist/components/Backdrop/BackdropSheets.module.css.js +42 -0
- package/dist/components/Backdrop/BackdropSheets.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropTabs.d.ts +20 -0
- package/dist/components/Backdrop/BackdropTabs.js +31 -0
- package/dist/components/Backdrop/BackdropTabs.js.map +1 -0
- package/dist/components/Backdrop/BackdropTabs.module.css.js +16 -0
- package/dist/components/Backdrop/BackdropTabs.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropTooltip.d.ts +21 -0
- package/dist/components/Backdrop/BackdropTooltip.js +15 -0
- package/dist/components/Backdrop/BackdropTooltip.js.map +1 -0
- package/dist/components/Backdrop/BackdropTooltip.module.css.js +18 -0
- package/dist/components/Backdrop/BackdropTooltip.module.css.js.map +1 -0
- package/dist/components/Backdrop/BackdropTopStrip.d.ts +65 -0
- package/dist/components/Backdrop/BackdropTopStrip.js +46 -0
- package/dist/components/Backdrop/BackdropTopStrip.js.map +1 -0
- package/dist/components/Backdrop/BackdropTopStrip.module.css.js +40 -0
- package/dist/components/Backdrop/BackdropTopStrip.module.css.js.map +1 -0
- package/dist/components/Backdrop/cx.d.ts +2 -0
- package/dist/components/Backdrop/cx.js +7 -0
- package/dist/components/Backdrop/cx.js.map +1 -0
- package/dist/components/Backdrop/icons.d.ts +70 -0
- package/dist/components/Backdrop/icons.js +103 -0
- package/dist/components/Backdrop/icons.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 +23 -4
|
@@ -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
|
|
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
|
|
2
|
-
const
|
|
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
|
-
},
|
|
11
|
-
person: (
|
|
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: (
|
|
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: (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ElementType, ReactNode } from 'react';
|
|
2
|
+
export interface BackdropAppMenuItem {
|
|
3
|
+
key: string;
|
|
4
|
+
label: string;
|
|
5
|
+
/** 18px stroke icon. The folded strip shows only this. */
|
|
6
|
+
icon: ReactNode;
|
|
7
|
+
href?: string;
|
|
8
|
+
onClick?: () => void;
|
|
9
|
+
active?: boolean;
|
|
10
|
+
/** A count badge ("5" waiting approvals). */
|
|
11
|
+
count?: ReactNode;
|
|
12
|
+
/** Spoken with the count: "5 waiting". */
|
|
13
|
+
countLabel?: string;
|
|
14
|
+
/** Not built yet: the row shows a "Soon" tag and does not navigate. */
|
|
15
|
+
soon?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export interface BackdropAppMenuSection {
|
|
18
|
+
/** A fixed 1–2 word group label ("Outreach"). Never collapsible. Omit for the first, unlabelled group. */
|
|
19
|
+
label?: string;
|
|
20
|
+
items: BackdropAppMenuItem[];
|
|
21
|
+
}
|
|
22
|
+
export interface BackdropAppMenuProps {
|
|
23
|
+
/** The app's name, beside its mark at the top. */
|
|
24
|
+
appName: string;
|
|
25
|
+
/** The app's 28px mark (ProductMark). */
|
|
26
|
+
mark: ReactNode;
|
|
27
|
+
/**
|
|
28
|
+
* The menu's main action. Always an OUTLINE button with a plus (round 5,
|
|
29
|
+
* decision 1): orange belongs to the page's own main action.
|
|
30
|
+
*/
|
|
31
|
+
action: {
|
|
32
|
+
label: string;
|
|
33
|
+
onClick?: () => void;
|
|
34
|
+
href?: string;
|
|
35
|
+
};
|
|
36
|
+
sections: BackdropAppMenuSection[];
|
|
37
|
+
/** Settings, pinned to the bottom. Opens the hub's settings at this app. */
|
|
38
|
+
settings: BackdropAppMenuItem;
|
|
39
|
+
/**
|
|
40
|
+
* The first-time checklist pill above Settings ("Get started · 1 of 2"): a
|
|
41
|
+
* BackdropGetStarted. Gone once the steps are done.
|
|
42
|
+
*/
|
|
43
|
+
getStarted?: ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* Folded to the 56px icon strip (round 5, decision 3): names move to
|
|
46
|
+
* tooltips, group labels become short dividers. `auto` (default) follows the
|
|
47
|
+
* frame's compact layout (below 1200px, or BackdropFrame `compact`).
|
|
48
|
+
*/
|
|
49
|
+
folded?: boolean | 'auto';
|
|
50
|
+
/** The menu's landmark name: "Outbound menu". */
|
|
51
|
+
label: string;
|
|
52
|
+
linkComponent?: ElementType;
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function BackdropAppMenu(props: BackdropAppMenuProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsxs as c, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import l from "./BackdropAppMenu.module.css.js";
|
|
3
|
+
import { cx as N } from "./cx.js";
|
|
4
|
+
import { Plus as p } from "./icons.js";
|
|
5
|
+
import { BackdropBadge as v, BackdropSoonTag as k } from "./BackdropBasics.js";
|
|
6
|
+
import { BackdropTooltip as b } from "./BackdropTooltip.js";
|
|
7
|
+
function x(a) {
|
|
8
|
+
const { folded: d = "auto", label: r, className: s } = a;
|
|
9
|
+
return /* @__PURE__ */ c("nav", { className: N(l.menu, s), "aria-label": r, "data-folded": String(d), children: [
|
|
10
|
+
d !== !0 && /* @__PURE__ */ e(g, { ...a }),
|
|
11
|
+
d !== !1 && /* @__PURE__ */ e(L, { ...a })
|
|
12
|
+
] });
|
|
13
|
+
}
|
|
14
|
+
function u({ item: a, Link: d, folded: r }) {
|
|
15
|
+
const s = a.soon !== void 0, t = a.href !== void 0 && !s ? d : "button", i = a.href !== void 0 && !s ? { href: a.href } : { type: "button", "aria-disabled": s || void 0 }, n = /* @__PURE__ */ c(
|
|
16
|
+
t,
|
|
17
|
+
{
|
|
18
|
+
...i,
|
|
19
|
+
onClick: s ? void 0 : a.onClick,
|
|
20
|
+
"aria-current": a.active ? "page" : void 0,
|
|
21
|
+
"aria-label": r ? a.label : void 0,
|
|
22
|
+
className: r ? l.foldedItem : l.item,
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ e("span", { className: l.itemIcon, children: a.icon }),
|
|
25
|
+
!r && /* @__PURE__ */ e("span", { className: l.itemLabel, children: a.label }),
|
|
26
|
+
!r && a.count !== void 0 && /* @__PURE__ */ e(v, { label: a.countLabel, children: a.count }),
|
|
27
|
+
!r && s && /* @__PURE__ */ e(k, { children: a.soon }),
|
|
28
|
+
r && a.count !== void 0 && /* @__PURE__ */ e("span", { className: l.foldedDot, "aria-hidden": "true" })
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
return r ? /* @__PURE__ */ e(b, { content: a.label, side: "right", labelsTrigger: !0, children: n }) : n;
|
|
33
|
+
}
|
|
34
|
+
function g({ appName: a, mark: d, action: r, sections: s, settings: t, getStarted: i, linkComponent: n = "a" }) {
|
|
35
|
+
const h = r.href !== void 0 ? n : "button";
|
|
36
|
+
return /* @__PURE__ */ c("div", { className: l.open, children: [
|
|
37
|
+
/* @__PURE__ */ c("div", { className: l.header, children: [
|
|
38
|
+
/* @__PURE__ */ e("span", { className: l.mark, children: d }),
|
|
39
|
+
/* @__PURE__ */ e("span", { className: l.appName, children: a })
|
|
40
|
+
] }),
|
|
41
|
+
/* @__PURE__ */ c(h, { ...r.href !== void 0 ? { href: r.href } : { type: "button" }, onClick: r.onClick, className: l.action, children: [
|
|
42
|
+
/* @__PURE__ */ e(p, {}),
|
|
43
|
+
/* @__PURE__ */ e("span", { children: r.label })
|
|
44
|
+
] }),
|
|
45
|
+
/* @__PURE__ */ e("div", { className: l.sections, children: s.map((o, m) => /* @__PURE__ */ c("div", { className: l.section, children: [
|
|
46
|
+
o.label && /* @__PURE__ */ e("p", { className: l.groupLabel, children: o.label }),
|
|
47
|
+
/* @__PURE__ */ e("ul", { className: l.list, children: o.items.map((f) => /* @__PURE__ */ e("li", { children: /* @__PURE__ */ e(u, { item: f, Link: n, folded: !1 }) }, f.key)) })
|
|
48
|
+
] }, o.label ?? m)) }),
|
|
49
|
+
/* @__PURE__ */ e("div", { className: l.fill }),
|
|
50
|
+
i && /* @__PURE__ */ e("div", { className: l.getStarted, children: i }),
|
|
51
|
+
/* @__PURE__ */ e(u, { item: t, Link: n, folded: !1 })
|
|
52
|
+
] });
|
|
53
|
+
}
|
|
54
|
+
function L({ mark: a, appName: d, action: r, sections: s, settings: t, linkComponent: i = "a" }) {
|
|
55
|
+
const n = r.href !== void 0 ? i : "button";
|
|
56
|
+
return /* @__PURE__ */ c("div", { className: l.folded, children: [
|
|
57
|
+
/* @__PURE__ */ e("span", { className: l.foldedMark, children: /* @__PURE__ */ e("span", { className: l.mark, role: "img", "aria-label": d, children: a }) }),
|
|
58
|
+
/* @__PURE__ */ e(b, { content: r.label, side: "right", labelsTrigger: !0, children: /* @__PURE__ */ e(
|
|
59
|
+
n,
|
|
60
|
+
{
|
|
61
|
+
...r.href !== void 0 ? { href: r.href } : { type: "button" },
|
|
62
|
+
onClick: r.onClick,
|
|
63
|
+
"aria-label": r.label,
|
|
64
|
+
className: l.foldedAction,
|
|
65
|
+
children: /* @__PURE__ */ e(p, {})
|
|
66
|
+
}
|
|
67
|
+
) }),
|
|
68
|
+
/* @__PURE__ */ e("ul", { className: l.foldedList, children: s.map((h, o) => /* @__PURE__ */ c("li", { className: l.foldedSection, children: [
|
|
69
|
+
o > 0 && /* @__PURE__ */ e("span", { className: l.foldedDivider, "aria-hidden": "true" }),
|
|
70
|
+
/* @__PURE__ */ e("ul", { className: l.list, children: h.items.map((m) => /* @__PURE__ */ e("li", { children: /* @__PURE__ */ e(u, { item: m, Link: i, folded: !0 }) }, m.key)) })
|
|
71
|
+
] }, h.label ?? o)) }),
|
|
72
|
+
/* @__PURE__ */ e("div", { className: l.fill }),
|
|
73
|
+
/* @__PURE__ */ e(u, { item: t, Link: i, folded: !0 })
|
|
74
|
+
] });
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
x as BackdropAppMenu
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=BackdropAppMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackdropAppMenu.js","sources":["../../../src/components/Backdrop/BackdropAppMenu.tsx"],"sourcesContent":["import type { ElementType, ReactNode } from 'react'\nimport styles from './BackdropAppMenu.module.css'\nimport { cx } from './cx'\nimport { Plus } from './icons'\nimport { BackdropBadge, BackdropSoonTag } from './BackdropBasics'\nimport { BackdropTooltip } from './BackdropTooltip'\n\nexport interface BackdropAppMenuItem {\n key: string\n label: string\n /** 18px stroke icon. The folded strip shows only this. */\n icon: ReactNode\n href?: string\n onClick?: () => void\n active?: boolean\n /** A count badge (\"5\" waiting approvals). */\n count?: ReactNode\n /** Spoken with the count: \"5 waiting\". */\n countLabel?: string\n /** Not built yet: the row shows a \"Soon\" tag and does not navigate. */\n soon?: ReactNode\n}\n\nexport interface BackdropAppMenuSection {\n /** A fixed 1–2 word group label (\"Outreach\"). Never collapsible. Omit for the first, unlabelled group. */\n label?: string\n items: BackdropAppMenuItem[]\n}\n\nexport interface BackdropAppMenuProps {\n /** The app's name, beside its mark at the top. */\n appName: string\n /** The app's 28px mark (ProductMark). */\n mark: ReactNode\n /**\n * The menu's main action. Always an OUTLINE button with a plus (round 5,\n * decision 1): orange belongs to the page's own main action.\n */\n action: { label: string; onClick?: () => void; href?: string }\n sections: BackdropAppMenuSection[]\n /** Settings, pinned to the bottom. Opens the hub's settings at this app. */\n settings: BackdropAppMenuItem\n /**\n * The first-time checklist pill above Settings (\"Get started · 1 of 2\"): a\n * BackdropGetStarted. Gone once the steps are done.\n */\n getStarted?: ReactNode\n /**\n * Folded to the 56px icon strip (round 5, decision 3): names move to\n * tooltips, group labels become short dividers. `auto` (default) follows the\n * frame's compact layout (below 1200px, or BackdropFrame `compact`).\n */\n folded?: boolean | 'auto'\n /** The menu's landmark name: \"Outbound menu\". */\n label: string\n linkComponent?: ElementType\n className?: string\n}\n\n/*\n * Layer 2's menu column. Open: 232px -- mark and app name, the outline main\n * action, the pages (groups past ~5 pages, one level), Settings at the bottom.\n * Folded: 56px of icons with the same order and the same active chip.\n *\n * `auto` renders both and lets CSS show one, so the switch costs no state and\n * no hydration. Only the visible one is in the accessibility tree.\n */\nexport function BackdropAppMenu(props: BackdropAppMenuProps) {\n const { folded = 'auto', label, className } = props\n return (\n <nav className={cx(styles.menu, className)} aria-label={label} data-folded={String(folded)}>\n {folded !== true && <OpenMenu {...props} />}\n {folded !== false && <FoldedMenu {...props} />}\n </nav>\n )\n}\n\nfunction Row({ item, Link, folded }: { item: BackdropAppMenuItem; Link: ElementType; folded: boolean }) {\n const isSoon = item.soon !== undefined\n const Element: ElementType = item.href !== undefined && !isSoon ? Link : 'button'\n const elementProps =\n item.href !== undefined && !isSoon ? { href: item.href } : { type: 'button', 'aria-disabled': isSoon || undefined }\n const row = (\n <Element\n {...elementProps}\n onClick={isSoon ? undefined : item.onClick}\n aria-current={item.active ? 'page' : undefined}\n aria-label={folded ? item.label : undefined}\n className={folded ? styles.foldedItem : styles.item}\n >\n <span className={styles.itemIcon}>{item.icon}</span>\n {!folded && <span className={styles.itemLabel}>{item.label}</span>}\n {!folded && item.count !== undefined && <BackdropBadge label={item.countLabel}>{item.count}</BackdropBadge>}\n {!folded && isSoon && <BackdropSoonTag>{item.soon}</BackdropSoonTag>}\n {folded && item.count !== undefined && <span className={styles.foldedDot} aria-hidden=\"true\" />}\n </Element>\n )\n return folded ? (\n <BackdropTooltip content={item.label} side=\"right\" labelsTrigger>\n {row}\n </BackdropTooltip>\n ) : (\n row\n )\n}\n\nfunction OpenMenu({ appName, mark, action, sections, settings, getStarted, linkComponent = 'a' }: BackdropAppMenuProps) {\n const ActionElement: ElementType = action.href !== undefined ? linkComponent : 'button'\n return (\n <div className={styles.open}>\n <div className={styles.header}>\n <span className={styles.mark}>{mark}</span>\n <span className={styles.appName}>{appName}</span>\n </div>\n <ActionElement {...(action.href !== undefined ? { href: action.href } : { type: 'button' })} onClick={action.onClick} className={styles.action}>\n <Plus />\n <span>{action.label}</span>\n </ActionElement>\n <div className={styles.sections}>\n {sections.map((section, index) => (\n <div key={section.label ?? index} className={styles.section}>\n {section.label && <p className={styles.groupLabel}>{section.label}</p>}\n <ul className={styles.list}>\n {section.items.map((item) => (\n <li key={item.key}>\n <Row item={item} Link={linkComponent} folded={false} />\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n <div className={styles.fill} />\n {getStarted && <div className={styles.getStarted}>{getStarted}</div>}\n <Row item={settings} Link={linkComponent} folded={false} />\n </div>\n )\n}\n\nfunction FoldedMenu({ mark, appName, action, sections, settings, linkComponent = 'a' }: BackdropAppMenuProps) {\n const ActionElement: ElementType = action.href !== undefined ? linkComponent : 'button'\n return (\n <div className={styles.folded}>\n <span className={styles.foldedMark}>\n <span className={styles.mark} role=\"img\" aria-label={appName}>\n {mark}\n </span>\n </span>\n <BackdropTooltip content={action.label} side=\"right\" labelsTrigger>\n <ActionElement\n {...(action.href !== undefined ? { href: action.href } : { type: 'button' })}\n onClick={action.onClick}\n aria-label={action.label}\n className={styles.foldedAction}\n >\n <Plus />\n </ActionElement>\n </BackdropTooltip>\n <ul className={styles.foldedList}>\n {sections.map((section, index) => (\n <li key={section.label ?? index} className={styles.foldedSection}>\n {index > 0 && <span className={styles.foldedDivider} aria-hidden=\"true\" />}\n <ul className={styles.list}>\n {section.items.map((item) => (\n <li key={item.key}>\n <Row item={item} Link={linkComponent} folded />\n </li>\n ))}\n </ul>\n </li>\n ))}\n </ul>\n <div className={styles.fill} />\n <Row item={settings} Link={linkComponent} folded />\n </div>\n )\n}\n"],"names":["BackdropAppMenu","props","folded","label","className","jsxs","cx","styles","jsx","OpenMenu","FoldedMenu","Row","item","Link","isSoon","Element","elementProps","row","BackdropBadge","BackdropSoonTag","BackdropTooltip","appName","mark","action","sections","settings","getStarted","linkComponent","ActionElement","Plus","section","index"],"mappings":"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"}
|