@eifi1/ui-kit 0.43.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.
Files changed (134) hide show
  1. package/README.md +11 -7
  2. package/dist/admin/admin-action-log-labels.js +9 -0
  3. package/dist/admin/admin-action-log-labels.js.map +1 -1
  4. package/dist/admin/admin-action-log.d.ts +18 -1
  5. package/dist/admin/admin-action-log.js +25 -16
  6. package/dist/admin/admin-action-log.js.map +1 -1
  7. package/dist/admin/admin-area-labels.d.ts +6 -0
  8. package/dist/admin/admin-area-labels.js +1 -0
  9. package/dist/admin/admin-area-labels.js.map +1 -1
  10. package/dist/admin/mail-test-card-labels.d.ts +3 -0
  11. package/dist/admin/mail-test-card-labels.js +17 -0
  12. package/dist/admin/mail-test-card-labels.js.map +1 -0
  13. package/dist/admin/mail-test-card.d.ts +74 -0
  14. package/dist/admin/mail-test-card.js +80 -0
  15. package/dist/admin/mail-test-card.js.map +1 -0
  16. package/dist/billing/provider-page.d.ts +9 -0
  17. package/dist/billing/provider-page.js.map +1 -1
  18. package/dist/components/data-table-mobile.js +17 -2
  19. package/dist/components/data-table-mobile.js.map +1 -1
  20. package/dist/components/data-table-shared.d.ts +2 -0
  21. package/dist/components/data-table-shared.js +8 -0
  22. package/dist/components/data-table-shared.js.map +1 -1
  23. package/dist/components/data-table.js +1 -6
  24. package/dist/components/data-table.js.map +1 -1
  25. package/dist/eslint/index.js +1 -1
  26. package/dist/i18n/defaults.js +9 -1
  27. package/dist/i18n/defaults.js.map +1 -1
  28. package/dist/i18n/german.js +42 -2
  29. package/dist/i18n/german.js.map +1 -1
  30. package/dist/i18n/kit-labels.d.ts +13 -0
  31. package/dist/i18n/kit-labels.js.map +1 -1
  32. package/dist/i18n/locales/en.js +60 -4
  33. package/dist/i18n/locales/en.js.map +3 -3
  34. package/dist/i18n/locales/es.js +40 -0
  35. package/dist/i18n/locales/es.js.map +1 -1
  36. package/dist/i18n/locales/fr.js +42 -2
  37. package/dist/i18n/locales/fr.js.map +1 -1
  38. package/dist/i18n/locales/hu.js +40 -0
  39. package/dist/i18n/locales/hu.js.map +1 -1
  40. package/dist/i18n/locales/it.js +41 -1
  41. package/dist/i18n/locales/it.js.map +1 -1
  42. package/dist/i18n/locales/zh.js +40 -0
  43. package/dist/i18n/locales/zh.js.map +1 -1
  44. package/dist/i18n/review.js +13 -1
  45. package/dist/i18n/review.js.map +1 -1
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +5 -0
  48. package/dist/index.js.map +1 -1
  49. package/dist/lib/format.d.ts +2 -1
  50. package/dist/lib/format.js +6 -1
  51. package/dist/lib/format.js.map +1 -1
  52. package/dist/offline/pwa-update-prompt-labels.d.ts +2 -0
  53. package/dist/offline/pwa-update-prompt-labels.js +9 -0
  54. package/dist/offline/pwa-update-prompt-labels.js.map +1 -0
  55. package/dist/offline/pwa-update-prompt.d.ts +32 -0
  56. package/dist/offline/pwa-update-prompt.js +36 -0
  57. package/dist/offline/pwa-update-prompt.js.map +1 -0
  58. package/dist/offline/query-persistence.d.ts +100 -0
  59. package/dist/offline/query-persistence.js +179 -0
  60. package/dist/offline/query-persistence.js.map +1 -0
  61. package/dist/offline/shared-computer-labels.d.ts +2 -0
  62. package/dist/offline/shared-computer-labels.js +9 -0
  63. package/dist/offline/shared-computer-labels.js.map +1 -0
  64. package/dist/offline/shared-computer.d.ts +44 -0
  65. package/dist/offline/shared-computer.js +51 -0
  66. package/dist/offline/shared-computer.js.map +1 -0
  67. package/dist/offline/sign-out-pending-dialog-labels.d.ts +2 -0
  68. package/dist/offline/sign-out-pending-dialog-labels.js +14 -0
  69. package/dist/offline/sign-out-pending-dialog-labels.js.map +1 -0
  70. package/dist/offline/sign-out-pending-dialog.d.ts +58 -0
  71. package/dist/offline/sign-out-pending-dialog.js +66 -0
  72. package/dist/offline/sign-out-pending-dialog.js.map +1 -0
  73. package/dist/offline.d.ts +8 -0
  74. package/dist/offline.js +24 -0
  75. package/dist/offline.js.map +1 -0
  76. package/dist/shell/admin-menu.d.ts +24 -0
  77. package/dist/shell/admin-menu.js +12 -3
  78. package/dist/shell/admin-menu.js.map +1 -1
  79. package/dist/shell.d.ts +2 -0
  80. package/dist/shell.js +2 -0
  81. package/dist/shell.js.map +1 -1
  82. package/dist/tour/tour-catalogue-labels.js +2 -2
  83. package/dist/tour/tour-catalogue-labels.js.map +1 -1
  84. package/dist/tour/tour-catalogue.d.ts +7 -1
  85. package/dist/tour/tour-catalogue.js.map +1 -1
  86. package/dist/tour/tour-info.d.ts +2 -2
  87. package/dist/tour/tour-info.js.map +1 -1
  88. package/dist/tour/tour.d.ts +38 -2
  89. package/dist/tour/tour.js +42 -3
  90. package/dist/tour/tour.js.map +1 -1
  91. package/dist/tour/use-tour-progress.d.ts +165 -12
  92. package/dist/tour/use-tour-progress.js +271 -25
  93. package/dist/tour/use-tour-progress.js.map +1 -1
  94. package/dist/tour.d.ts +3 -1
  95. package/dist/tour.js +4 -1
  96. package/dist/tour.js.map +1 -1
  97. package/package.json +25 -1
  98. package/src/admin/admin-action-log-labels.ts +9 -0
  99. package/src/admin/admin-action-log.tsx +41 -5
  100. package/src/admin/admin-area-labels.ts +7 -0
  101. package/src/admin/mail-test-card-labels.ts +19 -0
  102. package/src/admin/mail-test-card.tsx +186 -0
  103. package/src/billing/provider-page.ts +9 -0
  104. package/src/components/data-table-mobile.tsx +26 -3
  105. package/src/components/data-table-shared.tsx +18 -1
  106. package/src/components/data-table.tsx +1 -15
  107. package/src/i18n/defaults.ts +8 -0
  108. package/src/i18n/german.ts +48 -2
  109. package/src/i18n/kit-labels.tsx +13 -0
  110. package/src/i18n/locales/en.ts +13 -1
  111. package/src/i18n/locales/es.ts +46 -0
  112. package/src/i18n/locales/fr.ts +48 -2
  113. package/src/i18n/locales/hu.ts +42 -0
  114. package/src/i18n/locales/it.ts +47 -1
  115. package/src/i18n/locales/zh.ts +40 -0
  116. package/src/i18n/review.ts +12 -0
  117. package/src/index.ts +3 -0
  118. package/src/lib/format.ts +13 -2
  119. package/src/offline/pwa-update-prompt-labels.ts +9 -0
  120. package/src/offline/pwa-update-prompt.tsx +72 -0
  121. package/src/offline/query-persistence.ts +323 -0
  122. package/src/offline/shared-computer-labels.ts +8 -0
  123. package/src/offline/shared-computer.tsx +87 -0
  124. package/src/offline/sign-out-pending-dialog-labels.ts +21 -0
  125. package/src/offline/sign-out-pending-dialog.tsx +135 -0
  126. package/src/offline.ts +26 -0
  127. package/src/shell/admin-menu.tsx +40 -3
  128. package/src/shell.ts +4 -0
  129. package/src/tour/tour-catalogue-labels.ts +2 -2
  130. package/src/tour/tour-catalogue.tsx +7 -1
  131. package/src/tour/tour-info.ts +2 -2
  132. package/src/tour/tour.tsx +87 -3
  133. package/src/tour/use-tour-progress.ts +501 -34
  134. package/src/tour.ts +5 -1
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";
@@ -54,11 +54,11 @@ export const DEFAULT_TOUR_CATALOGUE_LABELS: TourCatalogueLabels = {
54
54
  groupOption: (group, count) => `${group} (${count})`,
55
55
  stateLabel: "Filter tours",
56
56
  stateAll: (count) => `All (${count})`,
57
- stateTodo: (count) => `To do (${count})`,
57
+ stateTodo: (count) => `Not taken (${count})`,
58
58
  stateDone: (count) => `Completed (${count})`,
59
59
  showing: (shown, total) => `Showing ${shown} of ${total} tours`,
60
60
  noMatches: "No tour matches",
61
- noMatchesHint: "Try another word, or clear the filters.",
61
+ noMatchesHint: "Try another word, or clear the filter.",
62
62
  clearFilters: "Clear filters",
63
63
  badgeNew: "New",
64
64
  badgeUpdated: "Updated",
@@ -30,7 +30,12 @@ export interface TourCatalogueCardProps {
30
30
  onLaunch: (id: string) => void;
31
31
  /** "Mark as not done". Without it, the link is not drawn. */
32
32
  onReset?: (id: string) => void;
33
- /** Formats the completion and update days. Default: the kit's medium date in the provider's locale. */
33
+ /**
34
+ * Formats the completion and update days. Default: the kit's medium date in the provider's locale.
35
+ * It gets the completion's ISO instant and the update's bare `"YYYY-MM-DD"` (`updated.date`): read
36
+ * the bare day on its calendar (the kit's `formatDate`, `parseIsoDate`), never `new Date(day)`,
37
+ * which is UTC midnight and the day before west of UTC (round 0.45, N9).
38
+ */
34
39
  formatDate?: (iso: string) => string;
35
40
  labels?: LabelOverride<TourCatalogueLabels>;
36
41
  className?: string;
@@ -161,6 +166,7 @@ export interface TourCatalogueProps extends TourCatalogueFilter {
161
166
  onReset?: (id: string) => void;
162
167
  /** Above the filter bar: the app's page header with its points tile (§6.1). */
163
168
  header?: ReactNode;
169
+ /** Each card's `formatDate`: see {@link TourCatalogueCardProps.formatDate} (a bare day for the update). */
164
170
  formatDate?: (iso: string) => string;
165
171
  /** The search's locale (case folding). Default: the provider's. */
166
172
  locale?: string;
@@ -59,8 +59,8 @@ export interface TourGroup {
59
59
 
60
60
  /**
61
61
  * Which tours are completed and when: tour id → the ISO timestamp of the completion, or
62
- * `null` for one recorded without a date (kastlan's old id list, §6.2). A tour that is
63
- * not a key is untaken.
62
+ * `null` for one recorded without a date (kastlan's old id list, §6.2; stored as `""`,
63
+ * §9.6.6). A tour that is not a key is untaken.
64
64
  */
65
65
  export type TourCompletions = Readonly<Record<string, string | null>>;
66
66
 
package/src/tour/tour.tsx CHANGED
@@ -17,6 +17,8 @@ import { sameRect } from "../hooks/use-anchored-rect";
17
17
  import { dirOf, horizontalStep } from "../lib/direction";
18
18
  import type { Direction } from "../lib/direction";
19
19
  import { DEFAULT_TOUR_LABELS } from "./tour-labels";
20
+ import type { TourInfo } from "./tour-info";
21
+ import { completeStored, rememberAccount, rememberMigrate, type UseTourProgressOptions } from "./use-tour-progress";
20
22
 
21
23
  /**
22
24
  * `start`/`end` follow the reading direction (`end` is the right side in LTR and the
@@ -67,16 +69,34 @@ export { DEFAULT_TOUR_LABELS };
67
69
 
68
70
  interface StartOptions {
69
71
  /** Stable id of the tour, surfaced as `activeId` so the app can mirror it to
70
- * the URL (deep-link / evaluation tracking) and record completion. */
72
+ * the URL (deep-link / evaluation tracking) and record completion. Defaults to
73
+ * `tour.id`. */
71
74
  id?: string;
75
+ /** The tour being started, as the app lists it (docs/tours-harmonization.md §9.6.1).
76
+ * When its last step is finished, the provider records the completion in its
77
+ * `progress` and hands this to `onComplete`. */
78
+ tour?: TourInfo;
72
79
  /** Start at this step index instead of 0 (clamped) — for URL/deep-link resume. */
73
80
  startIndex?: number;
74
81
  /** Fires synchronously when the tour starts (e.g. enable a mock interceptor). */
75
82
  onStart?: () => void;
83
+ /** After the last step: runs after the provider's progress write and `onComplete`. */
76
84
  onFinish?: () => void;
77
85
  onSkip?: () => void;
78
86
  }
79
87
 
88
+ /** What `TourProvider`'s `onComplete` learns besides the tour (§9.1, §9.6). */
89
+ export interface TourCompletion {
90
+ /**
91
+ * `true` when the completion counts anew (the first time, or the first time after the
92
+ * tour's update, §8): the moment for the app's points toast. `false` for a replay, and
93
+ * when the stored text is in an old shape no `migrate` reads: nothing is overwritten
94
+ * then, and the error goes to `console.error` (keksdose B-1). `undefined` when the provider has no `progress` or the start carried no `tour`, so
95
+ * nothing was recorded.
96
+ */
97
+ awarded: boolean | undefined;
98
+ }
99
+
80
100
  interface TourContextValue {
81
101
  active: boolean;
82
102
  /** Id of the running tour (from {@link StartOptions.id}), else null. */
@@ -129,6 +149,11 @@ export function useTourOptional(): TourContextValue | null {
129
149
  return useContext(TourContext);
130
150
  }
131
151
 
152
+ /** The kit's dev switch (as number-input's): a development build, never under vitest. */
153
+ function devWarnings(): boolean {
154
+ return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
155
+ }
156
+
132
157
  /**
133
158
  * Provides the guided-tour engine: a dimmed spotlight overlay + step card driven
134
159
  * by a list of {@link TourStep}s. Domain-free — the app supplies the steps (copy,
@@ -136,13 +161,32 @@ export function useTourOptional(): TourContextValue | null {
136
161
  * Mount once inside the router; start a tour from anywhere via {@link useTour},
137
162
  * which also exposes step control (`next`/`prev`/`goToStep`) and the current
138
163
  * `index`/`total` for URL mirroring, `awaitClick` and deep-link resume.
164
+ *
165
+ * **Completion in one place** (docs/tours-harmonization.md §9.6): give the provider the
166
+ * app's `progress` (the same `{storageKey, migrate}` as `useTourProgress`) and start each
167
+ * tour with `start(steps, {tour})`. When the last step is finished the provider writes the
168
+ * completion ONCE, through the store every `useTourProgress` reader of the key shares
169
+ * (never a second copy), then calls `onComplete(tour, {awarded})`, then the start's own
170
+ * `onFinish`. A skip or `stop()` records nothing and calls neither. The app removes its
171
+ * own `complete()` from `onFinish`, or that second write answers `false`.
172
+ *
173
+ * Both props are optional: without `progress` or a `tour` on the start, `onComplete`
174
+ * still fires, with `awarded: undefined` (and `tour` as given, possibly `undefined`). A
175
+ * new `storageKey` (an account switch on a shared PC) is followed on the next
176
+ * completion, without a remount.
139
177
  */
140
178
  export function TourProvider({
141
179
  children,
142
180
  labels,
181
+ progress,
182
+ onComplete,
143
183
  }: {
144
184
  children: ReactNode;
145
185
  labels?: LabelOverride<TourLabels>;
186
+ /** The app's tour progress: the same options its `useTourProgress` readers use. */
187
+ progress?: UseTourProgressOptions;
188
+ /** A tour's last step was finished; see {@link TourCompletion}. */
189
+ onComplete?: (tour: TourInfo | undefined, completion: TourCompletion) => void;
146
190
  }) {
147
191
  // `labels` over the provider's `tour` over English. Resolved once here and handed
148
192
  // down: the step card is portalled, but context crosses portals anyway — this is
@@ -152,6 +196,23 @@ export function TourProvider({
152
196
  const [index, setIndex] = useState(0);
153
197
  const [activeId, setActiveId] = useState<string | null>(null);
154
198
  const optsRef = useRef<StartOptions>({});
199
+ // The latest props, read at the finish: a new `storageKey` or callback needs no remount.
200
+ const progressRef = useRef(progress);
201
+ const onCompleteRef = useRef(onComplete);
202
+ useLayoutEffect(() => {
203
+ progressRef.current = progress;
204
+ onCompleteRef.current = onComplete;
205
+ });
206
+ // The provider's migrate becomes the key's, for readers that pass none (keksdose B-2).
207
+ const storageKey = progress?.storageKey;
208
+ const migrate = progress?.migrate;
209
+ // 0.45: and its `account` (§10.3), so a finish on an account key is sent as readers' are.
210
+ const account = progress?.account;
211
+ useLayoutEffect(() => {
212
+ if (storageKey !== undefined) rememberMigrate(storageKey, migrate);
213
+ if (storageKey !== undefined) rememberAccount(storageKey, account);
214
+ }, [storageKey, migrate, account]);
215
+ const warnedNoTourRef = useRef(false);
155
216
  // Live mirrors so the step-control callbacks can stay stable (identity-safe for
156
217
  // consumers) while still reading the current steps/index. Synced in a layout
157
218
  // effect — after the commit, before paint and before any passive effect or event
@@ -169,7 +230,7 @@ export function TourProvider({
169
230
  optsRef.current = opts ?? {};
170
231
  optsRef.current.onStart?.();
171
232
  setIndex(Math.min(Math.max(0, opts?.startIndex ?? 0), Math.max(0, s.length - 1)));
172
- setActiveId(opts?.id ?? null);
233
+ setActiveId(opts?.id ?? opts?.tour?.id ?? null);
173
234
  setSteps(s);
174
235
  }, []);
175
236
  const stop = useCallback(() => {
@@ -177,7 +238,30 @@ export function TourProvider({
177
238
  setActiveId(null);
178
239
  }, []);
179
240
  const finish = useCallback(() => {
180
- optsRef.current.onFinish?.();
241
+ // Once per tour: a second "Done" before the overlay unmounts records nothing more.
242
+ if (!stepsRef.current) return;
243
+ stepsRef.current = null;
244
+ const { tour, onFinish } = optsRef.current;
245
+ const store = progressRef.current;
246
+ // Exactly one write per completion (§9.6.2), so `awarded` is what `complete(tour)` answered.
247
+ let awarded: boolean | undefined;
248
+ if (store && tour) {
249
+ try {
250
+ awarded = completeStored(store.storageKey, store.migrate, tour);
251
+ } catch (error) {
252
+ // A stored text no migrate reads is left as it is (keksdose B-1); the tour still ends.
253
+ console.error(error);
254
+ awarded = false;
255
+ }
256
+ } else if (store && !tour && devWarnings() && !warnedNoTourRef.current) {
257
+ warnedNoTourRef.current = true;
258
+ console.warn(
259
+ "[ui-kit] TourProvider has `progress`, but a finished tour's start carried no `tour`: " +
260
+ "nothing was recorded and `awarded` is undefined. Start it with `start(steps, { tour })`.",
261
+ );
262
+ }
263
+ onCompleteRef.current?.(tour, { awarded });
264
+ onFinish?.();
181
265
  setSteps(null);
182
266
  setActiveId(null);
183
267
  }, []);