@eifi1/ui-kit 0.16.1 → 0.17.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 (86) hide show
  1. package/README.md +7 -7
  2. package/dist/chart.d.ts +1 -1
  3. package/dist/components/amount-input.d.ts +9 -1
  4. package/dist/components/amount-input.js.map +1 -1
  5. package/dist/components/button-group.d.ts +1 -1
  6. package/dist/components/calculator.d.ts +1 -1
  7. package/dist/components/copy-button.d.ts +1 -1
  8. package/dist/components/data-table-cells.d.ts +1 -1
  9. package/dist/components/data-table-filter-popover.d.ts +1 -1
  10. package/dist/components/data-table-filters.d.ts +1 -1
  11. package/dist/components/data-table.d.ts +1 -1
  12. package/dist/components/data-table.js +91 -3
  13. package/dist/components/data-table.js.map +1 -1
  14. package/dist/components/facing-pair.d.ts +1 -1
  15. package/dist/components/file-button.d.ts +1 -1
  16. package/dist/components/file-dropzone.d.ts +1 -1
  17. package/dist/components/form-actions.d.ts +1 -1
  18. package/dist/components/form-actions.js +32 -1
  19. package/dist/components/form-actions.js.map +1 -1
  20. package/dist/components/line-items.d.ts +30 -6
  21. package/dist/components/line-items.js +77 -33
  22. package/dist/components/line-items.js.map +1 -1
  23. package/dist/components/list.d.ts +13 -1
  24. package/dist/components/list.js.map +1 -1
  25. package/dist/components/money-field.d.ts +1 -1
  26. package/dist/components/number-field.d.ts +1 -1
  27. package/dist/components/number-input.d.ts +1 -1
  28. package/dist/components/numpad-sheet.d.ts +1 -1
  29. package/dist/components/progress-bar.d.ts +11 -1
  30. package/dist/components/progress-bar.js +14 -4
  31. package/dist/components/progress-bar.js.map +1 -1
  32. package/dist/components/series-chart.d.ts +1 -1
  33. package/dist/components/settings-fields.d.ts +1 -1
  34. package/dist/components/signed-amount.d.ts +12 -3
  35. package/dist/components/signed-amount.js +6 -4
  36. package/dist/components/signed-amount.js.map +1 -1
  37. package/dist/components/text-link.d.ts +1 -1
  38. package/dist/components/time-input.d.ts +1 -1
  39. package/dist/components/ui.d.ts +1 -1
  40. package/dist/components/ui.js +25 -3
  41. package/dist/components/ui.js.map +1 -1
  42. package/dist/components/use-table-state.d.ts +1 -1
  43. package/dist/{data-table-CfVXtCeG.d.ts → data-table-CpkWUJBY.d.ts} +41 -1
  44. package/dist/data-table.d.ts +1 -1
  45. package/dist/hooks/use-file-drop.d.ts +1 -1
  46. package/dist/i18n/defaults.d.ts +1 -1
  47. package/dist/i18n/kit-labels.d.ts +1 -1
  48. package/dist/i18n/locales/de-CH-informal.d.ts +1 -1
  49. package/dist/i18n/locales/de-CH.d.ts +1 -1
  50. package/dist/i18n/locales/de-informal.d.ts +1 -1
  51. package/dist/i18n/locales/de.d.ts +1 -1
  52. package/dist/i18n/locales/en.d.ts +71 -0
  53. package/dist/i18n/locales/en.js +95 -0
  54. package/dist/i18n/locales/en.js.map +1 -0
  55. package/dist/i18n/locales/es.d.ts +1 -1
  56. package/dist/i18n/locales/fr.d.ts +1 -1
  57. package/dist/i18n/locales/hu.d.ts +1 -1
  58. package/dist/i18n/locales/it.d.ts +1 -1
  59. package/dist/i18n/locales/zh.d.ts +1 -1
  60. package/dist/index.d.ts +4 -4
  61. package/dist/{kit-labels-DE--LmCk.d.ts → kit-labels-C4uB_zmg.d.ts} +57 -5
  62. package/dist/rhf/fields.d.ts +1 -1
  63. package/dist/rhf/form.d.ts +1 -1
  64. package/dist/rhf/line-items.js.map +1 -1
  65. package/dist/rhf.d.ts +1 -1
  66. package/dist/search/global-search.d.ts +22 -4
  67. package/dist/search/global-search.js +31 -3
  68. package/dist/search/global-search.js.map +1 -1
  69. package/dist/search.d.ts +1 -1
  70. package/dist/shell/app-shell.d.ts +1 -1
  71. package/dist/shell/top-bar-brand.d.ts +1 -1
  72. package/dist/shell.d.ts +1 -1
  73. package/dist/wizard/stepper-nav.d.ts +1 -1
  74. package/dist/wizard.d.ts +1 -1
  75. package/package.json +1 -1
  76. package/src/components/amount-input.tsx +8 -0
  77. package/src/components/data-table.tsx +165 -3
  78. package/src/components/form-actions.tsx +73 -3
  79. package/src/components/line-items.tsx +116 -40
  80. package/src/components/list.tsx +13 -1
  81. package/src/components/progress-bar.tsx +20 -4
  82. package/src/components/signed-amount.tsx +16 -5
  83. package/src/components/ui.tsx +57 -3
  84. package/src/i18n/locales/en.ts +116 -0
  85. package/src/rhf/line-items.tsx +2 -1
  86. package/src/search/global-search.tsx +57 -6
@@ -0,0 +1,116 @@
1
+ import { DEFAULT_UI_KIT_LABELS } from "../defaults";
2
+ import { formatFileSize } from "../kit-labels";
3
+ import type { UiKitLabels } from "../kit-labels";
4
+
5
+ /**
6
+ * The kit's English words with every count and file size formatted for
7
+ * `numberLocale` — `uiKitLabelsEn("en-CH")` reads "12’345 rows", where the defaults
8
+ * print "12345". For `<UiKitProvider labels={uiKitLabelsEn("en-CH")}>`.
9
+ *
10
+ * A factory and not a constant on purpose: {@link DEFAULT_UI_KIT_LABELS} prints counts
11
+ * raw and has since 0.1, so an app that passes nothing must keep seeing exactly that.
12
+ * Formatting is something an app asks for, by calling this.
13
+ *
14
+ * Derived rather than copied: every word is the default's own, spread in, and only the
15
+ * labels that print a number are restated — with the same wording and the same
16
+ * singular/plural split — so a key added to the defaults is here the moment it lands.
17
+ * Row and line numbers (`lineItems.row`, `measuredGrid.cell`) are positions, not
18
+ * counts, and stay as the defaults print them, as in the other catalogues.
19
+ */
20
+ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
21
+ const num = new Intl.NumberFormat(numberLocale);
22
+ const n = (value: number) => num.format(value);
23
+ const d = DEFAULT_UI_KIT_LABELS;
24
+
25
+ return {
26
+ ...d,
27
+ feedbackAttachment: {
28
+ ...d.feedbackAttachment,
29
+ attachmentLimit: (max) =>
30
+ `Up to ${n(max)} ${max === 1 ? "attachment" : "attachments"} — remove one to add another.`,
31
+ },
32
+ measuredGrid: {
33
+ ...d.measuredGrid,
34
+ points: (count) => (count === 1 ? "1 point" : `${n(count)} points`),
35
+ problems: (count) =>
36
+ count === 1 ? "1 cell is not a number" : `${n(count)} cells are not numbers`,
37
+ },
38
+ dataTable: {
39
+ ...d.dataTable,
40
+ filterResults: (shown, total) => `${n(shown)} of ${n(total)} rows`,
41
+ pageChanged: (page, totalPages) => `Page ${n(page)} of ${n(totalPages)}`,
42
+ pageRange: (from, to, total) => `${n(from)}–${n(to)} / ${n(total)}`,
43
+ rowCount: (total) => n(total),
44
+ columnsCount: (visible, total) => `Columns (${n(visible)}/${n(total)})`,
45
+ },
46
+ calendarHeatmap: {
47
+ ...d.calendarHeatmap,
48
+ truncated: (count) =>
49
+ `Showing the most recent days; ${n(count)} earlier ${count === 1 ? "day is" : "days are"} not shown.`,
50
+ },
51
+ combobox: {
52
+ ...d.combobox,
53
+ selectedCount: (count) => `${n(count)} selected`,
54
+ resultCount: (count) => (count === 1 ? "1 result" : `${n(count)} results`),
55
+ minChars: (count) =>
56
+ count === 1 ? "Type at least 1 character" : `Type at least ${n(count)} characters`,
57
+ },
58
+ multiSelect: {
59
+ ...d.multiSelect,
60
+ selectedCount: (count) => n(count),
61
+ },
62
+ chipInput: {
63
+ ...d.chipInput,
64
+ atLimit: (max) => `Limit of ${n(max)} reached`,
65
+ },
66
+ iconPicker: {
67
+ ...d.iconPicker,
68
+ resultCount: (count) => (count === 1 ? "1 icon" : `${n(count)} icons`),
69
+ },
70
+ bulkActionBar: {
71
+ ...d.bulkActionBar,
72
+ selected: (count) => `${n(count)} selected`,
73
+ },
74
+ file: {
75
+ // The kit's own `Intl` unit formatting, pinned to this locale ("3.4 MB").
76
+ size: (bytes) => formatFileSize(bytes, numberLocale),
77
+ },
78
+ filePicker: {
79
+ ...d.filePicker,
80
+ rejectedCount: (name, maxFiles) =>
81
+ `“${name}” was not added: at most ${n(maxFiles)} ${maxFiles === 1 ? "file" : "files"}`,
82
+ rejectedMany: (count) => `${n(count)} files were not added`,
83
+ rejectedPick: (count) =>
84
+ count === 1 ? "The file was not added" : `None of the ${n(count)} files were added`,
85
+ selected: (count, firstName) =>
86
+ count === 1 ? `“${firstName}” selected` : `${n(count)} files selected`,
87
+ },
88
+ wizard: {
89
+ ...d.wizard,
90
+ step: (current, total) => `Step ${n(current)} of ${n(total)}`,
91
+ },
92
+ tour: {
93
+ ...d.tour,
94
+ step: (current, total) => `${n(current)} / ${n(total)}`,
95
+ },
96
+ passwordStrength: {
97
+ ...d.passwordStrength,
98
+ ruleLength: (minLength) => `At least ${n(minLength)} characters`,
99
+ tooLong: (maxBytes) =>
100
+ `At most ${n(maxBytes)} characters (accents and emoji count for more than one).`,
101
+ },
102
+ floatingPanel: {
103
+ ...d.floatingPanel,
104
+ badge: (count) => `${n(count)} new`,
105
+ },
106
+ imageGrid: {
107
+ ...d.imageGrid,
108
+ item: (index, count) => `Image ${n(index)} of ${n(count)}`,
109
+ },
110
+ lightbox: {
111
+ ...d.lightbox,
112
+ counter: (index, count) => `${n(index)} / ${n(count)}`,
113
+ position: (index, count) => `Image ${n(index)} of ${n(count)}`,
114
+ },
115
+ };
116
+ }
@@ -24,7 +24,8 @@
24
24
  * list's `error`. Each cell's `render` gets `name`, the row's path
25
25
  * (`"components.2"`), to bind its field to.
26
26
  *
27
- * `summary`, `fieldLabels` and `narrowColumns` pass through to {@link LineItems}. With
27
+ * `summary`, `fieldLabels`, `narrowColumns`, `removePlacement` and `removeAlign` pass
28
+ * through to {@link LineItems}. With
28
29
  * `fieldLabels="floating"` a cell's `fieldLabel` is the column's name; the bound
29
30
  * fields' own `label` is a `FormLabel` ABOVE the control, so for the floating
30
31
  * one render the kit field through `RhfField`:
@@ -42,8 +42,20 @@ export const DEFAULT_GLOBAL_SEARCH_LABELS: GlobalSearchLabels = {
42
42
  export interface GlobalSearchSource {
43
43
  /** Stable key — the effect that runs the source is keyed by it, not by identity. */
44
44
  id: string;
45
- /** The group its results are shown under (an entry's own `group` is ignored). */
45
+ /** The group its results are shown under (an entry's own `group` is ignored) — unless
46
+ * `groups` is set, where it is only the home of entries that name no declared group. */
46
47
  group: string;
48
+ /**
49
+ * Split ONE request into several groups — kastlan's record search answers with
50
+ * properties, units, contacts, leases and invoices at once, and one source per group
51
+ * meant a dedupe layer so the network still saw one request. Each entry is then shown
52
+ * under the group its own `group` names: a declared `key` (shown by its `label`) or a
53
+ * plain string (key and heading at once). The groups appear in THIS order whatever the
54
+ * answer's order, each kept to `limit` rows; an entry naming no declared group falls
55
+ * under `group`. While the request runs, or when it fails, the source shows one status
56
+ * line under its first group, not one per group. Unset: one group, as before.
57
+ */
58
+ groups?: readonly (string | GlobalSearchSourceGroup)[];
47
59
  /** Called with the trimmed query. Its results are shown AS GIVEN — the source ranked
48
60
  * them — and never re-filtered by the index's matcher. `signal` aborts when the query
49
61
  * moves on or the palette closes; a stale answer is dropped either way. */
@@ -52,7 +64,7 @@ export interface GlobalSearchSource {
52
64
  minChars?: number;
53
65
  /** Quiet period after the last keystroke before it is asked. Default 200ms. */
54
66
  debounceMs?: number;
55
- /** Rows kept. Default: `indexOptions.groupLimit` (8). */
67
+ /** Rows kept — per group, when `groups` is set. Default: `indexOptions.groupLimit` (8). */
56
68
  limit?: number;
57
69
  /** Mask every row of this source for session replay, hints (amounts) included. An
58
70
  * entry's own `redact` still wins. */
@@ -62,6 +74,13 @@ export interface GlobalSearchSource {
62
74
  onError?: (error: unknown) => void;
63
75
  }
64
76
 
77
+ /** One of a multi-group source's groups: the `key` its entries name in `group`, and the
78
+ * heading shown — so a server's type ("unit") need not be translated per entry. */
79
+ export interface GlobalSearchSourceGroup {
80
+ key: string;
81
+ label: string;
82
+ }
83
+
65
84
  /** What a custom trigger gets: everything the default button uses. */
66
85
  export interface GlobalSearchTriggerProps {
67
86
  open: () => void;
@@ -90,7 +109,7 @@ export interface GlobalSearchProps {
90
109
  * neither re-indexes nor loops, and the icons and handlers used are always the latest.
91
110
  */
92
111
  entries?: readonly SearchEntry[];
93
- /** The async sources, each streamed into its own group. */
112
+ /** The async sources, each streamed into its own group — or several (`groups`). */
94
113
  sources?: readonly GlobalSearchSource[];
95
114
  /** What the empty query shows, under the `suggestions` heading. */
96
115
  suggestions?: readonly GlobalSearchSuggestion[];
@@ -311,7 +330,17 @@ function GlobalSearchImpl({
311
330
  const byId = useMemo(() => new Map(list.map((e) => [e.id, e])), [sig]);
312
331
 
313
332
  const sourceKey = (sources ?? [])
314
- .map((s) => [s.id, s.group, s.minChars ?? 2, s.debounceMs ?? 200, s.limit, s.redact].join("\u0001"))
333
+ .map((s) =>
334
+ [
335
+ s.id,
336
+ s.group,
337
+ (s.groups ?? []).map((g) => (typeof g === "string" ? g : `${g.key}\u0003${g.label}`)).join("\u0004"),
338
+ s.minChars ?? 2,
339
+ s.debounceMs ?? 200,
340
+ s.limit,
341
+ s.redact,
342
+ ].join("\u0001"),
343
+ )
315
344
  .join("\u0002");
316
345
  const sourceList = useMemo(() => sources ?? [], [sourceKey]);
317
346
 
@@ -434,10 +463,12 @@ function GlobalSearchImpl({
434
463
  for (const source of sourceList) {
435
464
  if (q.length < (source.minChars ?? 2)) continue;
436
465
  const result = sourceResults[source.id];
466
+ const declared = (source.groups ?? []).map((g) => (typeof g === "string" ? { key: g, label: g } : g));
437
467
  const status = (label: string, icon: ReactNode): CommandItem => ({
438
468
  id: `src:${source.id}:status`,
439
469
  label,
440
- group: source.group,
470
+ // One line for the one request, under the group that will head its answer.
471
+ group: declared[0]?.label ?? source.group,
441
472
  icon,
442
473
  kind: "status",
443
474
  onSelect: noop,
@@ -448,10 +479,30 @@ function GlobalSearchImpl({
448
479
  out.push(status(pl.loading, <Loader2 className="size-3.5 animate-spin" />));
449
480
  } else if (result.status === "error") {
450
481
  out.push(status(pl.error ?? DEFAULT_COMMAND_PALETTE_LABELS.error!, <AlertCircle className="size-3.5" />));
451
- } else {
482
+ } else if (!declared.length) {
452
483
  for (const entry of result.entries.slice(0, source.limit ?? groupLimit)) {
453
484
  out.push(toItem(`src:${source.id}:`, entry, source.group, source.redact ?? redactLabels));
454
485
  }
486
+ } else {
487
+ // Bucket by the entry's own group, then emit in the DECLARED order — the answer's
488
+ // order must not reshuffle the headings between keystrokes. Within a group the
489
+ // source's ranking stands. The group key is in the row id: two record types may
490
+ // share an id ("1"), and the palette keys rows by it.
491
+ const buckets = new Map<string, { label: string; entries: SearchEntry[] }>();
492
+ for (const g of declared) buckets.set(g.key, { label: g.label, entries: [] });
493
+ const rest: SearchEntry[] = [];
494
+ for (const entry of result.entries) {
495
+ const bucket = entry.group === undefined ? undefined : buckets.get(entry.group);
496
+ (bucket ? bucket.entries : rest).push(entry);
497
+ }
498
+ const limit = source.limit ?? groupLimit;
499
+ const emit = (key: string, label: string, list: SearchEntry[]) => {
500
+ for (const entry of list.slice(0, limit)) {
501
+ out.push(toItem(`src:${source.id}:${key}:`, entry, label, source.redact ?? redactLabels));
502
+ }
503
+ };
504
+ for (const [key, { label, entries: list }] of buckets) emit(key, label, list);
505
+ emit("~", source.group, rest);
455
506
  }
456
507
  }
457
508