@eifi1/ui-kit 0.44.0 → 0.46.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 +29 -14
- 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 +15 -0
- package/dist/admin/admin-area-labels.js +6 -1
- package/dist/admin/admin-area-labels.js.map +1 -1
- package/dist/admin/admin-overview.d.ts +16 -1
- package/dist/admin/admin-overview.js +12 -1
- package/dist/admin/admin-overview.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 +75 -0
- package/dist/admin/mail-test-card.js +82 -0
- package/dist/admin/mail-test-card.js.map +1 -0
- package/dist/components/appearance-labels.d.ts +10 -0
- package/dist/components/appearance-labels.js +6 -1
- package/dist/components/appearance-labels.js.map +1 -1
- package/dist/components/data-table-column-settings.js +2 -2
- package/dist/components/data-table-column-settings.js.map +1 -1
- package/dist/components/data-table-head.js +2 -2
- package/dist/components/data-table-head.js.map +1 -1
- package/dist/components/data-table-mobile.js +17 -2
- package/dist/components/data-table-mobile.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +18 -2
- package/dist/components/data-table-row-actions.js +86 -2
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +2 -0
- package/dist/components/data-table-shared.js +17 -2
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +43 -2
- package/dist/components/data-table.js +10 -10
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-format-setting.d.ts +29 -0
- package/dist/components/date-format-setting.js +36 -0
- package/dist/components/date-format-setting.js.map +1 -0
- package/dist/components/swipeable-row.d.ts +8 -0
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tabs.d.ts +3 -2
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/translation-review-editor.js +1 -0
- package/dist/components/translation-review-editor.js.map +1 -1
- package/dist/components/translation-review-labels.d.ts +5 -0
- package/dist/components/translation-review-labels.js +2 -0
- package/dist/components/translation-review-labels.js.map +1 -1
- package/dist/components/translation-review.d.ts +18 -8
- package/dist/components/translation-review.js +64 -7
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/write-lock-labels.js +2 -1
- package/dist/components/write-lock-labels.js.map +1 -1
- package/dist/components/write-lock.d.ts +33 -3
- package/dist/components/write-lock.js +9 -0
- package/dist/components/write-lock.js.map +1 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +4 -2
- package/dist/feedback/feedback-row-detail.js +17 -26
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback/feedback-swipe.d.ts +18 -0
- package/dist/feedback/feedback-swipe.js +10 -1
- package/dist/feedback/feedback-swipe.js.map +1 -1
- package/dist/i18n/defaults.js +9 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.js +81 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +25 -1
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +97 -7
- package/dist/i18n/locales/en.js.map +3 -3
- package/dist/i18n/locales/es.js +81 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +81 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +81 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +81 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +81 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +16 -2
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/date-format.d.ts +81 -0
- package/dist/lib/date-format.js +56 -0
- package/dist/lib/date-format.js.map +1 -0
- package/dist/lib/format.d.ts +5 -1
- package/dist/lib/format.js +11 -5
- package/dist/lib/format.js.map +1 -1
- package/dist/notifications/device-zone-report.d.ts +27 -0
- package/dist/notifications/device-zone-report.js +24 -0
- package/dist/notifications/device-zone-report.js.map +1 -0
- package/dist/notifications/feedback-notification-types-labels.d.ts +7 -1
- package/dist/notifications/feedback-notification-types-labels.js +4 -1
- package/dist/notifications/feedback-notification-types-labels.js.map +1 -1
- package/dist/notifications/feedback-notification-types.d.ts +11 -5
- package/dist/notifications/feedback-notification-types.js +2 -1
- package/dist/notifications/feedback-notification-types.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +33 -0
- package/dist/notifications/notification-bell.js +74 -0
- package/dist/notifications/notification-bell.js.map +1 -0
- package/dist/notifications/notification-inbox.d.ts +77 -0
- package/dist/notifications/notification-inbox.js +206 -0
- package/dist/notifications/notification-inbox.js.map +1 -0
- package/dist/notifications/notification-types-setting.d.ts +6 -4
- package/dist/notifications/notification-types-setting.js +103 -45
- package/dist/notifications/notification-types-setting.js.map +1 -1
- package/dist/notifications/notification-types.d.ts +19 -7
- package/dist/notifications/notifications-labels.d.ts +41 -7
- package/dist/notifications/notifications-labels.js +18 -1
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/push-devices-setting.d.ts +20 -3
- package/dist/notifications/push-devices-setting.js +93 -33
- package/dist/notifications/push-devices-setting.js.map +1 -1
- package/dist/notifications/push-worker.d.ts +108 -0
- package/dist/notifications/push-worker.js +62 -0
- package/dist/notifications/push-worker.js.map +1 -0
- package/dist/notifications/quiet-hours-setting.d.ts +29 -17
- package/dist/notifications/quiet-hours-setting.js +9 -41
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/notifications/time-zone-select.d.ts +5 -2
- package/dist/notifications/time-zone-select.js.map +1 -1
- package/dist/notifications/use-push-device.d.ts +4 -17
- package/dist/notifications/use-push-device.js +7 -15
- package/dist/notifications/use-push-device.js.map +1 -1
- package/dist/offline/local-first.d.ts +33 -0
- package/dist/offline/local-first.js +39 -0
- package/dist/offline/local-first.js.map +1 -0
- 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 +125 -0
- package/dist/offline/query-persistence.js +183 -0
- package/dist/offline/query-persistence.js.map +1 -0
- package/dist/offline/service-worker-updates.d.ts +46 -0
- package/dist/offline/service-worker-updates.js +39 -0
- package/dist/offline/service-worker-updates.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 +11 -0
- package/dist/offline.js +33 -0
- package/dist/offline.js.map +1 -0
- package/dist/push-worker.d.ts +2 -0
- package/dist/push-worker.js +14 -0
- package/dist/push-worker.js.map +1 -0
- package/dist/shell/account-menu-text-size.d.ts +33 -0
- package/dist/shell/account-menu-text-size.js +51 -0
- package/dist/shell/account-menu-text-size.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/top-bar-actions.d.ts +6 -2
- package/dist/shell/top-bar-actions.js +1 -0
- package/dist/shell/top-bar-actions.js.map +1 -1
- package/dist/shell.d.ts +4 -0
- package/dist/shell.js +4 -0
- package/dist/shell.js.map +1 -1
- package/dist/sync/db.d.ts +76 -0
- package/dist/sync/db.js +24 -0
- package/dist/sync/db.js.map +1 -0
- package/dist/sync/engine.d.ts +124 -0
- package/dist/sync/engine.js +474 -0
- package/dist/sync/engine.js.map +1 -0
- package/dist/sync/hooks.d.ts +30 -0
- package/dist/sync/hooks.js +67 -0
- package/dist/sync/hooks.js.map +1 -0
- package/dist/sync/testing.d.ts +28 -0
- package/dist/sync/testing.js +41 -0
- package/dist/sync/testing.js.map +1 -0
- package/dist/sync/wire.d.ts +50 -0
- package/dist/sync/wire.js +2 -0
- package/dist/sync/wire.js.map +1 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.js +22 -0
- package/dist/sync.js.map +1 -0
- package/dist/tour/tour-catalogue.d.ts +7 -1
- package/dist/tour/tour-catalogue.js.map +1 -1
- package/dist/tour/tour-labels.js +1 -0
- package/dist/tour/tour-labels.js.map +1 -1
- package/dist/tour/tour.d.ts +30 -0
- package/dist/tour/tour.js +54 -8
- 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 +43 -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 +20 -0
- package/src/admin/admin-overview.tsx +37 -2
- package/src/admin/mail-test-card-labels.ts +19 -0
- package/src/admin/mail-test-card.tsx +190 -0
- package/src/components/appearance-labels.ts +16 -0
- package/src/components/data-table-column-settings.tsx +2 -2
- package/src/components/data-table-head.tsx +2 -2
- package/src/components/data-table-mobile.tsx +26 -3
- package/src/components/data-table-row-actions.tsx +118 -2
- package/src/components/data-table-shared.tsx +39 -3
- package/src/components/data-table.tsx +52 -21
- package/src/components/date-format-setting.tsx +64 -0
- package/src/components/swipeable-row.tsx +8 -0
- package/src/components/tabs.tsx +4 -3
- package/src/components/translation-review-editor.tsx +2 -0
- package/src/components/translation-review-labels.ts +7 -0
- package/src/components/translation-review.tsx +102 -14
- package/src/components/write-lock-labels.ts +1 -0
- package/src/components/write-lock.tsx +36 -3
- package/src/data-table.ts +1 -0
- package/src/feedback/feedback-row-detail.tsx +18 -23
- package/src/feedback/feedback-swipe.tsx +28 -1
- package/src/i18n/defaults.ts +8 -0
- package/src/i18n/german.ts +90 -4
- package/src/i18n/kit-labels.tsx +31 -1
- package/src/i18n/locales/en.ts +13 -0
- package/src/i18n/locales/es.ts +90 -4
- package/src/i18n/locales/fr.ts +90 -4
- package/src/i18n/locales/hu.ts +86 -4
- package/src/i18n/locales/it.ts +90 -4
- package/src/i18n/locales/zh.ts +84 -2
- package/src/i18n/review.ts +15 -1
- package/src/index.ts +45 -0
- package/src/lib/date-format.ts +151 -0
- package/src/lib/format.ts +23 -6
- package/src/notifications/device-zone-report.ts +54 -0
- package/src/notifications/feedback-notification-types-labels.ts +10 -1
- package/src/notifications/feedback-notification-types.ts +12 -5
- package/src/notifications/notification-bell.tsx +116 -0
- package/src/notifications/notification-inbox.tsx +304 -0
- package/src/notifications/notification-types-setting.tsx +151 -55
- package/src/notifications/notification-types.ts +20 -8
- package/src/notifications/notifications-labels.ts +69 -11
- package/src/notifications/push-devices-setting.tsx +123 -34
- package/src/notifications/push-worker.ts +160 -0
- package/src/notifications/quiet-hours-setting.tsx +41 -58
- package/src/notifications/time-zone-select.tsx +5 -2
- package/src/notifications/use-push-device.ts +20 -33
- package/src/offline/local-first.ts +76 -0
- 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 +356 -0
- package/src/offline/service-worker-updates.ts +81 -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 +32 -0
- package/src/push-worker.ts +25 -0
- package/src/shell/account-menu-text-size.tsx +77 -0
- package/src/shell/admin-menu.tsx +40 -3
- package/src/shell/top-bar-actions.tsx +6 -1
- package/src/shell.ts +7 -0
- package/src/sync/db.ts +103 -0
- package/src/sync/engine.ts +699 -0
- package/src/sync/hooks.ts +93 -0
- package/src/sync/testing.ts +73 -0
- package/src/sync/wire.ts +61 -0
- package/src/sync.ts +29 -0
- package/src/tour/tour-catalogue.tsx +7 -1
- package/src/tour/tour-labels.ts +1 -0
- package/src/tour/tour.tsx +105 -9
- package/src/tour/use-tour-progress.ts +378 -12
- package/src/tour.ts +3 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* vite-plugin-pwa's `registerSW` from `virtual:pwa-register` (`registerType: "prompt"`) —
|
|
5
|
+
* the app passes it in, since a virtual module is the app's build's, not the kit's. It
|
|
6
|
+
* answers the `updateSW(reloadPage)` function.
|
|
7
|
+
*/
|
|
8
|
+
export type RegisterServiceWorker = (options: {
|
|
9
|
+
onNeedRefresh?: () => void;
|
|
10
|
+
onOfflineReady?: () => void;
|
|
11
|
+
}) => (reloadPage?: boolean) => Promise<void>;
|
|
12
|
+
|
|
13
|
+
/** What {@link createServiceWorkerUpdates} answers: one store per app, a module constant. */
|
|
14
|
+
export interface ServiceWorkerUpdates {
|
|
15
|
+
/** Registers the service worker — once, however often it is called. Call it at start,
|
|
16
|
+
* before the router renders, so the sign-in pages get the offline shell too. */
|
|
17
|
+
start: () => void;
|
|
18
|
+
/** Whether a new version waits. */
|
|
19
|
+
needRefresh: () => boolean;
|
|
20
|
+
/** For `useSyncExternalStore`. */
|
|
21
|
+
subscribe: (listener: () => void) => () => void;
|
|
22
|
+
/** Activates the waiting version and reloads (`updateSW(true)`). Before `start`, nothing. */
|
|
23
|
+
reload: () => void;
|
|
24
|
+
/** The person closed the prompt: the version keeps waiting, the prompt stays down. */
|
|
25
|
+
dismiss: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 0.46 (kastlan K3, docs/local-first-harmonization.md §1.5 D8): the service worker
|
|
30
|
+
* registered ONCE AT START, and its "a new version waits" state as a small store the
|
|
31
|
+
* prompt reads wherever it lives. vite-plugin-pwa's `useRegisterSW` registers when the
|
|
32
|
+
* component that calls it mounts: an app whose {@link PwaUpdatePrompt} sits in its
|
|
33
|
+
* signed-in shell (it reads the tour state, the route) would register only after sign-in,
|
|
34
|
+
* and the sign-in pages would lose the offline shell on a first visit.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* // shared/pwa/sw-updates.ts
|
|
38
|
+
* import { registerSW } from "virtual:pwa-register";
|
|
39
|
+
* export const swUpdates = createServiceWorkerUpdates(registerSW);
|
|
40
|
+
* // main.tsx, before render: swUpdates.start();
|
|
41
|
+
* // the shell: <PwaUpdatePrompt {...useServiceWorkerUpdate(swUpdates)} hold={tourRunning} />
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function createServiceWorkerUpdates(register: RegisterServiceWorker): ServiceWorkerUpdates {
|
|
45
|
+
let waiting = false;
|
|
46
|
+
let update: ((reloadPage?: boolean) => Promise<void>) | null = null;
|
|
47
|
+
const listeners = new Set<() => void>();
|
|
48
|
+
const emit = () => listeners.forEach((listener) => listener());
|
|
49
|
+
const set = (value: boolean) => {
|
|
50
|
+
if (waiting === value) return;
|
|
51
|
+
waiting = value;
|
|
52
|
+
emit();
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
start: () => {
|
|
56
|
+
if (update) return;
|
|
57
|
+
update = register({ onNeedRefresh: () => set(true) });
|
|
58
|
+
},
|
|
59
|
+
needRefresh: () => waiting,
|
|
60
|
+
subscribe: (listener) => {
|
|
61
|
+
listeners.add(listener);
|
|
62
|
+
return () => {
|
|
63
|
+
listeners.delete(listener);
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
reload: () => {
|
|
67
|
+
void update?.(true);
|
|
68
|
+
},
|
|
69
|
+
dismiss: () => set(false),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** {@link PwaUpdatePrompt}'s props from the store: `needRefresh`, `onReload`, `onDismiss`. */
|
|
74
|
+
export function useServiceWorkerUpdate(updates: ServiceWorkerUpdates): {
|
|
75
|
+
needRefresh: boolean;
|
|
76
|
+
onReload: () => void;
|
|
77
|
+
onDismiss: () => void;
|
|
78
|
+
} {
|
|
79
|
+
const needRefresh = useSyncExternalStore(updates.subscribe, updates.needRefresh, () => false);
|
|
80
|
+
return { needRefresh, onReload: updates.reload, onDismiss: updates.dismiss };
|
|
81
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The English words of ./shared-computer, in a module with no component code (0.34, KS U8).
|
|
2
|
+
|
|
3
|
+
import type { SharedComputerLabels } from "./shared-computer";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_SHARED_COMPUTER_LABELS: SharedComputerLabels = {
|
|
6
|
+
label: "This is a shared computer",
|
|
7
|
+
hint: "No copy of your data stays on this device. Pages then open only with a connection.",
|
|
8
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
|
|
3
|
+
import { Checkbox } from "../components/checkbox";
|
|
4
|
+
import { readSessionStored, readStored, removeStored, writeSessionStored, writeStored } from "../lib/safe-storage";
|
|
5
|
+
import { DEFAULT_SHARED_COMPUTER_LABELS } from "./shared-computer-labels";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* "This is a shared computer" — a choice on the sign-in form that persists nothing
|
|
9
|
+
* (docs/local-first-harmonization.md §1.5 D6, D7). When it is on, the app does not mount
|
|
10
|
+
* the query persistence (`createQueryPersistence`): the cache lives in memory only.
|
|
11
|
+
*
|
|
12
|
+
* The choice must live exactly as long as the session it governs. By default it is held in
|
|
13
|
+
* `sessionStorage`, which fits a session that ends with the tab. An app whose session
|
|
14
|
+
* outlives the tab (a refresh token in `localStorage`: kastlan) passes `storage: "local"`
|
|
15
|
+
* and calls `setSharedComputer(false)` at every end of that session (logout, a failed
|
|
16
|
+
* refresh), beside removing the token. `isSharedComputer()` reads both places, so a choice
|
|
17
|
+
* made in either keeps the cache in memory. Where storage is blocked, `isSharedComputer()`
|
|
18
|
+
* is `false` and `setSharedComputer` does nothing.
|
|
19
|
+
*/
|
|
20
|
+
export interface SharedComputerLabels {
|
|
21
|
+
label: string;
|
|
22
|
+
/** The one line under it: what the choice does. */
|
|
23
|
+
hint: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Defined in ./shared-computer-labels, which holds no component code (0.34, KS U8).
|
|
27
|
+
export { DEFAULT_SHARED_COMPUTER_LABELS };
|
|
28
|
+
|
|
29
|
+
const KEY = "eifi1-shared-computer";
|
|
30
|
+
|
|
31
|
+
/** Where the choice is held: `"session"` (this tab, the default) or `"local"` (as long as
|
|
32
|
+
* a session kept in `localStorage`, until `setSharedComputer(false)`). */
|
|
33
|
+
export type SharedComputerStorage = "session" | "local";
|
|
34
|
+
|
|
35
|
+
/** Whether this session was signed in as on a shared computer, the choice held in either
|
|
36
|
+
* place. `false` when storage is unavailable. */
|
|
37
|
+
export function isSharedComputer(): boolean {
|
|
38
|
+
return readSessionStored(KEY) === "1" || readStored(KEY) === "1";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Records the choice in `options.storage` (default `"session"`), and only there; `false`
|
|
42
|
+
* forgets it in both places. */
|
|
43
|
+
export function setSharedComputer(on: boolean, options: { storage?: SharedComputerStorage } = {}): void {
|
|
44
|
+
const local = options.storage === "local";
|
|
45
|
+
writeSessionStored(KEY, on && !local ? "1" : null);
|
|
46
|
+
if (on && local) writeStored(KEY, "1");
|
|
47
|
+
else removeStored(KEY);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SharedComputerChoiceProps {
|
|
51
|
+
/** Controlled. Left out, the box starts from {@link isSharedComputer}. */
|
|
52
|
+
checked?: boolean;
|
|
53
|
+
/** Told after the choice is stored. */
|
|
54
|
+
onCheckedChange?: (on: boolean) => void;
|
|
55
|
+
/** Where the choice is stored: see {@link setSharedComputer}. Default `"session"`. */
|
|
56
|
+
storage?: SharedComputerStorage;
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
className?: string;
|
|
59
|
+
labels?: LabelOverride<SharedComputerLabels>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function SharedComputerChoice({
|
|
63
|
+
checked,
|
|
64
|
+
onCheckedChange,
|
|
65
|
+
storage,
|
|
66
|
+
disabled,
|
|
67
|
+
className,
|
|
68
|
+
labels: labelsProp,
|
|
69
|
+
}: SharedComputerChoiceProps) {
|
|
70
|
+
const labels = useKitLabels("sharedComputer", DEFAULT_SHARED_COMPUTER_LABELS, labelsProp);
|
|
71
|
+
const [own, setOwn] = useState(isSharedComputer);
|
|
72
|
+
const on = checked ?? own;
|
|
73
|
+
return (
|
|
74
|
+
<Checkbox
|
|
75
|
+
className={className}
|
|
76
|
+
label={labels.label}
|
|
77
|
+
description={labels.hint}
|
|
78
|
+
checked={on}
|
|
79
|
+
disabled={disabled}
|
|
80
|
+
onCheckedChange={(next) => {
|
|
81
|
+
setSharedComputer(next, { storage });
|
|
82
|
+
setOwn(next);
|
|
83
|
+
onCheckedChange?.(next);
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The English words of ./sign-out-pending-dialog, in a module with no component code: the
|
|
2
|
+
// English catalogue (src/i18n/defaults.ts) imports them without the component (0.34, KS U8).
|
|
3
|
+
// keksdose's reviewed words (nav.logout_queued_*, account-menu.tsx:247-317) are the canon.
|
|
4
|
+
|
|
5
|
+
import type { SignOutPendingDialogLabels } from "./sign-out-pending-dialog";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS: SignOutPendingDialogLabels = {
|
|
8
|
+
title: "Some changes have not been saved to the server yet",
|
|
9
|
+
lost: (count) =>
|
|
10
|
+
count === 1
|
|
11
|
+
? "1 change is still waiting to sync. Logging out clears this device’s copy, and it is the only copy."
|
|
12
|
+
: `${count} changes are still waiting to sync. Logging out clears this device’s copy, and it is the only copy.`,
|
|
13
|
+
kept: (count) =>
|
|
14
|
+
count === 1
|
|
15
|
+
? "1 change is still waiting to sync. It stays on this device for you and syncs the next time you log in here."
|
|
16
|
+
: `${count} changes are still waiting to sync. They stay on this device for you and sync the next time you log in here.`,
|
|
17
|
+
syncNow: "Sync now",
|
|
18
|
+
signOutAnyway: "Log out anyway",
|
|
19
|
+
discardWaiting: "Discard waiting changes",
|
|
20
|
+
cancel: "Cancel",
|
|
21
|
+
};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { useKitLabels, type LabelOverride } from "../i18n/kit-labels";
|
|
4
|
+
import { Button } from "../components/button";
|
|
5
|
+
import { DialogFrame } from "../components/dialog-frame";
|
|
6
|
+
import { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "./sign-out-pending-dialog-labels";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The question before an explicit sign-out while changes still wait to sync — keksdose's
|
|
10
|
+
* confirm (account-menu.tsx:247-317) as a kit dialog (docs/local-first-harmonization.md
|
|
11
|
+
* §1.5 D6, §1.6). Show it only when `queued > 0` and only on a sign-out someone asked for:
|
|
12
|
+
* never from a session-ended hook, which cannot ask anyone.
|
|
13
|
+
*
|
|
14
|
+
* "Sync now" is the primary action: the queued writes are the person's own data. It runs
|
|
15
|
+
* `onSyncNow`, is busy while that runs, then calls `onClose` without signing out — the
|
|
16
|
+
* count has moved and the caller re-evaluates (signs out at once when nothing is left, or
|
|
17
|
+
* opens this again with the new number). "Log out anyway" stays as the escape hatch, usable
|
|
18
|
+
* while a sync runs (a hanging one would trap the person otherwise): a device that cannot
|
|
19
|
+
* reach the server must still be able to sign out. Only a running discard holds it.
|
|
20
|
+
*
|
|
21
|
+
* The words depend on what the app does with the queue at sign-out (`keepsChanges`):
|
|
22
|
+
* kastlan keeps the outbox for its owner, keksdose wipes it — and keksdose's copy must not
|
|
23
|
+
* say the changes stay.
|
|
24
|
+
*/
|
|
25
|
+
export interface SignOutPendingDialogLabels {
|
|
26
|
+
title: string;
|
|
27
|
+
/** `keepsChanges={false}`: signing out destroys the device's copy. */
|
|
28
|
+
lost: (count: number) => string;
|
|
29
|
+
/** `keepsChanges`: the queue stays on the device for its owner. */
|
|
30
|
+
kept: (count: number) => string;
|
|
31
|
+
syncNow: string;
|
|
32
|
+
signOutAnyway: string;
|
|
33
|
+
/** Only with `onDiscardWaiting` and `waitingForPlan > 0` (the billing kit's words). */
|
|
34
|
+
discardWaiting: string;
|
|
35
|
+
cancel: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Defined in ./sign-out-pending-dialog-labels, which holds no component code (0.34, KS U8).
|
|
39
|
+
export { DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS };
|
|
40
|
+
|
|
41
|
+
export interface SignOutPendingDialogProps {
|
|
42
|
+
open: boolean;
|
|
43
|
+
/** Changes not yet on the server: pending + blocked. The tour `:pending` record is not
|
|
44
|
+
* one of them (D6). */
|
|
45
|
+
queued: number;
|
|
46
|
+
/** Of those, the ones waiting for a plan (billing's read-only refusal). */
|
|
47
|
+
waitingForPlan: number;
|
|
48
|
+
/** Runs one sync round. The dialog closes when it settles, whatever it managed. */
|
|
49
|
+
onSyncNow: () => Promise<unknown> | void;
|
|
50
|
+
/** Signs out now, queue or not. */
|
|
51
|
+
onSignOutAnyway: () => void;
|
|
52
|
+
/** Lets the plan-waiting changes go; the dialog then signs out as asked. Without it
|
|
53
|
+
* (or with `waitingForPlan` 0) the button is not shown. */
|
|
54
|
+
onDiscardWaiting?: () => Promise<unknown> | void;
|
|
55
|
+
/** `true` (kastlan): the changes stay on this device for their owner. `false`
|
|
56
|
+
* (keksdose): they are lost. Picks the words. */
|
|
57
|
+
keepsChanges: boolean;
|
|
58
|
+
/** Room under the sentence — the billing kit's `waitingChanges` line. Shown only while
|
|
59
|
+
* `waitingForPlan > 0`, so a 0-count line never shows. */
|
|
60
|
+
extra?: ReactNode;
|
|
61
|
+
/** Cancel, Escape, the X — and after "Sync now". */
|
|
62
|
+
onClose: () => void;
|
|
63
|
+
labels?: LabelOverride<SignOutPendingDialogLabels>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function SignOutPendingDialog({
|
|
67
|
+
open,
|
|
68
|
+
queued,
|
|
69
|
+
waitingForPlan,
|
|
70
|
+
onSyncNow,
|
|
71
|
+
onSignOutAnyway,
|
|
72
|
+
onDiscardWaiting,
|
|
73
|
+
keepsChanges,
|
|
74
|
+
extra,
|
|
75
|
+
onClose,
|
|
76
|
+
labels: labelsProp,
|
|
77
|
+
}: SignOutPendingDialogProps) {
|
|
78
|
+
const labels = useKitLabels("signOutPending", DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS, labelsProp);
|
|
79
|
+
const [syncing, setSyncing] = useState(false);
|
|
80
|
+
const [discarding, setDiscarding] = useState(false);
|
|
81
|
+
|
|
82
|
+
const syncNow = async () => {
|
|
83
|
+
setSyncing(true);
|
|
84
|
+
try {
|
|
85
|
+
await onSyncNow();
|
|
86
|
+
} catch {
|
|
87
|
+
/* whatever the sync managed, the decision is the person's again */
|
|
88
|
+
} finally {
|
|
89
|
+
setSyncing(false);
|
|
90
|
+
}
|
|
91
|
+
onClose();
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const discard = async () => {
|
|
95
|
+
if (!onDiscardWaiting) return;
|
|
96
|
+
setDiscarding(true);
|
|
97
|
+
try {
|
|
98
|
+
await onDiscardWaiting();
|
|
99
|
+
} finally {
|
|
100
|
+
setDiscarding(false);
|
|
101
|
+
}
|
|
102
|
+
onSignOutAnyway();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<DialogFrame
|
|
107
|
+
open={open}
|
|
108
|
+
role="alertdialog"
|
|
109
|
+
onClose={onClose}
|
|
110
|
+
title={labels.title}
|
|
111
|
+
description={keepsChanges ? labels.kept(queued) : labels.lost(queued)}
|
|
112
|
+
closeButton
|
|
113
|
+
actions={
|
|
114
|
+
<>
|
|
115
|
+
<Button variant="secondary" onClick={onClose}>
|
|
116
|
+
{labels.cancel}
|
|
117
|
+
</Button>
|
|
118
|
+
{onDiscardWaiting && waitingForPlan > 0 && (
|
|
119
|
+
<Button variant="secondary" pending={discarding} disabled={syncing} onClick={() => void discard()}>
|
|
120
|
+
{labels.discardWaiting}
|
|
121
|
+
</Button>
|
|
122
|
+
)}
|
|
123
|
+
<Button pending={syncing} disabled={discarding} onClick={() => void syncNow()}>
|
|
124
|
+
{labels.syncNow}
|
|
125
|
+
</Button>
|
|
126
|
+
<Button variant="secondary" disabled={discarding} onClick={onSignOutAnyway}>
|
|
127
|
+
{labels.signOutAnyway}
|
|
128
|
+
</Button>
|
|
129
|
+
</>
|
|
130
|
+
}
|
|
131
|
+
>
|
|
132
|
+
{waitingForPlan > 0 && (typeof extra === "string" ? <p className="text-sm text-muted">{extra}</p> : extra)}
|
|
133
|
+
</DialogFrame>
|
|
134
|
+
);
|
|
135
|
+
}
|
package/src/offline.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// `@eifi1/ui-kit/offline` — local-first phase 1 (docs/local-first-harmonization.md §1.6,
|
|
2
|
+
// 0.45): the persisted query cache with its scrub list, the sign-out question while changes
|
|
3
|
+
// wait, the service-worker update prompt, and the shared-computer choice.
|
|
4
|
+
//
|
|
5
|
+
// The only entry point that requires `@tanstack/react-query`,
|
|
6
|
+
// `@tanstack/react-query-persist-client`, `@tanstack/query-async-storage-persister` and
|
|
7
|
+
// `idb-keyval` — optional peers for that reason. NOT re-exported from the main barrel, which
|
|
8
|
+
// would make them everyone's install; the packaging contract test holds that line.
|
|
9
|
+
export { createQueryPersistence, DEFAULT_SCRUB_FIELDS, isCacheSafe, removeStaleQueryCopies } from "./offline/query-persistence";
|
|
10
|
+
export type {
|
|
11
|
+
QueryPersistence,
|
|
12
|
+
QueryPersistenceOptions,
|
|
13
|
+
QueryPersistenceStorage,
|
|
14
|
+
RemoveStaleQueryCopiesOptions,
|
|
15
|
+
ScrubField,
|
|
16
|
+
} from "./offline/query-persistence";
|
|
17
|
+
export { SignOutPendingDialog, DEFAULT_SIGN_OUT_PENDING_DIALOG_LABELS } from "./offline/sign-out-pending-dialog";
|
|
18
|
+
export type { SignOutPendingDialogProps, SignOutPendingDialogLabels } from "./offline/sign-out-pending-dialog";
|
|
19
|
+
export { PwaUpdatePrompt, DEFAULT_PWA_UPDATE_PROMPT_LABELS } from "./offline/pwa-update-prompt";
|
|
20
|
+
export type { PwaUpdatePromptProps, PwaUpdatePromptLabels } from "./offline/pwa-update-prompt";
|
|
21
|
+
// 0.46 (kastlan K3): the service worker registered once at start, its update state as a store.
|
|
22
|
+
export { createServiceWorkerUpdates, useServiceWorkerUpdate } from "./offline/service-worker-updates";
|
|
23
|
+
export type { RegisterServiceWorker, ServiceWorkerUpdates } from "./offline/service-worker-updates";
|
|
24
|
+
export {
|
|
25
|
+
SharedComputerChoice,
|
|
26
|
+
DEFAULT_SHARED_COMPUTER_LABELS,
|
|
27
|
+
isSharedComputer,
|
|
28
|
+
setSharedComputer,
|
|
29
|
+
} from "./offline/shared-computer";
|
|
30
|
+
export type { SharedComputerChoiceProps, SharedComputerLabels, SharedComputerStorage } from "./offline/shared-computer";
|
|
31
|
+
// 0.46 (§2.3): the leaf utilities, shared with `@eifi1/ui-kit/sync` from one module that imports no peer.
|
|
32
|
+
export { isOfflineError, readLocalFirst, stampAfter, writeLocalFirst } from "./offline/local-first";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// `@eifi1/ui-kit/push-worker` — the service worker's half of Web Push (0.46,
|
|
2
|
+
// docs/notifications-harmonization.md §10.6 rule 5): re-registering a subscription the browser
|
|
3
|
+
// rotated. A worker bundle imports from here, never from the main barrel, which reaches React
|
|
4
|
+
// and the DOM components:
|
|
5
|
+
//
|
|
6
|
+
// // sw.ts
|
|
7
|
+
// import { handlePushSubscriptionChange } from "@eifi1/ui-kit/push-worker";
|
|
8
|
+
//
|
|
9
|
+
// Worker-safe: this entry reaches no package and no module but src/notifications/push-worker.ts
|
|
10
|
+
// (packaging-contract.test.ts, scripts/check-module-graph.mjs, scripts/verify-package.mjs).
|
|
11
|
+
// The main barrel keeps re-exporting the same names, so an existing import keeps working.
|
|
12
|
+
export {
|
|
13
|
+
PUSH_SUBSCRIPTION_CHANGED,
|
|
14
|
+
handlePushSubscriptionChange,
|
|
15
|
+
pushSubscriptionInput,
|
|
16
|
+
resubscribeAfterChange,
|
|
17
|
+
} from "./notifications/push-worker";
|
|
18
|
+
export type {
|
|
19
|
+
PushReplaceInput,
|
|
20
|
+
PushResubscribeOutcome,
|
|
21
|
+
PushSubscriptionChangeEventLike,
|
|
22
|
+
PushSubscriptionChangeOptions,
|
|
23
|
+
PushSubscriptionInput,
|
|
24
|
+
PushWorkerScope,
|
|
25
|
+
} from "./notifications/push-worker";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { AArrowDown, AArrowUp, ALargeSmall } from "lucide-react";
|
|
2
|
+
import { IconButton } from "../components/icon-button";
|
|
3
|
+
import { useAppearanceLabels, type AppearanceLabels } from "../components/appearance-labels";
|
|
4
|
+
import type { LabelOverride } from "../i18n/kit-labels";
|
|
5
|
+
import { TEXT_SIZES, useTextSize, type TextSize } from "../theme/text-size";
|
|
6
|
+
|
|
7
|
+
export interface AccountMenuTextSizeProps {
|
|
8
|
+
/** The size in force. Default: the one on `<html>` ({@link useTextSize}), which is the
|
|
9
|
+
* size whoever applied it put there. */
|
|
10
|
+
value?: TextSize;
|
|
11
|
+
/**
|
|
12
|
+
* A step: the next size down or up. The app applies it and keeps it as its Settings ▸
|
|
13
|
+
* Appearance does — `useAccountAppearance`'s `pickTextSize` (this device, and the
|
|
14
|
+
* account when signed in), with `appearance.saveFailed` as the toast when the account
|
|
15
|
+
* refused. The kit does not apply it itself, so the two places can never disagree.
|
|
16
|
+
*/
|
|
17
|
+
onPick: (size: TextSize) => void;
|
|
18
|
+
/** The row's name. Default: `appearance.textSize` ("Text size"). */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** The `appearance` words (`textSize`, `textSizes`, `textSmaller`, `textLarger`). */
|
|
21
|
+
labels?: LabelOverride<AppearanceLabels>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The text size one tap away, INSIDE the account menu (0.46, keksdose live #426, its
|
|
26
|
+
* `TextSizeRow`): the row's icon and name, the size in force, and A− / A+, which step
|
|
27
|
+
* through {@link TEXT_SIZES} (Normal → Large → Extra large). A− is disabled at the
|
|
28
|
+
* smallest, A+ at the largest. The browser's pinch zoom stays as it is — this is the
|
|
29
|
+
* quick path to the setting, not a replacement for zoom.
|
|
30
|
+
*
|
|
31
|
+
* It renders its own `<li>`: drop it into `TopBarActionMenu`'s `children` beside
|
|
32
|
+
* `AccountMenuLanguage`. The menu promotes the two buttons to `menuitem`s, so ↑/↓ reach
|
|
33
|
+
* them like every other row, and it stays open on a step: the person sees the page grow
|
|
34
|
+
* behind it and can step again. The size's name is a polite live region, so a step is
|
|
35
|
+
* announced.
|
|
36
|
+
*/
|
|
37
|
+
export function AccountMenuTextSize({ value, onPick, label, labels: labelsProp }: AccountMenuTextSizeProps) {
|
|
38
|
+
const labels = useAppearanceLabels(labelsProp);
|
|
39
|
+
const onHtml = useTextSize().size;
|
|
40
|
+
const size = value ?? onHtml;
|
|
41
|
+
const index = TEXT_SIZES.indexOf(size);
|
|
42
|
+
const step = (by: -1 | 1) => {
|
|
43
|
+
const next = TEXT_SIZES[index + by];
|
|
44
|
+
if (next) onPick(next);
|
|
45
|
+
};
|
|
46
|
+
return (
|
|
47
|
+
<li data-account-menu-text-size="" className="flex items-center gap-2 px-3 py-1.5 text-sm">
|
|
48
|
+
<ALargeSmall aria-hidden className="size-4 shrink-0 text-muted" />
|
|
49
|
+
<span className="min-w-0 flex-1 truncate">
|
|
50
|
+
{label ?? labels.textSize}
|
|
51
|
+
<span className="ms-2 text-xs text-muted" aria-live="polite">
|
|
52
|
+
{labels.textSizes[size]}
|
|
53
|
+
</span>
|
|
54
|
+
</span>
|
|
55
|
+
<IconButton
|
|
56
|
+
size="sm"
|
|
57
|
+
label={labels.textSmaller}
|
|
58
|
+
labelVisible={false}
|
|
59
|
+
tooltip={false}
|
|
60
|
+
disabled={index <= 0}
|
|
61
|
+
onClick={() => step(-1)}
|
|
62
|
+
>
|
|
63
|
+
<AArrowDown />
|
|
64
|
+
</IconButton>
|
|
65
|
+
<IconButton
|
|
66
|
+
size="sm"
|
|
67
|
+
label={labels.textLarger}
|
|
68
|
+
labelVisible={false}
|
|
69
|
+
tooltip={false}
|
|
70
|
+
disabled={index >= TEXT_SIZES.length - 1}
|
|
71
|
+
onClick={() => step(1)}
|
|
72
|
+
>
|
|
73
|
+
<AArrowUp />
|
|
74
|
+
</IconButton>
|
|
75
|
+
</li>
|
|
76
|
+
);
|
|
77
|
+
}
|
package/src/shell/admin-menu.tsx
CHANGED
|
@@ -25,11 +25,19 @@ function badgeText(badge: number | null | undefined): string | null {
|
|
|
25
25
|
* pending count's tone, with the number for the eye and `adminArea.waiting` for a reader
|
|
26
26
|
* ("Administration, 3 waiting"). `null` for no count.
|
|
27
27
|
*/
|
|
28
|
-
function WaitingBadge({
|
|
28
|
+
function WaitingBadge({
|
|
29
|
+
badge,
|
|
30
|
+
labels,
|
|
31
|
+
className,
|
|
32
|
+
}: {
|
|
33
|
+
badge: number | null | undefined;
|
|
34
|
+
labels: AdminAreaLabels;
|
|
35
|
+
className?: string;
|
|
36
|
+
}) {
|
|
29
37
|
const text = badgeText(badge);
|
|
30
38
|
if (text === null) return null;
|
|
31
39
|
return (
|
|
32
|
-
<Chip size="sm" variant="solid" tone="warning" data-slot="admin-badge">
|
|
40
|
+
<Chip size="sm" variant="solid" tone="warning" data-slot="admin-badge" className={className}>
|
|
33
41
|
<span aria-hidden>{text}</span>
|
|
34
42
|
{/* The comma parts it from the row's label in the name: "Administration, 3 waiting". */}
|
|
35
43
|
<span className="sr-only-fixed">, {labels.waiting(badge as number)}</span>
|
|
@@ -37,6 +45,35 @@ function WaitingBadge({ badge, labels }: { badge: number | null | undefined; lab
|
|
|
37
45
|
);
|
|
38
46
|
}
|
|
39
47
|
|
|
48
|
+
export interface AdminWaitingBadgeProps {
|
|
49
|
+
/** The waiting count: only work that waits on this admin. `0`, `null` or none draws
|
|
50
|
+
* nothing; past 99 it reads "99+". */
|
|
51
|
+
count: number | null | undefined;
|
|
52
|
+
/** Prop > `<UiKitProvider labels={{ adminArea }}>` > English. */
|
|
53
|
+
labels?: LabelOverride<AdminAreaLabels>;
|
|
54
|
+
className?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The waiting pill of {@link AdminMenuRow} on its own (0.45, docs/admin-harmonization.md
|
|
59
|
+
* §9.3.4): the `solid` warning count, "99+" past 99, nothing for 0. Fit for `IconButton`'s
|
|
60
|
+
* `badge` — kastlan's top-bar platform button. There the badge is DECORATIVE (`aria-hidden`
|
|
61
|
+
* by IconButton), so the button's name carries the count: `adminArea.waitingLabel`.
|
|
62
|
+
*
|
|
63
|
+
* const area = useAdminAreaLabels();
|
|
64
|
+
* <IconButton
|
|
65
|
+
* label={waiting > 0 ? area.waitingLabel("Platform", waiting) : "Platform"}
|
|
66
|
+
* badge={<AdminWaitingBadge count={waiting} />}
|
|
67
|
+
* …
|
|
68
|
+
* />
|
|
69
|
+
*
|
|
70
|
+
* Read on its own (outside an IconButton), the pill still says ", 3 waiting" to a reader.
|
|
71
|
+
*/
|
|
72
|
+
export function AdminWaitingBadge({ count, labels: labelsProp, className }: AdminWaitingBadgeProps) {
|
|
73
|
+
const labels = useAdminAreaLabels(labelsProp);
|
|
74
|
+
return <WaitingBadge badge={count} labels={labels} className={className} />;
|
|
75
|
+
}
|
|
76
|
+
|
|
40
77
|
export interface AdminMenuEntryOptions {
|
|
41
78
|
/** The admin area. Default `/admin`. */
|
|
42
79
|
href?: string;
|
|
@@ -136,7 +173,7 @@ export function AdminMenuRow({ href = "/admin", badge, onNavigate, current, labe
|
|
|
136
173
|
renderLink={({ href: to, ...p }) => <Link to={to} {...p} />}
|
|
137
174
|
onClick={onNavigate ? () => onNavigate() : undefined}
|
|
138
175
|
leading={<ShieldCheck className="size-4" aria-hidden />}
|
|
139
|
-
trailing={badgeText(badge) === null ? undefined : <
|
|
176
|
+
trailing={badgeText(badge) === null ? undefined : <AdminWaitingBadge count={badge} labels={labels} />}
|
|
140
177
|
current={current}
|
|
141
178
|
>
|
|
142
179
|
{labels.title}
|
|
@@ -24,6 +24,10 @@ export interface TopBarActionsProps {
|
|
|
24
24
|
dev?: ReactNode;
|
|
25
25
|
/** kastlan's platform button. */
|
|
26
26
|
platform?: ReactNode;
|
|
27
|
+
/** 0.46 (docs/notifications-harmonization.md §10.1): `NotificationBell`, right before the
|
|
28
|
+
* account menu. Optional per app: an app shows the bell or an unread dot on the avatar,
|
|
29
|
+
* never both (keksdose keeps its dot and leaves this empty). */
|
|
30
|
+
notifications?: ReactNode;
|
|
27
31
|
/** The account menu. When it is set the signed-out slots are not drawn. */
|
|
28
32
|
account?: ReactNode;
|
|
29
33
|
/** Signed out (§6.6): the bar's `LanguageMenu`, since there is no account menu to hold
|
|
@@ -45,6 +49,7 @@ export const TOP_BAR_ACTION_ORDER = [
|
|
|
45
49
|
"sync",
|
|
46
50
|
"dev",
|
|
47
51
|
"platform",
|
|
52
|
+
"notifications",
|
|
48
53
|
"account",
|
|
49
54
|
"language",
|
|
50
55
|
"signIn",
|
|
@@ -55,7 +60,7 @@ const present = (node: ReactNode) => node !== undefined && node !== null && node
|
|
|
55
60
|
/**
|
|
56
61
|
* The top bar's actions in the one order every app shares (§2.1): company switcher ·
|
|
57
62
|
* Search · Theme · Feedback · Tours (· Assistant) · Sync · dev role · dev plan · platform ·
|
|
58
|
-
* account menu — and, signed out, Language · Sign in where the account menu goes (§6.6).
|
|
63
|
+
* notifications (the bell, 0.46) · account menu — and, signed out, Language · Sign in where the account menu goes (§6.6).
|
|
59
64
|
* The app fills the slots it has; an empty slot is skipped, so it leaves no gap.
|
|
60
65
|
*
|
|
61
66
|
* It draws a fragment, no box of its own: hand it to `TopBar`'s `actions`, whose row
|
package/src/shell.ts
CHANGED
|
@@ -20,6 +20,10 @@ export type { AdminMenuEntryOptions, AdminMenuRowProps } from "./shell/admin-men
|
|
|
20
20
|
// 0.43.0 (kastlan): the entry with the provider's words.
|
|
21
21
|
export { useAdminMenuEntry } from "./shell/admin-menu";
|
|
22
22
|
export type { UseAdminMenuEntryOptions } from "./shell/admin-menu";
|
|
23
|
+
// 0.45.0 (docs/admin-harmonization.md §9.3.4): the row's waiting pill on its own, for
|
|
24
|
+
// IconButton's `badge` (kastlan's platform button); its name is `adminArea.waitingLabel`.
|
|
25
|
+
export { AdminWaitingBadge } from "./shell/admin-menu";
|
|
26
|
+
export type { AdminWaitingBadgeProps } from "./shell/admin-menu";
|
|
23
27
|
// 0.31.0: the settings and admin pages' shell (docs/settings-harmonization.md §3, §5).
|
|
24
28
|
export { SettingsLayout, SETTINGS_SEARCH_THRESHOLD } from "./settings/settings-layout";
|
|
25
29
|
export type { SettingsLayoutProps, SettingsLayoutWidth, SettingsDocumentTitleParts } from "./settings/settings-layout";
|
|
@@ -78,6 +82,9 @@ export { TopBarActions, TOP_BAR_ACTION_ORDER } from "./shell/top-bar-actions";
|
|
|
78
82
|
export type { TopBarActionsProps } from "./shell/top-bar-actions";
|
|
79
83
|
export { AccountMenuLanguage } from "./shell/account-menu-language";
|
|
80
84
|
export type { AccountMenuLanguageProps, AccountMenuLanguageOption } from "./shell/account-menu-language";
|
|
85
|
+
// 0.46.0 (keksdose live #426): A− / A+ for the text size, beside the language.
|
|
86
|
+
export { AccountMenuTextSize } from "./shell/account-menu-text-size";
|
|
87
|
+
export type { AccountMenuTextSizeProps } from "./shell/account-menu-text-size";
|
|
81
88
|
export { TourMenu } from "./shell/tour-menu";
|
|
82
89
|
export type { TourMenuProps, TourMenuTour } from "./shell/tour-menu";
|
|
83
90
|
export { SyncStatusIndicator, syncState } from "./shell/sync-status-indicator";
|