@eifi1/ui-kit 0.52.0 → 0.53.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/README.md +47 -54
- package/dist/billing/cancel-subscription.d.ts +5 -14
- package/dist/billing/cancel-subscription.js +1 -3
- package/dist/billing/cancel-subscription.js.map +1 -1
- package/dist/billing/plan-card.d.ts +5 -13
- package/dist/billing/plan-card.js +3 -11
- package/dist/billing/plan-card.js.map +1 -1
- package/dist/billing/plan-switch.d.ts +5 -14
- package/dist/billing/plan-switch.js +1 -3
- package/dist/billing/plan-switch.js.map +1 -1
- package/dist/chart.d.ts +3 -14
- package/dist/chart.js +64 -11
- package/dist/chart.js.map +1 -1
- package/dist/components/date-field.d.ts +1 -1
- package/dist/components/date-field.js.map +1 -1
- package/dist/components/date-picker.d.ts +1 -1
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +4 -17
- package/dist/components/date-range-picker.js +2 -2
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/nav-pills.d.ts +1 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/series-chart-axes.d.ts +1 -1
- package/dist/components/series-chart-axes.js.map +1 -1
- package/dist/components/series-chart.d.ts +64 -19
- package/dist/components/series-chart.js +2 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/treemap-labels.d.ts +6 -1
- package/dist/components/treemap-labels.js +2 -2
- package/dist/components/treemap-labels.js.map +1 -1
- package/dist/components/treemap.d.ts +29 -4
- package/dist/components/treemap.js +22 -9
- package/dist/components/treemap.js.map +1 -1
- package/dist/data-table.d.ts +1 -6
- package/dist/data-table.js +1 -9
- package/dist/data-table.js.map +1 -1
- package/dist/demo/demo-countdown.d.ts +0 -5
- package/dist/demo/demo-countdown.js +0 -4
- package/dist/demo/demo-countdown.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-mutation.d.ts +11 -23
- package/dist/feedback/feedback-mutation.js +1 -9
- package/dist/feedback/feedback-mutation.js.map +1 -1
- package/dist/feedback/feedback-public-list.d.ts +1 -6
- package/dist/feedback/feedback-public-list.js +1 -2
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +0 -8
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +3 -50
- package/dist/feedback/feedback-row-detail.js +0 -8
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-status-undo.d.ts +3 -19
- package/dist/feedback/feedback-status-undo.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +0 -9
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/feedback.d.ts +6 -40
- package/dist/feedback.js +88 -46
- package/dist/feedback.js.map +1 -1
- package/dist/i18n/defaults.js +0 -4
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +2 -19
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -14
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +2 -30
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +2 -19
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +2 -19
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +2 -19
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +2 -20
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +2 -19
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +9 -4
- package/dist/i18n/review.js.map +1 -1
- package/dist/i18n/shared-labels.js +0 -1
- package/dist/i18n/shared-labels.js.map +1 -1
- package/dist/index.d.ts +15 -64
- package/dist/index.js +22 -102
- package/dist/index.js.map +1 -1
- package/dist/rhf/fields.d.ts +2 -2
- package/dist/rhf/fields.js.map +1 -1
- package/dist/search/search-index.d.ts +0 -6
- package/dist/search/search-index.js +0 -4
- package/dist/search/search-index.js.map +1 -1
- package/dist/search.d.ts +1 -8
- package/dist/search.js +2 -7
- package/dist/search.js.map +1 -1
- package/dist/shell/account-menu-language.d.ts +7 -21
- package/dist/shell/account-menu-language.js +1 -3
- package/dist/shell/account-menu-language.js.map +1 -1
- package/dist/shell/account-menu-text-size.d.ts +9 -23
- package/dist/shell/account-menu-text-size.js +2 -3
- package/dist/shell/account-menu-text-size.js.map +1 -1
- package/dist/shell/app-shell.d.ts +0 -10
- package/dist/shell/app-shell.js +1 -7
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/company-switcher.d.ts +1 -1
- package/dist/shell/company-switcher.js.map +1 -1
- package/dist/shell/dev-menu.d.ts +1 -1
- package/dist/shell/dev-menu.js.map +1 -1
- package/dist/shell/top-bar-parts-labels.d.ts +1 -1
- package/dist/shell/top-bar-parts-labels.js.map +1 -1
- package/dist/shell.d.ts +7 -29
- package/dist/shell.js +12 -42
- package/dist/shell.js.map +1 -1
- package/dist/sync.d.ts +2 -6
- package/dist/sync.js +2 -15
- package/dist/sync.js.map +1 -1
- package/dist/theme/chart-palette.d.ts +0 -5
- package/dist/theme/chart-palette.js +0 -2
- package/dist/theme/chart-palette.js.map +1 -1
- package/package.json +1 -1
- package/src/billing/cancel-subscription.tsx +5 -16
- package/src/billing/plan-card.tsx +7 -24
- package/src/billing/plan-switch.tsx +5 -16
- package/src/chart.ts +62 -13
- package/src/components/date-field.tsx +1 -1
- package/src/components/date-picker.tsx +0 -1
- package/src/components/date-range-picker.tsx +6 -22
- package/src/components/nav-pills.tsx +1 -1
- package/src/components/series-chart-axes.ts +6 -1
- package/src/components/series-chart.tsx +78 -21
- package/src/components/treemap-labels.ts +11 -3
- package/src/components/treemap.tsx +65 -12
- package/src/data-table.ts +1 -11
- package/src/demo/demo-countdown.ts +3 -8
- package/src/feedback/feedback-mutation.ts +12 -33
- package/src/feedback/feedback-public-list.tsx +1 -7
- package/src/feedback/feedback-row-detail-labels.ts +0 -8
- package/src/feedback/feedback-row-detail.tsx +8 -58
- package/src/feedback/feedback-status-undo.ts +4 -18
- package/src/feedback/feedback-thread.tsx +0 -10
- package/src/feedback.ts +89 -48
- package/src/i18n/defaults.ts +0 -4
- package/src/i18n/german.ts +2 -19
- package/src/i18n/kit-labels.tsx +5 -14
- package/src/i18n/locales/es.ts +2 -19
- package/src/i18n/locales/fr.ts +2 -19
- package/src/i18n/locales/hu.ts +2 -19
- package/src/i18n/locales/it.ts +2 -20
- package/src/i18n/locales/zh.ts +2 -19
- package/src/i18n/review.ts +9 -4
- package/src/i18n/shared-labels.ts +0 -1
- package/src/index.ts +30 -112
- package/src/rhf/fields.tsx +2 -2
- package/src/search/search-index.ts +0 -15
- package/src/search.ts +8 -8
- package/src/shell/account-menu-language.tsx +7 -27
- package/src/shell/account-menu-text-size.tsx +13 -32
- package/src/shell/app-shell.tsx +0 -13
- package/src/shell/company-switcher.tsx +1 -1
- package/src/shell/dev-menu.tsx +1 -1
- package/src/shell/top-bar-parts-labels.ts +1 -1
- package/src/shell.ts +17 -44
- package/src/sync.ts +2 -15
- package/src/theme/chart-palette.ts +0 -2
- package/tokens.css +4 -4
- package/dist/billing/dialog-close.d.ts +0 -29
- package/dist/billing/dialog-close.js +0 -8
- package/dist/billing/dialog-close.js.map +0 -1
- package/dist/components/field-sync-labels.d.ts +0 -3
- package/dist/components/field-sync-labels.js +0 -12
- package/dist/components/field-sync-labels.js.map +0 -1
- package/dist/components/field-sync.d.ts +0 -186
- package/dist/components/field-sync.js +0 -269
- package/dist/components/field-sync.js.map +0 -1
- package/dist/components/section-nav.d.ts +0 -44
- package/dist/components/section-nav.js +0 -33
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/sign-chip-labels.d.ts +0 -3
- package/dist/components/sign-chip-labels.js +0 -11
- package/dist/components/sign-chip-labels.js.map +0 -1
- package/dist/components/sign-chip.d.ts +0 -77
- package/dist/components/sign-chip.js +0 -47
- package/dist/components/sign-chip.js.map +0 -1
- package/dist/lib/isin.d.ts +0 -38
- package/dist/lib/isin.js +0 -36
- package/dist/lib/isin.js.map +0 -1
- package/dist/shell/role-switcher.d.ts +0 -28
- package/dist/shell/role-switcher.js +0 -31
- package/dist/shell/role-switcher.js.map +0 -1
- package/src/billing/dialog-close.ts +0 -34
- package/src/components/field-sync-labels.ts +0 -14
- package/src/components/field-sync.tsx +0 -504
- package/src/components/section-nav.tsx +0 -72
- package/src/components/sign-chip-labels.ts +0 -13
- package/src/components/sign-chip.tsx +0 -133
- package/src/lib/isin.ts +0 -72
- package/src/shell/role-switcher.tsx +0 -53
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/dev-menu.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { FlaskConical, Undo2 } from \"lucide-react\";\nimport { DEFAULT_TOP_BAR_LABELS, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"./topbar-action-menu\";\nimport { useTopBarPartsLabels, type DevSwitchersLabels } from \"./top-bar-parts-labels\";\n// The kit's one storage guard, which 0.51 wrote here again by hand (DUP-8, refactor audit\n// 2026-10-30). Storage blocked, \"Back to\" lasts as long as this page.\nimport { readSessionStored, writeSessionStored } from \"../lib/safe-storage\";\n\n/** Where the role the session started with is kept, unless the app names its own key. */\nexport const DEV_ORIGINAL_ROLE_KEY = \"ui-kit:dev-original-role\";\n\n/**\n * One of the app's own groups in the {@link DevMenu} (docs/top-bar-harmonization.md\n * §2.7.1): keksdose's budget role, its \"today\" override, its resets.\n *\n * The rows are plain {@link TopBarMenuEntry}s, and there is no dev item type beside them\n * (the contract with keksdose, 0.51): a choice of one is radio rows (`checked`), a toggle\n * is a `checkable: \"checkbox\"` row, an action is a plain row. No form fields — a menu is\n * a `role=\"menu\"`, where a text or date input has no place in the arrow keys; a free\n * value is a \"Custom…\" row that opens the app's own dialog.\n */\nexport interface DevMenuSection {\n /** Unique among the sections. The rows' keys need be unique only within theirs: the\n * menu prefixes them with this. */\n key: string;\n /** The group's heading, drawn above its rows and announced as the group's name. */\n title: ReactNode;\n /** The rows, as `TopBarActionMenu` takes them. A `divider` ends the group, as there. */\n entries: readonly TopBarMenuEntry[];\n}\n\nexport interface DevMenuProps<R extends string, P extends string> {\n /**\n * Draw anything at all. The app passes `import.meta.env.DEV` AND its probe of the dev\n * route (§2.7.1): the constant keeps the menu off a production build's screen, the\n * probe off a dev build whose server has the route shut (§6.1) — flasks with nothing\n * behind them. Neither takes code out of the bundle; see `sections`.\n */\n enabled: boolean;\n roles?: readonly R[];\n /** The role the server enforces now. Named `currentRole`, not `role`, because `role` on\n * a JSX element is the ARIA role (as `RoleChip`'s `value`). */\n currentRole?: R;\n /** Apply a role — the app calls server-kit's `POST /dev/switch {role}` and re-reads\n * `/auth/me` (§2.7, §6.2). */\n onRole?: (role: R) => void;\n plans?: readonly P[];\n /** The plan the server enforces now. Left out (not known yet — kastlan's overview is a\n * 404 while billing is off), the plan group still renders, with no row checked; a\n * `GET /dev/switch` answers it. */\n currentPlan?: P;\n /** Apply a plan — `POST /dev/switch {plan}`, then the overview (§6.3). */\n onPlan?: (plan: P) => void;\n /**\n * The role to offer \"Back to {role}\" for. Left out, the kit keeps it itself: the role\n * before the first switch of this browser session, in `sessionStorage` under\n * `storageKey` — the server answers with the NEW role after a switch, so only the\n * browser still knows the old one.\n */\n originalRole?: R;\n /** The `sessionStorage` key for the kept role. Default {@link DEV_ORIGINAL_ROLE_KEY};\n * an app with several accounts on one browser may scope it per user. */\n storageKey?: string;\n /** Billing is off locally: the plan group says plans change nothing, and stays usable\n * (§6.3). */\n billingOff?: boolean;\n /** A role's or a plan's words in the menu. Default: the value itself. */\n roleLabel?: (role: R) => ReactNode;\n planLabel?: (plan: P) => ReactNode;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { dev } }}>` > English. */\n labels?: LabelOverride<DevSwitchersLabels>;\n /** 0.51: the app's own dev groups, after the kit's role and plan, in this order. Their\n * DEFINITIONS belong in a module only a dev build imports (§2.7.1): `enabled={false}`\n * hides the menu, but the code of a section built inline stays in the bundle. */\n sections?: readonly DevMenuSection[];\n /**\n * 0.51: an override is on that the developer could forget — a \"today\" that is not\n * today, a simulated slow server. The flask wears a dot, and its name says so (\"Dev\n * tools · Role: ADMIN · Plan: free Overrides on\"), since a dot alone tells a screen\n * reader nothing. The app decides what counts; the kit cannot see into its sections.\n */\n active?: boolean;\n}\n\nconst flask = <FlaskConical className=\"size-5 text-warning\" />;\n\n/** A label's text for a sentence, or the raw value when the label is not text. */\nconst textOf = (label: ReactNode, value: string) =>\n typeof label === \"string\" || typeof label === \"number\" ? String(label) : value;\n\n/** A row someone can pick — what makes a menu worth drawing. */\nconst isRow = (entry: TopBarMenuEntry) => entry.kind !== \"heading\" && entry.kind !== \"divider\";\n\n/**\n * The top bar's local-dev menu (docs/top-bar-harmonization.md §2.7, §2.7.1, §6.2, §6.3):\n * ONE amber flask, `RoleSwitcher`'s, over the kit's Account section and the app's own.\n *\n * 0.42's `DevSwitchers` drew a flask per switcher and had no place for anything else,\n * and an app has more dev controls than a role and a plan — keksdose's budget role, a\n * \"today\" override, resets (agreed with keksdose for 0.51). So the kit's part is now the\n * menu, and the app's rows go in it.\n *\n * - **The Account section** comes first: the role group, ending in \"Back to {role}\" once\n * the role differs from the one the session started with (the dev route needs only a\n * signed-in user, so a switch to a lesser role can always switch back); then the plan\n * group, which with `billingOff` notes that plans change nothing. Each renders only\n * with its list and handler.\n * - **The app's `sections`** follow, each a group under its own heading.\n * - **`active`** puts a dot on the flask for an override that is easy to forget.\n *\n * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls\n * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.\n *\n * From `md` up the groups stand side by side, one column each, in that order (0.51.4,\n * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: \"Each group gets a column\").\n * \"Back to {role}\" stays under the roles. A desktop too narrow for all of them wraps\n * the columns onto a second row; a phone keeps the one scrolling column.\n */\nexport function DevMenu<R extends string, P extends string>({\n enabled,\n roles,\n currentRole: role,\n onRole,\n plans,\n currentPlan: plan,\n onPlan,\n originalRole,\n storageKey = DEV_ORIGINAL_ROLE_KEY,\n billingOff = false,\n roleLabel = (r) => r,\n planLabel = (p) => p,\n labels: labelsProp,\n sections = [],\n active = false,\n}: DevMenuProps<R, P>) {\n const bar = useKitLabels(\"topBar\", DEFAULT_TOP_BAR_LABELS);\n const words = useTopBarPartsLabels(labelsProp ? { dev: labelsProp } : undefined).dev;\n const [kept, setKept] = useState<string | null>(() => (enabled ? readSessionStored(storageKey) : null));\n // 0.43 (keksdose): `enabled` may start false and turn true — an app's GET probe of the\n // dev route — so the kept role is read again then, or \"Back to {role}\" is lost after a\n // reload. Adjusted during render, React's pattern for state that follows a prop.\n const [wasEnabled, setWasEnabled] = useState(enabled);\n if (wasEnabled !== enabled) {\n setWasEnabled(enabled);\n if (enabled) setKept(readSessionStored(storageKey));\n }\n if (!enabled) return null;\n\n const original = (originalRole ?? kept ?? undefined) as R | undefined;\n\n const pickRole = (next: R) => {\n if (originalRole === undefined) {\n if (next === original) {\n // Back where the session began: nothing left to go back to.\n writeSessionStored(storageKey, null);\n setKept(null);\n } else if (kept === null && role !== undefined && next !== role) {\n writeSessionStored(storageKey, role);\n setKept(role);\n }\n }\n onRole?.(next);\n };\n\n const roleGroup: TopBarMenuEntry[] =\n roles && roles.length > 0 && role !== undefined && onRole\n ? [\n { kind: \"heading\", key: \"head-role\", label: words.roleHeading },\n ...roles.map((r): TopBarMenuEntry => ({\n key: `role-${r}`,\n label: roleLabel(r),\n checked: r === role,\n onSelect: () => pickRole(r),\n })),\n // Behind a rule, which ends the role group: \"Back to\" is a way out, not a role.\n ...(original !== undefined && original !== role\n ? ([\n { kind: \"divider\", key: \"sep-back\" },\n {\n key: \"back\",\n icon: <Undo2 className=\"size-4\" />,\n label: words.backTo(textOf(roleLabel(original), original)),\n onSelect: () => pickRole(original),\n },\n ] satisfies TopBarMenuEntry[])\n : []),\n ]\n : [];\n\n const planGroup: TopBarMenuEntry[] =\n plans && plans.length > 0 && onPlan\n ? [\n {\n kind: \"heading\",\n key: \"head-plan\",\n // The billing-off note is part of the group's NAME, under the heading. In 0.42's\n // own plan menu it was a trailing `<li>`: not a row, so the arrow keys passed\n // over it and a screen reader moving through the menu never reached it. Named,\n // it is read on the way into the plans. And in one menu a trailing note would\n // sit under the app's sections, not under the plans.\n label: billingOff ? (\n <>\n {words.planHeading}{\" \"}\n <span\n data-dev-billing-off=\"\"\n className=\"mt-0.5 block font-normal normal-case tracking-normal text-xs text-muted\"\n >\n {words.billingOff}\n </span>\n </>\n ) : (\n words.planHeading\n ),\n },\n ...plans.map((p): TopBarMenuEntry => ({\n key: `plan-${p}`,\n label: planLabel(p),\n checked: p === plan,\n onSelect: () => onPlan(p),\n })),\n ]\n : [];\n\n // The app's rows keyed under their section: two sections may well both have a \"reset\".\n const appGroups = sections.flatMap((section): TopBarMenuEntry[] => [\n { kind: \"heading\", key: `section:${section.key}`, label: section.title },\n ...section.entries.map((entry): TopBarMenuEntry => ({ ...entry, key: `${section.key}:${entry.key}` })),\n ]);\n\n const entries = [...roleGroup, ...planGroup, ...appGroups];\n if (!entries.some(isRow)) return null;\n\n // The menu, then the role and the plan, as 0.42's two triggers named them: the bar\n // shows the flask only, so its name (and tooltip) is where they are read.\n const name = [\n words.menu,\n roleGroup.length > 0 && role !== undefined ? bar.role(role) : null,\n planGroup.length > 0 && plan !== undefined ? words.plan(plan) : null,\n ]\n .filter((part): part is string => part !== null)\n .join(\" · \");\n\n return (\n <TopBarActionMenu\n icon={flask}\n // The badge's words join the trigger's name, as the tours compass's NEW dot does.\n // `danger`, the badge's default: an amber dot would vanish into the amber flask.\n iconBadge={active ? { label: words.overridesOn } : null}\n aria-label={name}\n entries={entries}\n scrollable\n columns\n />\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/shell/dev-menu.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { FlaskConical, Undo2 } from \"lucide-react\";\nimport { DEFAULT_TOP_BAR_LABELS, useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { TopBarActionMenu, type TopBarMenuEntry } from \"./topbar-action-menu\";\nimport { useTopBarPartsLabels, type DevSwitchersLabels } from \"./top-bar-parts-labels\";\n// The kit's one storage guard, which 0.51 wrote here again by hand (DUP-8, refactor audit\n// 2026-10-30). Storage blocked, \"Back to\" lasts as long as this page.\nimport { readSessionStored, writeSessionStored } from \"../lib/safe-storage\";\n\n/** Where the role the session started with is kept, unless the app names its own key. */\nexport const DEV_ORIGINAL_ROLE_KEY = \"ui-kit:dev-original-role\";\n\n/**\n * One of the app's own groups in the {@link DevMenu} (docs/top-bar-harmonization.md\n * §2.7.1): keksdose's budget role, its \"today\" override, its resets.\n *\n * The rows are plain {@link TopBarMenuEntry}s, and there is no dev item type beside them\n * (the contract with keksdose, 0.51): a choice of one is radio rows (`checked`), a toggle\n * is a `checkable: \"checkbox\"` row, an action is a plain row. No form fields — a menu is\n * a `role=\"menu\"`, where a text or date input has no place in the arrow keys; a free\n * value is a \"Custom…\" row that opens the app's own dialog.\n */\nexport interface DevMenuSection {\n /** Unique among the sections. The rows' keys need be unique only within theirs: the\n * menu prefixes them with this. */\n key: string;\n /** The group's heading, drawn above its rows and announced as the group's name. */\n title: ReactNode;\n /** The rows, as `TopBarActionMenu` takes them. A `divider` ends the group, as there. */\n entries: readonly TopBarMenuEntry[];\n}\n\nexport interface DevMenuProps<R extends string, P extends string> {\n /**\n * Draw anything at all. The app passes `import.meta.env.DEV` AND its probe of the dev\n * route (§2.7.1): the constant keeps the menu off a production build's screen, the\n * probe off a dev build whose server has the route shut (§6.1) — flasks with nothing\n * behind them. Neither takes code out of the bundle; see `sections`.\n */\n enabled: boolean;\n roles?: readonly R[];\n /** The role the server enforces now. Named `currentRole`, not `role`, because `role` on\n * a JSX element is the ARIA role (as `RoleChip`'s `value`). */\n currentRole?: R;\n /** Apply a role — the app calls server-kit's `POST /dev/switch {role}` and re-reads\n * `/auth/me` (§2.7, §6.2). */\n onRole?: (role: R) => void;\n plans?: readonly P[];\n /** The plan the server enforces now. Left out (not known yet — kastlan's overview is a\n * 404 while billing is off), the plan group still renders, with no row checked; a\n * `GET /dev/switch` answers it. */\n currentPlan?: P;\n /** Apply a plan — `POST /dev/switch {plan}`, then the overview (§6.3). */\n onPlan?: (plan: P) => void;\n /**\n * The role to offer \"Back to {role}\" for. Left out, the kit keeps it itself: the role\n * before the first switch of this browser session, in `sessionStorage` under\n * `storageKey` — the server answers with the NEW role after a switch, so only the\n * browser still knows the old one.\n */\n originalRole?: R;\n /** The `sessionStorage` key for the kept role. Default {@link DEV_ORIGINAL_ROLE_KEY};\n * an app with several accounts on one browser may scope it per user. */\n storageKey?: string;\n /** Billing is off locally: the plan group says plans change nothing, and stays usable\n * (§6.3). */\n billingOff?: boolean;\n /** A role's or a plan's words in the menu. Default: the value itself. */\n roleLabel?: (role: R) => ReactNode;\n planLabel?: (plan: P) => ReactNode;\n /** Prop > `<UiKitProvider labels={{ topBarParts: { dev } }}>` > English. */\n labels?: LabelOverride<DevSwitchersLabels>;\n /** 0.51: the app's own dev groups, after the kit's role and plan, in this order. Their\n * DEFINITIONS belong in a module only a dev build imports (§2.7.1): `enabled={false}`\n * hides the menu, but the code of a section built inline stays in the bundle. */\n sections?: readonly DevMenuSection[];\n /**\n * 0.51: an override is on that the developer could forget — a \"today\" that is not\n * today, a simulated slow server. The flask wears a dot, and its name says so (\"Dev\n * tools · Role: ADMIN · Plan: free Overrides on\"), since a dot alone tells a screen\n * reader nothing. The app decides what counts; the kit cannot see into its sections.\n */\n active?: boolean;\n}\n\nconst flask = <FlaskConical className=\"size-5 text-warning\" />;\n\n/** A label's text for a sentence, or the raw value when the label is not text. */\nconst textOf = (label: ReactNode, value: string) =>\n typeof label === \"string\" || typeof label === \"number\" ? String(label) : value;\n\n/** A row someone can pick — what makes a menu worth drawing. */\nconst isRow = (entry: TopBarMenuEntry) => entry.kind !== \"heading\" && entry.kind !== \"divider\";\n\n/**\n * The top bar's local-dev menu (docs/top-bar-harmonization.md §2.7, §2.7.1, §6.2, §6.3):\n * ONE amber flask over the kit's Account section and the app's own.\n *\n * 0.42's `DevSwitchers` drew a flask per switcher and had no place for anything else,\n * and an app has more dev controls than a role and a plan — keksdose's budget role, a\n * \"today\" override, resets (agreed with keksdose for 0.51). So the kit's part is now the\n * menu, and the app's rows go in it.\n *\n * - **The Account section** comes first: the role group, ending in \"Back to {role}\" once\n * the role differs from the one the session started with (the dev route needs only a\n * signed-in user, so a switch to a lesser role can always switch back); then the plan\n * group, which with `billingOff` notes that plans change nothing. Each renders only\n * with its list and handler.\n * - **The app's `sections`** follow, each a group under its own heading.\n * - **`active`** puts a dot on the flask for an override that is easy to forget.\n *\n * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls\n * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.\n *\n * From `md` up the groups stand side by side, one column each, in that order (0.51.4,\n * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: \"Each group gets a column\").\n * \"Back to {role}\" stays under the roles. A desktop too narrow for all of them wraps\n * the columns onto a second row; a phone keeps the one scrolling column.\n */\nexport function DevMenu<R extends string, P extends string>({\n enabled,\n roles,\n currentRole: role,\n onRole,\n plans,\n currentPlan: plan,\n onPlan,\n originalRole,\n storageKey = DEV_ORIGINAL_ROLE_KEY,\n billingOff = false,\n roleLabel = (r) => r,\n planLabel = (p) => p,\n labels: labelsProp,\n sections = [],\n active = false,\n}: DevMenuProps<R, P>) {\n const bar = useKitLabels(\"topBar\", DEFAULT_TOP_BAR_LABELS);\n const words = useTopBarPartsLabels(labelsProp ? { dev: labelsProp } : undefined).dev;\n const [kept, setKept] = useState<string | null>(() => (enabled ? readSessionStored(storageKey) : null));\n // 0.43 (keksdose): `enabled` may start false and turn true — an app's GET probe of the\n // dev route — so the kept role is read again then, or \"Back to {role}\" is lost after a\n // reload. Adjusted during render, React's pattern for state that follows a prop.\n const [wasEnabled, setWasEnabled] = useState(enabled);\n if (wasEnabled !== enabled) {\n setWasEnabled(enabled);\n if (enabled) setKept(readSessionStored(storageKey));\n }\n if (!enabled) return null;\n\n const original = (originalRole ?? kept ?? undefined) as R | undefined;\n\n const pickRole = (next: R) => {\n if (originalRole === undefined) {\n if (next === original) {\n // Back where the session began: nothing left to go back to.\n writeSessionStored(storageKey, null);\n setKept(null);\n } else if (kept === null && role !== undefined && next !== role) {\n writeSessionStored(storageKey, role);\n setKept(role);\n }\n }\n onRole?.(next);\n };\n\n const roleGroup: TopBarMenuEntry[] =\n roles && roles.length > 0 && role !== undefined && onRole\n ? [\n { kind: \"heading\", key: \"head-role\", label: words.roleHeading },\n ...roles.map((r): TopBarMenuEntry => ({\n key: `role-${r}`,\n label: roleLabel(r),\n checked: r === role,\n onSelect: () => pickRole(r),\n })),\n // Behind a rule, which ends the role group: \"Back to\" is a way out, not a role.\n ...(original !== undefined && original !== role\n ? ([\n { kind: \"divider\", key: \"sep-back\" },\n {\n key: \"back\",\n icon: <Undo2 className=\"size-4\" />,\n label: words.backTo(textOf(roleLabel(original), original)),\n onSelect: () => pickRole(original),\n },\n ] satisfies TopBarMenuEntry[])\n : []),\n ]\n : [];\n\n const planGroup: TopBarMenuEntry[] =\n plans && plans.length > 0 && onPlan\n ? [\n {\n kind: \"heading\",\n key: \"head-plan\",\n // The billing-off note is part of the group's NAME, under the heading. In 0.42's\n // own plan menu it was a trailing `<li>`: not a row, so the arrow keys passed\n // over it and a screen reader moving through the menu never reached it. Named,\n // it is read on the way into the plans. And in one menu a trailing note would\n // sit under the app's sections, not under the plans.\n label: billingOff ? (\n <>\n {words.planHeading}{\" \"}\n <span\n data-dev-billing-off=\"\"\n className=\"mt-0.5 block font-normal normal-case tracking-normal text-xs text-muted\"\n >\n {words.billingOff}\n </span>\n </>\n ) : (\n words.planHeading\n ),\n },\n ...plans.map((p): TopBarMenuEntry => ({\n key: `plan-${p}`,\n label: planLabel(p),\n checked: p === plan,\n onSelect: () => onPlan(p),\n })),\n ]\n : [];\n\n // The app's rows keyed under their section: two sections may well both have a \"reset\".\n const appGroups = sections.flatMap((section): TopBarMenuEntry[] => [\n { kind: \"heading\", key: `section:${section.key}`, label: section.title },\n ...section.entries.map((entry): TopBarMenuEntry => ({ ...entry, key: `${section.key}:${entry.key}` })),\n ]);\n\n const entries = [...roleGroup, ...planGroup, ...appGroups];\n if (!entries.some(isRow)) return null;\n\n // The menu, then the role and the plan, as 0.42's two triggers named them: the bar\n // shows the flask only, so its name (and tooltip) is where they are read.\n const name = [\n words.menu,\n roleGroup.length > 0 && role !== undefined ? bar.role(role) : null,\n planGroup.length > 0 && plan !== undefined ? words.plan(plan) : null,\n ]\n .filter((part): part is string => part !== null)\n .join(\" · \");\n\n return (\n <TopBarActionMenu\n icon={flask}\n // The badge's words join the trigger's name, as the tours compass's NEW dot does.\n // `danger`, the badge's default: an amber dot would vanish into the amber flask.\n iconBadge={active ? { label: words.overridesOn } : null}\n aria-label={name}\n entries={entries}\n scrollable\n columns\n />\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -5,7 +5,7 @@ import { type LabelOverride } from "../i18n/kit-labels.js";
|
|
|
5
5
|
* `DevSwitchers`, whose words these still are, §2.7.1). A module
|
|
6
6
|
* of words with no component code, like every `*-labels.ts` (0.34, KS U8). The language
|
|
7
7
|
* sub-list (`AccountMenuLanguage`) names itself with `topBar.language`, and the role in the
|
|
8
|
-
* dev flask's name is `topBar.role
|
|
8
|
+
* dev flask's name is `topBar.role`.
|
|
9
9
|
*/
|
|
10
10
|
/** `TourMenu`'s words (§2.4). */
|
|
11
11
|
export interface TourMenuLabels {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/top-bar-parts-labels.ts"],"sourcesContent":["import { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\n\n/**\n * The `topBarParts` namespace (0.42, docs/top-bar-harmonization.md §3.1): the words of the\n * top bar's shared parts — `TourMenu`, `SyncStatusIndicator` and `DevMenu` (0.42's\n * `DevSwitchers`, whose words these still are, §2.7.1). A module\n * of words with no component code, like every `*-labels.ts` (0.34, KS U8). The language\n * sub-list (`AccountMenuLanguage`) names itself with `topBar.language`, and the role in the\n * dev flask's name is `topBar.role
|
|
1
|
+
{"version":3,"sources":["../../src/shell/top-bar-parts-labels.ts"],"sourcesContent":["import { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\n\n/**\n * The `topBarParts` namespace (0.42, docs/top-bar-harmonization.md §3.1): the words of the\n * top bar's shared parts — `TourMenu`, `SyncStatusIndicator` and `DevMenu` (0.42's\n * `DevSwitchers`, whose words these still are, §2.7.1). A module\n * of words with no component code, like every `*-labels.ts` (0.34, KS U8). The language\n * sub-list (`AccountMenuLanguage`) names itself with `topBar.language`, and the role in the\n * dev flask's name is `topBar.role`.\n */\n\n/** `TourMenu`'s words (§2.4). */\nexport interface TourMenuLabels {\n /** The compass's name, and the menu's. */\n menu: string;\n /** The NEW dot on the compass, for a reader: it joins the trigger's name. */\n newDot: string;\n /** The heading over the tours that start on the open page. */\n thisPage: string;\n /** The heading over the untaken new tours. */\n newTours: string;\n /** The last row, the way to the app's tours page (only with `href`). */\n allTours: string;\n /** The chip on a new tour's row. */\n badgeNew: string;\n /** What the ✓ on a taken tour's row says to a reader. */\n completed: string;\n /** The chip on a tour reset by an update (0.43, docs/tours-harmonization.md §8). */\n badgeUpdated: string;\n /** The dot on the compass while only updated tours light it, for a reader. */\n updatedDot: string;\n /** The heading over the untaken new tours when updated ones are among them. */\n newAndUpdated: string;\n}\n\n/** `SyncStatusIndicator`'s words (§2.6). The \"waiting for a plan\" state is\n * `billing.waitingChanges`, the kit's words for it since billing §12.5. */\nexport interface SyncStatusLabels {\n /** Writes this device cannot send (keksdose: no key for the budget). Ranked first. */\n blocked: (count: number) => string;\n offline: string;\n /** The server refused the last round. */\n refused: string;\n syncing: string;\n pending: (count: number) => string;\n /** Settled, with the time of the last good round (already formatted). */\n syncedAt: (time: string) => string;\n /** Settled, before the first round. */\n idle: string;\n /** Joined to the state when a tap syncs: what the tap does. */\n syncHint: string;\n /** Joined to the state while offline, when a tap can do nothing. */\n offlineHint: string;\n /** The state and what a tap does, as one tooltip: \"Offline — nothing to sync…\". */\n withHint: (state: string, hint: string) => string;\n /** The state with the app's `scope` words before it: \"Handovers on this device: …\". */\n scoped: (scope: string, state: string) => string;\n /** The short words beside the cloud (`showLabel`): offline. */\n offlineShort: string;\n /** The short words while syncing. */\n syncingShort: string;\n /** The short words while changes wait (pending, held for a plan, or blocked). */\n waitingShort: (count: number) => string;\n /** The short words after a refused round. */\n refusedShort: string;\n /** Offline in an online-only app (`storage=\"none\"`): nothing is kept on the device. */\n offlineUnsaved: string;\n /** A round under way in an online-only app — it saves, it doesn't sync. Also its short\n * words beside the cloud. */\n saving: string;\n}\n\n/**\n * `DevMenu`'s words (§2.7, §2.7.1, §6.2, §6.3). Still named for 0.42's `DevSwitchers`: the\n * namespace is the same `topBarParts.dev`, and an app's label tree keeps compiling.\n */\nexport interface DevSwitchersLabels {\n /** 0.51: the flask's name, and the menu's — the role and the plan follow it. */\n menu: string;\n /** 0.51: what the dot on the flask means while `active`, joined to its name — an\n * override the developer could forget is on (a \"today\", a slow server). */\n overridesOn: string;\n /** The heading of the role group. */\n roleHeading: string;\n /** The plan in the flask's name, given the active plan. */\n plan: (value: string) => string;\n /** The heading of the plan group. */\n planHeading: string;\n /** The row back to the role the session started with. */\n backTo: (role: string) => string;\n /** The plan group's note while billing is off locally. */\n billingOff: string;\n}\n\nexport interface TopBarPartsLabels {\n tours: TourMenuLabels;\n sync: SyncStatusLabels;\n dev: DevSwitchersLabels;\n}\n\nexport const DEFAULT_TOP_BAR_PARTS_LABELS: TopBarPartsLabels = {\n tours: {\n menu: \"Guided tours\",\n newDot: \"New tours\",\n thisPage: \"On this page\",\n newTours: \"New tours\",\n allTours: \"All tours…\",\n badgeNew: \"New\",\n completed: \"Completed\",\n badgeUpdated: \"Updated\",\n updatedDot: \"Updated tours\",\n newAndUpdated: \"New and updated\",\n },\n sync: {\n blocked: (count) =>\n count === 1 ? \"1 change cannot be sent from this device\" : `${count} changes cannot be sent from this device`,\n offline: \"Offline — changes are saved on this device and will sync when you reconnect\",\n refused: \"The server refused the last sync\",\n syncing: \"Syncing…\",\n pending: (count) => (count === 1 ? \"1 change waiting to sync\" : `${count} changes waiting to sync`),\n syncedAt: (time) => `Synced at ${time}`,\n idle: \"Up to date\",\n syncHint: \"Tap to sync now\",\n offlineHint: \"nothing to sync while offline\",\n withHint: (state, hint) => `${state} — ${hint}`,\n scoped: (scope, state) => `${scope}: ${state}`,\n offlineShort: \"Offline\",\n syncingShort: \"Syncing…\",\n waitingShort: (count) => `${count} waiting`,\n refusedShort: \"Not synced\",\n offlineUnsaved: \"Offline — changes can't be saved until you reconnect\",\n saving: \"Saving…\",\n },\n dev: {\n menu: \"Dev tools\",\n overridesOn: \"Overrides on\",\n roleHeading: \"Dev · role\",\n plan: (value) => `Plan: ${value}`,\n planHeading: \"Dev · plan\",\n backTo: (role) => `Back to ${role}`,\n billingOff: \"Billing is off locally: plans change nothing\",\n },\n};\n\n/** The `topBarParts` namespace, resolved — English, then the provider, then `labels`. */\nexport function useTopBarPartsLabels(labels?: LabelOverride<TopBarPartsLabels>): TopBarPartsLabels {\n return useKitLabels(\"topBarParts\", DEFAULT_TOP_BAR_PARTS_LABELS, labels);\n}\n"],"mappings":";AAAA,SAAS,oBAAwC;AAoG1C,MAAM,+BAAkD;AAAA,EAC7D,OAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW;AAAA,IACX,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,eAAe;AAAA,EACjB;AAAA,EACA,MAAM;AAAA,IACJ,SAAS,CAAC,UACR,UAAU,IAAI,6CAA6C,GAAG,KAAK;AAAA,IACrE,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS,CAAC,UAAW,UAAU,IAAI,6BAA6B,GAAG,KAAK;AAAA,IACxE,UAAU,CAAC,SAAS,aAAa,IAAI;AAAA,IACrC,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,UAAU,CAAC,OAAO,SAAS,GAAG,KAAK,WAAM,IAAI;AAAA,IAC7C,QAAQ,CAAC,OAAO,UAAU,GAAG,KAAK,KAAK,KAAK;AAAA,IAC5C,cAAc;AAAA,IACd,cAAc;AAAA,IACd,cAAc,CAAC,UAAU,GAAG,KAAK;AAAA,IACjC,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,QAAQ;AAAA,EACV;AAAA,EACA,KAAK;AAAA,IACH,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,MAAM,CAAC,UAAU,SAAS,KAAK;AAAA,IAC/B,aAAa;AAAA,IACb,QAAQ,CAAC,SAAS,WAAW,IAAI;AAAA,IACjC,YAAY;AAAA,EACd;AACF;AAGO,SAAS,qBAAqB,QAA8D;AACjG,SAAO,aAAa,eAAe,8BAA8B,MAAM;AACzE;","names":[]}
|
package/dist/shell.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
export * from "./shell/topbar-controls.js";
|
|
2
2
|
export * from "./shell/top-bar.js";
|
|
3
|
-
export
|
|
4
|
-
export {
|
|
5
|
-
/** @deprecated 0.52 — `AppShell`, which picks its phone bar with it (API-4); removed in 0.53 */
|
|
6
|
-
pickMobileBar,
|
|
7
|
-
/** @deprecated 0.52 — `AppShell`, which splits its phone bar with it (API-4); removed in 0.53 */
|
|
8
|
-
splitMobileBar, } from "./shell/app-shell.js";
|
|
3
|
+
export { DEFAULT_APP_SHELL_MORE_LABELS, DEFAULT_MOBILE_MAX_ITEMS, AppShell, type AppShellSubItem, type AppShellNavItem, type AppShellSidebarFooterItem, type AppShellMoreLabels, type AppShellProps, } from "./shell/app-shell.js";
|
|
9
4
|
export * from "./shell/option-switcher-menu.js";
|
|
10
|
-
export * from "./shell/role-switcher.js";
|
|
11
5
|
export * from "./shell/topbar-action-menu.js";
|
|
12
6
|
export * from "./shell/top-bar-brand.js";
|
|
13
7
|
export * from "./shell/auth-layout.js";
|
|
@@ -19,21 +13,11 @@ export { useAdminMenuEntry } from "./shell/admin-menu.js";
|
|
|
19
13
|
export type { UseAdminMenuEntryOptions } from "./shell/admin-menu.js";
|
|
20
14
|
export { AdminWaitingBadge } from "./shell/admin-menu.js";
|
|
21
15
|
export type { AdminWaitingBadgeProps } from "./shell/admin-menu.js";
|
|
22
|
-
export { SettingsLayout
|
|
23
|
-
/** @deprecated 0.52 — `SettingsLayout`, which shows its search past that many entries (API-4); removed in 0.53 */
|
|
24
|
-
SETTINGS_SEARCH_THRESHOLD, } from "./settings/settings-layout.js";
|
|
16
|
+
export { SettingsLayout } from "./settings/settings-layout.js";
|
|
25
17
|
export type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from "./settings/settings-layout.js";
|
|
26
|
-
export { useSettingsRoute,
|
|
27
|
-
/** @deprecated 0.52 — `useSettingsRoute`, which resolves the location with it (API-4); removed in 0.53 */
|
|
28
|
-
resolveSettingsLocation, settingsFromListState,
|
|
29
|
-
/** @deprecated 0.52 — `settingsFromListState` and `useSettingsRoute`, which write and read it (API-4); removed in 0.53 */
|
|
30
|
-
SETTINGS_FROM_LIST_STATE, } from "./settings/use-settings-route.js";
|
|
18
|
+
export { useSettingsRoute, settingsFromListState } from "./settings/use-settings-route.js";
|
|
31
19
|
export type { UseSettingsRouteOptions, SettingsRoute, SettingsLayoutMode, SettingsLocation, SettingsLocationRules, SettingsLocationResult, } from "./settings/use-settings-route.js";
|
|
32
|
-
export { useSettingsFocus, useFocusParam
|
|
33
|
-
/** @deprecated 0.52 — `useSettingsFocus` / `useFocusParam`, which ring the card with it (API-4); removed in 0.53 */
|
|
34
|
-
SETTINGS_FOCUS_RING,
|
|
35
|
-
/** @deprecated 0.52 — `useSettingsFocus` / `useFocusParam`, which hold the ring that long (API-4); removed in 0.53 */
|
|
36
|
-
SETTINGS_FOCUS_MS, } from "./settings/use-settings-focus.js";
|
|
20
|
+
export { useSettingsFocus, useFocusParam } from "./settings/use-settings-focus.js";
|
|
37
21
|
export { SettingsSection } from "./settings/settings-section.js";
|
|
38
22
|
export type { SettingsSectionProps } from "./settings/settings-section.js";
|
|
39
23
|
export { useSettingsLayout } from "./settings/settings-context.js";
|
|
@@ -46,9 +30,7 @@ export { RootEntry, RedirectIfAuthed, useLastVisitedPage, readLastVisitedPage, c
|
|
|
46
30
|
export type { RootEntryProps, RedirectIfAuthedProps, RoutingSession, LastVisitedPageOptions, PathPattern, } from "./landing/routing.js";
|
|
47
31
|
export { PublicHeader } from "./landing/public-header.js";
|
|
48
32
|
export type { PublicHeaderProps, PublicHeaderBrand } from "./landing/public-header.js";
|
|
49
|
-
export { TopBarActions
|
|
50
|
-
/** @deprecated 0.52 — `TopBarActions`, which puts its slots in that order (API-4); removed in 0.53 */
|
|
51
|
-
TOP_BAR_ACTION_ORDER, } from "./shell/top-bar-actions.js";
|
|
33
|
+
export { TopBarActions } from "./shell/top-bar-actions.js";
|
|
52
34
|
export type { TopBarActionsProps } from "./shell/top-bar-actions.js";
|
|
53
35
|
export { AccountMenuLanguage } from "./shell/account-menu-language.js";
|
|
54
36
|
export type { AccountMenuLanguageProps, AccountMenuLanguageOption } from "./shell/account-menu-language.js";
|
|
@@ -56,13 +38,9 @@ export { AccountMenuTextSize } from "./shell/account-menu-text-size.js";
|
|
|
56
38
|
export type { AccountMenuTextSizeProps } from "./shell/account-menu-text-size.js";
|
|
57
39
|
export { TourMenu } from "./shell/tour-menu.js";
|
|
58
40
|
export type { TourMenuProps, TourMenuTour } from "./shell/tour-menu.js";
|
|
59
|
-
export { SyncStatusIndicator
|
|
60
|
-
/** @deprecated 0.52 — `SyncStatusIndicator`, which derives its state with it (API-4); removed in 0.53 */
|
|
61
|
-
syncState, } from "./shell/sync-status-indicator.js";
|
|
41
|
+
export { SyncStatusIndicator } from "./shell/sync-status-indicator.js";
|
|
62
42
|
export type { SyncStatus, SyncState, SyncStatusScope, SyncStatusIndicatorProps } from "./shell/sync-status-indicator.js";
|
|
63
|
-
export { DevMenu
|
|
64
|
-
/** @deprecated 0.52 — `DevMenu`, which keeps the role there unless `storageKey` names another key (API-4); removed in 0.53 */
|
|
65
|
-
DEV_ORIGINAL_ROLE_KEY, } from "./shell/dev-menu.js";
|
|
43
|
+
export { DevMenu } from "./shell/dev-menu.js";
|
|
66
44
|
export type { DevMenuProps, DevMenuSection } from "./shell/dev-menu.js";
|
|
67
45
|
export { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from "./shell/top-bar-parts-labels.js";
|
|
68
46
|
export type { TopBarPartsLabels, TourMenuLabels, SyncStatusLabels, DevSwitchersLabels, } from "./shell/top-bar-parts-labels.js";
|
package/dist/shell.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
export * from "./shell/topbar-controls.js";
|
|
3
3
|
export * from "./shell/top-bar.js";
|
|
4
|
-
export * from "./shell/app-shell.js";
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
DEFAULT_APP_SHELL_MORE_LABELS,
|
|
6
|
+
DEFAULT_MOBILE_MAX_ITEMS,
|
|
7
|
+
AppShell
|
|
8
8
|
} from "./shell/app-shell.js";
|
|
9
9
|
export * from "./shell/option-switcher-menu.js";
|
|
10
|
-
export * from "./shell/role-switcher.js";
|
|
11
10
|
export * from "./shell/topbar-action-menu.js";
|
|
12
11
|
export * from "./shell/top-bar-brand.js";
|
|
13
12
|
export * from "./shell/auth-layout.js";
|
|
@@ -15,22 +14,9 @@ import { FillHeight, FILL_HEIGHT_CLASS } from "./shell/fill-height.js";
|
|
|
15
14
|
import { adminMenuEntry, AdminMenuRow } from "./shell/admin-menu.js";
|
|
16
15
|
import { useAdminMenuEntry } from "./shell/admin-menu.js";
|
|
17
16
|
import { AdminWaitingBadge } from "./shell/admin-menu.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "./settings/settings-layout.js";
|
|
22
|
-
import {
|
|
23
|
-
useSettingsRoute,
|
|
24
|
-
resolveSettingsLocation,
|
|
25
|
-
settingsFromListState,
|
|
26
|
-
SETTINGS_FROM_LIST_STATE
|
|
27
|
-
} from "./settings/use-settings-route.js";
|
|
28
|
-
import {
|
|
29
|
-
useSettingsFocus,
|
|
30
|
-
useFocusParam,
|
|
31
|
-
SETTINGS_FOCUS_RING,
|
|
32
|
-
SETTINGS_FOCUS_MS
|
|
33
|
-
} from "./settings/use-settings-focus.js";
|
|
17
|
+
import { SettingsLayout } from "./settings/settings-layout.js";
|
|
18
|
+
import { useSettingsRoute, settingsFromListState } from "./settings/use-settings-route.js";
|
|
19
|
+
import { useSettingsFocus, useFocusParam } from "./settings/use-settings-focus.js";
|
|
34
20
|
import { SettingsSection } from "./settings/settings-section.js";
|
|
35
21
|
import { useSettingsLayout } from "./settings/settings-context.js";
|
|
36
22
|
import { SettingsHeadingLevel, useSettingsHeadingLevel } from "./settings/settings-heading.js";
|
|
@@ -51,30 +37,23 @@ import {
|
|
|
51
37
|
DEFAULT_LAST_VISITED_EXCLUDES
|
|
52
38
|
} from "./landing/routing.js";
|
|
53
39
|
import { PublicHeader } from "./landing/public-header.js";
|
|
54
|
-
import {
|
|
55
|
-
TopBarActions,
|
|
56
|
-
TOP_BAR_ACTION_ORDER
|
|
57
|
-
} from "./shell/top-bar-actions.js";
|
|
40
|
+
import { TopBarActions } from "./shell/top-bar-actions.js";
|
|
58
41
|
import { AccountMenuLanguage } from "./shell/account-menu-language.js";
|
|
59
42
|
import { AccountMenuTextSize } from "./shell/account-menu-text-size.js";
|
|
60
43
|
import { TourMenu } from "./shell/tour-menu.js";
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
syncState
|
|
64
|
-
} from "./shell/sync-status-indicator.js";
|
|
65
|
-
import {
|
|
66
|
-
DevMenu,
|
|
67
|
-
DEV_ORIGINAL_ROLE_KEY
|
|
68
|
-
} from "./shell/dev-menu.js";
|
|
44
|
+
import { SyncStatusIndicator } from "./shell/sync-status-indicator.js";
|
|
45
|
+
import { DevMenu } from "./shell/dev-menu.js";
|
|
69
46
|
import { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from "./shell/top-bar-parts-labels.js";
|
|
70
47
|
export {
|
|
71
48
|
AccountMenuLanguage,
|
|
72
49
|
AccountMenuTextSize,
|
|
73
50
|
AdminMenuRow,
|
|
74
51
|
AdminWaitingBadge,
|
|
52
|
+
AppShell,
|
|
53
|
+
DEFAULT_APP_SHELL_MORE_LABELS,
|
|
75
54
|
DEFAULT_LAST_VISITED_EXCLUDES,
|
|
55
|
+
DEFAULT_MOBILE_MAX_ITEMS,
|
|
76
56
|
DEFAULT_TOP_BAR_PARTS_LABELS,
|
|
77
|
-
DEV_ORIGINAL_ROLE_KEY,
|
|
78
57
|
DevMenu,
|
|
79
58
|
FILL_HEIGHT_CLASS,
|
|
80
59
|
FillHeight,
|
|
@@ -82,28 +61,19 @@ export {
|
|
|
82
61
|
PublicHeader,
|
|
83
62
|
RedirectIfAuthed,
|
|
84
63
|
RootEntry,
|
|
85
|
-
SETTINGS_FOCUS_MS,
|
|
86
|
-
SETTINGS_FOCUS_RING,
|
|
87
|
-
SETTINGS_FROM_LIST_STATE,
|
|
88
|
-
SETTINGS_SEARCH_THRESHOLD,
|
|
89
64
|
SettingsHeadingLevel,
|
|
90
65
|
SettingsLayout,
|
|
91
66
|
SettingsSection,
|
|
92
67
|
SyncStatusIndicator,
|
|
93
|
-
TOP_BAR_ACTION_ORDER,
|
|
94
68
|
TopBarActions,
|
|
95
69
|
TourMenu,
|
|
96
70
|
adminMenuEntry,
|
|
97
71
|
clearLastVisitedPage,
|
|
98
|
-
pickMobileBar,
|
|
99
72
|
readLastVisitedPage,
|
|
100
|
-
resolveSettingsLocation,
|
|
101
73
|
safeNextPath,
|
|
102
74
|
settingsFromListState,
|
|
103
75
|
settingsHref,
|
|
104
76
|
settingsSearchEntries,
|
|
105
|
-
splitMobileBar,
|
|
106
|
-
syncState,
|
|
107
77
|
useAdminMenuEntry,
|
|
108
78
|
useFocusParam,
|
|
109
79
|
useLastVisitedPage,
|
package/dist/shell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell.ts"],"sourcesContent":["// `@eifi1/ui-kit/shell` — the composable app chrome.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: AppShell renders NavLink and TopBarActionMenu renders Link.\nexport * from \"./shell/topbar-controls\";\nexport * from \"./shell/top-bar\";\
|
|
1
|
+
{"version":3,"sources":["../src/shell.ts"],"sourcesContent":["// `@eifi1/ui-kit/shell` — the composable app chrome.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: AppShell renders NavLink and TopBarActionMenu renders Link.\nexport * from \"./shell/topbar-controls\";\nexport * from \"./shell/top-bar\";\n// Named, not `*`: 0.53 took `pickMobileBar` off the surface (deprecated in 0.52, refactor\n// audit 2026-10-30, API-4) — AppShell's own — and removed `splitMobileBar`, which nothing\n// called any more.\nexport {\n DEFAULT_APP_SHELL_MORE_LABELS,\n DEFAULT_MOBILE_MAX_ITEMS,\n AppShell,\n type AppShellSubItem,\n type AppShellNavItem,\n type AppShellSidebarFooterItem,\n type AppShellMoreLabels,\n type AppShellProps,\n} from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/topbar-action-menu\";\nexport * from \"./shell/top-bar-brand\";\nexport * from \"./shell/auth-layout\";\n// 0.40.0: the list page's fill (docs/page-layout-harmonization.md §1.3).\nexport { FillHeight, FILL_HEIGHT_CLASS } from \"./shell/fill-height\";\nexport type { FillHeightProps } from \"./shell/fill-height\";\n// 0.42.0: the account menu's \"Administration\" row, as an entry and as a HoverMenu row\n// (docs/admin-harmonization.md §2.1, §6.6).\nexport { adminMenuEntry, AdminMenuRow } from \"./shell/admin-menu\";\nexport type { AdminMenuEntryOptions, AdminMenuRowProps } from \"./shell/admin-menu\";\n// 0.43.0 (kastlan): the entry with the provider's words.\nexport { useAdminMenuEntry } from \"./shell/admin-menu\";\nexport type { UseAdminMenuEntryOptions } from \"./shell/admin-menu\";\n// 0.45.0 (docs/admin-harmonization.md §9.3.4): the row's waiting pill on its own, for\n// IconButton's `badge` (kastlan's platform button); its name is `adminArea.waitingLabel`.\nexport { AdminWaitingBadge } from \"./shell/admin-menu\";\nexport type { AdminWaitingBadgeProps } from \"./shell/admin-menu\";\n// 0.31.0: the settings and admin pages' shell (docs/settings-harmonization.md §3, §5).\nexport { SettingsLayout } from \"./settings/settings-layout\";\nexport type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from \"./settings/settings-layout\";\nexport { useSettingsRoute, settingsFromListState } from \"./settings/use-settings-route\";\nexport type {\n UseSettingsRouteOptions,\n SettingsRoute,\n SettingsLayoutMode,\n SettingsLocation,\n SettingsLocationRules,\n SettingsLocationResult,\n} from \"./settings/use-settings-route\";\nexport { useSettingsFocus, useFocusParam } from \"./settings/use-settings-focus\";\nexport { SettingsSection } from \"./settings/settings-section\";\nexport type { SettingsSectionProps } from \"./settings/settings-section\";\nexport { useSettingsLayout } from \"./settings/settings-context\";\nexport type { SettingsLayoutContextValue } from \"./settings/settings-context\";\nexport { SettingsHeadingLevel, useSettingsHeadingLevel } from \"./settings/settings-heading\";\nexport type { SettingsHeadingLevelProps, SettingsHeadingTag } from \"./settings/settings-heading\";\nexport {\n settingsSearchEntries,\n visibleSettingsGroups,\n visibleSettingsEntries,\n settingsHref,\n} from \"./settings/settings-catalogue\";\nexport type { SettingsGroup, SettingsEntry, SettingsSearchEntriesOptions } from \"./settings/settings-catalogue\";\n// 0.31.0: the public pages' header and the resume (docs/landing-demo-harmonization.md §3, §4.1).\nexport {\n RootEntry,\n RedirectIfAuthed,\n useLastVisitedPage,\n readLastVisitedPage,\n clearLastVisitedPage,\n safeNextPath,\n NEXT_PARAM,\n DEFAULT_LAST_VISITED_EXCLUDES,\n} from \"./landing/routing\";\nexport type {\n RootEntryProps,\n RedirectIfAuthedProps,\n RoutingSession,\n LastVisitedPageOptions,\n PathPattern,\n} from \"./landing/routing\";\nexport { PublicHeader } from \"./landing/public-header\";\nexport type { PublicHeaderProps, PublicHeaderBrand } from \"./landing/public-header\";\n// 0.42.0: the top bar's shared parts (docs/top-bar-harmonization.md §3.1) — the slots in\n// §2.1's order, the account menu's language sub-list, the tours menu, the sync status, the\n// local-dev menu, and their words.\nexport { TopBarActions } from \"./shell/top-bar-actions\";\nexport type { TopBarActionsProps } from \"./shell/top-bar-actions\";\nexport { AccountMenuLanguage } from \"./shell/account-menu-language\";\nexport type { AccountMenuLanguageProps, AccountMenuLanguageOption } from \"./shell/account-menu-language\";\n// 0.46.0 (keksdose live #426): A− / A+ for the text size, beside the language.\nexport { AccountMenuTextSize } from \"./shell/account-menu-text-size\";\nexport type { AccountMenuTextSizeProps } from \"./shell/account-menu-text-size\";\nexport { TourMenu } from \"./shell/tour-menu\";\nexport type { TourMenuProps, TourMenuTour } from \"./shell/tour-menu\";\nexport { SyncStatusIndicator } from \"./shell/sync-status-indicator\";\nexport type { SyncStatus, SyncState, SyncStatusScope, SyncStatusIndicatorProps } from \"./shell/sync-status-indicator\";\n// 0.51 (§2.7.1, keksdose): one flask, the kit's Account section and the app's own dev\n// sections. 0.42's `DevSwitchers`, a DevMenu with no sections since 0.51, went in 0.52.\nexport { DevMenu } from \"./shell/dev-menu\";\nexport type { DevMenuProps, DevMenuSection } from \"./shell/dev-menu\";\nexport { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from \"./shell/top-bar-parts-labels\";\nexport type {\n TopBarPartsLabels,\n TourMenuLabels,\n SyncStatusLabels,\n DevSwitchersLabels,\n} from \"./shell/top-bar-parts-labels\";\n"],"mappings":";AAIA,cAAc;AACd,cAAc;AAId;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,SAAS,YAAY,yBAAyB;AAI9C,SAAS,gBAAgB,oBAAoB;AAG7C,SAAS,yBAAyB;AAIlC,SAAS,yBAAyB;AAGlC,SAAS,sBAAsB;AAE/B,SAAS,kBAAkB,6BAA6B;AASxD,SAAS,kBAAkB,qBAAqB;AAChD,SAAS,uBAAuB;AAEhC,SAAS,yBAAyB;AAElC,SAAS,sBAAsB,+BAA+B;AAE9D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,oBAAoB;AAK7B,SAAS,qBAAqB;AAE9B,SAAS,2BAA2B;AAGpC,SAAS,2BAA2B;AAEpC,SAAS,gBAAgB;AAEzB,SAAS,2BAA2B;AAIpC,SAAS,eAAe;AAExB,SAAS,8BAA8B,4BAA4B;","names":[]}
|
package/dist/sync.d.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
export { createSyncDb, rowByPk, SYNC_DB_PK_INDEX
|
|
2
|
-
/** @deprecated 0.52 — `createSyncDb`, which declares them (`extraStores` for an app's own) (API-4); removed in 0.53 */
|
|
3
|
-
SYNC_DB_STORES, } from "./sync/db.js";
|
|
1
|
+
export { createSyncDb, rowByPk, SYNC_DB_PK_INDEX } from "./sync/db.js";
|
|
4
2
|
export type { CreateSyncDbOptions, OutboxChange, OutboxStatus, SyncDb, SyncDbUpgrade, SyncedRow, SyncMetaEntry, } from "./sync/db.js";
|
|
5
|
-
export { createSyncEngine, DEFAULT_HOLD_CODES,
|
|
6
|
-
/** @deprecated 0.52 — `createSyncEngine`, whose `backoffMs` defaults to it (API-4); removed in 0.53 */
|
|
7
|
-
DEFAULT_SYNC_BACKOFF_MS, releaseSyncEngine, syncEngineFor, } from "./sync/engine.js";
|
|
3
|
+
export { createSyncEngine, DEFAULT_HOLD_CODES, releaseSyncEngine, syncEngineFor } from "./sync/engine.js";
|
|
8
4
|
export type { SyncCounts, SyncEngine, SyncEngineOptions, SyncEngineState, SyncRoundResult, SyncStep, SyncStepContext, SyncStepResult, } from "./sync/engine.js";
|
|
9
5
|
export { useSyncQuery, useSyncStatus, useSyncWorker } from "./sync/hooks.js";
|
|
10
6
|
export type { SyncPageCursor, SyncRefusalBody, SyncRejectionBody, SyncRequestBody, SyncResponseBody, SyncWireChange, } from "./sync/wire.js";
|
package/dist/sync.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
rowByPk,
|
|
5
|
-
SYNC_DB_PK_INDEX,
|
|
6
|
-
SYNC_DB_STORES
|
|
7
|
-
} from "./sync/db.js";
|
|
8
|
-
import {
|
|
9
|
-
createSyncEngine,
|
|
10
|
-
DEFAULT_HOLD_CODES,
|
|
11
|
-
DEFAULT_SYNC_BACKOFF_MS,
|
|
12
|
-
releaseSyncEngine,
|
|
13
|
-
syncEngineFor
|
|
14
|
-
} from "./sync/engine.js";
|
|
2
|
+
import { createSyncDb, rowByPk, SYNC_DB_PK_INDEX } from "./sync/db.js";
|
|
3
|
+
import { createSyncEngine, DEFAULT_HOLD_CODES, releaseSyncEngine, syncEngineFor } from "./sync/engine.js";
|
|
15
4
|
import { useSyncQuery, useSyncStatus, useSyncWorker } from "./sync/hooks.js";
|
|
16
5
|
import { isOfflineError, readLocalFirst, stampAfter, uuid5, writeLocalFirst } from "./offline/local-first.js";
|
|
17
6
|
export {
|
|
18
7
|
DEFAULT_HOLD_CODES,
|
|
19
|
-
DEFAULT_SYNC_BACKOFF_MS,
|
|
20
8
|
SYNC_DB_PK_INDEX,
|
|
21
|
-
SYNC_DB_STORES,
|
|
22
9
|
createSyncDb,
|
|
23
10
|
createSyncEngine,
|
|
24
11
|
isOfflineError,
|
package/dist/sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sync.ts"],"sourcesContent":["// `@eifi1/ui-kit/sync` — local-first phase 3 (docs/local-first-harmonization.md §2.3, 0.46):\n// the sync engine on a Dexie copy, its worker and status hooks, and the live read of the copy.\n//\n// The only entry point that imports `dexie` — an optional peer for that reason. It never loads\n// `/offline`'s TanStack and idb-keyval peers: the leaf utilities both entries export live in\n// one module of their own (src/offline/local-first.ts). NOT re-exported from the main barrel.\nexport {
|
|
1
|
+
{"version":3,"sources":["../src/sync.ts"],"sourcesContent":["// `@eifi1/ui-kit/sync` — local-first phase 3 (docs/local-first-harmonization.md §2.3, 0.46):\n// the sync engine on a Dexie copy, its worker and status hooks, and the live read of the copy.\n//\n// The only entry point that imports `dexie` — an optional peer for that reason. It never loads\n// `/offline`'s TanStack and idb-keyval peers: the leaf utilities both entries export live in\n// one module of their own (src/offline/local-first.ts). NOT re-exported from the main barrel.\nexport { createSyncDb, rowByPk, SYNC_DB_PK_INDEX } from \"./sync/db\";\nexport type {\n CreateSyncDbOptions,\n OutboxChange,\n OutboxStatus,\n SyncDb,\n SyncDbUpgrade,\n SyncedRow,\n SyncMetaEntry,\n} from \"./sync/db\";\nexport { createSyncEngine, DEFAULT_HOLD_CODES, releaseSyncEngine, syncEngineFor } from \"./sync/engine\";\nexport type {\n SyncCounts,\n SyncEngine,\n SyncEngineOptions,\n SyncEngineState,\n SyncRoundResult,\n SyncStep,\n SyncStepContext,\n SyncStepResult,\n} from \"./sync/engine\";\nexport { useSyncQuery, useSyncStatus, useSyncWorker } from \"./sync/hooks\";\nexport type {\n SyncPageCursor,\n SyncRefusalBody,\n SyncRejectionBody,\n SyncRequestBody,\n SyncResponseBody,\n SyncWireChange,\n} from \"./sync/wire\";\nexport { isOfflineError, readLocalFirst, stampAfter, uuid5, writeLocalFirst } from \"./offline/local-first\";\n"],"mappings":";AAMA,SAAS,cAAc,SAAS,wBAAwB;AAUxD,SAAS,kBAAkB,oBAAoB,mBAAmB,qBAAqB;AAWvF,SAAS,cAAc,eAAe,qBAAqB;AAS3D,SAAS,gBAAgB,gBAAgB,YAAY,OAAO,uBAAuB;","names":[]}
|
|
@@ -54,11 +54,6 @@ export declare const CHART_ROLES: {
|
|
|
54
54
|
readonly baseline: "var(--chart-ink)";
|
|
55
55
|
readonly outline: "var(--chart-ink)";
|
|
56
56
|
};
|
|
57
|
-
/** `--chart-ink` per theme, as hex (tokens.css holds the same two). */
|
|
58
|
-
export declare const CHART_INK_HEX: {
|
|
59
|
-
readonly light: "#1a1a17";
|
|
60
|
-
readonly dark: "#f0efe8";
|
|
61
|
-
};
|
|
62
57
|
/** `--chart-muted` per theme, as hex (tokens.css holds the same two). */
|
|
63
58
|
export declare const CHART_MUTED_HEX: {
|
|
64
59
|
readonly light: "#6b6a63";
|
|
@@ -27,7 +27,6 @@ const CHART_ROLES = {
|
|
|
27
27
|
baseline: "var(--chart-ink)",
|
|
28
28
|
outline: "var(--chart-ink)"
|
|
29
29
|
};
|
|
30
|
-
const CHART_INK_HEX = { light: "#1a1a17", dark: "#f0efe8" };
|
|
31
30
|
const CHART_MUTED_HEX = { light: "#6b6a63", dark: "#a8a79f" };
|
|
32
31
|
const EDGE_MIN_CONTRAST = 3;
|
|
33
32
|
function chartEdge(fill, surfaces, ink = CHART_ROLES.outline) {
|
|
@@ -85,7 +84,6 @@ function clamp01(t) {
|
|
|
85
84
|
}
|
|
86
85
|
export {
|
|
87
86
|
CHART_COLORS,
|
|
88
|
-
CHART_INK_HEX,
|
|
89
87
|
CHART_MUTED_HEX,
|
|
90
88
|
CHART_ROLES,
|
|
91
89
|
CVD_ORDER,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/theme/chart-palette.ts"],"sourcesContent":["// Chart colour system. Single source of truth lives in the design tokens\n// (:root / .dark in tokens.css): brand = indigo, money = teal/amber/purple,\n// chart-1..8 = anyplot's \"Imprint\" hues (https://anyplot.ai/palette, 0.49 — Paul Tol\n// \"Muted\" before, still selectable as the `paul-tol` preset).\n// Returning CSS vars here makes every Recharts series theme-aware for free — the\n// SVG fill/stroke resolves --chart-N against the current light/dark token set.\n\nimport { contrast, parseHex as parseRgbHex } from \"./color\";\n\n/** How many categorical slots `paletteFor` cycles through: anyplot's eight hues. Amber,\n * anyplot's ninth colour, is a semantic warning anchor and not a series slot. A preset\n * that carries more hues (Paul Tol's nine) still writes them as `--chart-N`, but the\n * by-index ramp wraps after eight, so the same series keeps the same colour whichever\n * preset is active. */\nexport const PALETTE_SIZE = 8;\n\n/** anyplot's colour-vision-deficiency-optimal order over the eight slots — the order\n * that keeps the FIRST n series furthest apart under protan/deutan/tritan simulation.\n * An option (`paletteFor(i, { order: \"cvd\" })`), not the default: the default order is\n * anyplot's own slot order, so series 1 is always the green. */\nexport const CVD_ORDER = [0, 1, 7, 6, 4, 5, 2, 3] as const;\n\nexport interface PaletteForOptions {\n /** `\"slot\"` (default): anyplot's slot order. `\"cvd\"`: {@link CVD_ORDER}. */\n order?: \"slot\" | \"cvd\";\n}\n\n/** CVD-safe categorical colour for series `index`, as a theme-aware CSS var.\n *\n * The first series is always slot 0 (`--chart-1`, anyplot's green), and the ramp\n * wraps after {@link PALETTE_SIZE}.\n *\n * Wraps in both directions, the way `strokeDash` does: JS `%` keeps the sign,\n * so `-1 % 8` is `-1` and the plain formula named `--chart-0`, a token that does not\n * exist — a series with no stroke at all. A fractional index is truncated, and one\n * that is not a number (NaN, ±Infinity — an `indexOf` miss divided by something)\n * takes the first colour rather than `var(--chart-NaN)`. */\nexport function paletteFor(index: number, options?: PaletteForOptions): string {\n const i = Number.isFinite(index) ? Math.trunc(index) : 0;\n const position = ((i % PALETTE_SIZE) + PALETTE_SIZE) % PALETTE_SIZE;\n const slot = options?.order === \"cvd\" ? CVD_ORDER[position] : position;\n return `var(--chart-${slot + 1})`;\n}\n\n/** Semantic money colours, theme-aware via tokens. Pair with +/- and arrow\n cues at the call site so colour is never the only signal (CVD). */\nexport const CHART_COLORS = {\n income: \"var(--money-income)\",\n expense: \"var(--money-expense)\",\n net: \"var(--money-net)\",\n neutral: \"var(--money-neutral)\",\n assigned: \"var(--brand)\",\n activity: \"var(--money-expense)\",\n /** A generic total line — not money-coloured, the chart's neutral ink. */\n total: \"var(--chart-ink)\",\n} as const;\n\n/**\n * What a chart draws that is NOT a series (anyplot's roles, 0.49). Two inks, which are\n * the only chart colours that change between light and dark — the eight hues do not:\n *\n * - `other` / `rest` / `disabled` and confidence-`band` fills → the MUTED ink. A\n * treemap's \"rest\" bucket or a pie's \"Other\" slice takes this instead of wrapping\n * round to a hue the reader will match to a real series.\n * - `total` / `baseline` / `outline` → the neutral INK.\n */\nexport const CHART_ROLES = {\n other: \"var(--chart-muted)\",\n rest: \"var(--chart-muted)\",\n disabled: \"var(--chart-muted)\",\n band: \"var(--chart-muted)\",\n total: \"var(--chart-ink)\",\n baseline: \"var(--chart-ink)\",\n outline: \"var(--chart-ink)\",\n} as const;\n\n/** `--chart-ink` per theme, as hex (tokens.css holds the same two). */\nexport const CHART_INK_HEX = { light: \"#1a1a17\", dark: \"#f0efe8\" } as const;\n/** `--chart-muted` per theme, as hex (tokens.css holds the same two). */\nexport const CHART_MUTED_HEX = { light: \"#6b6a63\", dark: \"#a8a79f\" } as const;\n\n/** The 3:1 WCAG 1.4.11 asks of a filled graphical object against its surface. */\nexport const EDGE_MIN_CONTRAST = 3;\n\n/**\n * The 1px edge a FILLED mark (a bar, an area, a treemap tile) needs, or `undefined`.\n *\n * anyplot's answer to hues that sit under 3:1 on the ground (cyan, lime, lavender, ochre\n * and green on cream; matte red and rose on the dark ground): outline the shape in ink,\n * so its boundary is legible while the fill keeps the hue that identifies the series.\n * Only those — a hue that clears 3:1 on every surface given stays unstroked, because an\n * outline on every bar is noise.\n *\n * `fill` must be a measurable `#rgb`/`#rrggbb`; `surfaces` are the grounds the mark can\n * sit on (the card and the page), and the WORST of them decides. Anything it cannot\n * measure gets no edge rather than a guessed one.\n */\nexport function chartEdge(\n fill: string,\n surfaces: readonly string[],\n ink: string = CHART_ROLES.outline,\n): string | undefined {\n if (!parseRgbHex(fill)) return undefined;\n const measurable = surfaces.filter((s) => parseRgbHex(s));\n if (measurable.length === 0) return undefined;\n const worst = Math.min(...measurable.map((s) => contrast(fill, s)));\n return worst < EDGE_MIN_CONTRAST ? ink : undefined;\n}\n\n// ── Raw hex stops, by theme ────────────────────────────────────────────────\n// The heatmaps interpolate colours in JS (CSS vars can't be lerped), so they\n// need concrete hex per theme — i.e. the same colours DEFAULT_PRESET already\n// holds, written down a second time.\n//\n// They are duplicated rather than imported because these two objects are the whole\n// of the `@eifi1/ui-kit/chart` subpath's colour layer, and importing the preset bank\n// would pull every preset's full token set into that chunk to read two of them. What\n// keeps the copies together is `chart-palette.test.ts` — \"has not drifted from\n// DEFAULT_PRESET\" — which is not optional: they HAD drifted, five of the six light\n// heat stops, and the light `neutral` was still Tailwind's slate-100, a cool grey on\n// the warm cream page this theme has used since #328.\n//\n// 0.49: the same eight hues in both themes — anyplot does not re-tune a hue for the dark\n// ground, and neither does the kit any more; `chartEdge` outlines the two that fall\n// under 3:1 there (matte red, rose) instead of lifting them into different colours.\nconst IMPRINT_HEX = [\"#009e73\", \"#c475fd\", \"#4467a3\", \"#bd8233\", \"#ae3030\", \"#2abccd\", \"#954477\", \"#99b314\"] as const;\nexport const PALETTE_HEX = {\n light: IMPRINT_HEX,\n dark: IMPRINT_HEX,\n} as const;\n\n/** A heatmap's interpolation stops for one theme. Open string fields so any\n palette preset (not just the built-in default) can supply its own stops. */\nexport interface HeatStops {\n neutral: string;\n under: string;\n over: string;\n seqLow: string;\n seqHigh: string;\n empty: string;\n}\n\n/** Diverging (variance: under←0→over) and sequential (spending intensity)\n heatmap stops, per theme — anyplot's scales (0.49). Sequential runs anyplot green →\n blue; diverging runs blue (under) ↔ paper/ink (0, the midpoint) ↔ matte red (over).\n The hues are the same in both themes; only the midpoint flips with the ground. */\nexport const HEATMAP_HEX = {\n light: {\n neutral: \"#faf8f1\", // anyplot's light paper — the diverging scale's 0\n under: \"#4467a3\",\n over: \"#ae3030\",\n seqLow: \"#009e73\",\n seqHigh: \"#4467a3\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n dark: {\n neutral: \"#1a1a17\", // anyplot's ink — the diverging scale's 0 on the dark ground\n under: \"#4467a3\",\n over: \"#ae3030\",\n seqLow: \"#009e73\",\n seqHigh: \"#4467a3\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n} as const;\n\n/** Linear interpolate two #rrggbb hexes; `t` in [0,1]. Returns #rrggbb. */\nexport function lerpHex(a: string, b: string, t: number): string {\n const pa = parseHex(a) ?? UNREADABLE;\n const pb = parseHex(b) ?? UNREADABLE;\n const c = (i: number) => Math.round(pa[i] + (pb[i] - pa[i]) * clamp01(t));\n return `#${[c(0), c(1), c(2)].map((v) => v.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n/** The two inks a cell/treemap label can be set in. Near-black and near-white rather\n than #000/#fff: at full black-on-full-white the label stops looking like part of the\n same type system as the rest of the page. */\nconst INK_DARK = \"#1a1a1a\";\nconst INK_LIGHT = \"#f8fafc\";\n\n/** Pick a legible text colour (near-black / near-white) for a solid hex fill, by\n MEASURING both against it and keeping the one that wins.\n\n This used to flip on Rec.601 luma > 0.6, which is a brightness heuristic and not a\n contrast measure — the two disagree on six of the eighteen default chart hues, a\n third of the ramp, and on each of those the label got the worse of the two inks.\n `#44aa99` is the plainest case: luma calls it dark, so it took white, which measures\n 2.69:1 on it; near-black measures 6.18:1. keksdose measured this too and forked the\n function locally for its treemap, leaving its other two call sites on this one.\n\n A fill this cannot read gets `currentColor` — inherit, i.e. whatever the\n surrounding text already uses. That is the only answer that is right on both\n themes, because an unreadable fill is usually a translucent one (`HEATMAP_HEX.*\n .empty` is an `rgba()` string, and the variance heatmap does pass it here for any\n category with zero assigned and positive activity — a refund). Picking a constant\n instead is what produced the earlier bug: the unparsed fill fell through to the\n light ink, near-white on a near-white cell, and the figure was invisible. */\nexport function textOn(hex: string): string {\n const rgb = parseRgbHex(hex);\n if (!rgb) return \"currentColor\";\n // Ties go to the dark ink: at equal measured contrast it is the one that keeps a\n // small label looking like type rather than like a glow.\n return contrast(rgb, INK_DARK) >= contrast(rgb, INK_LIGHT) ? INK_DARK : INK_LIGHT;\n}\n\n/** Mid-grey, for a stop {@link lerpHex} has to interpolate but cannot read. It has\n * to return SOME valid colour, and a grey is the one that misleads least. */\nconst UNREADABLE: [number, number, number] = [0x80, 0x80, 0x80];\n\n/**\n * `#rgb` or `#rrggbb` to components, or **null** when it is neither.\n *\n * A tuple-shaped shim over `color.ts`'s parser, which is the same function and is\n * already the one `contrast` validates with — so {@link lerpHex} and {@link textOn}\n * cannot disagree about what counts as a colour.\n *\n * The **null** is the whole point of it. This used to `parseInt` whatever it was given\n * and hand the NaNs on, and both callers propagated them silently: {@link lerpHex}\n * produced the literal string `\"#nannannan\"`, and {@link textOn} fell through to the\n * LIGHT label colour.\n *\n * That is not hypothetical — a non-hex value ships in this very module.\n * `HEATMAP_HEX.*.empty` is an `rgba(…)` string, and the variance heatmap paints a\n * figure on an `empty` cell whenever a category has zero assigned and positive\n * activity (a refund). On the light theme the cell is near-white and the label came\n * back near-white: the number was there, and unreadable.\n */\nfunction parseHex(hex: string): [number, number, number] | null {\n const rgb = parseRgbHex(hex);\n return rgb ? [rgb.r, rgb.g, rgb.b] : null;\n}\n\nfunction clamp01(t: number): number {\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/theme/chart-palette.ts"],"sourcesContent":["// Chart colour system. Single source of truth lives in the design tokens\n// (:root / .dark in tokens.css): brand = indigo, money = teal/amber/purple,\n// chart-1..8 = anyplot's \"Imprint\" hues (https://anyplot.ai/palette, 0.49 — Paul Tol\n// \"Muted\" before, still selectable as the `paul-tol` preset).\n// Returning CSS vars here makes every Recharts series theme-aware for free — the\n// SVG fill/stroke resolves --chart-N against the current light/dark token set.\n\nimport { contrast, parseHex as parseRgbHex } from \"./color\";\n\n/** How many categorical slots `paletteFor` cycles through: anyplot's eight hues. Amber,\n * anyplot's ninth colour, is a semantic warning anchor and not a series slot. A preset\n * that carries more hues (Paul Tol's nine) still writes them as `--chart-N`, but the\n * by-index ramp wraps after eight, so the same series keeps the same colour whichever\n * preset is active. */\nexport const PALETTE_SIZE = 8;\n\n/** anyplot's colour-vision-deficiency-optimal order over the eight slots — the order\n * that keeps the FIRST n series furthest apart under protan/deutan/tritan simulation.\n * An option (`paletteFor(i, { order: \"cvd\" })`), not the default: the default order is\n * anyplot's own slot order, so series 1 is always the green. */\nexport const CVD_ORDER = [0, 1, 7, 6, 4, 5, 2, 3] as const;\n\nexport interface PaletteForOptions {\n /** `\"slot\"` (default): anyplot's slot order. `\"cvd\"`: {@link CVD_ORDER}. */\n order?: \"slot\" | \"cvd\";\n}\n\n/** CVD-safe categorical colour for series `index`, as a theme-aware CSS var.\n *\n * The first series is always slot 0 (`--chart-1`, anyplot's green), and the ramp\n * wraps after {@link PALETTE_SIZE}.\n *\n * Wraps in both directions, the way `strokeDash` does: JS `%` keeps the sign,\n * so `-1 % 8` is `-1` and the plain formula named `--chart-0`, a token that does not\n * exist — a series with no stroke at all. A fractional index is truncated, and one\n * that is not a number (NaN, ±Infinity — an `indexOf` miss divided by something)\n * takes the first colour rather than `var(--chart-NaN)`. */\nexport function paletteFor(index: number, options?: PaletteForOptions): string {\n const i = Number.isFinite(index) ? Math.trunc(index) : 0;\n const position = ((i % PALETTE_SIZE) + PALETTE_SIZE) % PALETTE_SIZE;\n const slot = options?.order === \"cvd\" ? CVD_ORDER[position] : position;\n return `var(--chart-${slot + 1})`;\n}\n\n/** Semantic money colours, theme-aware via tokens. Pair with +/- and arrow\n cues at the call site so colour is never the only signal (CVD). */\nexport const CHART_COLORS = {\n income: \"var(--money-income)\",\n expense: \"var(--money-expense)\",\n net: \"var(--money-net)\",\n neutral: \"var(--money-neutral)\",\n assigned: \"var(--brand)\",\n activity: \"var(--money-expense)\",\n /** A generic total line — not money-coloured, the chart's neutral ink. */\n total: \"var(--chart-ink)\",\n} as const;\n\n/**\n * What a chart draws that is NOT a series (anyplot's roles, 0.49). Two inks, which are\n * the only chart colours that change between light and dark — the eight hues do not:\n *\n * - `other` / `rest` / `disabled` and confidence-`band` fills → the MUTED ink. A\n * treemap's \"rest\" bucket or a pie's \"Other\" slice takes this instead of wrapping\n * round to a hue the reader will match to a real series.\n * - `total` / `baseline` / `outline` → the neutral INK.\n */\nexport const CHART_ROLES = {\n other: \"var(--chart-muted)\",\n rest: \"var(--chart-muted)\",\n disabled: \"var(--chart-muted)\",\n band: \"var(--chart-muted)\",\n total: \"var(--chart-ink)\",\n baseline: \"var(--chart-ink)\",\n outline: \"var(--chart-ink)\",\n} as const;\n\n/** `--chart-muted` per theme, as hex (tokens.css holds the same two). */\nexport const CHART_MUTED_HEX = { light: \"#6b6a63\", dark: \"#a8a79f\" } as const;\n\n/** The 3:1 WCAG 1.4.11 asks of a filled graphical object against its surface. */\nexport const EDGE_MIN_CONTRAST = 3;\n\n/**\n * The 1px edge a FILLED mark (a bar, an area, a treemap tile) needs, or `undefined`.\n *\n * anyplot's answer to hues that sit under 3:1 on the ground (cyan, lime, lavender, ochre\n * and green on cream; matte red and rose on the dark ground): outline the shape in ink,\n * so its boundary is legible while the fill keeps the hue that identifies the series.\n * Only those — a hue that clears 3:1 on every surface given stays unstroked, because an\n * outline on every bar is noise.\n *\n * `fill` must be a measurable `#rgb`/`#rrggbb`; `surfaces` are the grounds the mark can\n * sit on (the card and the page), and the WORST of them decides. Anything it cannot\n * measure gets no edge rather than a guessed one.\n */\nexport function chartEdge(\n fill: string,\n surfaces: readonly string[],\n ink: string = CHART_ROLES.outline,\n): string | undefined {\n if (!parseRgbHex(fill)) return undefined;\n const measurable = surfaces.filter((s) => parseRgbHex(s));\n if (measurable.length === 0) return undefined;\n const worst = Math.min(...measurable.map((s) => contrast(fill, s)));\n return worst < EDGE_MIN_CONTRAST ? ink : undefined;\n}\n\n// ── Raw hex stops, by theme ────────────────────────────────────────────────\n// The heatmaps interpolate colours in JS (CSS vars can't be lerped), so they\n// need concrete hex per theme — i.e. the same colours DEFAULT_PRESET already\n// holds, written down a second time.\n//\n// They are duplicated rather than imported because these two objects are the whole\n// of the `@eifi1/ui-kit/chart` subpath's colour layer, and importing the preset bank\n// would pull every preset's full token set into that chunk to read two of them. What\n// keeps the copies together is `chart-palette.test.ts` — \"has not drifted from\n// DEFAULT_PRESET\" — which is not optional: they HAD drifted, five of the six light\n// heat stops, and the light `neutral` was still Tailwind's slate-100, a cool grey on\n// the warm cream page this theme has used since #328.\n//\n// 0.49: the same eight hues in both themes — anyplot does not re-tune a hue for the dark\n// ground, and neither does the kit any more; `chartEdge` outlines the two that fall\n// under 3:1 there (matte red, rose) instead of lifting them into different colours.\nconst IMPRINT_HEX = [\"#009e73\", \"#c475fd\", \"#4467a3\", \"#bd8233\", \"#ae3030\", \"#2abccd\", \"#954477\", \"#99b314\"] as const;\nexport const PALETTE_HEX = {\n light: IMPRINT_HEX,\n dark: IMPRINT_HEX,\n} as const;\n\n/** A heatmap's interpolation stops for one theme. Open string fields so any\n palette preset (not just the built-in default) can supply its own stops. */\nexport interface HeatStops {\n neutral: string;\n under: string;\n over: string;\n seqLow: string;\n seqHigh: string;\n empty: string;\n}\n\n/** Diverging (variance: under←0→over) and sequential (spending intensity)\n heatmap stops, per theme — anyplot's scales (0.49). Sequential runs anyplot green →\n blue; diverging runs blue (under) ↔ paper/ink (0, the midpoint) ↔ matte red (over).\n The hues are the same in both themes; only the midpoint flips with the ground. */\nexport const HEATMAP_HEX = {\n light: {\n neutral: \"#faf8f1\", // anyplot's light paper — the diverging scale's 0\n under: \"#4467a3\",\n over: \"#ae3030\",\n seqLow: \"#009e73\",\n seqHigh: \"#4467a3\",\n empty: \"rgba(148,163,184,0.14)\",\n },\n dark: {\n neutral: \"#1a1a17\", // anyplot's ink — the diverging scale's 0 on the dark ground\n under: \"#4467a3\",\n over: \"#ae3030\",\n seqLow: \"#009e73\",\n seqHigh: \"#4467a3\",\n empty: \"rgba(148,163,184,0.10)\",\n },\n} as const;\n\n/** Linear interpolate two #rrggbb hexes; `t` in [0,1]. Returns #rrggbb. */\nexport function lerpHex(a: string, b: string, t: number): string {\n const pa = parseHex(a) ?? UNREADABLE;\n const pb = parseHex(b) ?? UNREADABLE;\n const c = (i: number) => Math.round(pa[i] + (pb[i] - pa[i]) * clamp01(t));\n return `#${[c(0), c(1), c(2)].map((v) => v.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n/** The two inks a cell/treemap label can be set in. Near-black and near-white rather\n than #000/#fff: at full black-on-full-white the label stops looking like part of the\n same type system as the rest of the page. */\nconst INK_DARK = \"#1a1a1a\";\nconst INK_LIGHT = \"#f8fafc\";\n\n/** Pick a legible text colour (near-black / near-white) for a solid hex fill, by\n MEASURING both against it and keeping the one that wins.\n\n This used to flip on Rec.601 luma > 0.6, which is a brightness heuristic and not a\n contrast measure — the two disagree on six of the eighteen default chart hues, a\n third of the ramp, and on each of those the label got the worse of the two inks.\n `#44aa99` is the plainest case: luma calls it dark, so it took white, which measures\n 2.69:1 on it; near-black measures 6.18:1. keksdose measured this too and forked the\n function locally for its treemap, leaving its other two call sites on this one.\n\n A fill this cannot read gets `currentColor` — inherit, i.e. whatever the\n surrounding text already uses. That is the only answer that is right on both\n themes, because an unreadable fill is usually a translucent one (`HEATMAP_HEX.*\n .empty` is an `rgba()` string, and the variance heatmap does pass it here for any\n category with zero assigned and positive activity — a refund). Picking a constant\n instead is what produced the earlier bug: the unparsed fill fell through to the\n light ink, near-white on a near-white cell, and the figure was invisible. */\nexport function textOn(hex: string): string {\n const rgb = parseRgbHex(hex);\n if (!rgb) return \"currentColor\";\n // Ties go to the dark ink: at equal measured contrast it is the one that keeps a\n // small label looking like type rather than like a glow.\n return contrast(rgb, INK_DARK) >= contrast(rgb, INK_LIGHT) ? INK_DARK : INK_LIGHT;\n}\n\n/** Mid-grey, for a stop {@link lerpHex} has to interpolate but cannot read. It has\n * to return SOME valid colour, and a grey is the one that misleads least. */\nconst UNREADABLE: [number, number, number] = [0x80, 0x80, 0x80];\n\n/**\n * `#rgb` or `#rrggbb` to components, or **null** when it is neither.\n *\n * A tuple-shaped shim over `color.ts`'s parser, which is the same function and is\n * already the one `contrast` validates with — so {@link lerpHex} and {@link textOn}\n * cannot disagree about what counts as a colour.\n *\n * The **null** is the whole point of it. This used to `parseInt` whatever it was given\n * and hand the NaNs on, and both callers propagated them silently: {@link lerpHex}\n * produced the literal string `\"#nannannan\"`, and {@link textOn} fell through to the\n * LIGHT label colour.\n *\n * That is not hypothetical — a non-hex value ships in this very module.\n * `HEATMAP_HEX.*.empty` is an `rgba(…)` string, and the variance heatmap paints a\n * figure on an `empty` cell whenever a category has zero assigned and positive\n * activity (a refund). On the light theme the cell is near-white and the label came\n * back near-white: the number was there, and unreadable.\n */\nfunction parseHex(hex: string): [number, number, number] | null {\n const rgb = parseRgbHex(hex);\n return rgb ? [rgb.r, rgb.g, rgb.b] : null;\n}\n\nfunction clamp01(t: number): number {\n return t < 0 ? 0 : t > 1 ? 1 : t;\n}\n"],"mappings":";AAOA,SAAS,UAAU,YAAY,mBAAmB;AAO3C,MAAM,eAAe;AAMrB,MAAM,YAAY,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAiBzC,SAAS,WAAW,OAAe,SAAqC;AAC7E,QAAM,IAAI,OAAO,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,IAAI;AACvD,QAAM,YAAa,IAAI,eAAgB,gBAAgB;AACvD,QAAM,OAAO,SAAS,UAAU,QAAQ,UAAU,QAAQ,IAAI;AAC9D,SAAO,eAAe,OAAO,CAAC;AAChC;AAIO,MAAM,eAAe;AAAA,EAC1B,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,KAAK;AAAA,EACL,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA;AAAA,EAEV,OAAO;AACT;AAWO,MAAM,cAAc;AAAA,EACzB,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AACX;AAGO,MAAM,kBAAkB,EAAE,OAAO,WAAW,MAAM,UAAU;AAG5D,MAAM,oBAAoB;AAe1B,SAAS,UACd,MACA,UACA,MAAc,YAAY,SACN;AACpB,MAAI,CAAC,YAAY,IAAI,EAAG,QAAO;AAC/B,QAAM,aAAa,SAAS,OAAO,CAAC,MAAM,YAAY,CAAC,CAAC;AACxD,MAAI,WAAW,WAAW,EAAG,QAAO;AACpC,QAAM,QAAQ,KAAK,IAAI,GAAG,WAAW,IAAI,CAAC,MAAM,SAAS,MAAM,CAAC,CAAC,CAAC;AAClE,SAAO,QAAQ,oBAAoB,MAAM;AAC3C;AAkBA,MAAM,cAAc,CAAC,WAAW,WAAW,WAAW,WAAW,WAAW,WAAW,WAAW,SAAS;AACpG,MAAM,cAAc;AAAA,EACzB,OAAO;AAAA,EACP,MAAM;AACR;AAiBO,MAAM,cAAc;AAAA,EACzB,OAAO;AAAA,IACL,SAAS;AAAA;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA;AAAA,IACT,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AACF;AAGO,SAAS,QAAQ,GAAW,GAAW,GAAmB;AAC/D,QAAM,KAAK,SAAS,CAAC,KAAK;AAC1B,QAAM,KAAK,SAAS,CAAC,KAAK;AAC1B,QAAM,IAAI,CAAC,MAAc,KAAK,MAAM,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,QAAQ,CAAC,CAAC;AACxE,SAAO,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AACpF;AAKA,MAAM,WAAW;AACjB,MAAM,YAAY;AAmBX,SAAS,OAAO,KAAqB;AAC1C,QAAM,MAAM,YAAY,GAAG;AAC3B,MAAI,CAAC,IAAK,QAAO;AAGjB,SAAO,SAAS,KAAK,QAAQ,KAAK,SAAS,KAAK,SAAS,IAAI,WAAW;AAC1E;AAIA,MAAM,aAAuC,CAAC,KAAM,KAAM,GAAI;AAoB9D,SAAS,SAAS,KAA8C;AAC9D,QAAM,MAAM,YAAY,GAAG;AAC3B,SAAO,MAAM,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,IAAI;AACvC;AAEA,SAAS,QAAQ,GAAmB;AAClC,SAAO,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;AACjC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
|
@@ -8,19 +8,14 @@ import { DialogFrame } from "../components/dialog-frame";
|
|
|
8
8
|
import { COMMIT_EXCEPT_BILLING } from "../components/write-lock";
|
|
9
9
|
import { useBillingLabels } from "./billing-labels";
|
|
10
10
|
import type { BillingLabels } from "./billing-labels";
|
|
11
|
-
import { dialogClose } from "./dialog-close";
|
|
12
|
-
import type { WithDialogClose } from "./dialog-close";
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
interface CancelSubscriptionConfirmFields {
|
|
12
|
+
export interface CancelSubscriptionConfirmProps {
|
|
16
13
|
open: boolean;
|
|
17
14
|
/** The admin dismissed it — Back, Escape, the backdrop. Confirm leaves it open: close it
|
|
18
15
|
* once `POST /billing/cancel` answers. 0.52: every other kit dialog's `onClose`
|
|
19
|
-
* (CONTRIBUTING §4; refactor audit 2026-10-30, API-5).
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* `false` when there is no `onClose`. */
|
|
23
|
-
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
* (CONTRIBUTING §4; refactor audit 2026-10-30, API-5); required since 0.53 removed
|
|
17
|
+
* `onOpenChange` (deprecated in 0.52). */
|
|
18
|
+
onClose: () => void;
|
|
24
19
|
/** The overview's `current_period_end`: the day the subscription ends, in a sentence —
|
|
25
20
|
* the `<UiKitProvider formatDate>` (the app's date preference) when it set one, else the
|
|
26
21
|
* kit's `formatDate`, `"medium"`, in the provider's locale and `timeZone` (0.52). */
|
|
@@ -36,11 +31,6 @@ interface CancelSubscriptionConfirmFields {
|
|
|
36
31
|
labels?: LabelOverride<BillingLabels>;
|
|
37
32
|
}
|
|
38
33
|
|
|
39
|
-
/** What {@link CancelSubscriptionConfirm} takes: `onClose`, or alone the deprecated
|
|
40
|
-
* `onOpenChange`, is required (0.52 code review: with neither it compiled, and nothing
|
|
41
|
-
* could dismiss it). */
|
|
42
|
-
export type CancelSubscriptionConfirmProps = WithDialogClose<CancelSubscriptionConfirmFields>;
|
|
43
|
-
|
|
44
34
|
/**
|
|
45
35
|
* The in-app cancel's confirm (docs/billing-harmonization.md §16.2 decision 2, §16.4,
|
|
46
36
|
* ui-kit 0.42): "Cancel subscription" for an admin the provider's portal doesn't open for
|
|
@@ -68,7 +58,6 @@ export type CancelSubscriptionConfirmProps = WithDialogClose<CancelSubscriptionC
|
|
|
68
58
|
export function CancelSubscriptionConfirm({
|
|
69
59
|
open,
|
|
70
60
|
onClose,
|
|
71
|
-
onOpenChange,
|
|
72
61
|
periodEnd,
|
|
73
62
|
onConfirm,
|
|
74
63
|
pending = false,
|
|
@@ -84,7 +73,7 @@ export function CancelSubscriptionConfirm({
|
|
|
84
73
|
open={open}
|
|
85
74
|
title={labels.cancelConfirmTitle}
|
|
86
75
|
description={labels.cancelConfirmBody(day(periodEnd))}
|
|
87
|
-
onClose={
|
|
76
|
+
onClose={onClose}
|
|
88
77
|
busy={pending}
|
|
89
78
|
data-cancel-subscription-confirm=""
|
|
90
79
|
actions={(close) => (
|