@eifi1/ui-kit 0.44.0 → 0.45.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 +10 -6
- package/dist/admin/admin-action-log-labels.js +9 -0
- package/dist/admin/admin-action-log-labels.js.map +1 -1
- package/dist/admin/admin-action-log.d.ts +18 -1
- package/dist/admin/admin-action-log.js +25 -16
- package/dist/admin/admin-action-log.js.map +1 -1
- package/dist/admin/admin-area-labels.d.ts +6 -0
- package/dist/admin/admin-area-labels.js +1 -0
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/mail-test-card-labels.d.ts +3 -0
- package/dist/admin/mail-test-card-labels.js +17 -0
- package/dist/admin/mail-test-card-labels.js.map +1 -0
- package/dist/admin/mail-test-card.d.ts +74 -0
- package/dist/admin/mail-test-card.js +80 -0
- package/dist/admin/mail-test-card.js.map +1 -0
- package/dist/components/data-table-mobile.js +17 -2
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +2 -0
- package/dist/components/data-table-shared.js +8 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.js +1 -6
- package/dist/components/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/i18n/defaults.js +9 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +40 -0
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +13 -0
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +57 -1
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +40 -0
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +40 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +40 -0
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +40 -0
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +40 -0
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +13 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/format.d.ts +2 -1
- package/dist/lib/format.js +6 -1
- package/dist/lib/format.js.map +1 -1
- package/dist/offline/pwa-update-prompt-labels.d.ts +2 -0
- package/dist/offline/pwa-update-prompt-labels.js +9 -0
- package/dist/offline/pwa-update-prompt-labels.js.map +1 -0
- package/dist/offline/pwa-update-prompt.d.ts +32 -0
- package/dist/offline/pwa-update-prompt.js +36 -0
- package/dist/offline/pwa-update-prompt.js.map +1 -0
- package/dist/offline/query-persistence.d.ts +100 -0
- package/dist/offline/query-persistence.js +179 -0
- package/dist/offline/query-persistence.js.map +1 -0
- package/dist/offline/shared-computer-labels.d.ts +2 -0
- package/dist/offline/shared-computer-labels.js +9 -0
- package/dist/offline/shared-computer-labels.js.map +1 -0
- package/dist/offline/shared-computer.d.ts +44 -0
- package/dist/offline/shared-computer.js +51 -0
- package/dist/offline/shared-computer.js.map +1 -0
- package/dist/offline/sign-out-pending-dialog-labels.d.ts +2 -0
- package/dist/offline/sign-out-pending-dialog-labels.js +14 -0
- package/dist/offline/sign-out-pending-dialog-labels.js.map +1 -0
- package/dist/offline/sign-out-pending-dialog.d.ts +58 -0
- package/dist/offline/sign-out-pending-dialog.js +66 -0
- package/dist/offline/sign-out-pending-dialog.js.map +1 -0
- package/dist/offline.d.ts +8 -0
- package/dist/offline.js +24 -0
- package/dist/offline.js.map +1 -0
- package/dist/shell/admin-menu.d.ts +24 -0
- package/dist/shell/admin-menu.js +12 -3
- package/dist/shell/admin-menu.js.map +1 -1
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/tour/tour-catalogue.d.ts +7 -1
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour.js +4 -2
- package/dist/tour/tour.js.map +1 -1
- package/dist/tour/use-tour-progress.d.ts +99 -1
- package/dist/tour/use-tour-progress.js +228 -12
- package/dist/tour/use-tour-progress.js.map +1 -1
- package/dist/tour.d.ts +2 -0
- package/dist/tour.js +2 -0
- package/dist/tour.js.map +1 -1
- package/package.json +25 -1
- package/src/admin/admin-action-log-labels.ts +9 -0
- package/src/admin/admin-action-log.tsx +41 -5
- package/src/admin/admin-area-labels.ts +7 -0
- package/src/admin/mail-test-card-labels.ts +19 -0
- package/src/admin/mail-test-card.tsx +186 -0
- package/src/components/data-table-mobile.tsx +26 -3
- package/src/components/data-table-shared.tsx +18 -1
- package/src/components/data-table.tsx +1 -15
- package/src/i18n/defaults.ts +8 -0
- package/src/i18n/german.ts +46 -0
- package/src/i18n/kit-labels.tsx +13 -0
- package/src/i18n/locales/en.ts +12 -0
- package/src/i18n/locales/es.ts +46 -0
- package/src/i18n/locales/fr.ts +46 -0
- package/src/i18n/locales/hu.ts +42 -0
- package/src/i18n/locales/it.ts +46 -0
- package/src/i18n/locales/zh.ts +40 -0
- package/src/i18n/review.ts +12 -0
- package/src/index.ts +3 -0
- package/src/lib/format.ts +13 -2
- package/src/offline/pwa-update-prompt-labels.ts +9 -0
- package/src/offline/pwa-update-prompt.tsx +72 -0
- package/src/offline/query-persistence.ts +323 -0
- package/src/offline/shared-computer-labels.ts +8 -0
- package/src/offline/shared-computer.tsx +87 -0
- package/src/offline/sign-out-pending-dialog-labels.ts +21 -0
- package/src/offline/sign-out-pending-dialog.tsx +135 -0
- package/src/offline.ts +26 -0
- package/src/shell/admin-menu.tsx +40 -3
- package/src/shell.ts +4 -0
- package/src/tour/tour-catalogue.tsx +7 -1
- package/src/tour/tour.tsx +5 -2
- package/src/tour/use-tour-progress.ts +378 -12
- package/src/tour.ts +3 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type LabelOverride } from "../i18n/kit-labels.js";
|
|
2
|
+
import { DEFAULT_SHARED_COMPUTER_LABELS } from "./shared-computer-labels.js";
|
|
3
|
+
/**
|
|
4
|
+
* "This is a shared computer" — a choice on the sign-in form that persists nothing
|
|
5
|
+
* (docs/local-first-harmonization.md §1.5 D6, D7). When it is on, the app does not mount
|
|
6
|
+
* the query persistence (`createQueryPersistence`): the cache lives in memory only.
|
|
7
|
+
*
|
|
8
|
+
* The choice must live exactly as long as the session it governs. By default it is held in
|
|
9
|
+
* `sessionStorage`, which fits a session that ends with the tab. An app whose session
|
|
10
|
+
* outlives the tab (a refresh token in `localStorage`: kastlan) passes `storage: "local"`
|
|
11
|
+
* and calls `setSharedComputer(false)` at every end of that session (logout, a failed
|
|
12
|
+
* refresh), beside removing the token. `isSharedComputer()` reads both places, so a choice
|
|
13
|
+
* made in either keeps the cache in memory. Where storage is blocked, `isSharedComputer()`
|
|
14
|
+
* is `false` and `setSharedComputer` does nothing.
|
|
15
|
+
*/
|
|
16
|
+
export interface SharedComputerLabels {
|
|
17
|
+
label: string;
|
|
18
|
+
/** The one line under it: what the choice does. */
|
|
19
|
+
hint: string;
|
|
20
|
+
}
|
|
21
|
+
export { DEFAULT_SHARED_COMPUTER_LABELS };
|
|
22
|
+
/** Where the choice is held: `"session"` (this tab, the default) or `"local"` (as long as
|
|
23
|
+
* a session kept in `localStorage`, until `setSharedComputer(false)`). */
|
|
24
|
+
export type SharedComputerStorage = "session" | "local";
|
|
25
|
+
/** Whether this session was signed in as on a shared computer, the choice held in either
|
|
26
|
+
* place. `false` when storage is unavailable. */
|
|
27
|
+
export declare function isSharedComputer(): boolean;
|
|
28
|
+
/** Records the choice in `options.storage` (default `"session"`), and only there; `false`
|
|
29
|
+
* forgets it in both places. */
|
|
30
|
+
export declare function setSharedComputer(on: boolean, options?: {
|
|
31
|
+
storage?: SharedComputerStorage;
|
|
32
|
+
}): void;
|
|
33
|
+
export interface SharedComputerChoiceProps {
|
|
34
|
+
/** Controlled. Left out, the box starts from {@link isSharedComputer}. */
|
|
35
|
+
checked?: boolean;
|
|
36
|
+
/** Told after the choice is stored. */
|
|
37
|
+
onCheckedChange?: (on: boolean) => void;
|
|
38
|
+
/** Where the choice is stored: see {@link setSharedComputer}. Default `"session"`. */
|
|
39
|
+
storage?: SharedComputerStorage;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
className?: string;
|
|
42
|
+
labels?: LabelOverride<SharedComputerLabels>;
|
|
43
|
+
}
|
|
44
|
+
export declare function SharedComputerChoice({ checked, onCheckedChange, storage, disabled, className, labels: labelsProp, }: SharedComputerChoiceProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
5
|
+
import { Checkbox } from "../components/checkbox.js";
|
|
6
|
+
import { readSessionStored, readStored, removeStored, writeSessionStored, writeStored } from "../lib/safe-storage.js";
|
|
7
|
+
import { DEFAULT_SHARED_COMPUTER_LABELS } from "./shared-computer-labels.js";
|
|
8
|
+
const KEY = "eifi1-shared-computer";
|
|
9
|
+
function isSharedComputer() {
|
|
10
|
+
return readSessionStored(KEY) === "1" || readStored(KEY) === "1";
|
|
11
|
+
}
|
|
12
|
+
function setSharedComputer(on, options = {}) {
|
|
13
|
+
const local = options.storage === "local";
|
|
14
|
+
writeSessionStored(KEY, on && !local ? "1" : null);
|
|
15
|
+
if (on && local) writeStored(KEY, "1");
|
|
16
|
+
else removeStored(KEY);
|
|
17
|
+
}
|
|
18
|
+
function SharedComputerChoice({
|
|
19
|
+
checked,
|
|
20
|
+
onCheckedChange,
|
|
21
|
+
storage,
|
|
22
|
+
disabled,
|
|
23
|
+
className,
|
|
24
|
+
labels: labelsProp
|
|
25
|
+
}) {
|
|
26
|
+
const labels = useKitLabels("sharedComputer", DEFAULT_SHARED_COMPUTER_LABELS, labelsProp);
|
|
27
|
+
const [own, setOwn] = useState(isSharedComputer);
|
|
28
|
+
const on = checked ?? own;
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
Checkbox,
|
|
31
|
+
{
|
|
32
|
+
className,
|
|
33
|
+
label: labels.label,
|
|
34
|
+
description: labels.hint,
|
|
35
|
+
checked: on,
|
|
36
|
+
disabled,
|
|
37
|
+
onCheckedChange: (next) => {
|
|
38
|
+
setSharedComputer(next, { storage });
|
|
39
|
+
setOwn(next);
|
|
40
|
+
onCheckedChange?.(next);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
DEFAULT_SHARED_COMPUTER_LABELS,
|
|
47
|
+
SharedComputerChoice,
|
|
48
|
+
isSharedComputer,
|
|
49
|
+
setSharedComputer
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=shared-computer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/offline/shared-computer.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { Checkbox } from \"../components/checkbox\";\nimport { readSessionStored, readStored, removeStored, writeSessionStored, writeStored } from \"../lib/safe-storage\";\nimport { DEFAULT_SHARED_COMPUTER_LABELS } from \"./shared-computer-labels\";\n\n/**\n * \"This is a shared computer\" — a choice on the sign-in form that persists nothing\n * (docs/local-first-harmonization.md §1.5 D6, D7). When it is on, the app does not mount\n * the query persistence (`createQueryPersistence`): the cache lives in memory only.\n *\n * The choice must live exactly as long as the session it governs. By default it is held in\n * `sessionStorage`, which fits a session that ends with the tab. An app whose session\n * outlives the tab (a refresh token in `localStorage`: kastlan) passes `storage: \"local\"`\n * and calls `setSharedComputer(false)` at every end of that session (logout, a failed\n * refresh), beside removing the token. `isSharedComputer()` reads both places, so a choice\n * made in either keeps the cache in memory. Where storage is blocked, `isSharedComputer()`\n * is `false` and `setSharedComputer` does nothing.\n */\nexport interface SharedComputerLabels {\n label: string;\n /** The one line under it: what the choice does. */\n hint: string;\n}\n\n// Defined in ./shared-computer-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_SHARED_COMPUTER_LABELS };\n\nconst KEY = \"eifi1-shared-computer\";\n\n/** Where the choice is held: `\"session\"` (this tab, the default) or `\"local\"` (as long as\n * a session kept in `localStorage`, until `setSharedComputer(false)`). */\nexport type SharedComputerStorage = \"session\" | \"local\";\n\n/** Whether this session was signed in as on a shared computer, the choice held in either\n * place. `false` when storage is unavailable. */\nexport function isSharedComputer(): boolean {\n return readSessionStored(KEY) === \"1\" || readStored(KEY) === \"1\";\n}\n\n/** Records the choice in `options.storage` (default `\"session\"`), and only there; `false`\n * forgets it in both places. */\nexport function setSharedComputer(on: boolean, options: { storage?: SharedComputerStorage } = {}): void {\n const local = options.storage === \"local\";\n writeSessionStored(KEY, on && !local ? \"1\" : null);\n if (on && local) writeStored(KEY, \"1\");\n else removeStored(KEY);\n}\n\nexport interface SharedComputerChoiceProps {\n /** Controlled. Left out, the box starts from {@link isSharedComputer}. */\n checked?: boolean;\n /** Told after the choice is stored. */\n onCheckedChange?: (on: boolean) => void;\n /** Where the choice is stored: see {@link setSharedComputer}. Default `\"session\"`. */\n storage?: SharedComputerStorage;\n disabled?: boolean;\n className?: string;\n labels?: LabelOverride<SharedComputerLabels>;\n}\n\nexport function SharedComputerChoice({\n checked,\n onCheckedChange,\n storage,\n disabled,\n className,\n labels: labelsProp,\n}: SharedComputerChoiceProps) {\n const labels = useKitLabels(\"sharedComputer\", DEFAULT_SHARED_COMPUTER_LABELS, labelsProp);\n const [own, setOwn] = useState(isSharedComputer);\n const on = checked ?? own;\n return (\n <Checkbox\n className={className}\n label={labels.label}\n description={labels.hint}\n checked={on}\n disabled={disabled}\n onCheckedChange={(next) => {\n setSharedComputer(next, { storage });\n setOwn(next);\n onCheckedChange?.(next);\n }}\n />\n );\n}\n"],"mappings":";AAyEI;AAzEJ,SAAS,gBAAgB;AACzB,SAAS,oBAAwC;AACjD,SAAS,gBAAgB;AACzB,SAAS,mBAAmB,YAAY,cAAc,oBAAoB,mBAAmB;AAC7F,SAAS,sCAAsC;AAwB/C,MAAM,MAAM;AAQL,SAAS,mBAA4B;AAC1C,SAAO,kBAAkB,GAAG,MAAM,OAAO,WAAW,GAAG,MAAM;AAC/D;AAIO,SAAS,kBAAkB,IAAa,UAA+C,CAAC,GAAS;AACtG,QAAM,QAAQ,QAAQ,YAAY;AAClC,qBAAmB,KAAK,MAAM,CAAC,QAAQ,MAAM,IAAI;AACjD,MAAI,MAAM,MAAO,aAAY,KAAK,GAAG;AAAA,MAChC,cAAa,GAAG;AACvB;AAcO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAA8B;AAC5B,QAAM,SAAS,aAAa,kBAAkB,gCAAgC,UAAU;AACxF,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,gBAAgB;AAC/C,QAAM,KAAK,WAAW;AACtB,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAO,OAAO;AAAA,MACd,aAAa,OAAO;AAAA,MACpB,SAAS;AAAA,MACT;AAAA,MACA,iBAAiB,CAAC,SAAS;AACzB,0BAAkB,MAAM,EAAE,QAAQ,CAAC;AACnC,eAAO,IAAI;AACX,0BAAkB,IAAI;AAAA,MACxB;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
const DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS = {
|
|
3
|
+
title: "Some changes have not been saved to the server yet",
|
|
4
|
+
lost: (count) => count === 1 ? "1 change is still waiting to sync. Logging out clears this device\u2019s copy, and it is the only copy." : `${count} changes are still waiting to sync. Logging out clears this device\u2019s copy, and it is the only copy.`,
|
|
5
|
+
kept: (count) => count === 1 ? "1 change is still waiting to sync. It stays on this device for you and syncs the next time you log in here." : `${count} changes are still waiting to sync. They stay on this device for you and sync the next time you log in here.`,
|
|
6
|
+
syncNow: "Sync now",
|
|
7
|
+
signOutAnyway: "Log out anyway",
|
|
8
|
+
discardWaiting: "Discard waiting changes",
|
|
9
|
+
cancel: "Cancel"
|
|
10
|
+
};
|
|
11
|
+
export {
|
|
12
|
+
DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=sign-out-pending-dialog-labels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/offline/sign-out-pending-dialog-labels.ts"],"sourcesContent":["// The English words of ./sign-out-pending-dialog, in a module with no component code: the\n// English catalogue (src/i18n/defaults.ts) imports them without the component (0.34, KS U8).\n// keksdose's reviewed words (nav.logout_queued_*, account-menu.tsx:247-317) are the canon.\n\nimport type { SignOutPendingDialogLabels } from \"./sign-out-pending-dialog\";\n\nexport const DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS: SignOutPendingDialogLabels = {\n title: \"Some changes have not been saved to the server yet\",\n lost: (count) =>\n count === 1\n ? \"1 change is still waiting to sync. Logging out clears this device’s copy, and it is the only copy.\"\n : `${count} changes are still waiting to sync. Logging out clears this device’s copy, and it is the only copy.`,\n kept: (count) =>\n count === 1\n ? \"1 change is still waiting to sync. It stays on this device for you and syncs the next time you log in here.\"\n : `${count} changes are still waiting to sync. They stay on this device for you and sync the next time you log in here.`,\n syncNow: \"Sync now\",\n signOutAnyway: \"Log out anyway\",\n discardWaiting: \"Discard waiting changes\",\n cancel: \"Cancel\",\n};\n"],"mappings":";AAMO,MAAM,yCAAqE;AAAA,EAChF,OAAO;AAAA,EACP,MAAM,CAAC,UACL,UAAU,IACN,4GACA,GAAG,KAAK;AAAA,EACd,MAAM,CAAC,UACL,UAAU,IACN,gHACA,GAAG,KAAK;AAAA,EACd,SAAS;AAAA,EACT,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,QAAQ;AACV;","names":[]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type LabelOverride } from "../i18n/kit-labels.js";
|
|
3
|
+
import { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "./sign-out-pending-dialog-labels.js";
|
|
4
|
+
/**
|
|
5
|
+
* The question before an explicit sign-out while changes still wait to sync — keksdose's
|
|
6
|
+
* confirm (account-menu.tsx:247-317) as a kit dialog (docs/local-first-harmonization.md
|
|
7
|
+
* §1.5 D6, §1.6). Show it only when `queued > 0` and only on a sign-out someone asked for:
|
|
8
|
+
* never from a session-ended hook, which cannot ask anyone.
|
|
9
|
+
*
|
|
10
|
+
* "Sync now" is the primary action: the queued writes are the person's own data. It runs
|
|
11
|
+
* `onSyncNow`, is busy while that runs, then calls `onClose` without signing out — the
|
|
12
|
+
* count has moved and the caller re-evaluates (signs out at once when nothing is left, or
|
|
13
|
+
* opens this again with the new number). "Log out anyway" stays as the escape hatch, usable
|
|
14
|
+
* while a sync runs (a hanging one would trap the person otherwise): a device that cannot
|
|
15
|
+
* reach the server must still be able to sign out. Only a running discard holds it.
|
|
16
|
+
*
|
|
17
|
+
* The words depend on what the app does with the queue at sign-out (`keepsChanges`):
|
|
18
|
+
* kastlan keeps the outbox for its owner, keksdose wipes it — and keksdose's copy must not
|
|
19
|
+
* say the changes stay.
|
|
20
|
+
*/
|
|
21
|
+
export interface SignOutPendingDialogLabels {
|
|
22
|
+
title: string;
|
|
23
|
+
/** `keepsChanges={false}`: signing out destroys the device's copy. */
|
|
24
|
+
lost: (count: number) => string;
|
|
25
|
+
/** `keepsChanges`: the queue stays on the device for its owner. */
|
|
26
|
+
kept: (count: number) => string;
|
|
27
|
+
syncNow: string;
|
|
28
|
+
signOutAnyway: string;
|
|
29
|
+
/** Only with `onDiscardWaiting` and `waitingForPlan > 0` (the billing kit's words). */
|
|
30
|
+
discardWaiting: string;
|
|
31
|
+
cancel: string;
|
|
32
|
+
}
|
|
33
|
+
export { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS };
|
|
34
|
+
export interface SignOutPendingDialogProps {
|
|
35
|
+
open: boolean;
|
|
36
|
+
/** Changes not yet on the server: pending + blocked. The tour `:pending` record is not
|
|
37
|
+
* one of them (D6). */
|
|
38
|
+
queued: number;
|
|
39
|
+
/** Of those, the ones waiting for a plan (billing's read-only refusal). */
|
|
40
|
+
waitingForPlan: number;
|
|
41
|
+
/** Runs one sync round. The dialog closes when it settles, whatever it managed. */
|
|
42
|
+
onSyncNow: () => Promise<unknown> | void;
|
|
43
|
+
/** Signs out now, queue or not. */
|
|
44
|
+
onSignOutAnyway: () => void;
|
|
45
|
+
/** Lets the plan-waiting changes go; the dialog then signs out as asked. Without it
|
|
46
|
+
* (or with `waitingForPlan` 0) the button is not shown. */
|
|
47
|
+
onDiscardWaiting?: () => Promise<unknown> | void;
|
|
48
|
+
/** `true` (kastlan): the changes stay on this device for their owner. `false`
|
|
49
|
+
* (keksdose): they are lost. Picks the words. */
|
|
50
|
+
keepsChanges: boolean;
|
|
51
|
+
/** Room under the sentence — the billing kit's `waitingChanges` line. Shown only while
|
|
52
|
+
* `waitingForPlan > 0`, so a 0-count line never shows. */
|
|
53
|
+
extra?: ReactNode;
|
|
54
|
+
/** Cancel, Escape, the X — and after "Sync now". */
|
|
55
|
+
onClose: () => void;
|
|
56
|
+
labels?: LabelOverride<SignOutPendingDialogLabels>;
|
|
57
|
+
}
|
|
58
|
+
export declare function SignOutPendingDialog({ open, queued, waitingForPlan, onSyncNow, onSignOutAnyway, onDiscardWaiting, keepsChanges, extra, onClose, labels: labelsProp, }: SignOutPendingDialogProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
5
|
+
import { Button } from "../components/button.js";
|
|
6
|
+
import { DialogFrame } from "../components/dialog-frame.js";
|
|
7
|
+
import { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "./sign-out-pending-dialog-labels.js";
|
|
8
|
+
function SignOutPendingDialog({
|
|
9
|
+
open,
|
|
10
|
+
queued,
|
|
11
|
+
waitingForPlan,
|
|
12
|
+
onSyncNow,
|
|
13
|
+
onSignOutAnyway,
|
|
14
|
+
onDiscardWaiting,
|
|
15
|
+
keepsChanges,
|
|
16
|
+
extra,
|
|
17
|
+
onClose,
|
|
18
|
+
labels: labelsProp
|
|
19
|
+
}) {
|
|
20
|
+
const labels = useKitLabels("signOutPending", DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS, labelsProp);
|
|
21
|
+
const [syncing, setSyncing] = useState(false);
|
|
22
|
+
const [discarding, setDiscarding] = useState(false);
|
|
23
|
+
const syncNow = async () => {
|
|
24
|
+
setSyncing(true);
|
|
25
|
+
try {
|
|
26
|
+
await onSyncNow();
|
|
27
|
+
} catch {
|
|
28
|
+
} finally {
|
|
29
|
+
setSyncing(false);
|
|
30
|
+
}
|
|
31
|
+
onClose();
|
|
32
|
+
};
|
|
33
|
+
const discard = async () => {
|
|
34
|
+
if (!onDiscardWaiting) return;
|
|
35
|
+
setDiscarding(true);
|
|
36
|
+
try {
|
|
37
|
+
await onDiscardWaiting();
|
|
38
|
+
} finally {
|
|
39
|
+
setDiscarding(false);
|
|
40
|
+
}
|
|
41
|
+
onSignOutAnyway();
|
|
42
|
+
};
|
|
43
|
+
return /* @__PURE__ */ jsx(
|
|
44
|
+
DialogFrame,
|
|
45
|
+
{
|
|
46
|
+
open,
|
|
47
|
+
role: "alertdialog",
|
|
48
|
+
onClose,
|
|
49
|
+
title: labels.title,
|
|
50
|
+
description: keepsChanges ? labels.kept(queued) : labels.lost(queued),
|
|
51
|
+
closeButton: true,
|
|
52
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
53
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: labels.cancel }),
|
|
54
|
+
onDiscardWaiting && waitingForPlan > 0 && /* @__PURE__ */ jsx(Button, { variant: "secondary", pending: discarding, disabled: syncing, onClick: () => void discard(), children: labels.discardWaiting }),
|
|
55
|
+
/* @__PURE__ */ jsx(Button, { pending: syncing, disabled: discarding, onClick: () => void syncNow(), children: labels.syncNow }),
|
|
56
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", disabled: discarding, onClick: onSignOutAnyway, children: labels.signOutAnyway })
|
|
57
|
+
] }),
|
|
58
|
+
children: waitingForPlan > 0 && (typeof extra === "string" ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted", children: extra }) : extra)
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS,
|
|
64
|
+
SignOutPendingDialog
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=sign-out-pending-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/offline/sign-out-pending-dialog.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useKitLabels, type LabelOverride } from \"../i18n/kit-labels\";\nimport { Button } from \"../components/button\";\nimport { DialogFrame } from \"../components/dialog-frame\";\nimport { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from \"./sign-out-pending-dialog-labels\";\n\n/**\n * The question before an explicit sign-out while changes still wait to sync — keksdose's\n * confirm (account-menu.tsx:247-317) as a kit dialog (docs/local-first-harmonization.md\n * §1.5 D6, §1.6). Show it only when `queued > 0` and only on a sign-out someone asked for:\n * never from a session-ended hook, which cannot ask anyone.\n *\n * \"Sync now\" is the primary action: the queued writes are the person's own data. It runs\n * `onSyncNow`, is busy while that runs, then calls `onClose` without signing out — the\n * count has moved and the caller re-evaluates (signs out at once when nothing is left, or\n * opens this again with the new number). \"Log out anyway\" stays as the escape hatch, usable\n * while a sync runs (a hanging one would trap the person otherwise): a device that cannot\n * reach the server must still be able to sign out. Only a running discard holds it.\n *\n * The words depend on what the app does with the queue at sign-out (`keepsChanges`):\n * kastlan keeps the outbox for its owner, keksdose wipes it — and keksdose's copy must not\n * say the changes stay.\n */\nexport interface SignOutPendingDialogLabels {\n title: string;\n /** `keepsChanges={false}`: signing out destroys the device's copy. */\n lost: (count: number) => string;\n /** `keepsChanges`: the queue stays on the device for its owner. */\n kept: (count: number) => string;\n syncNow: string;\n signOutAnyway: string;\n /** Only with `onDiscardWaiting` and `waitingForPlan > 0` (the billing kit's words). */\n discardWaiting: string;\n cancel: string;\n}\n\n// Defined in ./sign-out-pending-dialog-labels, which holds no component code (0.34, KS U8).\nexport { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS };\n\nexport interface SignOutPendingDialogProps {\n open: boolean;\n /** Changes not yet on the server: pending + blocked. The tour `:pending` record is not\n * one of them (D6). */\n queued: number;\n /** Of those, the ones waiting for a plan (billing's read-only refusal). */\n waitingForPlan: number;\n /** Runs one sync round. The dialog closes when it settles, whatever it managed. */\n onSyncNow: () => Promise<unknown> | void;\n /** Signs out now, queue or not. */\n onSignOutAnyway: () => void;\n /** Lets the plan-waiting changes go; the dialog then signs out as asked. Without it\n * (or with `waitingForPlan` 0) the button is not shown. */\n onDiscardWaiting?: () => Promise<unknown> | void;\n /** `true` (kastlan): the changes stay on this device for their owner. `false`\n * (keksdose): they are lost. Picks the words. */\n keepsChanges: boolean;\n /** Room under the sentence — the billing kit's `waitingChanges` line. Shown only while\n * `waitingForPlan > 0`, so a 0-count line never shows. */\n extra?: ReactNode;\n /** Cancel, Escape, the X — and after \"Sync now\". */\n onClose: () => void;\n labels?: LabelOverride<SignOutPendingDialogLabels>;\n}\n\nexport function SignOutPendingDialog({\n open,\n queued,\n waitingForPlan,\n onSyncNow,\n onSignOutAnyway,\n onDiscardWaiting,\n keepsChanges,\n extra,\n onClose,\n labels: labelsProp,\n}: SignOutPendingDialogProps) {\n const labels = useKitLabels(\"signOutPending\", DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS, labelsProp);\n const [syncing, setSyncing] = useState(false);\n const [discarding, setDiscarding] = useState(false);\n\n const syncNow = async () => {\n setSyncing(true);\n try {\n await onSyncNow();\n } catch {\n /* whatever the sync managed, the decision is the person's again */\n } finally {\n setSyncing(false);\n }\n onClose();\n };\n\n const discard = async () => {\n if (!onDiscardWaiting) return;\n setDiscarding(true);\n try {\n await onDiscardWaiting();\n } finally {\n setDiscarding(false);\n }\n onSignOutAnyway();\n };\n\n return (\n <DialogFrame\n open={open}\n role=\"alertdialog\"\n onClose={onClose}\n title={labels.title}\n description={keepsChanges ? labels.kept(queued) : labels.lost(queued)}\n closeButton\n actions={\n <>\n <Button variant=\"secondary\" onClick={onClose}>\n {labels.cancel}\n </Button>\n {onDiscardWaiting && waitingForPlan > 0 && (\n <Button variant=\"secondary\" pending={discarding} disabled={syncing} onClick={() => void discard()}>\n {labels.discardWaiting}\n </Button>\n )}\n <Button pending={syncing} disabled={discarding} onClick={() => void syncNow()}>\n {labels.syncNow}\n </Button>\n <Button variant=\"secondary\" disabled={discarding} onClick={onSignOutAnyway}>\n {labels.signOutAnyway}\n </Button>\n </>\n }\n >\n {waitingForPlan > 0 && (typeof extra === \"string\" ? <p className=\"text-sm text-muted\">{extra}</p> : extra)}\n </DialogFrame>\n );\n}\n"],"mappings":";AAiHQ,mBACE,KADF;AAjHR,SAAS,gBAAgB;AAEzB,SAAS,oBAAwC;AACjD,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,8CAA8C;AA4DhD,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAA8B;AAC5B,QAAM,SAAS,aAAa,kBAAkB,wCAAwC,UAAU;AAChG,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAElD,QAAM,UAAU,YAAY;AAC1B,eAAW,IAAI;AACf,QAAI;AACF,YAAM,UAAU;AAAA,IAClB,QAAQ;AAAA,IAER,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AACA,YAAQ;AAAA,EACV;AAEA,QAAM,UAAU,YAAY;AAC1B,QAAI,CAAC,iBAAkB;AACvB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,iBAAiB;AAAA,IACzB,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AACA,oBAAgB;AAAA,EAClB;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL;AAAA,MACA,OAAO,OAAO;AAAA,MACd,aAAa,eAAe,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,MAAM;AAAA,MACpE,aAAW;AAAA,MACX,SACE,iCACE;AAAA,4BAAC,UAAO,SAAQ,aAAY,SAAS,SAClC,iBAAO,QACV;AAAA,QACC,oBAAoB,iBAAiB,KACpC,oBAAC,UAAO,SAAQ,aAAY,SAAS,YAAY,UAAU,SAAS,SAAS,MAAM,KAAK,QAAQ,GAC7F,iBAAO,gBACV;AAAA,QAEF,oBAAC,UAAO,SAAS,SAAS,UAAU,YAAY,SAAS,MAAM,KAAK,QAAQ,GACzE,iBAAO,SACV;AAAA,QACA,oBAAC,UAAO,SAAQ,aAAY,UAAU,YAAY,SAAS,iBACxD,iBAAO,eACV;AAAA,SACF;AAAA,MAGD,2BAAiB,MAAM,OAAO,UAAU,WAAW,oBAAC,OAAE,WAAU,sBAAsB,iBAAM,IAAO;AAAA;AAAA,EACtG;AAEJ;","names":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createQueryPersistence, DEFAULT_SCRUB_FIELDS, isCacheSafe } from "./offline/query-persistence.js";
|
|
2
|
+
export type { QueryPersistence, QueryPersistenceOptions, QueryPersistenceStorage, ScrubField, } from "./offline/query-persistence.js";
|
|
3
|
+
export { SignOutPendingDialog, DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "./offline/sign-out-pending-dialog.js";
|
|
4
|
+
export type { SignOutPendingDialogProps, SignOutPendingDialogLabels } from "./offline/sign-out-pending-dialog.js";
|
|
5
|
+
export { PwaUpdatePrompt, DEFAULT_PWA_UPDATE_PROMPT_LABELS } from "./offline/pwa-update-prompt.js";
|
|
6
|
+
export type { PwaUpdatePromptProps, PwaUpdatePromptLabels } from "./offline/pwa-update-prompt.js";
|
|
7
|
+
export { SharedComputerChoice, DEFAULT_SHARED_COMPUTER_LABELS, isSharedComputer, setSharedComputer, } from "./offline/shared-computer.js";
|
|
8
|
+
export type { SharedComputerChoiceProps, SharedComputerLabels, SharedComputerStorage } from "./offline/shared-computer.js";
|
package/dist/offline.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createQueryPersistence, DEFAULT_SCRUB_FIELDS, isCacheSafe } from "./offline/query-persistence.js";
|
|
3
|
+
import { SignOutPendingDialog, DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "./offline/sign-out-pending-dialog.js";
|
|
4
|
+
import { PwaUpdatePrompt, DEFAULT_PWA_UPDATE_PROMPT_LABELS } from "./offline/pwa-update-prompt.js";
|
|
5
|
+
import {
|
|
6
|
+
SharedComputerChoice,
|
|
7
|
+
DEFAULT_SHARED_COMPUTER_LABELS,
|
|
8
|
+
isSharedComputer,
|
|
9
|
+
setSharedComputer
|
|
10
|
+
} from "./offline/shared-computer.js";
|
|
11
|
+
export {
|
|
12
|
+
DEFAULT_PWA_UPDATE_PROMPT_LABELS,
|
|
13
|
+
DEFAULT_SCRUB_FIELDS,
|
|
14
|
+
DEFAULT_SHARED_COMPUTER_LABELS,
|
|
15
|
+
DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS,
|
|
16
|
+
PwaUpdatePrompt,
|
|
17
|
+
SharedComputerChoice,
|
|
18
|
+
SignOutPendingDialog,
|
|
19
|
+
createQueryPersistence,
|
|
20
|
+
isCacheSafe,
|
|
21
|
+
isSharedComputer,
|
|
22
|
+
setSharedComputer
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=offline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/offline.ts"],"sourcesContent":["// `@eifi1/ui-kit/offline` — local-first phase 1 (docs/local-first-harmonization.md §1.6,\n// 0.45): the persisted query cache with its scrub list, the sign-out question while changes\n// wait, the service-worker update prompt, and the shared-computer choice.\n//\n// The only entry point that requires `@tanstack/react-query`,\n// `@tanstack/react-query-persist-client`, `@tanstack/query-async-storage-persister` and\n// `idb-keyval` — optional peers for that reason. NOT re-exported from the main barrel, which\n// would make them everyone's install; the packaging contract test holds that line.\nexport { createQueryPersistence, DEFAULT_SCRUB_FIELDS, isCacheSafe } from \"./offline/query-persistence\";\nexport type {\n QueryPersistence,\n QueryPersistenceOptions,\n QueryPersistenceStorage,\n ScrubField,\n} from \"./offline/query-persistence\";\nexport { SignOutPendingDialog, DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from \"./offline/sign-out-pending-dialog\";\nexport type { SignOutPendingDialogProps, SignOutPendingDialogLabels } from \"./offline/sign-out-pending-dialog\";\nexport { PwaUpdatePrompt, DEFAULT_PWA_UPDATE_PROMPT_LABELS } from \"./offline/pwa-update-prompt\";\nexport type { PwaUpdatePromptProps, PwaUpdatePromptLabels } from \"./offline/pwa-update-prompt\";\nexport {\n SharedComputerChoice,\n DEFAULT_SHARED_COMPUTER_LABELS,\n isSharedComputer,\n setSharedComputer,\n} from \"./offline/shared-computer\";\nexport type { SharedComputerChoiceProps, SharedComputerLabels, SharedComputerStorage } from \"./offline/shared-computer\";\n"],"mappings":";AAQA,SAAS,wBAAwB,sBAAsB,mBAAmB;AAO1E,SAAS,sBAAsB,8CAA8C;AAE7E,SAAS,iBAAiB,wCAAwC;AAElE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import type { LabelOverride } from "../i18n/kit-labels.js";
|
|
2
2
|
import type { AdminAreaLabels } from "../admin/admin-area-labels.js";
|
|
3
3
|
import type { TopBarMenuEntry } from "./topbar-action-menu.js";
|
|
4
|
+
export interface AdminWaitingBadgeProps {
|
|
5
|
+
/** The waiting count: only work that waits on this admin. `0`, `null` or none draws
|
|
6
|
+
* nothing; past 99 it reads "99+". */
|
|
7
|
+
count: number | null | undefined;
|
|
8
|
+
/** Prop > `<UiKitProvider labels={{ adminArea }}>` > English. */
|
|
9
|
+
labels?: LabelOverride<AdminAreaLabels>;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The waiting pill of {@link AdminMenuRow} on its own (0.45, docs/admin-harmonization.md
|
|
14
|
+
* §9.3.4): the `solid` warning count, "99+" past 99, nothing for 0. Fit for `IconButton`'s
|
|
15
|
+
* `badge` — kastlan's top-bar platform button. There the badge is DECORATIVE (`aria-hidden`
|
|
16
|
+
* by IconButton), so the button's name carries the count: `adminArea.waitingLabel`.
|
|
17
|
+
*
|
|
18
|
+
* const area = useAdminAreaLabels();
|
|
19
|
+
* <IconButton
|
|
20
|
+
* label={waiting > 0 ? area.waitingLabel("Platform", waiting) : "Platform"}
|
|
21
|
+
* badge={<AdminWaitingBadge count={waiting} />}
|
|
22
|
+
* …
|
|
23
|
+
* />
|
|
24
|
+
*
|
|
25
|
+
* Read on its own (outside an IconButton), the pill still says ", 3 waiting" to a reader.
|
|
26
|
+
*/
|
|
27
|
+
export declare function AdminWaitingBadge({ count, labels: labelsProp, className }: AdminWaitingBadgeProps): import("react").JSX.Element;
|
|
4
28
|
export interface AdminMenuEntryOptions {
|
|
5
29
|
/** The admin area. Default `/admin`. */
|
|
6
30
|
href?: string;
|
package/dist/shell/admin-menu.js
CHANGED
|
@@ -9,10 +9,14 @@ function badgeText(badge) {
|
|
|
9
9
|
if (typeof badge !== "number" || !(badge > 0)) return null;
|
|
10
10
|
return badge > 99 ? "99+" : String(Math.floor(badge));
|
|
11
11
|
}
|
|
12
|
-
function WaitingBadge({
|
|
12
|
+
function WaitingBadge({
|
|
13
|
+
badge,
|
|
14
|
+
labels,
|
|
15
|
+
className
|
|
16
|
+
}) {
|
|
13
17
|
const text = badgeText(badge);
|
|
14
18
|
if (text === null) return null;
|
|
15
|
-
return /* @__PURE__ */ jsxs(Chip, { size: "sm", variant: "solid", tone: "warning", "data-slot": "admin-badge", children: [
|
|
19
|
+
return /* @__PURE__ */ jsxs(Chip, { size: "sm", variant: "solid", tone: "warning", "data-slot": "admin-badge", className, children: [
|
|
16
20
|
/* @__PURE__ */ jsx("span", { "aria-hidden": true, children: text }),
|
|
17
21
|
/* @__PURE__ */ jsxs("span", { className: "sr-only-fixed", children: [
|
|
18
22
|
", ",
|
|
@@ -20,6 +24,10 @@ function WaitingBadge({ badge, labels }) {
|
|
|
20
24
|
] })
|
|
21
25
|
] });
|
|
22
26
|
}
|
|
27
|
+
function AdminWaitingBadge({ count, labels: labelsProp, className }) {
|
|
28
|
+
const labels = useAdminAreaLabels(labelsProp);
|
|
29
|
+
return /* @__PURE__ */ jsx(WaitingBadge, { badge: count, labels, className });
|
|
30
|
+
}
|
|
23
31
|
function adminMenuEntry({
|
|
24
32
|
href = "/admin",
|
|
25
33
|
badge,
|
|
@@ -50,7 +58,7 @@ function AdminMenuRow({ href = "/admin", badge, onNavigate, current, labels: lab
|
|
|
50
58
|
renderLink: ({ href: to, ...p }) => /* @__PURE__ */ jsx(Link, { to, ...p }),
|
|
51
59
|
onClick: onNavigate ? () => onNavigate() : void 0,
|
|
52
60
|
leading: /* @__PURE__ */ jsx(ShieldCheck, { className: "size-4", "aria-hidden": true }),
|
|
53
|
-
trailing: badgeText(badge) === null ? void 0 : /* @__PURE__ */ jsx(
|
|
61
|
+
trailing: badgeText(badge) === null ? void 0 : /* @__PURE__ */ jsx(AdminWaitingBadge, { count: badge, labels }),
|
|
54
62
|
current,
|
|
55
63
|
children: labels.title
|
|
56
64
|
}
|
|
@@ -58,6 +66,7 @@ function AdminMenuRow({ href = "/admin", badge, onNavigate, current, labels: lab
|
|
|
58
66
|
}
|
|
59
67
|
export {
|
|
60
68
|
AdminMenuRow,
|
|
69
|
+
AdminWaitingBadge,
|
|
61
70
|
adminMenuEntry,
|
|
62
71
|
useAdminMenuEntry
|
|
63
72
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shell/admin-menu.tsx"],"sourcesContent":["import { Link } from \"react-router\";\nimport { ShieldCheck } from \"lucide-react\";\nimport { Chip } from \"../components/chip\";\nimport { MenuItem } from \"../components/menu-item\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_ADMIN_AREA_LABELS, useAdminAreaLabels } from \"../admin/admin-area-labels\";\nimport type { AdminAreaLabels } from \"../admin/admin-area-labels\";\nimport type { TopBarMenuEntry } from \"./topbar-action-menu\";\n\n/**\n * The account menu's \"Administration\" row (0.42.0, docs/admin-harmonization.md §2.1, §6.6,\n * §7): the way into the admin area everyone finds, in every app, for an admin only. Two\n * forms of one row — {@link adminMenuEntry} for `TopBarActionMenu`'s `entries`, and\n * {@link AdminMenuRow} for a menu built from `HoverMenu` children (Kurvenschmiede's).\n */\n\n/** The badge's text: nothing for none or 0, \"99+\" past 99. */\nfunction badgeText(badge: number | null | undefined): string | null {\n if (typeof badge !== \"number\" || !(badge > 0)) return null;\n return badge > 99 ? \"99+\" : String(Math.floor(badge));\n}\n\n/**\n * The waiting count at the row's end: the `solid` count pill of {@link Chip}, in the\n * pending count's tone, with the number for the eye and `adminArea.waiting` for a reader\n * (\"Administration, 3 waiting\"). `null` for no count.\n */\nfunction WaitingBadge({
|
|
1
|
+
{"version":3,"sources":["../../src/shell/admin-menu.tsx"],"sourcesContent":["import { Link } from \"react-router\";\nimport { ShieldCheck } from \"lucide-react\";\nimport { Chip } from \"../components/chip\";\nimport { MenuItem } from \"../components/menu-item\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport { DEFAULT_ADMIN_AREA_LABELS, useAdminAreaLabels } from \"../admin/admin-area-labels\";\nimport type { AdminAreaLabels } from \"../admin/admin-area-labels\";\nimport type { TopBarMenuEntry } from \"./topbar-action-menu\";\n\n/**\n * The account menu's \"Administration\" row (0.42.0, docs/admin-harmonization.md §2.1, §6.6,\n * §7): the way into the admin area everyone finds, in every app, for an admin only. Two\n * forms of one row — {@link adminMenuEntry} for `TopBarActionMenu`'s `entries`, and\n * {@link AdminMenuRow} for a menu built from `HoverMenu` children (Kurvenschmiede's).\n */\n\n/** The badge's text: nothing for none or 0, \"99+\" past 99. */\nfunction badgeText(badge: number | null | undefined): string | null {\n if (typeof badge !== \"number\" || !(badge > 0)) return null;\n return badge > 99 ? \"99+\" : String(Math.floor(badge));\n}\n\n/**\n * The waiting count at the row's end: the `solid` count pill of {@link Chip}, in the\n * pending count's tone, with the number for the eye and `adminArea.waiting` for a reader\n * (\"Administration, 3 waiting\"). `null` for no count.\n */\nfunction WaitingBadge({\n badge,\n labels,\n className,\n}: {\n badge: number | null | undefined;\n labels: AdminAreaLabels;\n className?: string;\n}) {\n const text = badgeText(badge);\n if (text === null) return null;\n return (\n <Chip size=\"sm\" variant=\"solid\" tone=\"warning\" data-slot=\"admin-badge\" className={className}>\n <span aria-hidden>{text}</span>\n {/* The comma parts it from the row's label in the name: \"Administration, 3 waiting\". */}\n <span className=\"sr-only-fixed\">, {labels.waiting(badge as number)}</span>\n </Chip>\n );\n}\n\nexport interface AdminWaitingBadgeProps {\n /** The waiting count: only work that waits on this admin. `0`, `null` or none draws\n * nothing; past 99 it reads \"99+\". */\n count: number | null | undefined;\n /** Prop > `<UiKitProvider labels={{ adminArea }}>` > English. */\n labels?: LabelOverride<AdminAreaLabels>;\n className?: string;\n}\n\n/**\n * The waiting pill of {@link AdminMenuRow} on its own (0.45, docs/admin-harmonization.md\n * §9.3.4): the `solid` warning count, \"99+\" past 99, nothing for 0. Fit for `IconButton`'s\n * `badge` — kastlan's top-bar platform button. There the badge is DECORATIVE (`aria-hidden`\n * by IconButton), so the button's name carries the count: `adminArea.waitingLabel`.\n *\n * const area = useAdminAreaLabels();\n * <IconButton\n * label={waiting > 0 ? area.waitingLabel(\"Platform\", waiting) : \"Platform\"}\n * badge={<AdminWaitingBadge count={waiting} />}\n * …\n * />\n *\n * Read on its own (outside an IconButton), the pill still says \", 3 waiting\" to a reader.\n */\nexport function AdminWaitingBadge({ count, labels: labelsProp, className }: AdminWaitingBadgeProps) {\n const labels = useAdminAreaLabels(labelsProp);\n return <WaitingBadge badge={count} labels={labels} className={className} />;\n}\n\nexport interface AdminMenuEntryOptions {\n /** The admin area. Default `/admin`. */\n href?: string;\n /**\n * The waiting count (§2.4 decision 15, §6.3): only work that waits on this admin —\n * `adminWaitingCount(attention, […])`. `0`, `null` or none shows no badge.\n */\n badge?: number | null;\n /**\n * The resolved `adminArea` words — pass `useAdminAreaLabels()`, or the row is ENGLISH\n * in every language: a plain function reads no provider (kastlan, 0.42 adoption). Builds\n * no hook, so the entry can be made in the list of entries. {@link useAdminMenuEntry}\n * (0.43) is the same entry with the provider's words read for you.\n */\n labels?: AdminAreaLabels;\n /** The entry's `key`. Default `\"administration\"`. */\n key?: string;\n /** The page already open: `aria-current` and a check mark. */\n current?: boolean;\n}\n\n/**\n * The \"Administration\" {@link TopBarMenuEntry}: a link row with the `ShieldCheck` icon,\n * the kit's word and the waiting count as its badge. Put it in the entries for an admin\n * only — the menu closes on the way, as any link row:\n *\n * const labels = useAdminAreaLabels();\n * entries={[…, ...(isAdmin ? [adminMenuEntry({ badge: waiting, labels })] : []), …]}\n *\n * A function, not a hook: it reads NO provider, so pass `labels` (`useAdminAreaLabels()`)\n * or the row says \"Administration\" in every language — or call {@link useAdminMenuEntry}\n * at the component's top.\n */\nexport function adminMenuEntry({\n href = \"/admin\",\n badge,\n labels = DEFAULT_ADMIN_AREA_LABELS,\n key = \"administration\",\n current,\n}: AdminMenuEntryOptions = {}): TopBarMenuEntry {\n return {\n kind: \"link\",\n key,\n to: href,\n icon: <ShieldCheck className=\"size-4\" aria-hidden />,\n label: labels.title,\n trailing: badgeText(badge) === null ? undefined : <WaitingBadge badge={badge} labels={labels} />,\n current,\n };\n}\n\nexport interface UseAdminMenuEntryOptions extends Omit<AdminMenuEntryOptions, \"labels\"> {\n /** Over the provider's `adminArea` words: prop > `<UiKitProvider labels={{ adminArea }}>`\n * > English. */\n labels?: LabelOverride<AdminAreaLabels>;\n}\n\n/**\n * {@link adminMenuEntry} with the provider's words (0.43, kastlan): the hook reads\n * `useAdminAreaLabels()` itself, so the row is in the app's language without a `labels`\n * to remember. Call it at the component's top, then put it in the entries:\n *\n * const admin = useAdminMenuEntry({ badge: waiting });\n * entries={[…, ...(isAdmin ? [admin] : []), …]}\n */\nexport function useAdminMenuEntry({ labels: labelsProp, ...options }: UseAdminMenuEntryOptions = {}): TopBarMenuEntry {\n const labels = useAdminAreaLabels(labelsProp);\n return adminMenuEntry({ ...options, labels });\n}\n\nexport interface AdminMenuRowProps {\n /** The admin area. Default `/admin`. */\n href?: string;\n /** As {@link AdminMenuEntryOptions.badge}. */\n badge?: number | null;\n /** Runs on the way — `HoverMenu`'s `close`. */\n onNavigate?: () => void;\n current?: boolean;\n /** Prop > `<UiKitProvider labels={{ adminArea }}>` > English. */\n labels?: LabelOverride<AdminAreaLabels>;\n}\n\n/**\n * The same row as {@link adminMenuEntry} for a menu drawn from `HoverMenu` children: the\n * `<li>` holding the router-link {@link MenuItem} `TopBarActionMenu` draws for a link\n * entry, so the two menus look alike.\n *\n * {(close) => <ul>… {isAdmin && <AdminMenuRow badge={waiting} onNavigate={close} />} …</ul>}\n */\nexport function AdminMenuRow({ href = \"/admin\", badge, onNavigate, current, labels: labelsProp }: AdminMenuRowProps) {\n // The provider's words, as every kit part (0.43: through the namespace's own hook).\n const labels = useAdminAreaLabels(labelsProp);\n return (\n <li>\n <MenuItem\n href={href}\n renderLink={({ href: to, ...p }) => <Link to={to} {...p} />}\n onClick={onNavigate ? () => onNavigate() : undefined}\n leading={<ShieldCheck className=\"size-4\" aria-hidden />}\n trailing={badgeText(badge) === null ? undefined : <AdminWaitingBadge count={badge} labels={labels} />}\n current={current}\n >\n {labels.title}\n </MenuItem>\n </li>\n );\n}\n"],"mappings":";AAwCM,cAEA,YAFA;AAxCN,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAEzB,SAAS,2BAA2B,0BAA0B;AAY9D,SAAS,UAAU,OAAiD;AAClE,MAAI,OAAO,UAAU,YAAY,EAAE,QAAQ,GAAI,QAAO;AACtD,SAAO,QAAQ,KAAK,QAAQ,OAAO,KAAK,MAAM,KAAK,CAAC;AACtD;AAOA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,OAAO,UAAU,KAAK;AAC5B,MAAI,SAAS,KAAM,QAAO;AAC1B,SACE,qBAAC,QAAK,MAAK,MAAK,SAAQ,SAAQ,MAAK,WAAU,aAAU,eAAc,WACrE;AAAA,wBAAC,UAAK,eAAW,MAAE,gBAAK;AAAA,IAExB,qBAAC,UAAK,WAAU,iBAAgB;AAAA;AAAA,MAAG,OAAO,QAAQ,KAAe;AAAA,OAAE;AAAA,KACrE;AAEJ;AA0BO,SAAS,kBAAkB,EAAE,OAAO,QAAQ,YAAY,UAAU,GAA2B;AAClG,QAAM,SAAS,mBAAmB,UAAU;AAC5C,SAAO,oBAAC,gBAAa,OAAO,OAAO,QAAgB,WAAsB;AAC3E;AAmCO,SAAS,eAAe;AAAA,EAC7B,OAAO;AAAA,EACP;AAAA,EACA,SAAS;AAAA,EACT,MAAM;AAAA,EACN;AACF,IAA2B,CAAC,GAAoB;AAC9C,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,IAAI;AAAA,IACJ,MAAM,oBAAC,eAAY,WAAU,UAAS,eAAW,MAAC;AAAA,IAClD,OAAO,OAAO;AAAA,IACd,UAAU,UAAU,KAAK,MAAM,OAAO,SAAY,oBAAC,gBAAa,OAAc,QAAgB;AAAA,IAC9F;AAAA,EACF;AACF;AAgBO,SAAS,kBAAkB,EAAE,QAAQ,YAAY,GAAG,QAAQ,IAA8B,CAAC,GAAoB;AACpH,QAAM,SAAS,mBAAmB,UAAU;AAC5C,SAAO,eAAe,EAAE,GAAG,SAAS,OAAO,CAAC;AAC9C;AAqBO,SAAS,aAAa,EAAE,OAAO,UAAU,OAAO,YAAY,SAAS,QAAQ,WAAW,GAAsB;AAEnH,QAAM,SAAS,mBAAmB,UAAU;AAC5C,SACE,oBAAC,QACC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,YAAY,CAAC,EAAE,MAAM,IAAI,GAAG,EAAE,MAAM,oBAAC,QAAK,IAAS,GAAG,GAAG;AAAA,MACzD,SAAS,aAAa,MAAM,WAAW,IAAI;AAAA,MAC3C,SAAS,oBAAC,eAAY,WAAU,UAAS,eAAW,MAAC;AAAA,MACrD,UAAU,UAAU,KAAK,MAAM,OAAO,SAAY,oBAAC,qBAAkB,OAAO,OAAO,QAAgB;AAAA,MACnG;AAAA,MAEC,iBAAO;AAAA;AAAA,EACV,GACF;AAEJ;","names":[]}
|
package/dist/shell.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export { adminMenuEntry, AdminMenuRow } from "./shell/admin-menu.js";
|
|
|
12
12
|
export type { AdminMenuEntryOptions, AdminMenuRowProps } from "./shell/admin-menu.js";
|
|
13
13
|
export { useAdminMenuEntry } from "./shell/admin-menu.js";
|
|
14
14
|
export type { UseAdminMenuEntryOptions } from "./shell/admin-menu.js";
|
|
15
|
+
export { AdminWaitingBadge } from "./shell/admin-menu.js";
|
|
16
|
+
export type { AdminWaitingBadgeProps } from "./shell/admin-menu.js";
|
|
15
17
|
export { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from "./settings/settings-layout.js";
|
|
16
18
|
export type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from "./settings/settings-layout.js";
|
|
17
19
|
export { useSettingsRoute, resolveSettingsLocation, settingsFromListState, SETTINGS_FROM_LIST_STATE, } from "./settings/use-settings-route.js";
|
package/dist/shell.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./shell/auth-layout.js";
|
|
|
10
10
|
import { FillHeight, FILL_HEIGHT_CLASS } from "./shell/fill-height.js";
|
|
11
11
|
import { adminMenuEntry, AdminMenuRow } from "./shell/admin-menu.js";
|
|
12
12
|
import { useAdminMenuEntry } from "./shell/admin-menu.js";
|
|
13
|
+
import { AdminWaitingBadge } from "./shell/admin-menu.js";
|
|
13
14
|
import { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from "./settings/settings-layout.js";
|
|
14
15
|
import {
|
|
15
16
|
useSettingsRoute,
|
|
@@ -47,6 +48,7 @@ import { DEFAULT_TOP_BAR_PARTS_LABELS, useTopBarPartsLabels } from "./shell/top-
|
|
|
47
48
|
export {
|
|
48
49
|
AccountMenuLanguage,
|
|
49
50
|
AdminMenuRow,
|
|
51
|
+
AdminWaitingBadge,
|
|
50
52
|
DEFAULT_LAST_VISITED_EXCLUDES,
|
|
51
53
|
DEFAULT_TOP_BAR_PARTS_LABELS,
|
|
52
54
|
DEV_ORIGINAL_ROLE_KEY,
|
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\";\nexport * from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/role-switcher\";\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.31.0: the settings and admin pages' shell (docs/settings-harmonization.md §3, §5).\nexport { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from \"./settings/settings-layout\";\nexport type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from \"./settings/settings-layout\";\nexport {\n useSettingsRoute,\n resolveSettingsLocation,\n settingsFromListState,\n SETTINGS_FROM_LIST_STATE,\n} 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, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } 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 switchers, and their words.\nexport { TopBarActions, TOP_BAR_ACTION_ORDER } 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\";\nexport { TourMenu } from \"./shell/tour-menu\";\nexport type { TourMenuProps, TourMenuTour } from \"./shell/tour-menu\";\nexport { SyncStatusIndicator, syncState } from \"./shell/sync-status-indicator\";\nexport type { SyncStatus, SyncState, SyncStatusScope, SyncStatusIndicatorProps } from \"./shell/sync-status-indicator\";\nexport { DevSwitchers, DEV_ORIGINAL_ROLE_KEY } from \"./shell/dev-switchers\";\nexport type { DevSwitchersProps } from \"./shell/dev-switchers\";\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;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,SAAS,YAAY,yBAAyB;AAI9C,SAAS,gBAAgB,oBAAoB;AAG7C,SAAS,yBAAyB;AAGlC,SAAS,gBAAgB,iCAAiC;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,SAAS,kBAAkB,qBAAqB,yBAAyB;AACzE,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,eAAe,4BAA4B;AAEpD,SAAS,2BAA2B;AAEpC,SAAS,gBAAgB;AAEzB,SAAS,qBAAqB,iBAAiB;AAE/C,SAAS,cAAc,6BAA6B;AAEpD,SAAS,8BAA8B,4BAA4B;","names":[]}
|
|
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\";\nexport * from \"./shell/app-shell\";\nexport * from \"./shell/option-switcher-menu\";\nexport * from \"./shell/role-switcher\";\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, SETTINGS_SEARCH_THRESHOLD } from \"./settings/settings-layout\";\nexport type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from \"./settings/settings-layout\";\nexport {\n useSettingsRoute,\n resolveSettingsLocation,\n settingsFromListState,\n SETTINGS_FROM_LIST_STATE,\n} 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, SETTINGS_FOCUS_RING, SETTINGS_FOCUS_MS } 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 switchers, and their words.\nexport { TopBarActions, TOP_BAR_ACTION_ORDER } 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\";\nexport { TourMenu } from \"./shell/tour-menu\";\nexport type { TourMenuProps, TourMenuTour } from \"./shell/tour-menu\";\nexport { SyncStatusIndicator, syncState } from \"./shell/sync-status-indicator\";\nexport type { SyncStatus, SyncState, SyncStatusScope, SyncStatusIndicatorProps } from \"./shell/sync-status-indicator\";\nexport { DevSwitchers, DEV_ORIGINAL_ROLE_KEY } from \"./shell/dev-switchers\";\nexport type { DevSwitchersProps } from \"./shell/dev-switchers\";\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;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAEd,SAAS,YAAY,yBAAyB;AAI9C,SAAS,gBAAgB,oBAAoB;AAG7C,SAAS,yBAAyB;AAIlC,SAAS,yBAAyB;AAGlC,SAAS,gBAAgB,iCAAiC;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP,SAAS,kBAAkB,qBAAqB,yBAAyB;AACzE,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,eAAe,4BAA4B;AAEpD,SAAS,2BAA2B;AAEpC,SAAS,gBAAgB;AAEzB,SAAS,qBAAqB,iBAAiB;AAE/C,SAAS,cAAc,6BAA6B;AAEpD,SAAS,8BAA8B,4BAA4B;","names":[]}
|
|
@@ -14,7 +14,12 @@ export interface TourCatalogueCardProps {
|
|
|
14
14
|
onLaunch: (id: string) => void;
|
|
15
15
|
/** "Mark as not done". Without it, the link is not drawn. */
|
|
16
16
|
onReset?: (id: string) => void;
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Formats the completion and update days. Default: the kit's medium date in the provider's locale.
|
|
19
|
+
* It gets the completion's ISO instant and the update's bare `"YYYY-MM-DD"` (`updated.date`): read
|
|
20
|
+
* the bare day on its calendar (the kit's `formatDate`, `parseIsoDate`), never `new Date(day)`,
|
|
21
|
+
* which is UTC midnight and the day before west of UTC (round 0.45, N9).
|
|
22
|
+
*/
|
|
18
23
|
formatDate?: (iso: string) => string;
|
|
19
24
|
labels?: LabelOverride<TourCatalogueLabels>;
|
|
20
25
|
className?: string;
|
|
@@ -44,6 +49,7 @@ export interface TourCatalogueProps extends TourCatalogueFilter {
|
|
|
44
49
|
onReset?: (id: string) => void;
|
|
45
50
|
/** Above the filter bar: the app's page header with its points tile (§6.1). */
|
|
46
51
|
header?: ReactNode;
|
|
52
|
+
/** Each card's `formatDate`: see {@link TourCatalogueCardProps.formatDate} (a bare day for the update). */
|
|
47
53
|
formatDate?: (iso: string) => string;
|
|
48
54
|
/** The search's locale (case folding). Default: the provider's. */
|
|
49
55
|
locale?: string;
|