@eifi1/ui-kit 0.7.0 → 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 +5 -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 +5 -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 +19 -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 +56 -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 +5 -0
- package/dist/components/number-input.d.ts +5 -0
- package/dist/components/numpad-sheet.d.ts +5 -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/settings-fields.d.ts +2 -0
- 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/time-input.d.ts +2 -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/tooltip.d.ts +10 -4
- package/dist/components/tooltip.js +23 -6
- package/dist/components/tooltip.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 +92 -14
- package/dist/components/ui.js +112 -30
- 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 +5 -0
- package/dist/i18n/defaults.js +7 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +17 -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 +5 -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 +5 -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 +5 -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 +5 -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 +5 -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 +5 -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 +5 -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 +26 -11
- 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/rhf/form.d.ts +2 -0
- package/dist/rhf.d.ts +2 -0
- 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/shell/app-shell.js +2 -2
- package/dist/shell/app-shell.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/tooltip.tsx +40 -12
- package/src/components/tree-view.tsx +589 -0
- package/src/components/treemap.tsx +10 -2
- package/src/components/ui.tsx +268 -42
- 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/shell/app-shell.tsx +2 -2
- 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.
|
package/src/shell/app-shell.tsx
CHANGED
|
@@ -299,7 +299,7 @@ export function AppShell({
|
|
|
299
299
|
{sidebarFooter?.(collapsed)}
|
|
300
300
|
<div className="border-t border-[var(--border)] p-2">
|
|
301
301
|
{collapsed ? (
|
|
302
|
-
<Tooltip label={labels.expand} side="
|
|
302
|
+
<Tooltip label={labels.expand} side="end" portal className="block">
|
|
303
303
|
<button
|
|
304
304
|
type="button"
|
|
305
305
|
onClick={() => setCollapsed(false)}
|
|
@@ -569,7 +569,7 @@ function SidebarNavItem({
|
|
|
569
569
|
|
|
570
570
|
if (hasSub) return <SidebarFlyout item={item}>{link}</SidebarFlyout>;
|
|
571
571
|
return collapsed ? (
|
|
572
|
-
<Tooltip label={item.label} side="
|
|
572
|
+
<Tooltip label={item.label} side="end" portal className="block">
|
|
573
573
|
{link}
|
|
574
574
|
</Tooltip>
|
|
575
575
|
) : (
|
|
@@ -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
|
|