@eifi1/ui-kit 0.7.1 → 0.8.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 +32 -17
- package/dist/chart.d.ts +3 -2
- package/dist/components/alert-banner.d.ts +59 -10
- package/dist/components/alert-banner.js +114 -8
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +3 -0
- package/dist/components/button-group.d.ts +26 -0
- package/dist/components/button-group.js +49 -0
- package/dist/components/button-group.js.map +1 -0
- package/dist/components/calculator.d.ts +3 -0
- package/dist/components/chart-zoom.d.ts +63 -14
- package/dist/components/chart-zoom.js +82 -16
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/chip.d.ts +67 -2
- package/dist/components/chip.js +73 -8
- package/dist/components/chip.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +120 -0
- package/dist/components/confirm-dialog.js +96 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/copy-button.d.ts +62 -0
- package/dist/components/copy-button.js +95 -0
- package/dist/components/copy-button.js.map +1 -0
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filters.d.ts +1 -1
- package/dist/components/data-table-pagination.d.ts +5 -1
- package/dist/components/data-table-pagination.js +71 -58
- package/dist/components/data-table-pagination.js.map +1 -1
- package/dist/components/data-table-sort.d.ts +28 -5
- package/dist/components/data-table-sort.js +11 -7
- package/dist/components/data-table-sort.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +544 -423
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +51 -5
- package/dist/components/date-picker.js +152 -40
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +60 -0
- package/dist/components/description-list.js +112 -0
- package/dist/components/description-list.js.map +1 -0
- package/dist/components/disclosure.d.ts +37 -2
- package/dist/components/disclosure.js +14 -5
- package/dist/components/disclosure.js.map +1 -1
- package/dist/components/facing-pair.d.ts +2 -0
- package/dist/components/file-button.d.ts +17 -1
- package/dist/components/file-button.js +8 -1
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +54 -9
- package/dist/components/file-dropzone.js +100 -70
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/floating-panel.d.ts +107 -0
- package/dist/components/floating-panel.js +206 -0
- package/dist/components/floating-panel.js.map +1 -0
- package/dist/components/full-bleed-dialog.d.ts +12 -1
- package/dist/components/full-bleed-dialog.js +26 -5
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/modal.d.ts +6 -2
- package/dist/components/modal.js +2 -1
- package/dist/components/modal.js.map +1 -1
- package/dist/components/number-field.d.ts +3 -0
- package/dist/components/number-input.d.ts +3 -0
- package/dist/components/numpad-sheet.d.ts +3 -0
- package/dist/components/progress-bar.d.ts +54 -0
- package/dist/components/progress-bar.js +94 -0
- package/dist/components/progress-bar.js.map +1 -0
- package/dist/components/scroll-area.d.ts +53 -0
- package/dist/components/scroll-area.js +86 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/separator.d.ts +23 -0
- package/dist/components/separator.js +24 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/series-chart-ticks.d.ts +34 -1
- package/dist/components/series-chart-ticks.js +83 -1
- package/dist/components/series-chart-ticks.js.map +1 -1
- package/dist/components/series-chart.d.ts +314 -19
- package/dist/components/series-chart.js +510 -120
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/skeleton.d.ts +36 -0
- package/dist/components/skeleton.js +35 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/stat-tile.d.ts +60 -6
- package/dist/components/stat-tile.js +60 -26
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/table.d.ts +83 -0
- package/dist/components/table.js +166 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/toggle-legend.d.ts +3 -2
- package/dist/components/toggle-legend.js +1 -1
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/tree-view.d.ts +129 -0
- package/dist/components/tree-view.js +376 -0
- package/dist/components/tree-view.js.map +1 -0
- package/dist/components/treemap.d.ts +9 -2
- package/dist/components/treemap.js +2 -1
- package/dist/components/treemap.js.map +1 -1
- package/dist/components/ui.d.ts +87 -13
- package/dist/components/ui.js +111 -29
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -1
- package/dist/{data-table-filters-noy0Abvi.d.ts → data-table-filters-CF1PXqjQ.d.ts} +73 -3
- package/dist/data-table.d.ts +2 -2
- package/dist/data-table.js.map +1 -1
- package/dist/hooks/use-copy-to-clipboard.d.ts +28 -0
- package/dist/hooks/use-copy-to-clipboard.js +81 -0
- package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
- package/dist/hooks/use-debounce.d.ts +43 -0
- package/dist/hooks/use-debounce.js +56 -0
- package/dist/hooks/use-debounce.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +72 -0
- package/dist/hooks/use-file-drop.js +57 -0
- package/dist/hooks/use-file-drop.js.map +1 -0
- package/dist/i18n/defaults.d.ts +3 -0
- package/dist/i18n/defaults.js +7 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -0
- package/dist/i18n/kit-labels.js +6 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +43 -0
- package/dist/i18n/locales/de-CH-informal.js +8 -0
- package/dist/i18n/locales/de-CH-informal.js.map +1 -0
- package/dist/i18n/locales/de-CH.d.ts +3 -0
- package/dist/i18n/locales/de-CH.js +1 -14
- package/dist/i18n/locales/de-CH.js.map +1 -1
- package/dist/i18n/locales/de-informal.d.ts +64 -0
- package/dist/i18n/locales/de-informal.js +37 -0
- package/dist/i18n/locales/de-informal.js.map +1 -0
- package/dist/i18n/locales/de.d.ts +3 -0
- package/dist/i18n/locales/de.js +25 -0
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +3 -0
- package/dist/i18n/locales/es.js +25 -0
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +3 -0
- package/dist/i18n/locales/fr.js +25 -0
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +3 -0
- package/dist/i18n/locales/hu.js +25 -0
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +3 -0
- package/dist/i18n/locales/it.js +25 -0
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +3 -0
- package/dist/i18n/locales/zh.js +25 -0
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/swiss.d.ts +13 -0
- package/dist/i18n/swiss.js +19 -0
- package/dist/i18n/swiss.js.map +1 -0
- package/dist/index.d.ts +25 -10
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/dates.d.ts +37 -1
- package/dist/lib/dates.js +19 -0
- package/dist/lib/dates.js.map +1 -1
- package/dist/search/command-palette.d.ts +15 -1
- package/dist/search/command-palette.js +10 -2
- package/dist/search/command-palette.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +35 -2
- package/dist/wizard/stepper-nav.js +54 -22
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard/types.d.ts +107 -5
- package/dist/wizard/types.js +2 -1
- package/dist/wizard/types.js.map +1 -1
- package/dist/wizard/use-wizard.js +141 -21
- package/dist/wizard/use-wizard.js.map +1 -1
- package/dist/wizard/wizard-summary.d.ts +10 -2
- package/dist/wizard/wizard-summary.js +3 -1
- package/dist/wizard/wizard-summary.js.map +1 -1
- package/dist/wizard.d.ts +4 -1
- package/dist/wizard.js.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-banner.tsx +211 -20
- package/src/components/button-group.tsx +75 -0
- package/src/components/chart-zoom.tsx +158 -20
- package/src/components/chip.tsx +160 -9
- package/src/components/confirm-dialog.tsx +242 -0
- package/src/components/copy-button.tsx +158 -0
- package/src/components/data-table-pagination.tsx +19 -6
- package/src/components/data-table-sort.ts +49 -10
- package/src/components/data-table.tsx +293 -24
- package/src/components/date-picker.tsx +262 -57
- package/src/components/description-list.tsx +174 -0
- package/src/components/disclosure.tsx +58 -5
- package/src/components/file-button.tsx +22 -2
- package/src/components/file-dropzone.tsx +198 -100
- package/src/components/floating-panel.tsx +341 -0
- package/src/components/full-bleed-dialog.tsx +48 -5
- package/src/components/modal.tsx +9 -2
- package/src/components/progress-bar.tsx +163 -0
- package/src/components/scroll-area.tsx +129 -0
- package/src/components/separator.tsx +37 -0
- package/src/components/series-chart-ticks.ts +135 -0
- package/src/components/series-chart.tsx +934 -67
- package/src/components/skeleton.tsx +65 -0
- package/src/components/stat-tile.tsx +160 -27
- package/src/components/table.tsx +263 -0
- package/src/components/toggle-legend.tsx +4 -3
- package/src/components/tree-view.tsx +589 -0
- package/src/components/treemap.tsx +10 -2
- package/src/components/ui.tsx +263 -39
- package/src/data-table.ts +2 -0
- package/src/hooks/use-copy-to-clipboard.ts +122 -0
- package/src/hooks/use-debounce.ts +105 -0
- package/src/hooks/use-file-drop.ts +123 -0
- package/src/i18n/defaults.ts +6 -0
- package/src/i18n/kit-labels.tsx +19 -0
- package/src/i18n/locales/de-CH-informal.ts +10 -0
- package/src/i18n/locales/de-CH.ts +1 -16
- package/src/i18n/locales/de-informal.ts +61 -0
- package/src/i18n/locales/de.ts +25 -0
- package/src/i18n/locales/es.ts +25 -0
- package/src/i18n/locales/fr.ts +25 -0
- package/src/i18n/locales/hu.ts +25 -0
- package/src/i18n/locales/it.ts +25 -0
- package/src/i18n/locales/zh.ts +25 -0
- package/src/i18n/swiss.ts +25 -0
- package/src/index.ts +29 -0
- package/src/lib/dates.ts +48 -0
- package/src/search/command-palette.tsx +29 -3
- package/src/wizard/stepper-nav.tsx +102 -28
- package/src/wizard/types.ts +106 -4
- package/src/wizard/use-wizard.ts +211 -31
- package/src/wizard/wizard-summary.tsx +24 -12
- package/src/wizard.ts +3 -2
package/src/i18n/locales/it.ts
CHANGED
|
@@ -42,6 +42,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
42
42
|
},
|
|
43
43
|
pageContents: { title: "In questa pagina" },
|
|
44
44
|
common: {
|
|
45
|
+
dismiss: "Chiudi",
|
|
45
46
|
close: "Chiudi",
|
|
46
47
|
clear: "Cancella",
|
|
47
48
|
search: "Cerca",
|
|
@@ -125,6 +126,9 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
125
126
|
month: (monthYear) => monthYear,
|
|
126
127
|
},
|
|
127
128
|
datePicker: {
|
|
129
|
+
apply: "Applica",
|
|
130
|
+
cancel: "Annulla",
|
|
131
|
+
presets: "Intervalli rapidi",
|
|
128
132
|
panel: "Scegli una data",
|
|
129
133
|
rangePanel: "Scegli un intervallo di date",
|
|
130
134
|
clear: "Cancella",
|
|
@@ -241,6 +245,12 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
241
245
|
size: (bytes) => formatFileSize(bytes, numberLocale),
|
|
242
246
|
},
|
|
243
247
|
filePicker: {
|
|
248
|
+
dropzone: "Caricamento file",
|
|
249
|
+
browse: "Sfoglia",
|
|
250
|
+
empty: "Trascina qui un file",
|
|
251
|
+
emptyMultiple: "Trascina qui i file",
|
|
252
|
+
hint: (accept) => (accept ? `Accettati: ${accept}` : "Qualsiasi tipo di file"),
|
|
253
|
+
busy: "Caricamento…",
|
|
244
254
|
rejectedPick: (count) =>
|
|
245
255
|
count === 1 ? "Il file non è stato aggiunto" : `Nessuno dei ${count} file è stato aggiunto`,
|
|
246
256
|
// "file" is invariable in Italian ("1 file", "3 file"); only the verb agrees.
|
|
@@ -260,6 +270,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
260
270
|
cleared: "Tutti i file rimossi",
|
|
261
271
|
},
|
|
262
272
|
wizard: {
|
|
273
|
+
done: "Fine",
|
|
263
274
|
cancel: "Annulla",
|
|
264
275
|
back: "Indietro",
|
|
265
276
|
next: "Avanti",
|
|
@@ -354,6 +365,20 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
|
|
|
354
365
|
empty: "Nessun dato",
|
|
355
366
|
legend: "Serie",
|
|
356
367
|
},
|
|
368
|
+
confirmDialog: {
|
|
369
|
+
confirm: "Conferma",
|
|
370
|
+
cancel: "Annulla",
|
|
371
|
+
},
|
|
372
|
+
floatingPanel: {
|
|
373
|
+
close: "Chiudi",
|
|
374
|
+
},
|
|
375
|
+
copyButton: {
|
|
376
|
+
copy: "Copia",
|
|
377
|
+
copied: "Copiato",
|
|
378
|
+
failed: "Copia non riuscita",
|
|
379
|
+
copiedAnnouncement: "Copiato negli appunti",
|
|
380
|
+
failedAnnouncement: "Impossibile copiare negli appunti",
|
|
381
|
+
},
|
|
357
382
|
};
|
|
358
383
|
}
|
|
359
384
|
|
package/src/i18n/locales/zh.ts
CHANGED
|
@@ -40,6 +40,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
40
40
|
},
|
|
41
41
|
pageContents: { title: "本页目录" },
|
|
42
42
|
common: {
|
|
43
|
+
dismiss: "关闭",
|
|
43
44
|
close: "关闭",
|
|
44
45
|
clear: "清除",
|
|
45
46
|
search: "搜索",
|
|
@@ -120,6 +121,9 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
120
121
|
month: (monthYear) => monthYear,
|
|
121
122
|
},
|
|
122
123
|
datePicker: {
|
|
124
|
+
apply: "应用",
|
|
125
|
+
cancel: "取消",
|
|
126
|
+
presets: "快捷范围",
|
|
123
127
|
panel: "选择日期",
|
|
124
128
|
rangePanel: "选择日期范围",
|
|
125
129
|
clear: "清除",
|
|
@@ -235,6 +239,12 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
235
239
|
size: (bytes) => formatFileSize(bytes, numberLocale),
|
|
236
240
|
},
|
|
237
241
|
filePicker: {
|
|
242
|
+
dropzone: "文件上传",
|
|
243
|
+
browse: "浏览",
|
|
244
|
+
empty: "将文件拖放到此处",
|
|
245
|
+
emptyMultiple: "将文件拖放到此处",
|
|
246
|
+
hint: (accept) => (accept ? `支持:${accept}` : "任意文件类型"),
|
|
247
|
+
busy: "正在上传…",
|
|
238
248
|
rejectedPick: (count) => (count === 1 ? "未添加该文件" : `${count} 个文件均未添加`),
|
|
239
249
|
rejectedType: (name) => `“${name}”的文件类型不受支持`,
|
|
240
250
|
rejectedTypeOnly: (accept) => `仅限 ${accept} 文件`,
|
|
@@ -250,6 +260,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
250
260
|
cleared: "已移除所有文件",
|
|
251
261
|
},
|
|
252
262
|
wizard: {
|
|
263
|
+
done: "完成",
|
|
253
264
|
cancel: "取消",
|
|
254
265
|
back: "上一步",
|
|
255
266
|
next: "下一步",
|
|
@@ -342,6 +353,20 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
|
|
|
342
353
|
empty: "无数据",
|
|
343
354
|
legend: "数据系列",
|
|
344
355
|
},
|
|
356
|
+
confirmDialog: {
|
|
357
|
+
confirm: "确认",
|
|
358
|
+
cancel: "取消",
|
|
359
|
+
},
|
|
360
|
+
floatingPanel: {
|
|
361
|
+
close: "关闭",
|
|
362
|
+
},
|
|
363
|
+
copyButton: {
|
|
364
|
+
copy: "复制",
|
|
365
|
+
copied: "已复制",
|
|
366
|
+
failed: "复制失败",
|
|
367
|
+
copiedAnnouncement: "已复制到剪贴板",
|
|
368
|
+
failedAnnouncement: "无法复制到剪贴板",
|
|
369
|
+
},
|
|
345
370
|
};
|
|
346
371
|
}
|
|
347
372
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swiss Standard German respelling, shared by the `de-CH` and `de-CH-informal`
|
|
3
|
+
* translations. Internal: it lives outside `src/i18n/locales/` because every file there
|
|
4
|
+
* becomes a public `@eifi1/ui-kit/i18n/<code>` subpath.
|
|
5
|
+
*
|
|
6
|
+
* "ss" for every "ß" (Schliessen, Grösse) is the one systematic difference in UI text.
|
|
7
|
+
* Function labels are wrapped: their RESULT is respelled, so an argument the app passes
|
|
8
|
+
* in (a file name, a phrase to type) is respelled too — which is what a Swiss app
|
|
9
|
+
* writing "ss" everywhere wants.
|
|
10
|
+
*/
|
|
11
|
+
export function swiss<T>(value: T): T {
|
|
12
|
+
if (typeof value === "string") return respell(value) as T;
|
|
13
|
+
if (typeof value === "function") {
|
|
14
|
+
const fn = value as (...args: unknown[]) => unknown;
|
|
15
|
+
return ((...args: unknown[]) => swiss(fn(...args))) as T;
|
|
16
|
+
}
|
|
17
|
+
if (value && typeof value === "object") {
|
|
18
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, swiss(v)])) as T;
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function respell(text: string): string {
|
|
24
|
+
return text.replace(/ß/g, "ss").replace(/ẞ/g, "SS");
|
|
25
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,15 @@ export { useOverlayHistory } from "./hooks/use-overlay-history";
|
|
|
32
32
|
export { useCloseTransition, OVERLAY_EXIT_MS } from "./hooks/use-close-transition";
|
|
33
33
|
export { useRowSwipe } from "./hooks/use-row-swipe";
|
|
34
34
|
export type { SwipeStage, RowSwipeOptions, RowSwipeReturn } from "./hooks/use-row-swipe";
|
|
35
|
+
// Any element as a file drop target, screened like FileButton (kastlan).
|
|
36
|
+
export { useFileDrop, dragHasFiles } from "./hooks/use-file-drop";
|
|
37
|
+
export type { UseFileDropOptions, UseFileDropReturn, FileDropProps } from "./hooks/use-file-drop";
|
|
38
|
+
// kastlan's search boxes each carried a hand-rolled copy, two without the cleanup.
|
|
39
|
+
export { useDebounce, useDebouncedCallback } from "./hooks/use-debounce";
|
|
40
|
+
export type { DebouncedCallbackOptions, DebouncedFunction } from "./hooks/use-debounce";
|
|
41
|
+
// Copy that reports whether it worked — keksdose's copy buttons said "Copied" when it had not.
|
|
42
|
+
export { useCopyToClipboard, copyToClipboard } from "./hooks/use-copy-to-clipboard";
|
|
43
|
+
export type { CopyState, UseCopyToClipboardOptions, UseCopyToClipboardReturn } from "./hooks/use-copy-to-clipboard";
|
|
35
44
|
|
|
36
45
|
// ── theme / palettes ─────────────────────────────────────────────────────────
|
|
37
46
|
export * from "./theme/chart-palette";
|
|
@@ -83,6 +92,10 @@ export * from "./components/page-contents";
|
|
|
83
92
|
export * from "./components/disclosure";
|
|
84
93
|
export * from "./components/dialog-frame";
|
|
85
94
|
export * from "./components/danger-confirm";
|
|
95
|
+
// `useConfirm()` — the promise-based replacement for `window.confirm`, one host per app.
|
|
96
|
+
export * from "./components/confirm-dialog";
|
|
97
|
+
// A non-modal corner panel and its round trigger (keksdose's assistant launcher).
|
|
98
|
+
export * from "./components/floating-panel";
|
|
86
99
|
export * from "./components/swatch-picker";
|
|
87
100
|
export * from "./components/icon-picker";
|
|
88
101
|
export * from "./components/choice-card";
|
|
@@ -123,7 +136,21 @@ export * from "./components/grouped-picker";
|
|
|
123
136
|
export * from "./components/file-dropzone";
|
|
124
137
|
export * from "./components/mini-calendar";
|
|
125
138
|
export * from "./components/date-picker";
|
|
139
|
+
export * from "./components/tree-view";
|
|
126
140
|
export * from "./components/chart";
|
|
141
|
+
// 0.8.0 layout and feedback primitives the apps hand-rolled or took from shadcn/Radix.
|
|
142
|
+
export * from "./components/description-list";
|
|
143
|
+
export * from "./components/progress-bar";
|
|
144
|
+
// Named: skeleton.tsx also holds SKELETON_CLASS, the look StatTile shares — internal.
|
|
145
|
+
export { Skeleton } from "./components/skeleton";
|
|
146
|
+
export type { SkeletonProps, SkeletonShape } from "./components/skeleton";
|
|
147
|
+
export * from "./components/copy-button";
|
|
148
|
+
export * from "./components/button-group";
|
|
149
|
+
export * from "./components/table";
|
|
150
|
+
export * from "./components/separator";
|
|
151
|
+
// Named: scroll-area.tsx also holds the overflow hook and scrollbar class Table shares.
|
|
152
|
+
export { ScrollArea } from "./components/scroll-area";
|
|
153
|
+
export type { ScrollAreaProps } from "./components/scroll-area";
|
|
127
154
|
|
|
128
155
|
// ── data-table suite ─────────────────────────────────────────────────────────
|
|
129
156
|
export * from "./components/data-table-labels";
|
|
@@ -140,6 +167,8 @@ export type {
|
|
|
140
167
|
ServerPagination,
|
|
141
168
|
FilterState,
|
|
142
169
|
MobileSwipeActions,
|
|
170
|
+
DataTableDensity,
|
|
171
|
+
DataTableChrome,
|
|
143
172
|
} from "./components/data-table";
|
|
144
173
|
|
|
145
174
|
// ── shell (composable app chrome) ────────────────────────────────────────────
|
package/src/lib/dates.ts
CHANGED
|
@@ -185,3 +185,51 @@ export function dateRangePresets(): DateRangePreset[] {
|
|
|
185
185
|
{ key: "last_year", from: startOfYearIso(-1), to: endOfYearIso(-1) },
|
|
186
186
|
];
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
/** The last `n` WHOLE calendar months before the current one, as an inclusive
|
|
190
|
+
* `{ from, to }` — on 15 May, `n = 3` is 1 February … 30 April. Local-calendar, like
|
|
191
|
+
* everything here; `now` is for tests and for a host with its own clock. */
|
|
192
|
+
export function lastFullMonthsRange(n: number, now: Date = new Date()): { from: string; to: string } {
|
|
193
|
+
const from = new Date(now.getFullYear(), now.getMonth() - n, 1);
|
|
194
|
+
// Day 0 of the current month is the last day of the previous one.
|
|
195
|
+
const to = new Date(now.getFullYear(), now.getMonth(), 0);
|
|
196
|
+
return { from: toLocalIso(from), to: toLocalIso(to) };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** The last `n` WHOLE calendar years before the current one — in 2026, `n = 2` is
|
|
200
|
+
* 1 January 2024 … 31 December 2025. */
|
|
201
|
+
export function lastFullYearsRange(n: number, now: Date = new Date()): { from: string; to: string } {
|
|
202
|
+
const year = now.getFullYear();
|
|
203
|
+
return { from: `${year - n}-01-01`, to: `${year - 1}-12-31` };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface CalendarMonthPresetOptions {
|
|
207
|
+
/** How many whole months each month preset spans. Default `[3, 6]`. */
|
|
208
|
+
months?: readonly number[];
|
|
209
|
+
/** How many whole years each year preset spans. Default `[2]`. */
|
|
210
|
+
years?: readonly number[];
|
|
211
|
+
/** The day to count back from. Default: now, on the local calendar. */
|
|
212
|
+
now?: Date;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Month-ALIGNED presets, beside the rolling-day ones of {@link dateRangePresets}.
|
|
217
|
+
*
|
|
218
|
+
* kastlan's accounting reports are closed by the month: "the last three months" there
|
|
219
|
+
* means February, March and April, never "the 90 days up to this morning", which
|
|
220
|
+
* starts mid-month and ends with a day that is still being booked. `last_3_months`
|
|
221
|
+
* above is the rolling kind and stays that way for the data table's filter, so these
|
|
222
|
+
* are separate keys rather than a changed meaning under an old one.
|
|
223
|
+
*
|
|
224
|
+
* Keys are `last_<n>_full_months` and `last_<n>_full_years`; the host labels them (the
|
|
225
|
+
* same arrangement as `dateRangePresets`' keys and its `table.preset_*` strings). With
|
|
226
|
+
* `DateRangePicker`, pass the key as the preset's `id` so the choice keeps its
|
|
227
|
+
* identity while the dates are recomputed on every render.
|
|
228
|
+
*/
|
|
229
|
+
export function calendarMonthPresets(options: CalendarMonthPresetOptions = {}): DateRangePreset[] {
|
|
230
|
+
const { months = [3, 6], years = [2], now = new Date() } = options;
|
|
231
|
+
return [
|
|
232
|
+
...months.map((n) => ({ key: `last_${n}_full_months`, ...lastFullMonthsRange(n, now) })),
|
|
233
|
+
...years.map((n) => ({ key: `last_${n}_full_years`, ...lastFullYearsRange(n, now) })),
|
|
234
|
+
];
|
|
235
|
+
}
|
|
@@ -91,6 +91,20 @@ interface CommandPaletteProps {
|
|
|
91
91
|
* another keystroke. That is the ordinary first open on a cold cache.
|
|
92
92
|
*/
|
|
93
93
|
revision?: unknown;
|
|
94
|
+
/**
|
|
95
|
+
* The search text, CONTROLLED. Keksdose binds it to the URL's `q`, so a search
|
|
96
|
+
* survives a reload, can be shared as a link, and Back walks through it — none of
|
|
97
|
+
* which state inside the palette can do.
|
|
98
|
+
*
|
|
99
|
+
* Leave it out and the palette keeps its own, as before, starting empty on every
|
|
100
|
+
* open. Given, it is never reset on open: the owner decides what an open shows, and
|
|
101
|
+
* a palette that blanked the URL's `q` each time would fight its own address bar.
|
|
102
|
+
* `onQueryChange` is then how a keystroke gets anywhere, since it is the only writer.
|
|
103
|
+
*/
|
|
104
|
+
query?: string;
|
|
105
|
+
/** Every edit of the field's text. Fires in both modes — uncontrolled, it only
|
|
106
|
+
* observes. */
|
|
107
|
+
onQueryChange?: (query: string) => void;
|
|
94
108
|
labels?: Partial<CommandPaletteLabels>;
|
|
95
109
|
}
|
|
96
110
|
|
|
@@ -119,6 +133,8 @@ export function CommandPalette({
|
|
|
119
133
|
onClose,
|
|
120
134
|
search,
|
|
121
135
|
revision,
|
|
136
|
+
query: queryProp,
|
|
137
|
+
onQueryChange,
|
|
122
138
|
labels,
|
|
123
139
|
}: CommandPaletteProps) {
|
|
124
140
|
const l = useKitLabels("commandPalette", DEFAULTS_WITHOUT_DIALOG, labels);
|
|
@@ -129,7 +145,13 @@ export function CommandPalette({
|
|
|
129
145
|
const dialogName =
|
|
130
146
|
l.dialog ??
|
|
131
147
|
(l.placeholder !== DEFAULT_COMMAND_PALETTE_LABELS.placeholder ? l.placeholder : DEFAULT_DIALOG_NAME);
|
|
132
|
-
const [
|
|
148
|
+
const [ownQuery, setOwnQuery] = useState("");
|
|
149
|
+
const controlled = queryProp !== undefined;
|
|
150
|
+
const query = controlled ? queryProp : ownQuery;
|
|
151
|
+
const setQuery = (next: string) => {
|
|
152
|
+
if (!controlled) setOwnQuery(next);
|
|
153
|
+
onQueryChange?.(next);
|
|
154
|
+
};
|
|
133
155
|
const [results, setResults] = useState<CommandItem[]>([]);
|
|
134
156
|
const [loading, setLoading] = useState(false);
|
|
135
157
|
const [failed, setFailed] = useState(false);
|
|
@@ -160,11 +182,15 @@ export function CommandPalette({
|
|
|
160
182
|
useFocusTrap(dialogRef, { active: open, initialFocus: () => inputRef.current });
|
|
161
183
|
useBodyScrollLock(open);
|
|
162
184
|
|
|
163
|
-
// Reset when opened
|
|
185
|
+
// Reset when opened — the palette's OWN text only; a controlled query belongs to
|
|
186
|
+
// its owner (see the prop).
|
|
164
187
|
useEffect(() => {
|
|
165
188
|
if (!open) return;
|
|
166
|
-
|
|
189
|
+
if (!controlled) setOwnQuery("");
|
|
167
190
|
setActive(0);
|
|
191
|
+
// `controlled` is read at the open, not watched: a caller switching modes while
|
|
192
|
+
// the palette is open is not a reason to wipe what is typed.
|
|
193
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
194
|
}, [open]);
|
|
169
195
|
|
|
170
196
|
// Debounced, race-safe search.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useId, useMemo } from "react";
|
|
2
|
-
import type { ReactNode } from "react";
|
|
2
|
+
import type { ReactElement, ReactNode } from "react";
|
|
3
3
|
import { Check } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/cn";
|
|
5
|
-
import { Button } from "../components/ui";
|
|
5
|
+
import { Button, Spinner } from "../components/ui";
|
|
6
|
+
import type { ButtonVariant } from "../components/ui";
|
|
6
7
|
import { Modal } from "../components/modal";
|
|
7
8
|
import { alertFrameClass } from "../components/alert-banner";
|
|
8
9
|
import { WizardContextProvider } from "./wizard-context";
|
|
@@ -25,6 +26,12 @@ import type { UseWizardReturn, WizardLabels } from "./types";
|
|
|
25
26
|
* The `<StepperNav>` is also what publishes {@link WizardContextProvider}, which
|
|
26
27
|
* is how a mounted step registers its own validator. A step's
|
|
27
28
|
* `useRhfWizardStep` / `useWizardStepValidate` therefore only works inside one.
|
|
29
|
+
*
|
|
30
|
+
* Which buttons show is read off `wizard` (`canCancel`, `canGoBack`, `canDone`,
|
|
31
|
+
* `isCommitStep`), so the policy options — `cancellable`, `onExit`, `commits`,
|
|
32
|
+
* `onDone` — are all set on `useWizard`. What is set HERE is how the Finish button
|
|
33
|
+
* looks and what wraps it, because that is presentation and usually depends on
|
|
34
|
+
* state the app holds (a "replace everything" toggle, a write lock).
|
|
28
35
|
*/
|
|
29
36
|
export function StepperNav<TData extends Record<string, unknown>>({
|
|
30
37
|
wizard,
|
|
@@ -33,6 +40,9 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
33
40
|
description,
|
|
34
41
|
className,
|
|
35
42
|
labels,
|
|
43
|
+
finishVariant = "brand",
|
|
44
|
+
finishDisabled = false,
|
|
45
|
+
renderFinish,
|
|
36
46
|
}: {
|
|
37
47
|
wizard: UseWizardReturn<TData>;
|
|
38
48
|
children: ReactNode;
|
|
@@ -40,6 +50,30 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
40
50
|
description?: ReactNode;
|
|
41
51
|
className?: string;
|
|
42
52
|
labels?: Partial<WizardLabels>;
|
|
53
|
+
/** The Finish button's variant — `"danger"` for a destructive commit (an import
|
|
54
|
+
* that replaces what is there). Default `"brand"`. Next keeps `"brand"`. */
|
|
55
|
+
finishVariant?: ButtonVariant;
|
|
56
|
+
/** Disable Finish for a reason of the app's own, on top of the kit's gates
|
|
57
|
+
* (`canFinish`, submitting, validating). */
|
|
58
|
+
finishDisabled?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Wrap the Finish button: receives the kit's button element — already labelled,
|
|
61
|
+
* gated and wired to `wizard.finish` — and returns what to render in its place.
|
|
62
|
+
*
|
|
63
|
+
* ```tsx
|
|
64
|
+
* renderFinish={(button) => <SaveGuard lock={lock}>{button}</SaveGuard>}
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* A wrapper and not a replacement (`finishButton: (props) => …`) or a hook
|
|
68
|
+
* (`onBeforeFinish`): the app's need is to put something AROUND the commit — a
|
|
69
|
+
* write-lock tooltip, which has to sit on a wrapping element because a disabled
|
|
70
|
+
* button receives no pointer events — while the kit keeps owning what the button
|
|
71
|
+
* says and when it is enabled. A replacement would hand every app the gating to
|
|
72
|
+
* re-derive (the problem this component exists to end); `onBeforeFinish` can veto
|
|
73
|
+
* a click but cannot explain a disabled button. The element is a plain `<Button>`,
|
|
74
|
+
* so a wrapper that clones it with `disabled` works as expected.
|
|
75
|
+
*/
|
|
76
|
+
renderFinish?: (button: ReactElement) => ReactNode;
|
|
43
77
|
}) {
|
|
44
78
|
const l = useKitLabels("wizard", DEFAULT_WIZARD_LABELS, labels);
|
|
45
79
|
const cancelTitleId = useId();
|
|
@@ -57,6 +91,51 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
57
91
|
// (empty data after a reload) from firing an empty submit.
|
|
58
92
|
const canFinish = wizard.canFinish;
|
|
59
93
|
|
|
94
|
+
// Pending while the step's validators run (an async server check): a spinner and
|
|
95
|
+
// `aria-busy`, and disabled — the hook's own re-entry guard still refuses a second
|
|
96
|
+
// press, this just stops it looking like the first one did nothing.
|
|
97
|
+
const pending = wizard.isValidating;
|
|
98
|
+
const stepNextLabel = wizard.currentStep.nextLabel;
|
|
99
|
+
|
|
100
|
+
let forwardButton: ReactNode = null;
|
|
101
|
+
if (wizard.isCommitStep) {
|
|
102
|
+
const finishButton = (
|
|
103
|
+
<Button
|
|
104
|
+
variant={finishVariant}
|
|
105
|
+
data-tour="wizard-finish"
|
|
106
|
+
onClick={wizard.finish}
|
|
107
|
+
disabled={wizard.isSubmitting || pending || !canFinish || finishDisabled}
|
|
108
|
+
aria-busy={wizard.isSubmitting || pending || undefined}
|
|
109
|
+
>
|
|
110
|
+
{/* Decorative: the button's text is what a reader hears. */}
|
|
111
|
+
{pending && <Spinner label={null} className="size-4" />}
|
|
112
|
+
{wizard.isSubmitting ? l.submitting : (stepNextLabel ?? l.finish)}
|
|
113
|
+
</Button>
|
|
114
|
+
);
|
|
115
|
+
forwardButton = renderFinish ? renderFinish(finishButton) : finishButton;
|
|
116
|
+
} else if (wizard.isLastStep) {
|
|
117
|
+
// The last step after a commit. Nothing to finish; Done only when the app gave
|
|
118
|
+
// an `onDone` — otherwise the step carries its own way out.
|
|
119
|
+
forwardButton = wizard.canDone ? (
|
|
120
|
+
<Button variant="brand" data-tour="wizard-done" onClick={wizard.done}>
|
|
121
|
+
{stepNextLabel ?? l.done ?? DEFAULT_WIZARD_LABELS.done}
|
|
122
|
+
</Button>
|
|
123
|
+
) : null;
|
|
124
|
+
} else {
|
|
125
|
+
forwardButton = (
|
|
126
|
+
<Button
|
|
127
|
+
variant="brand"
|
|
128
|
+
data-tour="wizard-next"
|
|
129
|
+
onClick={wizard.goNext}
|
|
130
|
+
disabled={wizard.nextBlocked || pending}
|
|
131
|
+
aria-busy={pending || undefined}
|
|
132
|
+
>
|
|
133
|
+
{pending && <Spinner label={null} className="size-4" />}
|
|
134
|
+
{stepNextLabel ?? l.next}
|
|
135
|
+
</Button>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
60
139
|
return (
|
|
61
140
|
<div className={cn("flex flex-col gap-6", className)}>
|
|
62
141
|
{(title || description) && (
|
|
@@ -84,7 +163,9 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
84
163
|
<button
|
|
85
164
|
type="button"
|
|
86
165
|
onClick={() => wizard.goToStep(index)}
|
|
87
|
-
|
|
166
|
+
// `canGoToStep` adds the commit lock: once past a committing step,
|
|
167
|
+
// the steps up to it are done with and cannot be reopened.
|
|
168
|
+
disabled={status === "upcoming" || !wizard.canGoToStep(index)}
|
|
88
169
|
aria-current={status === "active" ? "step" : undefined}
|
|
89
170
|
className={cn(
|
|
90
171
|
"group flex flex-col items-center gap-1.5",
|
|
@@ -161,39 +242,32 @@ export function StepperNav<TData extends Record<string, unknown>>({
|
|
|
161
242
|
|
|
162
243
|
{/* Navigation bar */}
|
|
163
244
|
<div className="flex items-center justify-between border-t border-[var(--border)] pt-4">
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
245
|
+
{/* The empty span keeps the right-hand group on the right when Cancel is off. */}
|
|
246
|
+
{wizard.canCancel ? (
|
|
247
|
+
<Button variant="secondary" onClick={wizard.cancel}>
|
|
248
|
+
{l.cancel}
|
|
249
|
+
</Button>
|
|
250
|
+
) : (
|
|
251
|
+
<span />
|
|
252
|
+
)}
|
|
167
253
|
<div className="flex items-center gap-2">
|
|
168
|
-
{
|
|
169
|
-
|
|
254
|
+
{wizard.canGoBack && (
|
|
255
|
+
// Disabled, not hidden, while the commit runs: it comes back if the commit
|
|
256
|
+
// fails, and a button that vanishes mid-click is its own kind of surprise.
|
|
257
|
+
<Button variant="secondary" onClick={wizard.goBack} disabled={wizard.isSubmitting}>
|
|
170
258
|
{l.back}
|
|
171
259
|
</Button>
|
|
172
260
|
)}
|
|
173
|
-
{wizard.currentStep.optional && !wizard.isLastStep && (
|
|
174
|
-
<Button variant="ghost" onClick={wizard.skip} disabled={wizard.nextBlocked}>
|
|
175
|
-
{l.skip}
|
|
176
|
-
</Button>
|
|
177
|
-
)}
|
|
178
|
-
{wizard.isLastStep ? (
|
|
261
|
+
{wizard.currentStep.optional && !wizard.isLastStep && !wizard.isCommitStep && (
|
|
179
262
|
<Button
|
|
180
|
-
variant="
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
disabled={wizard.isSubmitting || !canFinish}
|
|
263
|
+
variant="ghost"
|
|
264
|
+
onClick={wizard.skip}
|
|
265
|
+
disabled={wizard.nextBlocked || wizard.isValidating}
|
|
184
266
|
>
|
|
185
|
-
{
|
|
186
|
-
</Button>
|
|
187
|
-
) : (
|
|
188
|
-
<Button
|
|
189
|
-
variant="brand"
|
|
190
|
-
data-tour="wizard-next"
|
|
191
|
-
onClick={wizard.goNext}
|
|
192
|
-
disabled={wizard.nextBlocked}
|
|
193
|
-
>
|
|
194
|
-
{l.next}
|
|
267
|
+
{l.skip}
|
|
195
268
|
</Button>
|
|
196
269
|
)}
|
|
270
|
+
{forwardButton}
|
|
197
271
|
</div>
|
|
198
272
|
</div>
|
|
199
273
|
|
package/src/wizard/types.ts
CHANGED
|
@@ -24,13 +24,83 @@ export interface WizardStepConfig {
|
|
|
24
24
|
* per-field error messages via `wizard.fieldErrors`.
|
|
25
25
|
*/
|
|
26
26
|
validate?: () => ValidateResult | Promise<ValidateResult>;
|
|
27
|
+
/**
|
|
28
|
+
* The forward button's label on THIS step, in place of `labels.next` — "Analyse",
|
|
29
|
+
* "Import", "Send code". On the committing step (see {@link commits}) it labels
|
|
30
|
+
* Finish instead of `labels.finish`; `labels.submitting` still replaces it while
|
|
31
|
+
* the commit runs. Like `label`, a node the app has already translated.
|
|
32
|
+
*/
|
|
33
|
+
nextLabel?: ReactNode;
|
|
34
|
+
/**
|
|
35
|
+
* This step's forward button COMMITS: it runs `onComplete` (via `finish`) instead
|
|
36
|
+
* of just advancing. Without the flag the last step commits, as it always has.
|
|
37
|
+
*
|
|
38
|
+
* Flag a step before the last and every step after it becomes a post-commit step —
|
|
39
|
+
* an import's "here is what happened, set up recurring rules?" page:
|
|
40
|
+
* - a successful `onComplete` advances onto the next step (a failed one stays, with
|
|
41
|
+
* `wizard.error`, exactly like a last-step Finish);
|
|
42
|
+
* - from there Back, the indicator and `goBack`/`goToStep` cannot cross back over
|
|
43
|
+
* the commit, and Cancel is hidden — there is nothing left to discard;
|
|
44
|
+
* - the last post-commit step's button is Done, which calls `onDone` (and is not
|
|
45
|
+
* shown when there is no `onDone`: the step then carries its own way out).
|
|
46
|
+
*
|
|
47
|
+
* Only the FIRST flagged step counts. Steps after it may still validate, skip and
|
|
48
|
+
* use `nextLabel` like any other.
|
|
49
|
+
*/
|
|
50
|
+
commits?: boolean;
|
|
27
51
|
}
|
|
28
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Where the active step lives. `true` (the default) mirrors it to `?step=N` through
|
|
55
|
+
* react-router, as every wizard before 0.8 did; `{ param }` renames that param (two
|
|
56
|
+
* wizards on one page, or a page that already uses `step`); `false` keeps it in
|
|
57
|
+
* memory only and needs no router at all.
|
|
58
|
+
*
|
|
59
|
+
* Read once, at mount: switching it on a mounted wizard has no effect.
|
|
60
|
+
*/
|
|
61
|
+
export type WizardUrlSync = boolean | { param?: string };
|
|
62
|
+
|
|
29
63
|
export interface UseWizardOptions<TData extends Record<string, unknown>> {
|
|
30
64
|
steps: WizardStepConfig[];
|
|
31
65
|
initialData?: Partial<TData>;
|
|
32
66
|
onComplete?: (data: TData) => void | Promise<void>;
|
|
33
67
|
onCancel?: () => void;
|
|
68
|
+
/**
|
|
69
|
+
* Leave the wizard BACKWARDS: when given, Back is shown on the first step too and
|
|
70
|
+
* calls this — keksdose's import panel, whose first step's Back returns to the
|
|
71
|
+
* dropzone the file came from. No confirm dialog: going back is not discarding.
|
|
72
|
+
*/
|
|
73
|
+
onExit?: () => void;
|
|
74
|
+
/**
|
|
75
|
+
* The Done button on the last step after a committing step (see
|
|
76
|
+
* {@link WizardStepConfig.commits}). Without it no Done button is rendered, and the
|
|
77
|
+
* post-commit step is expected to carry its own way out.
|
|
78
|
+
*/
|
|
79
|
+
onDone?: () => void;
|
|
80
|
+
/**
|
|
81
|
+
* Whether the chrome offers Cancel at all. Default `true`. `false` hides the button
|
|
82
|
+
* (`wizard.canCancel` is false); `wizard.cancel()` still works if the app calls it.
|
|
83
|
+
*/
|
|
84
|
+
cancellable?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Whether Cancel asks first ("Discard this form?"). Default `true`. `false` makes
|
|
87
|
+
* `cancel()` call `onCancel` straight away — for a wizard that has nothing to lose
|
|
88
|
+
* yet, or one whose data is kept elsewhere.
|
|
89
|
+
*/
|
|
90
|
+
confirmCancel?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Mirror the active step to the URL. Default `true` (`?step=N`, needs a router).
|
|
93
|
+
* See {@link WizardUrlSync}.
|
|
94
|
+
*
|
|
95
|
+
* The param is WRITTEN only, never read back (see the note in `use-wizard.ts`). It
|
|
96
|
+
* is removed again when the wizard is left through the kit: a confirmed Cancel,
|
|
97
|
+
* `onExit` from the first step's Back, and Done — each before the app's callback
|
|
98
|
+
* runs, so an app that navigates away navigates from a clean URL. It is NOT removed
|
|
99
|
+
* after `onComplete` on a last step: an app navigating away there would otherwise
|
|
100
|
+
* be pulled back by the kit's own URL write. An app that stays on the page after a
|
|
101
|
+
* final Finish drops the param itself, or uses `urlSync: false`.
|
|
102
|
+
*/
|
|
103
|
+
urlSync?: WizardUrlSync;
|
|
34
104
|
/**
|
|
35
105
|
* Shown when a step blocks with no per-field detail — an RHF step surfacing
|
|
36
106
|
* its own inline messages, say — so the user learns why the wizard did not
|
|
@@ -55,14 +125,35 @@ export interface UseWizardReturn<TData extends Record<string, unknown>> {
|
|
|
55
125
|
steps: WizardStepConfig[];
|
|
56
126
|
isFirstStep: boolean;
|
|
57
127
|
isLastStep: boolean;
|
|
58
|
-
/** True once every step before the
|
|
128
|
+
/** True once every step before the committing one (the last, unless a step is
|
|
129
|
+
* flagged `commits`) has been completed or skipped. */
|
|
59
130
|
canFinish: boolean;
|
|
131
|
+
/** The step whose forward button commits: the first `commits` step, else the last. */
|
|
132
|
+
commitStepIndex: number;
|
|
133
|
+
/** The active step is {@link commitStepIndex}: the chrome shows Finish, not Next. */
|
|
134
|
+
isCommitStep: boolean;
|
|
135
|
+
/** `onComplete` has resolved. Once the active step is past the committing one, Back
|
|
136
|
+
* cannot cross it and Cancel is hidden. */
|
|
137
|
+
committed: boolean;
|
|
138
|
+
/** Whether Back is offered here: not on the first step unless `onExit` was given,
|
|
139
|
+
* and never back across a commit. (Disabled, not hidden, while `isSubmitting`.) */
|
|
140
|
+
canGoBack: boolean;
|
|
141
|
+
/** Whether Cancel is offered: `cancellable`, and not after a commit. */
|
|
142
|
+
canCancel: boolean;
|
|
143
|
+
/** Whether Done is offered: the last step, past a commit, with an `onDone`. */
|
|
144
|
+
canDone: boolean;
|
|
145
|
+
/** Whether the indicator may jump to `index` — a completed/skipped/active step on
|
|
146
|
+
* the right side of any commit. `goToStep` refuses anything this rejects. */
|
|
147
|
+
canGoToStep: (index: number) => boolean;
|
|
60
148
|
goNext: () => Promise<void>;
|
|
149
|
+
/** One step back — or, on the first step, `onExit`. Refused while submitting. */
|
|
61
150
|
goBack: () => void;
|
|
62
151
|
goToStep: (index: number) => void;
|
|
63
152
|
skip: () => void;
|
|
64
153
|
cancel: () => void;
|
|
65
154
|
finish: () => Promise<void>;
|
|
155
|
+
/** Leave after the post-commit steps: drops the URL param, then calls `onDone`. */
|
|
156
|
+
done: () => void;
|
|
66
157
|
/** Register a validator for the mounted step (used by useRhfWizardStep). */
|
|
67
158
|
registerStepValidate: (
|
|
68
159
|
fn: () => ValidateResult | Promise<ValidateResult>,
|
|
@@ -82,6 +173,9 @@ export interface UseWizardReturn<TData extends Record<string, unknown>> {
|
|
|
82
173
|
|
|
83
174
|
// Loading / error
|
|
84
175
|
isSubmitting: boolean;
|
|
176
|
+
/** A `goNext`/`finish` is running the step's validators (an async server check,
|
|
177
|
+
* say). The chrome shows Next pending — spinner, `aria-busy`, disabled. */
|
|
178
|
+
isValidating: boolean;
|
|
85
179
|
error: string | null;
|
|
86
180
|
clearError: () => void;
|
|
87
181
|
|
|
@@ -102,8 +196,12 @@ export interface SummaryItem {
|
|
|
102
196
|
|
|
103
197
|
export interface SummarySection {
|
|
104
198
|
label: ReactNode;
|
|
105
|
-
/**
|
|
106
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Which step the section's edit button jumps back to. Leave it out for a section
|
|
201
|
+
* that belongs to no step — file-level counts, a total — and the card has no edit
|
|
202
|
+
* button, instead of borrowing one from whichever step it was parked under.
|
|
203
|
+
*/
|
|
204
|
+
stepIndex?: number;
|
|
107
205
|
items: SummaryItem[];
|
|
108
206
|
}
|
|
109
207
|
|
|
@@ -149,12 +247,15 @@ export interface WizardLabels {
|
|
|
149
247
|
/** `wizard.error` when `onComplete` rejects with nothing readable — a thrown
|
|
150
248
|
* non-Error, or an Error whose message is empty. Optional: see above. */
|
|
151
249
|
genericError?: string;
|
|
250
|
+
/** The button on the last step after a committing step (`onDone`). Optional: see
|
|
251
|
+
* `missingRequired`. */
|
|
252
|
+
done?: string;
|
|
152
253
|
}
|
|
153
254
|
|
|
154
255
|
/** Typed with the two optional keys made required, so code reading the defaults
|
|
155
256
|
* directly (the fallback in `useWizard`) needs no `!`. */
|
|
156
257
|
export const DEFAULT_WIZARD_LABELS: WizardLabels &
|
|
157
|
-
Required<Pick<WizardLabels, "missingRequired" | "genericError">> = {
|
|
258
|
+
Required<Pick<WizardLabels, "missingRequired" | "genericError" | "done">> = {
|
|
158
259
|
cancel: "Cancel",
|
|
159
260
|
back: "Back",
|
|
160
261
|
next: "Next",
|
|
@@ -171,6 +272,7 @@ export const DEFAULT_WIZARD_LABELS: WizardLabels &
|
|
|
171
272
|
edit: "Edit",
|
|
172
273
|
missingRequired: "Please fill in all required fields.",
|
|
173
274
|
genericError: "An error occurred",
|
|
275
|
+
done: "Done",
|
|
174
276
|
};
|
|
175
277
|
|
|
176
278
|
export function resolveWizardLabels(labels?: Partial<WizardLabels>): WizardLabels {
|