@boost33/ui 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +2894 -0
  2. package/dist/components/AccountMenu/AccountMenu.js +66 -0
  3. package/dist/components/AccountMenu/AccountMenu.js.map +1 -0
  4. package/dist/components/AccountMenu/AccountMenu.module.css.js +42 -0
  5. package/dist/components/AccountMenu/AccountMenu.module.css.js.map +1 -0
  6. package/dist/components/AccountMenu/icons.js +32 -0
  7. package/dist/components/AccountMenu/icons.js.map +1 -0
  8. package/dist/components/AccountMenu/labels.js +14 -0
  9. package/dist/components/AccountMenu/labels.js.map +1 -0
  10. package/dist/components/CommandPalette/CommandPalette.js +65 -0
  11. package/dist/components/CommandPalette/CommandPalette.js.map +1 -0
  12. package/dist/components/CommandPalette/CommandPalette.module.css.js +34 -0
  13. package/dist/components/CommandPalette/CommandPalette.module.css.js.map +1 -0
  14. package/dist/components/CommandPalette/icons.js +23 -0
  15. package/dist/components/CommandPalette/icons.js.map +1 -0
  16. package/dist/components/CommandPalette/labels.js +19 -0
  17. package/dist/components/CommandPalette/labels.js.map +1 -0
  18. package/dist/components/NotificationCenter/NotificationCenter.js +67 -0
  19. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -0
  20. package/dist/components/NotificationCenter/NotificationCenter.module.css.js +54 -0
  21. package/dist/components/NotificationCenter/NotificationCenter.module.css.js.map +1 -0
  22. package/dist/components/NotificationCenter/icons.js +45 -0
  23. package/dist/components/NotificationCenter/icons.js.map +1 -0
  24. package/dist/components/NotificationCenter/labels.js +26 -0
  25. package/dist/components/NotificationCenter/labels.js.map +1 -0
  26. package/dist/components/SettingsFrame/SettingsFrame.d.ts +91 -0
  27. package/dist/components/SettingsFrame/SettingsFrame.js +109 -0
  28. package/dist/components/SettingsFrame/SettingsFrame.js.map +1 -0
  29. package/dist/components/SettingsFrame/SettingsFrame.module.css.js +74 -0
  30. package/dist/components/SettingsFrame/SettingsFrame.module.css.js.map +1 -0
  31. package/dist/components/SettingsFrame/icons.d.ts +10 -0
  32. package/dist/components/SettingsFrame/icons.js +37 -0
  33. package/dist/components/SettingsFrame/icons.js.map +1 -0
  34. package/dist/components/SettingsFrame/labels.d.ts +36 -0
  35. package/dist/components/SettingsFrame/labels.js +18 -0
  36. package/dist/components/SettingsFrame/labels.js.map +1 -0
  37. package/dist/index.d.ts +17 -0
  38. package/dist/index.js +45 -29
  39. package/dist/index.js.map +1 -1
  40. package/dist/style.css +1 -1
  41. package/package.json +3 -2
@@ -0,0 +1,45 @@
1
+ import { jsxs as e, jsx as r } from "react/jsx-runtime";
2
+ import t from "./NotificationCenter.module.css.js";
3
+ function i() {
4
+ return /* @__PURE__ */ e(
5
+ "svg",
6
+ {
7
+ className: t.emptyGlyph,
8
+ viewBox: "0 0 16 16",
9
+ fill: "none",
10
+ stroke: "currentColor",
11
+ strokeWidth: "1.2",
12
+ strokeLinejoin: "round",
13
+ "aria-hidden": "true",
14
+ children: [
15
+ /* @__PURE__ */ r("path", { d: "M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z" }),
16
+ /* @__PURE__ */ r("path", { d: "M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13", strokeLinecap: "round" })
17
+ ]
18
+ }
19
+ );
20
+ }
21
+ function a({ size: o }) {
22
+ return /* @__PURE__ */ e(
23
+ "svg",
24
+ {
25
+ className: t.sourceMark,
26
+ width: o,
27
+ height: o,
28
+ viewBox: "0 0 40 40",
29
+ xmlns: "http://www.w3.org/2000/svg",
30
+ "aria-hidden": "true",
31
+ children: [
32
+ /* @__PURE__ */ r("rect", { className: t.sourceMarkGround, width: "40", height: "40" }),
33
+ /* @__PURE__ */ e("g", { transform: "translate(10 10) scale(1.25)", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round", children: [
34
+ /* @__PURE__ */ r("path", { d: "M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z" }),
35
+ /* @__PURE__ */ r("path", { d: "M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13", strokeLinecap: "round" })
36
+ ] })
37
+ ]
38
+ }
39
+ );
40
+ }
41
+ export {
42
+ i as EmptyBell,
43
+ a as SourceBellMark
44
+ };
45
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","sources":["../../../src/components/NotificationCenter/icons.tsx"],"sourcesContent":["// The notification centre's own glyphs. Both are bells, and they are two\n// drawings rather than one because they are drawn at two sizes on two grounds:\n// one is white ink inside a 22px dark tile, the other is a 28px outline standing\n// alone on the panel's surface. Everything else the panel draws is either\n// ProductMark's or plain text.\n\nimport styles from './NotificationCenter.module.css'\n\n/**\n * The bell on the empty state, standing alone above \"You're all caught up\".\n *\n * Decorative: the two lines beneath it say the same thing in words, so a screen\n * reader that announced it as well would say the state twice. It is sized in CSS\n * (`--spacing-7`) rather than by width/height attributes, which is why it takes\n * no size prop.\n */\nexport function EmptyBell() {\n return (\n <svg\n className={styles.emptyGlyph}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.2\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z\" />\n <path d=\"M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\n/**\n * The tile a row draws when its notification names no product -- an account or\n * security event, which is what the drawing's fifth row is.\n *\n * **Why this is not a `ProductMark` with an unknown slug.** It would draw the\n * right thing by accident: ADR-0003's safe degrade gives an unrecognised slug\n * the bare tile with no glyph, which is a product whose artwork has not landed.\n * A notification with no product is not that. It is a row that correctly has no\n * product, and drawing it as a gap says the mark failed to load. So the fallback\n * is a bell -- the panel's own symbol, which is what the drawing puts there.\n *\n * It is still drawn ProductMark's way rather than as a styled `div`: a `rect` on\n * a 40-unit grid, its corner taken from `--radius-product-tile`, so the two\n * kinds of tile in the same column share one corner at every size instead of\n * agreeing to a px value that only matches at 22px. The glyph is on the same\n * 16-unit grid as `EmptyBell` above and the group transform insets it into the\n * middle 20 of the 40, which is the inset ProductMark gives its four -- so the\n * bell's ink lands at the weight the product glyphs do (1.3 on the 16 grid,\n * scaled 1.25, is 0.89px of stroke at 22px, which is what Paper draws).\n *\n * Decorative like ProductMark is here: the row's text names the source.\n */\nexport function SourceBellMark({ size }: { size: number }) {\n return (\n <svg\n className={styles.sourceMark}\n width={size}\n height={size}\n viewBox=\"0 0 40 40\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect className={styles.sourceMarkGround} width=\"40\" height=\"40\" />\n <g transform=\"translate(10 10) scale(1.25)\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.3\" strokeLinejoin=\"round\">\n <path d=\"M8 2.5C6 2.5 4.6 4 4.6 6V8.3C4.6 8.9 4.4 9.4 4 9.8L3.3 10.6C3 10.9 3.2 11.5 3.7 11.5H12.3C12.8 11.5 13 10.9 12.7 10.6L12 9.8C11.6 9.4 11.4 8.9 11.4 8.3V6C11.4 4 10 2.5 8 2.5Z\" />\n <path d=\"M6.5 13C6.8 13.5 7.4 13.8 8 13.8C8.6 13.8 9.2 13.5 9.5 13\" strokeLinecap=\"round\" />\n </g>\n </svg>\n )\n}\n"],"names":["EmptyBell","jsxs","styles","jsx","SourceBellMark","size"],"mappings":";;AAgBO,SAASA,IAAY;AAC1B,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAO;AAAA,MAClB,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,QAAO;AAAA,MACP,aAAY;AAAA,MACZ,gBAAe;AAAA,MACf,eAAY;AAAA,MAEZ,UAAA;AAAA,QAAA,gBAAAC,EAAC,QAAA,EAAK,GAAE,iLAAA,CAAiL;AAAA,QACzL,gBAAAA,EAAC,QAAA,EAAK,GAAE,6DAA4D,eAAc,QAAA,CAAQ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhG;AAwBO,SAASC,EAAe,EAAE,MAAAC,KAA0B;AACzD,SACE,gBAAAJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAWC,EAAO;AAAA,MAClB,OAAOG;AAAA,MACP,QAAQA;AAAA,MACR,SAAQ;AAAA,MACR,OAAM;AAAA,MACN,eAAY;AAAA,MAEZ,UAAA;AAAA,QAAA,gBAAAF,EAAC,UAAK,WAAWD,EAAO,kBAAkB,OAAM,MAAK,QAAO,MAAK;AAAA,QACjE,gBAAAD,EAAC,KAAA,EAAE,WAAU,gCAA+B,MAAK,QAAO,QAAO,gBAAe,aAAY,OAAM,gBAAe,SAC7G,UAAA;AAAA,UAAA,gBAAAE,EAAC,QAAA,EAAK,GAAE,iLAAA,CAAiL;AAAA,UACzL,gBAAAA,EAAC,QAAA,EAAK,GAAE,6DAA4D,eAAc,QAAA,CAAQ;AAAA,QAAA,EAAA,CAC5F;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN;"}
@@ -0,0 +1,26 @@
1
+ import { fillTemplate as r } from "../../fill-template.js";
2
+ const l = Object.freeze({
3
+ title: "Notifications",
4
+ markAllAsRead: "Mark all as read",
5
+ all: "All",
6
+ unread: "Unread",
7
+ unreadItem: "Unread",
8
+ meta: "%source% · %time%",
9
+ viewAll: "View all notifications",
10
+ emptyTitle: "You're all caught up",
11
+ emptyBody: "Nothing from any product needs your attention."
12
+ });
13
+ function s(a) {
14
+ const i = Object.fromEntries(
15
+ Object.entries(a ?? {}).filter(([, e]) => e !== void 0)
16
+ ), t = { ...l, ...i };
17
+ return {
18
+ ...t,
19
+ meta: (e, o) => r(t.meta, { source: e, time: o })
20
+ };
21
+ }
22
+ export {
23
+ l as defaultNotificationCenterLabels,
24
+ s as resolveNotificationCenterLabels
25
+ };
26
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sources":["../../../src/components/NotificationCenter/labels.ts"],"sourcesContent":["// The notification centre's own user-facing words, each a field with an English\n// default. Every string a person can read or a screen reader can announce that\n// is not row data lives here, so the first two consumers -- both French-default\n// -- translate the panel without this package taking an i18n dependency\n// (CLAUDE.md; B33-849). The notifications themselves are not here: their title,\n// their source and their time are the host's data, supplied per row on `items`,\n// because the host is the only side that knows what happened (B33-494's data\n// decision, 2026-10-06).\n//\n// ── Which label contract this follows, and why it is not a fourth one ────────\n//\n// This is `WalletChipLabels`'s contract, in all four of its parts, and the\n// choice is deliberate. B33-1007 found `AppSwitcherLabels` claiming parity with\n// the chip while diverging from it three ways; two were closed by B33-919 and\n// the third -- the freeze -- is still open. Picking the chip means the third\n// labels module in this package lands on the shape the other two are converging\n// *to*, so B33-1007 closes by making the switcher match this file rather than by\n// this file becoming a fourth thing to reconcile. The four parts:\n//\n// 1. `labels` is a `Partial`, so a consumer translates the strings it wants\n// and inherits the rest -- and so adding a field here later is not a\n// compile error in four apps on a patch bump.\n// 2. A `resolve*` step filters explicitly-`undefined` fields before merging,\n// because a next-intl `t('...')` returns `undefined` on a missed key and a\n// plain spread copies that `undefined` across (B33-873).\n// 3. The defaults are `Object.freeze({ … } satisfies …)` with NO type\n// annotation. That is the spelling B33-916 settled for the chip: the\n// annotation widens `Readonly<T>` straight back to the mutable interface,\n// so a consumer writing to the frozen export gets no compile error and an\n// uncaught TypeError in production instead.\n// 4. The one string that carries values is a `%placeholder%` template -- never\n// fragments concatenated in the render, because word order is not a property\n// of English. The delimiter is `%name%` and not `{name}` because a brace is\n// ICU argument syntax and a consumer keeps these strings in an ICU catalogue\n// (B33-1277); the reasoning is in `src/fill-template.ts`.\n//\n// A template, and *only* a template. `meta` took a function of the same values\n// until B33-1278, which is the shape B33-880 had already recorded as unpassable:\n// React's Flight serializer throws on any function it finds in a Client\n// Component's props, so a labels object holding one cannot be passed from a\n// Server Component page -- which is what a host page is by default. The union was\n// type-legal and fatal in production, and nothing in the type said so. A meta line\n// needing logic a template cannot express is computed in the host's own catalogue\n// and passed here finished.\n\nimport { fillTemplate } from '../../fill-template'\n\n/** Every user-facing string the notification centre renders that is not row data. */\nexport interface NotificationCenterLabels {\n /**\n * The panel's heading, and through it the panel's accessible name. Default\n * \"Notifications\".\n *\n * It is drawn as a real heading and the panel is pointed at it with\n * `aria-labelledby`, rather than carrying a second copy of the same word in an\n * `aria-label`. ADR-0010 says a disclosure panel carries no widget role and\n * takes a name without one; it does not say the name has to be invisible, and\n * a name that is already on the screen is one fewer string to translate and\n * one fewer place for the two to disagree.\n */\n title: string\n /**\n * The header's action, which clears every unread marker. Default\n * \"Mark all as read\".\n *\n * Stage A renders the control and wires nothing to it -- the handler is Stage\n * B's, with the rest of the interaction (CLAUDE.md, \"Presentational-first\").\n */\n markAllAsRead: string\n /** The first filter tab, showing every notification. Default \"All\". */\n all: string\n /**\n * The second filter tab, showing only the unread ones. Default \"Unread\".\n *\n * The count beside it is not a label: it is `items` counted, so it needs no\n * translation and cannot disagree with the list under it.\n */\n unread: string\n /**\n * The marker on an unread row, announced rather than drawn. Default \"Unread\".\n *\n * The drawing carries unread as a 6px orange dot and nothing else, which is a\n * colour-only distinction and reaches nobody using a screen reader. This is\n * the same state in words, in the row, visually hidden (CLAUDE.md,\n * \"Accessibility is part of done\"). It repeats `unread` by default because it\n * is the same English word, and it is a separate field because a locale is\n * entitled to tag a row differently from how it names a filter.\n */\n unreadItem: string\n /**\n * The second line of a row, around the host's source and time. Supplied whole\n * so the consumer owns word order and the separator.\n *\n * Placeholders: `%source%`, `%time%`. Default (en) `%source% · %time%`.\n *\n * Both values are the host's, preformatted -- see `NotificationCenterItem`\n * for why the time arrives as a string this package never computes.\n */\n meta: string\n /** The footer link out to the full list. Default \"View all notifications\". */\n viewAll: string\n /** The empty state's headline. Default \"You're all caught up\". */\n emptyTitle: string\n /**\n * The line under the empty state's headline. Default\n * \"Nothing from any product needs your attention.\"\n *\n * **The empty state is not an edge case.** A host that has not wired a feed\n * yet renders it, which makes it the state most consumers see first (B33-494's\n * data decision). A locale that leaves it untranslated is a blank panel.\n */\n emptyBody: string\n}\n\n/**\n * The English defaults. A consumer that passes nothing renders the panel in\n * English, which is what the specimen route shows and what a human will be\n * looking at when they approve this component at its fidelity gate.\n *\n * Frozen, and frozen without a type annotation. It is a public export reaching\n * four products on a version bump: `const` protects the binding and not the\n * contents, so a consumer aliasing it -- `const mine = defaultNotificationCenterLabels;\n * mine.title = 'Notifications'` -- would silently rename every panel in the\n * process (B33-877), and an annotation here would widen the `Readonly` back off\n * the emitted `.d.ts` and turn that into a runtime TypeError rather than a\n * compile error (B33-916).\n *\n * The freeze is shallow. No field here is an object and none should become one\n * without re-reading B33-916's note: a nested label defeats the guard silently.\n */\nexport const defaultNotificationCenterLabels = Object.freeze({\n title: 'Notifications',\n markAllAsRead: 'Mark all as read',\n all: 'All',\n unread: 'Unread',\n unreadItem: 'Unread',\n meta: '%source% · %time%',\n viewAll: 'View all notifications',\n emptyTitle: \"You're all caught up\",\n emptyBody: 'Nothing from any product needs your attention.',\n} satisfies NotificationCenterLabels)\n\n/**\n * The labels as the render consumes them: `meta` presented as a function of its\n * two values, so the component writes `t.meta(…)` and never handles a template\n * itself. Internal, and the interpolation has to happen somewhere.\n */\nexport interface ResolvedNotificationCenterLabels extends Omit<NotificationCenterLabels, 'meta'> {\n meta: (source: string, time: string) => string\n}\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied, and present `meta` as a function of\n * its two values.\n *\n * `{ title: undefined }` is what one missing next-intl key looks like at the\n * call site. It must fall back to English rather than blank the heading -- which\n * here would also unname the panel, since the heading is what names it\n * (B33-873).\n */\nexport function resolveNotificationCenterLabels(\n labels?: Partial<NotificationCenterLabels>,\n): ResolvedNotificationCenterLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<NotificationCenterLabels>\n const merged = { ...defaultNotificationCenterLabels, ...supplied }\n return {\n ...merged,\n meta: (source, time) => fillTemplate(merged.meta, { source, time }),\n }\n}\n"],"names":["defaultNotificationCenterLabels","resolveNotificationCenterLabels","labels","supplied","value","merged","source","time","fillTemplate"],"mappings":";AAkIO,MAAMA,IAAkC,OAAO,OAAO;AAAA,EAC3D,OAAO;AAAA,EACP,eAAe;AAAA,EACf,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AACb,CAAoC;AAqB7B,SAASC,EACdC,GACkC;AAClC,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA,GAElEC,IAAS,EAAE,GAAGL,GAAiC,GAAGG,EAAA;AACxD,SAAO;AAAA,IACL,GAAGE;AAAA,IACH,MAAM,CAACC,GAAQC,MAASC,EAAaH,EAAO,MAAM,EAAE,QAAAC,GAAQ,MAAAC,EAAA,CAAM;AAAA,EAAA;AAEtE;"}
@@ -0,0 +1,91 @@
1
+ import { ElementType, ReactNode } from 'react';
2
+ import { SettingsFrameLabels } from './labels';
3
+ /** One entry in a sidebar group: a settings page, here or on another origin. */
4
+ export interface SettingsFrameItem {
5
+ /** A stable key for the row, unique within its group. */
6
+ id: string;
7
+ /** The row's visible name. */
8
+ label: string;
9
+ /**
10
+ * Where the row goes. A path renders through `linkComponent`; an absolute URL
11
+ * renders as a plain `<a>` and loads the page in full -- which is what a
12
+ * product's page on its own origin needs.
13
+ */
14
+ href: string;
15
+ /**
16
+ * The row's glyph: a 16px square slot, inked in `currentColor`. The frame
17
+ * sets the colour (muted at rest, foreground when active), so draw the glyph
18
+ * with `stroke="currentColor"` or `fill="currentColor"` and it follows.
19
+ * Decorative -- give it `aria-hidden`; the label is the row's name.
20
+ */
21
+ icon?: ReactNode;
22
+ /** The page being shown. Marks the row `aria-current="page"`. */
23
+ active?: boolean;
24
+ }
25
+ /** A titled run of items: "You", the account's name, or a product's name. */
26
+ export interface SettingsFrameGroup {
27
+ /** A stable key for the group. */
28
+ id: string;
29
+ /** The small uppercase heading over the group. Hidden below 768px. */
30
+ label: string;
31
+ items: SettingsFrameItem[];
32
+ }
33
+ /**
34
+ * An account other than the one being acted in. Switching is Identity's job,
35
+ * so `href` is normally Identity's account chooser on Identity's origin.
36
+ */
37
+ export interface SettingsFrameAccount {
38
+ id: string;
39
+ label: string;
40
+ href: string;
41
+ }
42
+ /** A plain labelled link: the back link, or a help link in the header. */
43
+ export interface SettingsFrameLink {
44
+ label: string;
45
+ href: string;
46
+ }
47
+ /** One step of the breadcrumb. The last one is the page being shown. */
48
+ export interface SettingsFrameCrumb {
49
+ label: string;
50
+ /** Optional. A crumb with no href is text, which is how the hub draws both. */
51
+ href?: string;
52
+ }
53
+ /** The page's heading block, across the top of the content column. */
54
+ export interface SettingsFrameHeading {
55
+ title: string;
56
+ description?: string;
57
+ /** The page's primary action, level with the title rather than below it. */
58
+ action?: ReactNode;
59
+ }
60
+ export interface SettingsFrameProps {
61
+ /** The link out of settings, at the head of the sidebar. */
62
+ back: SettingsFrameLink;
63
+ /** The sidebar's groups, in order. The frame renders exactly these. */
64
+ groups: SettingsFrameGroup[];
65
+ /** Accounts other than the acting one. The list is omitted when empty. */
66
+ otherAccounts?: SettingsFrameAccount[];
67
+ /**
68
+ * Where "Sign out" goes. Sign-out belongs to Identity, which owns the
69
+ * session; a host that dropped its own cookie would have revoked nothing. So
70
+ * this is a link to Identity, never a handler.
71
+ */
72
+ signOutHref: string;
73
+ /** The header's help links (the hub's "Feedback" and "Docs"). Hidden below 768px. */
74
+ helpLinks?: SettingsFrameLink[];
75
+ /** The header's trail, ending at the page being shown. */
76
+ breadcrumb: SettingsFrameCrumb[];
77
+ /** The page heading, drawn above `children`. Omit it and the content starts at the top. */
78
+ heading?: SettingsFrameHeading;
79
+ /** The header's trailing slot: the host's account menu. */
80
+ accountMenu?: ReactNode;
81
+ /** The page itself. */
82
+ children?: ReactNode;
83
+ /** The frame's own words. Partial; missing fields fall back to English. */
84
+ labels?: Partial<SettingsFrameLabels>;
85
+ /**
86
+ * The element a same-origin link renders as (ADR-0011). Defaults to `'a'`.
87
+ * Only consulted for paths: an absolute URL is always a plain `<a>`.
88
+ */
89
+ linkComponent?: ElementType;
90
+ }
91
+ export declare function SettingsFrame({ back, groups, otherAccounts, signOutHref, helpLinks, breadcrumb, heading, accountMenu, children, labels, linkComponent, }: SettingsFrameProps): import("react").JSX.Element;
@@ -0,0 +1,109 @@
1
+ import { jsx as a, jsxs as s } from "react/jsx-runtime";
2
+ import e from "./SettingsFrame.module.css.js";
3
+ import { ArrowLeftGlyph as L, SignOutGlyph as y, PanelGlyph as x } from "./icons.js";
4
+ import { resolveSettingsFrameLabels as S } from "./labels.js";
5
+ const T = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
6
+ function i({
7
+ href: n,
8
+ linkComponent: N,
9
+ className: t,
10
+ current: o,
11
+ children: m
12
+ }) {
13
+ const p = T.test(n) ? "a" : N;
14
+ return /* @__PURE__ */ a(p, { href: n, className: t, "aria-current": o ? "page" : void 0, children: m });
15
+ }
16
+ function H({
17
+ back: n,
18
+ groups: N,
19
+ otherAccounts: t = [],
20
+ signOutHref: o,
21
+ helpLinks: m = [],
22
+ breadcrumb: p,
23
+ heading: d,
24
+ accountMenu: v,
25
+ children: u,
26
+ labels: g,
27
+ linkComponent: c = "a"
28
+ }) {
29
+ const h = S(g);
30
+ return /* @__PURE__ */ a("div", { className: e.root, children: /* @__PURE__ */ s("div", { className: e.frame, children: [
31
+ /* @__PURE__ */ s("nav", { "aria-label": h.navigation, className: e.sidebar, children: [
32
+ /* @__PURE__ */ s(i, { href: n.href, linkComponent: c, className: e.back, children: [
33
+ /* @__PURE__ */ a(L, { className: e.glyph }),
34
+ /* @__PURE__ */ a("span", { className: e.backLabel, children: n.label })
35
+ ] }),
36
+ /* @__PURE__ */ a("div", { className: e.title, children: /* @__PURE__ */ a("span", { className: e.titleText, children: h.title }) }),
37
+ N.map((l) => /* @__PURE__ */ s("div", { className: e.group, children: [
38
+ /* @__PURE__ */ a("div", { className: e.groupLabel, children: /* @__PURE__ */ a("span", { className: e.groupLabelText, children: l.label }) }),
39
+ l.items.map((r) => /* @__PURE__ */ s(
40
+ i,
41
+ {
42
+ href: r.href,
43
+ linkComponent: c,
44
+ className: e.item,
45
+ current: r.active,
46
+ children: [
47
+ /* @__PURE__ */ a("span", { className: e.icon, children: r.icon }),
48
+ /* @__PURE__ */ a("span", { className: e.label, children: r.label })
49
+ ]
50
+ },
51
+ r.id
52
+ ))
53
+ ] }, l.id)),
54
+ t.length > 0 && /* @__PURE__ */ s("div", { className: e.group, children: [
55
+ /* @__PURE__ */ a("div", { className: e.groupLabel, children: /* @__PURE__ */ a("span", { className: e.groupLabelText, children: h.otherAccounts }) }),
56
+ t.map((l) => /* @__PURE__ */ s(i, { href: l.href, linkComponent: c, className: e.item, children: [
57
+ /* @__PURE__ */ a("span", { className: e.swatch, "aria-hidden": "true" }),
58
+ /* @__PURE__ */ a("span", { className: e.label, children: l.label })
59
+ ] }, l.id))
60
+ ] }),
61
+ /* @__PURE__ */ a("div", { className: e.spacer }),
62
+ /* @__PURE__ */ s(i, { href: o, linkComponent: c, className: `${e.item} ${e.signOut}`, children: [
63
+ /* @__PURE__ */ a(y, { className: e.glyph }),
64
+ /* @__PURE__ */ a("span", { className: e.label, children: h.signOut })
65
+ ] })
66
+ ] }),
67
+ /* @__PURE__ */ s("div", { className: e.column, children: [
68
+ /* @__PURE__ */ s("header", { className: e.header, children: [
69
+ /* @__PURE__ */ s("div", { className: e.headerStart, children: [
70
+ /* @__PURE__ */ a("span", { className: e.panelMark, children: /* @__PURE__ */ a(x, { className: e.glyph }) }),
71
+ /* @__PURE__ */ a("nav", { "aria-label": h.breadcrumb, children: /* @__PURE__ */ a("ol", { className: e.crumbs, children: p.map((l, r) => {
72
+ const b = r === p.length - 1, f = b ? e.crumbCurrent : e.crumb;
73
+ return /* @__PURE__ */ s("li", { className: e.crumbStep, children: [
74
+ r > 0 && /* @__PURE__ */ a("span", { className: e.crumbSeparator, "aria-hidden": "true", children: "›" }),
75
+ l.href ? /* @__PURE__ */ a(
76
+ i,
77
+ {
78
+ href: l.href,
79
+ linkComponent: c,
80
+ className: f,
81
+ current: b,
82
+ children: l.label
83
+ }
84
+ ) : /* @__PURE__ */ a("span", { className: f, "aria-current": b ? "page" : void 0, children: l.label })
85
+ ] }, `${r}-${l.label}`);
86
+ }) }) })
87
+ ] }),
88
+ /* @__PURE__ */ s("div", { className: e.headerEnd, children: [
89
+ m.length > 0 && /* @__PURE__ */ a("span", { className: e.helpLinks, children: m.map((l) => /* @__PURE__ */ a(i, { href: l.href, linkComponent: c, className: e.helpLink, children: l.label }, l.href)) }),
90
+ v
91
+ ] })
92
+ ] }),
93
+ /* @__PURE__ */ s("main", { className: e.content, children: [
94
+ d && /* @__PURE__ */ s("div", { className: e.pageHead, children: [
95
+ /* @__PURE__ */ s("div", { className: e.pageHeadText, children: [
96
+ /* @__PURE__ */ a("h1", { className: e.pageTitle, children: d.title }),
97
+ d.description && /* @__PURE__ */ a("p", { className: e.pageDescription, children: d.description })
98
+ ] }),
99
+ d.action
100
+ ] }),
101
+ u
102
+ ] })
103
+ ] })
104
+ ] }) });
105
+ }
106
+ export {
107
+ H as SettingsFrame
108
+ };
109
+ //# sourceMappingURL=SettingsFrame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsFrame.js","sources":["../../../src/components/SettingsFrame/SettingsFrame.tsx"],"sourcesContent":["import type { ElementType, ReactNode } from 'react'\nimport styles from './SettingsFrame.module.css'\nimport { ArrowLeftGlyph, PanelGlyph, SignOutGlyph } from './icons'\nimport { resolveSettingsFrameLabels, type SettingsFrameLabels } from './labels'\n\n/*\n * The settings frame (B33-1529): the full-page takeover the hub and every\n * product draw their settings pages in, so somebody moving from the hub's\n * settings to a product's sees one settings area rather than two apps.\n *\n * Estate decision, 22 Oct 2026: settings live in the hub's menu and each product\n * hosts its own settings pages (boost33-voice ADR-0008). The hub's sidebar\n * carries a group per product the account has; that group's entries are pages\n * the product serves on its own origin, drawn in this same frame. So the frame\n * is rendered by several apps on several origins, and two things follow.\n *\n * ── It takes its groups as data and knows no product ────────────────────────\n *\n * Which groups exist, what they are called and where their entries go is the\n * host's decision, made from what Identity says the account has. Nothing here\n * names a product, a section or a role, and no item is active because of\n * anything the frame worked out: the host says `active`.\n *\n * ── A link to another origin is a plain full-page link ──────────────────────\n *\n * The rule is read off the href, not off a flag: an href with a scheme\n * (`https:`, `mailto:`) or protocol-relative (`//`) is rendered as a plain\n * `<a>`, and a path is rendered through `linkComponent` (ADR-0011). A product's\n * pages are not on the hub's origin, so the hub passes them as absolute URLs and\n * a router's client-side `Link` never sees them; a host's own pages are paths\n * and keep client navigation. A host that writes one of its own pages as an\n * absolute URL gets a full page load, which is slower and still correct --\n * the safe direction to be wrong in.\n *\n * ── The referent is the hub's settings chrome, not Paper ────────────────────\n *\n * The ticket's last criterion is that the hub's settings render through this\n * frame with no visible change. So the measurements are the hub's, copied from\n * boost33-hub `src/app/settings/chrome.tsx`, `ui.tsx` (`PageHead`) and the\n * `.settings-*` rules in its `globals.css`, and the comments in\n * SettingsFrame.module.css name each one. Paper page 104 is where the hub got\n * them; where the two might disagree, the hub's rendering is what this frame\n * reproduces, and the frame's fidelity gate is where a person decides otherwise.\n *\n * APPROVED at its fidelity gate on 26 Oct 2026: Jamal judged it on the\n * specimen route, captured its three designed baselines (settingsframe-hub,\n * -voice, -phone) and approved its measurements into the spec (\"SettingsFrame,\n * recorded at its gate\"). Those now bind. Static markup with no state and no\n * handlers, and no Stage B is planned: a frame of links has nothing to wire.\n */\n\n/** One entry in a sidebar group: a settings page, here or on another origin. */\nexport interface SettingsFrameItem {\n /** A stable key for the row, unique within its group. */\n id: string\n /** The row's visible name. */\n label: string\n /**\n * Where the row goes. A path renders through `linkComponent`; an absolute URL\n * renders as a plain `<a>` and loads the page in full -- which is what a\n * product's page on its own origin needs.\n */\n href: string\n /**\n * The row's glyph: a 16px square slot, inked in `currentColor`. The frame\n * sets the colour (muted at rest, foreground when active), so draw the glyph\n * with `stroke=\"currentColor\"` or `fill=\"currentColor\"` and it follows.\n * Decorative -- give it `aria-hidden`; the label is the row's name.\n */\n icon?: ReactNode\n /** The page being shown. Marks the row `aria-current=\"page\"`. */\n active?: boolean\n}\n\n/** A titled run of items: \"You\", the account's name, or a product's name. */\nexport interface SettingsFrameGroup {\n /** A stable key for the group. */\n id: string\n /** The small uppercase heading over the group. Hidden below 768px. */\n label: string\n items: SettingsFrameItem[]\n}\n\n/**\n * An account other than the one being acted in. Switching is Identity's job,\n * so `href` is normally Identity's account chooser on Identity's origin.\n */\nexport interface SettingsFrameAccount {\n id: string\n label: string\n href: string\n}\n\n/** A plain labelled link: the back link, or a help link in the header. */\nexport interface SettingsFrameLink {\n label: string\n href: string\n}\n\n/** One step of the breadcrumb. The last one is the page being shown. */\nexport interface SettingsFrameCrumb {\n label: string\n /** Optional. A crumb with no href is text, which is how the hub draws both. */\n href?: string\n}\n\n/** The page's heading block, across the top of the content column. */\nexport interface SettingsFrameHeading {\n title: string\n description?: string\n /** The page's primary action, level with the title rather than below it. */\n action?: ReactNode\n}\n\nexport interface SettingsFrameProps {\n /** The link out of settings, at the head of the sidebar. */\n back: SettingsFrameLink\n /** The sidebar's groups, in order. The frame renders exactly these. */\n groups: SettingsFrameGroup[]\n /** Accounts other than the acting one. The list is omitted when empty. */\n otherAccounts?: SettingsFrameAccount[]\n /**\n * Where \"Sign out\" goes. Sign-out belongs to Identity, which owns the\n * session; a host that dropped its own cookie would have revoked nothing. So\n * this is a link to Identity, never a handler.\n */\n signOutHref: string\n /** The header's help links (the hub's \"Feedback\" and \"Docs\"). Hidden below 768px. */\n helpLinks?: SettingsFrameLink[]\n /** The header's trail, ending at the page being shown. */\n breadcrumb: SettingsFrameCrumb[]\n /** The page heading, drawn above `children`. Omit it and the content starts at the top. */\n heading?: SettingsFrameHeading\n /** The header's trailing slot: the host's account menu. */\n accountMenu?: ReactNode\n /** The page itself. */\n children?: ReactNode\n /** The frame's own words. Partial; missing fields fall back to English. */\n labels?: Partial<SettingsFrameLabels>\n /**\n * The element a same-origin link renders as (ADR-0011). Defaults to `'a'`.\n * Only consulted for paths: an absolute URL is always a plain `<a>`.\n */\n linkComponent?: ElementType\n}\n\n// A scheme (`https:`, `mailto:`) or protocol-relative `//`. Anything else is a\n// path on the host's own origin.\nconst OTHER_ORIGIN = /^(?:[a-z][a-z\\d+.-]*:|\\/\\/)/i\n\nfunction FrameLink({\n href,\n linkComponent,\n className,\n current,\n children,\n}: {\n href: string\n linkComponent: ElementType\n className?: string\n current?: boolean\n children: ReactNode\n}) {\n const Link: ElementType = OTHER_ORIGIN.test(href) ? 'a' : linkComponent\n return (\n <Link href={href} className={className} aria-current={current ? 'page' : undefined}>\n {children}\n </Link>\n )\n}\n\nexport function SettingsFrame({\n back,\n groups,\n otherAccounts = [],\n signOutHref,\n helpLinks = [],\n breadcrumb,\n heading,\n accountMenu,\n children,\n labels,\n linkComponent = 'a',\n}: SettingsFrameProps) {\n const text = resolveSettingsFrameLabels(labels)\n\n return (\n <div className={styles.root}>\n <div className={styles.frame}>\n <nav aria-label={text.navigation} className={styles.sidebar}>\n <FrameLink href={back.href} linkComponent={linkComponent} className={styles.back}>\n <ArrowLeftGlyph className={styles.glyph} />\n <span className={styles.backLabel}>{back.label}</span>\n </FrameLink>\n\n <div className={styles.title}>\n <span className={styles.titleText}>{text.title}</span>\n </div>\n\n {groups.map((group) => (\n <div key={group.id} className={styles.group}>\n <div className={styles.groupLabel}>\n <span className={styles.groupLabelText}>{group.label}</span>\n </div>\n {group.items.map((item) => (\n <FrameLink\n key={item.id}\n href={item.href}\n linkComponent={linkComponent}\n className={styles.item}\n current={item.active}\n >\n <span className={styles.icon}>{item.icon}</span>\n <span className={styles.label}>{item.label}</span>\n </FrameLink>\n ))}\n </div>\n ))}\n\n {otherAccounts.length > 0 && (\n <div className={styles.group}>\n <div className={styles.groupLabel}>\n <span className={styles.groupLabelText}>{text.otherAccounts}</span>\n </div>\n {otherAccounts.map((account) => (\n <FrameLink key={account.id} href={account.href} linkComponent={linkComponent} className={styles.item}>\n <span className={styles.swatch} aria-hidden=\"true\" />\n <span className={styles.label}>{account.label}</span>\n </FrameLink>\n ))}\n </div>\n )}\n\n <div className={styles.spacer} />\n\n <FrameLink href={signOutHref} linkComponent={linkComponent} className={`${styles.item} ${styles.signOut}`}>\n <SignOutGlyph className={styles.glyph} />\n <span className={styles.label}>{text.signOut}</span>\n </FrameLink>\n </nav>\n\n <div className={styles.column}>\n <header className={styles.header}>\n <div className={styles.headerStart}>\n <span className={styles.panelMark}>\n <PanelGlyph className={styles.glyph} />\n </span>\n <nav aria-label={text.breadcrumb}>\n <ol className={styles.crumbs}>\n {breadcrumb.map((crumb, index) => {\n const last = index === breadcrumb.length - 1\n const className = last ? styles.crumbCurrent : styles.crumb\n return (\n <li key={`${index}-${crumb.label}`} className={styles.crumbStep}>\n {index > 0 && (\n <span className={styles.crumbSeparator} aria-hidden=\"true\">\n ›\n </span>\n )}\n {crumb.href ? (\n <FrameLink\n href={crumb.href}\n linkComponent={linkComponent}\n className={className}\n current={last}\n >\n {crumb.label}\n </FrameLink>\n ) : (\n <span className={className} aria-current={last ? 'page' : undefined}>\n {crumb.label}\n </span>\n )}\n </li>\n )\n })}\n </ol>\n </nav>\n </div>\n\n <div className={styles.headerEnd}>\n {helpLinks.length > 0 && (\n <span className={styles.helpLinks}>\n {helpLinks.map((link) => (\n <FrameLink key={link.href} href={link.href} linkComponent={linkComponent} className={styles.helpLink}>\n {link.label}\n </FrameLink>\n ))}\n </span>\n )}\n {accountMenu}\n </div>\n </header>\n\n <main className={styles.content}>\n {heading && (\n <div className={styles.pageHead}>\n <div className={styles.pageHeadText}>\n <h1 className={styles.pageTitle}>{heading.title}</h1>\n {heading.description && <p className={styles.pageDescription}>{heading.description}</p>}\n </div>\n {heading.action}\n </div>\n )}\n {children}\n </main>\n </div>\n </div>\n </div>\n )\n}\n"],"names":["OTHER_ORIGIN","FrameLink","href","linkComponent","className","current","children","Link","jsx","SettingsFrame","back","groups","otherAccounts","signOutHref","helpLinks","breadcrumb","heading","accountMenu","labels","text","resolveSettingsFrameLabels","styles","jsxs","ArrowLeftGlyph","group","item","account","SignOutGlyph","PanelGlyph","crumb","index","last","link"],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ const a = "_root_1y1aa_41", e = "_frame_1y1aa_47", _ = "_sidebar_1y1aa_59", t = "_back_1y1aa_76", c = "_backLabel_1y1aa_84", n = "_glyph_1y1aa_91", r = "_title_1y1aa_99", o = "_titleText_1y1aa_103", s = "_group_1y1aa_112", p = "_groupLabel_1y1aa_124", l = "_groupLabelText_1y1aa_128", y = "_item_1y1aa_140", b = "_icon_1y1aa_157", u = "_label_1y1aa_171", i = "_swatch_1y1aa_186", g = "_spacer_1y1aa_195", m = "_signOut_1y1aa_201", d = "_column_1y1aa_207", h = "_header_1y1aa_216", k = "_headerStart_1y1aa_227", L = "_panelMark_1y1aa_237", T = "_crumbs_1y1aa_244", x = "_crumbStep_1y1aa_255", S = "_crumb_1y1aa_244", H = "_crumbSeparator_1y1aa_266", f = "_crumbCurrent_1y1aa_270", w = "_headerEnd_1y1aa_276", C = "_helpLinks_1y1aa_282", D = "_helpLink_1y1aa_282", E = "_content_1y1aa_298", M = "_pageHead_1y1aa_307", O = "_pageHeadText_1y1aa_318", j = "_pageTitle_1y1aa_325", q = "_pageDescription_1y1aa_335", v = {
2
+ root: a,
3
+ frame: e,
4
+ sidebar: _,
5
+ back: t,
6
+ backLabel: c,
7
+ glyph: n,
8
+ title: r,
9
+ titleText: o,
10
+ group: s,
11
+ groupLabel: p,
12
+ groupLabelText: l,
13
+ item: y,
14
+ icon: b,
15
+ label: u,
16
+ swatch: i,
17
+ spacer: g,
18
+ signOut: m,
19
+ column: d,
20
+ header: h,
21
+ headerStart: k,
22
+ panelMark: L,
23
+ crumbs: T,
24
+ crumbStep: x,
25
+ crumb: S,
26
+ crumbSeparator: H,
27
+ crumbCurrent: f,
28
+ headerEnd: w,
29
+ helpLinks: C,
30
+ helpLink: D,
31
+ content: E,
32
+ pageHead: M,
33
+ pageHeadText: O,
34
+ pageTitle: j,
35
+ pageDescription: q
36
+ };
37
+ export {
38
+ t as back,
39
+ c as backLabel,
40
+ d as column,
41
+ E as content,
42
+ S as crumb,
43
+ f as crumbCurrent,
44
+ H as crumbSeparator,
45
+ x as crumbStep,
46
+ T as crumbs,
47
+ v as default,
48
+ e as frame,
49
+ n as glyph,
50
+ s as group,
51
+ p as groupLabel,
52
+ l as groupLabelText,
53
+ h as header,
54
+ w as headerEnd,
55
+ k as headerStart,
56
+ D as helpLink,
57
+ C as helpLinks,
58
+ b as icon,
59
+ y as item,
60
+ u as label,
61
+ q as pageDescription,
62
+ M as pageHead,
63
+ O as pageHeadText,
64
+ j as pageTitle,
65
+ L as panelMark,
66
+ a as root,
67
+ _ as sidebar,
68
+ m as signOut,
69
+ g as spacer,
70
+ i as swatch,
71
+ r as title,
72
+ o as titleText
73
+ };
74
+ //# sourceMappingURL=SettingsFrame.module.css.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsFrame.module.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,10 @@
1
+ import { ReactElement } from 'react';
2
+ export declare function ArrowLeftGlyph({ className }: {
3
+ className?: string;
4
+ }): ReactElement;
5
+ export declare function SignOutGlyph({ className }: {
6
+ className?: string;
7
+ }): ReactElement;
8
+ export declare function PanelGlyph({ className }: {
9
+ className?: string;
10
+ }): ReactElement;
@@ -0,0 +1,37 @@
1
+ import { jsxs as n, jsx as t } from "react/jsx-runtime";
2
+ const h = {
3
+ viewBox: "0 0 24 24",
4
+ fill: "none",
5
+ stroke: "currentColor",
6
+ strokeWidth: "2",
7
+ strokeLinecap: "round",
8
+ strokeLinejoin: "round",
9
+ "aria-hidden": !0
10
+ };
11
+ function e({ className: r }) {
12
+ return /* @__PURE__ */ n("svg", { className: r, ...h, children: [
13
+ /* @__PURE__ */ t("path", { d: "M5 12l14 0" }),
14
+ /* @__PURE__ */ t("path", { d: "M5 12l6 6" }),
15
+ /* @__PURE__ */ t("path", { d: "M5 12l6 -6" })
16
+ ] });
17
+ }
18
+ function o({ className: r }) {
19
+ return /* @__PURE__ */ n("svg", { className: r, ...h, children: [
20
+ /* @__PURE__ */ t("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" }),
21
+ /* @__PURE__ */ t("path", { d: "M9 12h12l-3 -3" }),
22
+ /* @__PURE__ */ t("path", { d: "M18 15l3 -3" })
23
+ ] });
24
+ }
25
+ function d({ className: r }) {
26
+ return /* @__PURE__ */ n("svg", { className: r, ...h, children: [
27
+ /* @__PURE__ */ t("path", { d: "M4 4m0 2a2 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" }),
28
+ /* @__PURE__ */ t("path", { d: "M9 4v16" }),
29
+ /* @__PURE__ */ t("path", { d: "M15 10l-2 2l2 2" })
30
+ ] });
31
+ }
32
+ export {
33
+ e as ArrowLeftGlyph,
34
+ d as PanelGlyph,
35
+ o as SignOutGlyph
36
+ };
37
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","sources":["../../../src/components/SettingsFrame/icons.tsx"],"sourcesContent":["// The settings frame's own three glyphs: the back arrow, the sign-out door and\n// the header's panel mark. Drawn in `currentColor` so the element around each\n// one carries its ink -- the same arrangement as AccountMenu/icons.tsx and\n// AppSwitcher/icons.tsx, and what keeps the no-hardcoded-hex guard satisfied.\n//\n// All three are decorative. The back link and the sign-out link each carry a\n// visible name beside the glyph, and the panel mark has no behaviour at all (see\n// `.panelMark` in SettingsFrame.module.css). Hence `aria-hidden` on each.\n//\n// The path data is copied verbatim from the hub's `src/lib/icons.tsx`\n// (`arrowLeft`, `signOut`, `panel`), which copied it from Paper pages 103 and\n// 104. The hub's settings chrome is this frame's referent, so the hub's paths\n// are the ones reproduced -- not a lookalike from Tabler's current release.\n//\n// A host's sidebar glyphs are NOT here. Which picture labels \"Billing\" or\n// \"Call routing\" is the host's knowledge, and a frame that held a glyph per\n// settings page would have to learn what every product's pages are. Items take\n// `icon` as a ReactNode instead -- see `SettingsFrameItem`.\n\nimport type { ReactElement } from 'react'\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\nexport function ArrowLeftGlyph({ className }: { className?: string }): ReactElement {\n return (\n <svg className={className} {...COMMON}>\n <path d=\"M5 12l14 0\" />\n <path d=\"M5 12l6 6\" />\n <path d=\"M5 12l6 -6\" />\n </svg>\n )\n}\n\nexport function SignOutGlyph({ className }: { className?: string }): ReactElement {\n return (\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\nexport function PanelGlyph({ className }: { className?: string }): ReactElement {\n return (\n <svg className={className} {...COMMON}>\n <path d=\"M4 4m0 2a2 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 4v16\" />\n <path d=\"M15 10l-2 2l2 2\" />\n </svg>\n )\n}\n"],"names":["COMMON","ArrowLeftGlyph","className","jsxs","jsx","SignOutGlyph","PanelGlyph"],"mappings":";AAqBA,MAAMA,IAAS;AAAA,EACb,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,eAAe;AACjB;AAEO,SAASC,EAAe,EAAE,WAAAC,KAAmD;AAClF,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAAD,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAI,EAAC,QAAA,EAAK,GAAE,aAAA,CAAa;AAAA,IACrB,gBAAAA,EAAC,QAAA,EAAK,GAAE,YAAA,CAAY;AAAA,IACpB,gBAAAA,EAAC,QAAA,EAAK,GAAE,aAAA,CAAa;AAAA,EAAA,GACvB;AAEJ;AAEO,SAASC,EAAa,EAAE,WAAAH,KAAmD;AAChF,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAAD,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAI,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,GACxB;AAEJ;AAEO,SAASE,EAAW,EAAE,WAAAJ,KAAmD;AAC9E,SACE,gBAAAC,EAAC,OAAA,EAAI,WAAAD,GAAuB,GAAGF,GAC7B,UAAA;AAAA,IAAA,gBAAAI,EAAC,QAAA,EAAK,GAAE,kFAAA,CAAkF;AAAA,IAC1F,gBAAAA,EAAC,QAAA,EAAK,GAAE,UAAA,CAAU;AAAA,IAClB,gBAAAA,EAAC,QAAA,EAAK,GAAE,kBAAA,CAAkB;AAAA,EAAA,GAC5B;AAEJ;"}
@@ -0,0 +1,36 @@
1
+ /** Every user-facing string the settings frame renders that is not host data. */
2
+ export interface SettingsFrameLabels {
3
+ /** The visible title at the head of the sidebar. Default "Settings". */
4
+ title: string;
5
+ /**
6
+ * The sidebar's landmark name. A `<nav>` is announced by its name, and a page
7
+ * with two navs (this one and the breadcrumb) needs both named or a screen
8
+ * reader lists "navigation" twice. Default "Settings", matching the hub.
9
+ */
10
+ navigation: string;
11
+ /** The heading over the other-accounts list. Default "Other accounts". */
12
+ otherAccounts: string;
13
+ /** The sign-out link. Default "Sign out". */
14
+ signOut: string;
15
+ /** The breadcrumb's landmark name. Default "Breadcrumb". */
16
+ breadcrumb: string;
17
+ }
18
+ /**
19
+ * The English defaults. A consumer that passes nothing renders English.
20
+ *
21
+ * Frozen because it is a public export reaching four products on a version bump;
22
+ * see AccountMenu/labels.ts for why the freeze carries no type annotation.
23
+ */
24
+ export declare const defaultSettingsFrameLabels: Readonly<{
25
+ title: string;
26
+ navigation: string;
27
+ otherAccounts: string;
28
+ signOut: string;
29
+ breadcrumb: string;
30
+ }>;
31
+ /**
32
+ * Merge a consumer's partial `labels` onto the English defaults, treating an
33
+ * explicitly-`undefined` field as unsupplied -- the resolution every other
34
+ * labels module here performs (B33-873).
35
+ */
36
+ export declare function resolveSettingsFrameLabels(labels?: Partial<SettingsFrameLabels>): SettingsFrameLabels;
@@ -0,0 +1,18 @@
1
+ const r = Object.freeze({
2
+ title: "Settings",
3
+ navigation: "Settings",
4
+ otherAccounts: "Other accounts",
5
+ signOut: "Sign out",
6
+ breadcrumb: "Breadcrumb"
7
+ });
8
+ function i(e) {
9
+ const t = Object.fromEntries(
10
+ Object.entries(e ?? {}).filter(([, n]) => n !== void 0)
11
+ );
12
+ return { ...r, ...t };
13
+ }
14
+ export {
15
+ r as defaultSettingsFrameLabels,
16
+ i as resolveSettingsFrameLabels
17
+ };
18
+ //# sourceMappingURL=labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.js","sources":["../../../src/components/SettingsFrame/labels.ts"],"sourcesContent":["// The settings frame's own user-facing words.\n//\n// Everything else a person reads in the frame is data the host supplies and so\n// translates by construction: the back link's label, every group's label, every\n// item's label, the other accounts' names, the help links, the breadcrumb and\n// the page heading. Those are the host's because they differ per host -- the hub\n// says \"Back to the hub\" and a product says something else, and which groups\n// exist is the whole point of taking groups as data (B33-1529). What is left is\n// what is the same in every host: the sidebar's title and landmark name, the\n// heading over the other accounts, the sign-out link and the breadcrumb's\n// landmark name.\n//\n// It takes the label contract AccountMenu, NotificationCenter and the chip use,\n// in full, on day one:\n//\n// * `labels` is `Partial<SettingsFrameLabels>`, so a later field costs no\n// consumer a typecheck (CLAUDE.md, \"Versioning\");\n// * `resolveSettingsFrameLabels` filters explicitly-`undefined` fields before\n// merging, so a missed next-intl key falls back to English instead of\n// unnaming a landmark (B33-873);\n// * `defaultSettingsFrameLabels` is `Object.freeze({ ... } satisfies ...)`\n// with no type annotation, so the freeze survives into the emitted `.d.ts`\n// (B33-877, B33-916).\n//\n// No label carries a value, so none of the chip's `%placeholder%` machinery is\n// reproduced. If one arrives, it arrives delimited `%name%`, never `{name}`\n// (B33-1277; the reasoning is in `src/fill-template.ts`).\n\n/** Every user-facing string the settings frame renders that is not host data. */\nexport interface SettingsFrameLabels {\n /** The visible title at the head of the sidebar. Default \"Settings\". */\n title: string\n /**\n * The sidebar's landmark name. A `<nav>` is announced by its name, and a page\n * with two navs (this one and the breadcrumb) needs both named or a screen\n * reader lists \"navigation\" twice. Default \"Settings\", matching the hub.\n */\n navigation: string\n /** The heading over the other-accounts list. Default \"Other accounts\". */\n otherAccounts: string\n /** The sign-out link. Default \"Sign out\". */\n signOut: string\n /** The breadcrumb's landmark name. Default \"Breadcrumb\". */\n breadcrumb: 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 * see AccountMenu/labels.ts for why the freeze carries no type annotation.\n */\nexport const defaultSettingsFrameLabels = Object.freeze({\n title: 'Settings',\n navigation: 'Settings',\n otherAccounts: 'Other accounts',\n signOut: 'Sign out',\n breadcrumb: 'Breadcrumb',\n} satisfies SettingsFrameLabels)\n\n/**\n * Merge a consumer's partial `labels` onto the English defaults, treating an\n * explicitly-`undefined` field as unsupplied -- the resolution every other\n * labels module here performs (B33-873).\n */\nexport function resolveSettingsFrameLabels(labels?: Partial<SettingsFrameLabels>): SettingsFrameLabels {\n const supplied = Object.fromEntries(\n Object.entries(labels ?? {}).filter(([, value]) => value !== undefined),\n ) as Partial<SettingsFrameLabels>\n return { ...defaultSettingsFrameLabels, ...supplied }\n}\n"],"names":["defaultSettingsFrameLabels","resolveSettingsFrameLabels","labels","supplied","value"],"mappings":"AAoDO,MAAMA,IAA6B,OAAO,OAAO;AAAA,EACtD,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,SAAS;AAAA,EACT,YAAY;AACd,CAA+B;AAOxB,SAASC,EAA2BC,GAA4D;AACrG,QAAMC,IAAW,OAAO;AAAA,IACtB,OAAO,QAAQD,KAAU,EAAE,EAAE,OAAO,CAAC,CAAA,EAAGE,CAAK,MAAMA,MAAU,MAAS;AAAA,EAAA;AAExE,SAAO,EAAE,GAAGJ,GAA4B,GAAGG,EAAA;AAC7C;"}
package/dist/index.d.ts CHANGED
@@ -24,11 +24,28 @@ export { AppSwitcher } from './components/AppSwitcher/AppSwitcher';
24
24
  export type { AppSwitcherProps, AppSwitcherProduct, AppSwitcherHub, AppSwitcherAccount, } from './components/AppSwitcher/AppSwitcher';
25
25
  export { defaultAppSwitcherLabels } from './components/AppSwitcher/labels';
26
26
  export type { AppSwitcherLabels } from './components/AppSwitcher/labels';
27
+ export { NotificationCenter } from './components/NotificationCenter/NotificationCenter';
28
+ export type { NotificationCenterProps, NotificationCenterItem, } from './components/NotificationCenter/NotificationCenter';
29
+ export { defaultNotificationCenterLabels } from './components/NotificationCenter/labels';
30
+ export type { NotificationCenterLabels } from './components/NotificationCenter/labels';
31
+ export { AccountMenu } from './components/AccountMenu/AccountMenu';
32
+ export type { AccountMenuProps, AccountMenuPerson, AccountMenuOrg, AccountMenuGroup, AccountMenuItem, } from './components/AccountMenu/AccountMenu';
33
+ export { defaultAccountMenuLabels } from './components/AccountMenu/labels';
34
+ export type { AccountMenuLabels } from './components/AccountMenu/labels';
35
+ export type { AccountMenuIcon } from './components/AccountMenu/icons';
36
+ export { CommandPalette } from './components/CommandPalette/CommandPalette';
37
+ export type { CommandPaletteProps, CommandPaletteGroup, CommandPaletteResult, } from './components/CommandPalette/CommandPalette';
38
+ export { defaultCommandPaletteLabels } from './components/CommandPalette/labels';
39
+ export type { CommandPaletteLabels } from './components/CommandPalette/labels';
27
40
  export { DotMatrixLoader } from './components/DotMatrixLoader/DotMatrixLoader';
28
41
  export type { DotMatrixLoaderProps } from './components/DotMatrixLoader/DotMatrixLoader';
29
42
  export { LaunchLoader } from './components/LaunchLoader/LaunchLoader';
30
43
  export type { LaunchLoaderProps } from './components/LaunchLoader/LaunchLoader';
31
44
  export { launchBackdropCss } from './launch-gradient';
32
45
  export type { LaunchGradientStops } from './launch-gradient';
46
+ export { SettingsFrame } from './components/SettingsFrame/SettingsFrame';
47
+ export type { SettingsFrameProps, SettingsFrameGroup, SettingsFrameItem, SettingsFrameAccount, SettingsFrameLink, SettingsFrameCrumb, SettingsFrameHeading, } from './components/SettingsFrame/SettingsFrame';
48
+ export { defaultSettingsFrameLabels } from './components/SettingsFrame/labels';
49
+ export type { SettingsFrameLabels } from './components/SettingsFrame/labels';
33
50
  export { checkNavHeightBudget, navContentHeight, navBudgetForViewport } from './nav-height-budget';
34
51
  export type { NavHeightBudget, NavRowCosts, NavSectionShape } from './nav-height-budget';