@eifi1/ui-kit 0.11.0 → 0.12.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 +57 -32
- package/dist/chart.d.ts +3 -1
- package/dist/chart.js +2 -0
- package/dist/chart.js.map +1 -1
- package/dist/components/account-settings-labels.d.ts +105 -0
- package/dist/components/account-settings-labels.js +81 -0
- package/dist/components/account-settings-labels.js.map +1 -0
- package/dist/components/account-settings.d.ts +36 -52
- package/dist/components/account-settings.js +44 -8
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +24 -4
- package/dist/components/amount-input.js +5 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/authed-image.d.ts +68 -0
- package/dist/components/authed-image.js +117 -0
- package/dist/components/authed-image.js.map +1 -0
- package/dist/components/breadcrumbs.d.ts +2 -1
- package/dist/components/breadcrumbs.js +5 -2
- package/dist/components/breadcrumbs.js.map +1 -1
- package/dist/components/button-group.d.ts +51 -2
- package/dist/components/button-group.js +5 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +15 -4
- package/dist/components/calendar-heatmap.d.ts +41 -3
- package/dist/components/calendar-heatmap.js +48 -7
- package/dist/components/calendar-heatmap.js.map +1 -1
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +6 -3
- package/dist/components/chip.js.map +1 -1
- package/dist/components/choice-card.d.ts +35 -3
- package/dist/components/choice-card.js +83 -25
- package/dist/components/choice-card.js.map +1 -1
- package/dist/components/combobox-core.js +2 -0
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +2 -2
- package/dist/components/combobox.js +31 -9
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +51 -70
- package/dist/components/copy-button.js +1 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +63 -0
- package/dist/components/data-table-cells.js +64 -0
- package/dist/components/data-table-cells.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-filters.js +31 -1
- package/dist/components/data-table-filters.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +13 -0
- package/dist/components/data-table-labels.js +8 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table.d.ts +1 -1
- package/dist/components/data-table.js +102 -12
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +3 -1
- package/dist/components/date-picker.js +15 -2
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/description-list.d.ts +23 -3
- package/dist/components/description-list.js +23 -3
- package/dist/components/description-list.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +1 -0
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js +18 -3
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/error-boundary.d.ts +92 -0
- package/dist/components/error-boundary.js +111 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/components/field.d.ts +30 -4
- package/dist/components/field.js +40 -17
- package/dist/components/field.js.map +1 -1
- package/dist/components/file-button.d.ts +50 -208
- package/dist/components/file-dropzone.d.ts +49 -2
- package/dist/components/floating-panel.d.ts +13 -4
- package/dist/components/floating-panel.js +7 -3
- package/dist/components/floating-panel.js.map +1 -1
- package/dist/components/form-actions.d.ts +53 -0
- package/dist/components/form-actions.js +98 -0
- package/dist/components/form-actions.js.map +1 -0
- package/dist/components/image-grid.d.ts +67 -0
- package/dist/components/image-grid.js +112 -0
- package/dist/components/image-grid.js.map +1 -0
- package/dist/components/lightbox.d.ts +104 -0
- package/dist/components/lightbox.js +229 -0
- package/dist/components/lightbox.js.map +1 -0
- package/dist/components/line-items.d.ts +114 -0
- package/dist/components/line-items.js +238 -0
- package/dist/components/line-items.js.map +1 -0
- package/dist/components/list.d.ts +23 -2
- package/dist/components/list.js +32 -13
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-state.d.ts +24 -0
- package/dist/components/loading-state.js +32 -0
- package/dist/components/loading-state.js.map +1 -0
- package/dist/components/menu-item.d.ts +2 -1
- package/dist/components/menu-item.js +5 -1
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +13 -1
- package/dist/components/mini-calendar.js +46 -2
- package/dist/components/mini-calendar.js.map +1 -1
- package/dist/components/modal.d.ts +20 -1
- package/dist/components/modal.js +26 -2
- package/dist/components/modal.js.map +1 -1
- package/dist/components/month-picker.d.ts +21 -1
- package/dist/components/month-picker.js +139 -26
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +1 -1
- package/dist/components/multi-entity-combobox.js +19 -3
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/nav-pills.d.ts +2 -1
- package/dist/components/nav-pills.js +5 -1
- package/dist/components/nav-pills.js.map +1 -1
- package/dist/components/number-field.d.ts +24 -5
- package/dist/components/number-field.js +5 -7
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +25 -5
- package/dist/components/number-input.js +7 -1
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +15 -4
- package/dist/components/passkeys-setting.d.ts +67 -0
- package/dist/components/passkeys-setting.js +214 -0
- package/dist/components/passkeys-setting.js.map +1 -0
- package/dist/components/picker-sheet.js +2 -1
- package/dist/components/picker-sheet.js.map +1 -1
- package/dist/components/pie-chart-labels.d.ts +20 -0
- package/dist/components/pie-chart-labels.js +12 -0
- package/dist/components/pie-chart-labels.js.map +1 -0
- package/dist/components/pie-chart.d.ts +101 -0
- package/dist/components/pie-chart.js +283 -0
- package/dist/components/pie-chart.js.map +1 -0
- package/dist/components/progress-bar.d.ts +44 -3
- package/dist/components/progress-bar.js +58 -22
- package/dist/components/progress-bar.js.map +1 -1
- package/dist/components/qr-code.d.ts +34 -0
- package/dist/components/qr-code.js +57 -0
- package/dist/components/qr-code.js.map +1 -0
- package/dist/components/search-field.d.ts +15 -3
- package/dist/components/search-field.js +5 -1
- package/dist/components/search-field.js.map +1 -1
- package/dist/components/settings-fields.d.ts +49 -1
- package/dist/components/signed-amount.d.ts +121 -0
- package/dist/components/signed-amount.js +152 -0
- package/dist/components/signed-amount.js.map +1 -0
- package/dist/components/skeleton.d.ts +10 -3
- package/dist/components/skeleton.js +12 -2
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/stat-tile.d.ts +2 -1
- package/dist/components/stat-tile.js +5 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/table.d.ts +39 -5
- package/dist/components/table.js +45 -11
- package/dist/components/table.js.map +1 -1
- package/dist/components/text-link.d.ts +176 -0
- package/dist/components/text-link.js +109 -0
- package/dist/components/text-link.js.map +1 -0
- package/dist/components/time-input.d.ts +49 -1
- package/dist/components/toggle-group.d.ts +13 -0
- package/dist/components/toggle-group.js +35 -6
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +36 -3
- package/dist/components/toggle-legend.js +49 -0
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/ui.d.ts +51 -779
- package/dist/components/ui.js +215 -23
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +3 -52
- package/dist/components/use-table-state.js +128 -30
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/{data-table-filters-Dh9uF_S-.d.ts → data-table-Drun8Fk9.d.ts} +305 -61
- package/dist/data-table.d.ts +2 -1
- package/dist/data-table.js +5 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +26 -3
- package/dist/feedback/feedback-dialog.js +16 -2
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +133 -0
- package/dist/feedback/feedback-thread.js +237 -0
- package/dist/feedback/feedback-thread.js.map +1 -0
- package/dist/feedback.d.ts +2 -1
- package/dist/feedback.js +1 -0
- package/dist/feedback.js.map +1 -1
- package/dist/hooks/use-authed-src.d.ts +71 -0
- package/dist/hooks/use-authed-src.js +63 -0
- package/dist/hooks/use-authed-src.js.map +1 -0
- package/dist/hooks/use-file-drop.d.ts +49 -2
- package/dist/hooks/use-hotkey.d.ts +59 -0
- package/dist/hooks/use-hotkey.js +105 -0
- package/dist/hooks/use-hotkey.js.map +1 -0
- package/dist/hooks/use-search-param-state.d.ts +83 -0
- package/dist/hooks/use-search-param-state.js +94 -0
- package/dist/hooks/use-search-param-state.js.map +1 -0
- package/dist/i18n/defaults.d.ts +15 -4
- package/dist/i18n/defaults.js +31 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +46 -312
- package/dist/i18n/kit-labels.js +20 -5
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/de-CH-informal.d.ts +15 -4
- package/dist/i18n/locales/de-CH.d.ts +15 -4
- package/dist/i18n/locales/de-informal.d.ts +15 -4
- package/dist/i18n/locales/de.d.ts +15 -4
- package/dist/i18n/locales/de.js +156 -3
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +15 -4
- package/dist/i18n/locales/es.js +153 -3
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +15 -4
- package/dist/i18n/locales/fr.js +153 -3
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +15 -4
- package/dist/i18n/locales/hu.js +153 -3
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +15 -4
- package/dist/i18n/locales/it.js +153 -3
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +15 -4
- package/dist/i18n/locales/zh.js +153 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +37 -17
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
- package/dist/lib/format.d.ts +144 -0
- package/dist/lib/format.js +149 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/qr-encode.d.ts +44 -0
- package/dist/lib/qr-encode.js +338 -0
- package/dist/lib/qr-encode.js.map +1 -0
- package/dist/rhf/fields.d.ts +241 -0
- package/dist/rhf/fields.js +601 -0
- package/dist/rhf/fields.js.map +1 -0
- package/dist/rhf/form.d.ts +49 -1
- package/dist/rhf/line-items.d.ts +31 -0
- package/dist/rhf/line-items.js +59 -0
- package/dist/rhf/line-items.js.map +1 -0
- package/dist/rhf.d.ts +61 -1
- package/dist/rhf.js +2 -0
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +99 -2
- package/dist/shell/app-shell.js +58 -3
- package/dist/shell/app-shell.js.map +1 -1
- package/dist/shell/auth-layout.d.ts +74 -0
- package/dist/shell/auth-layout.js +97 -0
- package/dist/shell/auth-layout.js.map +1 -0
- package/dist/shell/top-bar-brand.d.ts +81 -0
- package/dist/shell/top-bar-brand.js +47 -0
- package/dist/shell/top-bar-brand.js.map +1 -0
- package/dist/shell/topbar-action-menu.d.ts +12 -1
- package/dist/shell/topbar-action-menu.js +16 -1
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +56 -1
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +48 -1
- package/dist/wizard/wizard-context.d.ts +24 -1
- package/dist/wizard/wizard-context.js +19 -2
- package/dist/wizard/wizard-context.js.map +1 -1
- package/dist/wizard/wizard-step.d.ts +20 -5
- package/dist/wizard/wizard-step.js +16 -3
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard.d.ts +50 -3
- package/package.json +1 -1
- package/src/chart.ts +4 -0
- package/src/components/account-settings-labels.ts +190 -0
- package/src/components/account-settings.tsx +110 -63
- package/src/components/amount-input.tsx +13 -1
- package/src/components/authed-image.tsx +190 -0
- package/src/components/breadcrumbs.tsx +8 -2
- package/src/components/button-group.tsx +7 -2
- package/src/components/calendar-heatmap.tsx +105 -10
- package/src/components/chip.tsx +8 -4
- package/src/components/choice-card.tsx +136 -25
- package/src/components/combobox-core.tsx +5 -0
- package/src/components/combobox.tsx +31 -9
- package/src/components/copy-button.tsx +3 -2
- package/src/components/data-table-cells.tsx +119 -0
- package/src/components/data-table-filters.ts +77 -0
- package/src/components/data-table-labels.ts +23 -0
- package/src/components/data-table.tsx +224 -13
- package/src/components/date-picker.tsx +19 -3
- package/src/components/description-list.tsx +52 -3
- package/src/components/entity-combobox.tsx +18 -3
- package/src/components/error-boundary.tsx +216 -0
- package/src/components/field.tsx +56 -5
- package/src/components/floating-panel.tsx +20 -7
- package/src/components/form-actions.tsx +191 -0
- package/src/components/image-grid.tsx +181 -0
- package/src/components/lightbox.tsx +374 -0
- package/src/components/line-items.tsx +385 -0
- package/src/components/list.tsx +65 -16
- package/src/components/loading-state.tsx +47 -0
- package/src/components/menu-item.tsx +7 -2
- package/src/components/mini-calendar.tsx +72 -3
- package/src/components/modal.tsx +61 -3
- package/src/components/month-picker.tsx +181 -26
- package/src/components/multi-entity-combobox.tsx +19 -3
- package/src/components/nav-pills.tsx +8 -1
- package/src/components/number-field.tsx +15 -10
- package/src/components/number-input.tsx +16 -1
- package/src/components/passkeys-setting.tsx +331 -0
- package/src/components/picker-sheet.tsx +7 -1
- package/src/components/pie-chart-labels.ts +32 -0
- package/src/components/pie-chart.tsx +479 -0
- package/src/components/progress-bar.tsx +129 -19
- package/src/components/qr-code.tsx +83 -0
- package/src/components/search-field.tsx +20 -4
- package/src/components/signed-amount.tsx +286 -0
- package/src/components/skeleton.tsx +21 -3
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/table.tsx +102 -13
- package/src/components/text-link.tsx +248 -0
- package/src/components/toggle-group.tsx +58 -10
- package/src/components/toggle-legend.tsx +87 -2
- package/src/components/ui.tsx +426 -36
- package/src/components/use-table-state.ts +265 -33
- package/src/data-table.ts +10 -0
- package/src/feedback/feedback-dialog.tsx +42 -3
- package/src/feedback/feedback-thread.tsx +434 -0
- package/src/feedback.ts +1 -0
- package/src/hooks/use-authed-src.ts +161 -0
- package/src/hooks/use-hotkey.ts +172 -0
- package/src/hooks/use-search-param-state.ts +187 -0
- package/src/i18n/defaults.ts +30 -0
- package/src/i18n/kit-labels.tsx +102 -8
- package/src/i18n/locales/de.ts +153 -0
- package/src/i18n/locales/es.ts +150 -0
- package/src/i18n/locales/fr.ts +153 -0
- package/src/i18n/locales/hu.ts +151 -0
- package/src/i18n/locales/it.ts +151 -0
- package/src/i18n/locales/zh.ts +150 -0
- package/src/index.ts +59 -0
- package/src/lib/format.ts +325 -0
- package/src/lib/qr-encode.ts +420 -0
- package/src/rhf/fields.tsx +1054 -0
- package/src/rhf/line-items.tsx +130 -0
- package/src/rhf.ts +6 -0
- package/src/shell/app-shell.tsx +147 -2
- package/src/shell/auth-layout.tsx +190 -0
- package/src/shell/top-bar-brand.tsx +76 -0
- package/src/shell/topbar-action-menu.tsx +21 -0
- package/src/shell.ts +2 -0
- package/src/wizard/wizard-context.tsx +41 -1
- package/src/wizard/wizard-step.tsx +43 -6
- package/tokens.css +12 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { useSearchParams } from "react-router";
|
|
2
|
+
import { useLocation, useSearchParams } from "react-router";
|
|
3
3
|
import { readStored, writeStored } from "../lib/safe-storage";
|
|
4
4
|
import {
|
|
5
5
|
decodeFilterValue,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
resolveFilter,
|
|
9
9
|
} from "./data-table-filters";
|
|
10
10
|
import { decodeSorts, encodeSorts, normalizeSorts, type SortState } from "./data-table-sort";
|
|
11
|
-
import type { DataTableColumn, FilterState } from "./data-table";
|
|
11
|
+
import type { DataTableColumn, FilterState, ServerPagination } from "./data-table";
|
|
12
12
|
|
|
13
13
|
// ---------- Persisted + URL-encoded view state ----------
|
|
14
14
|
//
|
|
@@ -51,16 +51,63 @@ const URL_FILTER_PREFIX = "f.";
|
|
|
51
51
|
const URL_SORT_KEY = "sort";
|
|
52
52
|
const URL_PAGE_KEY = "p";
|
|
53
53
|
const URL_PAGE_SIZE_KEY = "ps";
|
|
54
|
+
/** The free-text search term — managed only by {@link useTableUrlState}, and only when
|
|
55
|
+
* it is asked to (`search: true`); a plain `urlSync` table leaves a `q` alone. */
|
|
56
|
+
const URL_SEARCH_KEY = "q";
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
/**
|
|
59
|
+
* How a table is mirrored into the URL: `true`, or `{ prefix }` for a table that shares
|
|
60
|
+
* its page with another URL-synced one. The prefix is put verbatim in front of EVERY key
|
|
61
|
+
* the table writes and reads — `{ prefix: "inv." }` gives `inv.f.<column>`, `inv.sort`,
|
|
62
|
+
* `inv.p`, `inv.ps` (and `inv.q`) — so two tables on one page stop sharing a sort and a
|
|
63
|
+
* page. Without one the keys are unprefixed, as they always were.
|
|
64
|
+
*/
|
|
65
|
+
export type TableUrlSync = boolean | { prefix?: string };
|
|
66
|
+
|
|
67
|
+
/** The query keys of one table, with its prefix applied. */
|
|
68
|
+
interface UrlKeys {
|
|
69
|
+
filter: string;
|
|
70
|
+
sort: string;
|
|
71
|
+
page: string;
|
|
72
|
+
pageSize: string;
|
|
73
|
+
search: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function urlKeys(prefix = ""): UrlKeys {
|
|
77
|
+
return {
|
|
78
|
+
filter: prefix + URL_FILTER_PREFIX,
|
|
79
|
+
sort: prefix + URL_SORT_KEY,
|
|
80
|
+
page: prefix + URL_PAGE_KEY,
|
|
81
|
+
pageSize: prefix + URL_PAGE_SIZE_KEY,
|
|
82
|
+
search: prefix + URL_SEARCH_KEY,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** `urlSync` split into "is it on" and "under which prefix". */
|
|
87
|
+
function resolveTableUrlSync(urlSync: TableUrlSync | undefined): { enabled: boolean; prefix: string } {
|
|
88
|
+
if (!urlSync) return { enabled: false, prefix: "" };
|
|
89
|
+
return { enabled: true, prefix: urlSync === true ? "" : (urlSync.prefix ?? "") };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** What {@link readTableUrlState} found in a query string. */
|
|
93
|
+
export interface TableUrlState {
|
|
56
94
|
sorts?: SortState[];
|
|
57
95
|
filters?: FilterState;
|
|
58
|
-
|
|
59
|
-
|
|
96
|
+
/** `Infinity` for `ps=all`. */
|
|
97
|
+
pageSize?: number;
|
|
98
|
+
/** 0-based (the URL's `p` is 1-based). */
|
|
99
|
+
page?: number;
|
|
60
100
|
}
|
|
61
101
|
|
|
62
102
|
/**
|
|
63
|
-
*
|
|
103
|
+
* What a table's URL says, read out of a query string: the column filters (`f.<key>`),
|
|
104
|
+
* the sort, the page size and the 0-based page. Absent keys are absent here too, so a
|
|
105
|
+
* caller can tell "not in the link" from "the default". The same reader `urlSync` uses
|
|
106
|
+
* on mount, exported for an owner that keeps the state itself — see
|
|
107
|
+
* {@link useTableUrlState}.
|
|
108
|
+
*
|
|
109
|
+
* Pass the ROUTER's query string — the one `setSearchParams` writes — and the table's
|
|
110
|
+
* `prefix` when it has one (see {@link TableUrlSync}).
|
|
64
111
|
*
|
|
65
112
|
* This read `window.location.search` until 0.7.0, which is the same string only under a
|
|
66
113
|
* BrowserRouter. Under a HashRouter the router's query lives after the `#`
|
|
@@ -69,30 +116,35 @@ interface UrlState {
|
|
|
69
116
|
* empty state. Reading and writing through `useSearchParams` is one source for both
|
|
70
117
|
* directions under every router, MemoryRouter included.
|
|
71
118
|
*/
|
|
72
|
-
function
|
|
73
|
-
|
|
119
|
+
export function readTableUrlState<T>(
|
|
120
|
+
sp: URLSearchParams,
|
|
121
|
+
columns: DataTableColumn<T>[],
|
|
122
|
+
prefix?: string,
|
|
123
|
+
): TableUrlState {
|
|
124
|
+
const keys = urlKeys(prefix);
|
|
125
|
+
const out: TableUrlState = {};
|
|
74
126
|
|
|
75
127
|
const filters: FilterState = {};
|
|
76
128
|
for (const col of columns) {
|
|
77
129
|
const filter = resolveFilter(col);
|
|
78
130
|
if (!filter) continue;
|
|
79
|
-
const raw = sp.get(
|
|
131
|
+
const raw = sp.get(keys.filter + col.key);
|
|
80
132
|
if (raw == null) continue;
|
|
81
133
|
filters[col.key] = decodeFilterValue(filter, raw);
|
|
82
134
|
}
|
|
83
135
|
if (Object.keys(filters).length) out.filters = filters;
|
|
84
136
|
|
|
85
|
-
const sortRaw = sp.get(
|
|
137
|
+
const sortRaw = sp.get(keys.sort);
|
|
86
138
|
if (sortRaw) {
|
|
87
139
|
const decoded = decodeSorts(sortRaw, new Set(columns.map((c) => c.key)));
|
|
88
140
|
if (decoded.length) out.sorts = decoded;
|
|
89
141
|
}
|
|
90
142
|
|
|
91
|
-
const psRaw = sp.get(
|
|
143
|
+
const psRaw = sp.get(keys.pageSize);
|
|
92
144
|
if (psRaw === "all") out.pageSize = Infinity;
|
|
93
145
|
else if (psRaw && Number(psRaw) > 0) out.pageSize = Number(psRaw);
|
|
94
146
|
|
|
95
|
-
const pRaw = sp.get(
|
|
147
|
+
const pRaw = sp.get(keys.page);
|
|
96
148
|
if (pRaw && Number(pRaw) >= 1) out.page = Number(pRaw) - 1;
|
|
97
149
|
|
|
98
150
|
return out;
|
|
@@ -114,21 +166,22 @@ function urlSignature<T>(
|
|
|
114
166
|
pageSize: number,
|
|
115
167
|
page: number,
|
|
116
168
|
defaultPageSize: number,
|
|
169
|
+
keys: UrlKeys,
|
|
117
170
|
): string {
|
|
118
171
|
const parts: string[] = [];
|
|
119
172
|
for (const col of columns) {
|
|
120
173
|
const state = filters[col.key];
|
|
121
174
|
if (!state || !isFilterActive(state)) continue;
|
|
122
175
|
const enc = encodeFilterValue(state);
|
|
123
|
-
if (enc != null) parts.push(`${
|
|
176
|
+
if (enc != null) parts.push(`${keys.filter}${col.key}=${enc}`);
|
|
124
177
|
}
|
|
125
|
-
parts.push(`${
|
|
178
|
+
parts.push(`${keys.sort}=${encodeSorts(sorts)}`);
|
|
126
179
|
parts.push(
|
|
127
|
-
`${
|
|
180
|
+
`${keys.pageSize}=${
|
|
128
181
|
pageSize === Infinity ? "all" : pageSize === defaultPageSize ? "" : String(pageSize)
|
|
129
182
|
}`,
|
|
130
183
|
);
|
|
131
|
-
parts.push(`${
|
|
184
|
+
parts.push(`${keys.page}=${page > 0 ? String(page + 1) : ""}`);
|
|
132
185
|
return parts.join("&");
|
|
133
186
|
}
|
|
134
187
|
|
|
@@ -140,33 +193,44 @@ function writeUrlState<T>(
|
|
|
140
193
|
pageSize: number,
|
|
141
194
|
page: number,
|
|
142
195
|
defaultPageSize: number,
|
|
196
|
+
// `undefined`: the search term is not ours to manage, and a `q` is left as it is.
|
|
197
|
+
search: string | undefined,
|
|
198
|
+
// The current entry's route state, carried over the replace. A query rewrite is not
|
|
199
|
+
// a navigation, and dropping the state would, for one, lose the mark a URL-bound
|
|
200
|
+
// dialog's opener leaves there (see useDialogParam).
|
|
201
|
+
state: unknown,
|
|
202
|
+
keys: UrlKeys,
|
|
143
203
|
): void {
|
|
144
204
|
setSearchParams(
|
|
145
205
|
(prev) => {
|
|
146
206
|
const next = new URLSearchParams(prev);
|
|
147
207
|
// clear managed keys
|
|
148
|
-
for (const col of columns) next.delete(
|
|
149
|
-
next.delete(
|
|
150
|
-
next.delete(
|
|
151
|
-
next.delete(
|
|
208
|
+
for (const col of columns) next.delete(keys.filter + col.key);
|
|
209
|
+
next.delete(keys.sort);
|
|
210
|
+
next.delete(keys.page);
|
|
211
|
+
next.delete(keys.pageSize);
|
|
212
|
+
if (search !== undefined) {
|
|
213
|
+
next.delete(keys.search);
|
|
214
|
+
if (search.trim()) next.set(keys.search, search);
|
|
215
|
+
}
|
|
152
216
|
// write filters
|
|
153
217
|
for (const col of columns) {
|
|
154
218
|
const state = filters[col.key];
|
|
155
219
|
if (!state || !isFilterActive(state)) continue;
|
|
156
220
|
const enc = encodeFilterValue(state);
|
|
157
|
-
if (enc != null) next.set(
|
|
221
|
+
if (enc != null) next.set(keys.filter + col.key, enc);
|
|
158
222
|
}
|
|
159
223
|
// write sort
|
|
160
224
|
const sortEnc = encodeSorts(sorts);
|
|
161
|
-
if (sortEnc) next.set(
|
|
225
|
+
if (sortEnc) next.set(keys.sort, sortEnc);
|
|
162
226
|
// write page size if non-default
|
|
163
|
-
if (pageSize === Infinity) next.set(
|
|
164
|
-
else if (pageSize !== defaultPageSize) next.set(
|
|
227
|
+
if (pageSize === Infinity) next.set(keys.pageSize, "all");
|
|
228
|
+
else if (pageSize !== defaultPageSize) next.set(keys.pageSize, String(pageSize));
|
|
165
229
|
// write page if not first
|
|
166
|
-
if (page > 0) next.set(
|
|
230
|
+
if (page > 0) next.set(keys.page, String(page + 1));
|
|
167
231
|
return next;
|
|
168
232
|
},
|
|
169
|
-
{ replace: true },
|
|
233
|
+
{ replace: true, state },
|
|
170
234
|
);
|
|
171
235
|
}
|
|
172
236
|
|
|
@@ -194,7 +258,7 @@ export function useTableState<T>({
|
|
|
194
258
|
columns,
|
|
195
259
|
storageKey,
|
|
196
260
|
storageKeyPrefix,
|
|
197
|
-
urlSync,
|
|
261
|
+
urlSync: urlSyncOption,
|
|
198
262
|
defaultPageSize,
|
|
199
263
|
sortsProp,
|
|
200
264
|
filtersProp,
|
|
@@ -202,18 +266,21 @@ export function useTableState<T>({
|
|
|
202
266
|
columns: DataTableColumn<T>[];
|
|
203
267
|
storageKey?: string;
|
|
204
268
|
storageKeyPrefix: string;
|
|
205
|
-
urlSync?:
|
|
269
|
+
urlSync?: TableUrlSync;
|
|
206
270
|
defaultPageSize: number;
|
|
207
271
|
sortsProp?: SortState[];
|
|
208
272
|
filtersProp?: FilterState;
|
|
209
273
|
}) {
|
|
274
|
+
const { enabled: urlSync, prefix: urlPrefix } = resolveTableUrlSync(urlSyncOption);
|
|
275
|
+
const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);
|
|
210
276
|
// Called unconditionally (a Router is required either way — see src/data-table.ts),
|
|
211
|
-
// and the SAME pair is used to read and to write; see `
|
|
277
|
+
// and the SAME pair is used to read and to write; see `readTableUrlState`.
|
|
212
278
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
279
|
+
const location = useLocation();
|
|
213
280
|
// Read URL once on mount so URL-encoded views (e.g. shared links, deep links from
|
|
214
281
|
// other pages) populate initial state. After mount, state is local.
|
|
215
|
-
const [urlInitial] = useState<
|
|
216
|
-
urlSync ?
|
|
282
|
+
const [urlInitial] = useState<TableUrlState>(() =>
|
|
283
|
+
urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {},
|
|
217
284
|
);
|
|
218
285
|
const initial = useMemo(
|
|
219
286
|
() => loadPersisted(storageKey, storageKeyPrefix),
|
|
@@ -299,7 +366,7 @@ export function useTableState<T>({
|
|
|
299
366
|
|
|
300
367
|
const lastUrl = useRef<string | null>(null);
|
|
301
368
|
const urlSig = urlSync
|
|
302
|
-
? urlSignature(columns, sorts, filters, pageSize, page, defaultPageSize)
|
|
369
|
+
? urlSignature(columns, sorts, filters, pageSize, page, defaultPageSize, keys)
|
|
303
370
|
: "";
|
|
304
371
|
useEffect(() => {
|
|
305
372
|
if (!urlSync) return;
|
|
@@ -308,7 +375,7 @@ export function useTableState<T>({
|
|
|
308
375
|
// `columns` is deliberately not a dependency and does not need to be: the
|
|
309
376
|
// signature above is computed from it, so a column set that would produce a
|
|
310
377
|
// different address produces a different signature first.
|
|
311
|
-
writeUrlState(setSearchParams, columns, sorts, filters, pageSize, page, defaultPageSize);
|
|
378
|
+
writeUrlState(setSearchParams, columns, sorts, filters, pageSize, page, defaultPageSize, undefined, location.state, keys);
|
|
312
379
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
313
380
|
}, [urlSync, urlSig]);
|
|
314
381
|
|
|
@@ -330,3 +397,168 @@ export function useTableState<T>({
|
|
|
330
397
|
headRefs,
|
|
331
398
|
};
|
|
332
399
|
}
|
|
400
|
+
|
|
401
|
+
// ---------- Owner-held table state, mirrored to the URL ----------
|
|
402
|
+
|
|
403
|
+
export interface UseTableUrlStateOptions<T> {
|
|
404
|
+
/** The table's columns — read for their keys and filter types only. */
|
|
405
|
+
columns: DataTableColumn<T>[];
|
|
406
|
+
/** Default 25. A page size equal to it is left out of the URL. */
|
|
407
|
+
defaultPageSize?: number;
|
|
408
|
+
/** Mirror the state into the URL and seed it from there on mount. Default true.
|
|
409
|
+
* `{ prefix }` puts a prefix in front of every key, for a page with two URL-synced
|
|
410
|
+
* tables — see {@link TableUrlSync}. */
|
|
411
|
+
urlSync?: TableUrlSync;
|
|
412
|
+
/** Shorthand for `urlSync: { prefix }`; wins over a prefix given there. */
|
|
413
|
+
urlPrefix?: string;
|
|
414
|
+
/** Also hold a free-text search term, under `q`. Off by default, so a `q` that
|
|
415
|
+
* belongs to someone else on the page is never touched. */
|
|
416
|
+
search?: boolean;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** The part of {@link DataTableProps} a server-driven table takes from its owner. */
|
|
420
|
+
export interface TableUrlStateProps {
|
|
421
|
+
serverPagination: ServerPagination;
|
|
422
|
+
filters: FilterState;
|
|
423
|
+
onFiltersChange: (next: FilterState) => void;
|
|
424
|
+
sorts: SortState[];
|
|
425
|
+
onSortsChange: (next: SortState[]) => void;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export interface UseTableUrlStateReturn {
|
|
429
|
+
/** 0-based. */
|
|
430
|
+
page: number;
|
|
431
|
+
setPage: (page: number) => void;
|
|
432
|
+
pageSize: number;
|
|
433
|
+
/** Also back to the first page. */
|
|
434
|
+
setPageSize: (pageSize: number) => void;
|
|
435
|
+
filters: FilterState;
|
|
436
|
+
/** Also back to the first page. */
|
|
437
|
+
setFilters: (next: FilterState) => void;
|
|
438
|
+
sorts: SortState[];
|
|
439
|
+
/** Also back to the first page. */
|
|
440
|
+
setSorts: (next: SortState[]) => void;
|
|
441
|
+
/** The search term (`""` unless `search: true`). */
|
|
442
|
+
search: string;
|
|
443
|
+
/** Also back to the first page. */
|
|
444
|
+
setSearch: (next: string) => void;
|
|
445
|
+
/**
|
|
446
|
+
* Everything the table needs, to spread onto it:
|
|
447
|
+
* `<DataTable {...table.tableProps(data?.total ?? 0, { isLoading })} rows={…} … />`.
|
|
448
|
+
*/
|
|
449
|
+
tableProps: (total: number, options?: { isLoading?: boolean }) => TableUrlStateProps;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
interface OwnedState {
|
|
453
|
+
sorts: SortState[];
|
|
454
|
+
filters: FilterState;
|
|
455
|
+
pageSize: number;
|
|
456
|
+
page: number;
|
|
457
|
+
search: string;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* The controlled state of a `serverPagination` table — filters, sorts, page, page size
|
|
462
|
+
* and optionally a search term — kept by the OWNER and mirrored into the URL in the
|
|
463
|
+
* table's own scheme (`f.<key>`, `sort`, `p`, `ps`, and `q`).
|
|
464
|
+
*
|
|
465
|
+
* A server-driven table owns none of this: the owner has to hold it to turn it into
|
|
466
|
+
* the query that fetches `rows`, and until now it also had to restate the table's URL
|
|
467
|
+
* codec to keep the view bookmarkable (kastlan's use-server-table.ts copied
|
|
468
|
+
* `readUrlState` / `writeUrlState` to do it). Same rules as `urlSync`:
|
|
469
|
+
*
|
|
470
|
+
* * **The URL is read once, on mount** — a shared or deep link (see {@link filterHref})
|
|
471
|
+
* seeds the view; after that the state is local and written out with `replace`.
|
|
472
|
+
* * **Any change but the page itself goes back to page 1** — a narrower filter must not
|
|
473
|
+
* leave the user on page 7 of 2.
|
|
474
|
+
* * **Written by value**: an unchanged view writes nothing, however often the owner
|
|
475
|
+
* renders.
|
|
476
|
+
*
|
|
477
|
+
* Mapping the state onto the backend's query parameters stays the owner's: that is the
|
|
478
|
+
* part the kit cannot know.
|
|
479
|
+
*/
|
|
480
|
+
export function useTableUrlState<T>({
|
|
481
|
+
columns,
|
|
482
|
+
defaultPageSize = 25,
|
|
483
|
+
urlSync: urlSyncOption = true,
|
|
484
|
+
urlPrefix: urlPrefixOption,
|
|
485
|
+
search: manageSearch = false,
|
|
486
|
+
}: UseTableUrlStateOptions<T>): UseTableUrlStateReturn {
|
|
487
|
+
const resolved = resolveTableUrlSync(urlSyncOption);
|
|
488
|
+
const urlSync = resolved.enabled;
|
|
489
|
+
const urlPrefix = urlPrefixOption ?? resolved.prefix;
|
|
490
|
+
const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);
|
|
491
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
492
|
+
const location = useLocation();
|
|
493
|
+
const [state, setState] = useState<OwnedState>(() => {
|
|
494
|
+
const url = urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {};
|
|
495
|
+
return {
|
|
496
|
+
sorts: url.sorts ?? [],
|
|
497
|
+
filters: url.filters ?? {},
|
|
498
|
+
pageSize: url.pageSize ?? defaultPageSize,
|
|
499
|
+
page: url.page ?? 0,
|
|
500
|
+
search: urlSync && manageSearch ? (searchParams.get(keys.search) ?? "") : "",
|
|
501
|
+
};
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const lastUrl = useRef<string | null>(null);
|
|
505
|
+
const urlSig = urlSync
|
|
506
|
+
? urlSignature(columns, state.sorts, state.filters, state.pageSize, state.page, defaultPageSize, keys) +
|
|
507
|
+
(manageSearch ? `&${keys.search}=${state.search.trim() ? state.search : ""}` : "")
|
|
508
|
+
: "";
|
|
509
|
+
useEffect(() => {
|
|
510
|
+
if (!urlSync) return;
|
|
511
|
+
// First run included: a link that arrives with `p=1` or a default `ps` is
|
|
512
|
+
// normalised to the table's own spelling, as `urlSync` does.
|
|
513
|
+
if (lastUrl.current === urlSig) return;
|
|
514
|
+
lastUrl.current = urlSig;
|
|
515
|
+
writeUrlState(
|
|
516
|
+
setSearchParams,
|
|
517
|
+
columns,
|
|
518
|
+
state.sorts,
|
|
519
|
+
state.filters,
|
|
520
|
+
state.pageSize,
|
|
521
|
+
state.page,
|
|
522
|
+
defaultPageSize,
|
|
523
|
+
manageSearch ? state.search : undefined,
|
|
524
|
+
location.state,
|
|
525
|
+
keys,
|
|
526
|
+
);
|
|
527
|
+
// Keyed on the signature, which is computed from everything the write reads — see
|
|
528
|
+
// the same effect in useTableState.
|
|
529
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
530
|
+
}, [urlSync, urlSig]);
|
|
531
|
+
|
|
532
|
+
const setPage = (page: number) => setState((s) => ({ ...s, page }));
|
|
533
|
+
const setPageSize = (pageSize: number) => setState((s) => ({ ...s, pageSize, page: 0 }));
|
|
534
|
+
const setFilters = (filters: FilterState) => setState((s) => ({ ...s, filters, page: 0 }));
|
|
535
|
+
const setSorts = (sorts: SortState[]) => setState((s) => ({ ...s, sorts, page: 0 }));
|
|
536
|
+
const setSearch = (search: string) => setState((s) => ({ ...s, search, page: 0 }));
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
page: state.page,
|
|
540
|
+
setPage,
|
|
541
|
+
pageSize: state.pageSize,
|
|
542
|
+
setPageSize,
|
|
543
|
+
filters: state.filters,
|
|
544
|
+
setFilters,
|
|
545
|
+
sorts: state.sorts,
|
|
546
|
+
setSorts,
|
|
547
|
+
search: state.search,
|
|
548
|
+
setSearch,
|
|
549
|
+
tableProps: (total, options) => ({
|
|
550
|
+
serverPagination: {
|
|
551
|
+
page: state.page,
|
|
552
|
+
pageSize: state.pageSize,
|
|
553
|
+
total,
|
|
554
|
+
onPageChange: setPage,
|
|
555
|
+
onPageSizeChange: setPageSize,
|
|
556
|
+
isLoading: options?.isLoading,
|
|
557
|
+
},
|
|
558
|
+
filters: state.filters,
|
|
559
|
+
onFiltersChange: setFilters,
|
|
560
|
+
sorts: state.sorts,
|
|
561
|
+
onSortsChange: setSorts,
|
|
562
|
+
}),
|
|
563
|
+
};
|
|
564
|
+
}
|
package/src/data-table.ts
CHANGED
|
@@ -19,4 +19,14 @@ export type {
|
|
|
19
19
|
MobileSwipeActions,
|
|
20
20
|
DataTableDensity,
|
|
21
21
|
DataTableChrome,
|
|
22
|
+
DataTableRowAction,
|
|
22
23
|
} from "./components/data-table";
|
|
24
|
+
export * from "./components/data-table-cells";
|
|
25
|
+
export { useTableUrlState, readTableUrlState } from "./components/use-table-state";
|
|
26
|
+
export type {
|
|
27
|
+
TableUrlState,
|
|
28
|
+
TableUrlSync,
|
|
29
|
+
TableUrlStateProps,
|
|
30
|
+
UseTableUrlStateOptions,
|
|
31
|
+
UseTableUrlStateReturn,
|
|
32
|
+
} from "./components/use-table-state";
|
|
@@ -3,6 +3,7 @@ import type { ReactNode } from "react";
|
|
|
3
3
|
import { Button, Input, PHONE_QUERY, Select, Textarea } from "../components/ui";
|
|
4
4
|
import { Modal } from "../components/modal";
|
|
5
5
|
import { useMediaQuery } from "../hooks/use-media-query";
|
|
6
|
+
import { useKitLabels } from "../i18n/kit-labels";
|
|
6
7
|
import {
|
|
7
8
|
DEFAULT_ATTACHMENT_ACCEPT,
|
|
8
9
|
DEFAULT_MAX_ATTACHMENT_BYTES,
|
|
@@ -39,6 +40,38 @@ export interface FeedbackDialogLabels extends FeedbackAttachmentLabels {
|
|
|
39
40
|
save: string;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The dialog's own strings — the `feedbackDialog` namespace of `<UiKitProvider
|
|
45
|
+
* labels>` (0.12.0). The attachment keys are not repeated here: they are the
|
|
46
|
+
* `feedbackAttachment` namespace's, which the field inside already reads.
|
|
47
|
+
*
|
|
48
|
+
* Before, every key was required at the call site, so each app restated ten strings
|
|
49
|
+
* wherever it mounted the dialog (kastlan feedback-button.tsx built the object inline).
|
|
50
|
+
*/
|
|
51
|
+
export interface FeedbackDialogTextLabels {
|
|
52
|
+
title: string;
|
|
53
|
+
category: string;
|
|
54
|
+
subject: string;
|
|
55
|
+
body: string;
|
|
56
|
+
/** The heading over the attachment buttons. */
|
|
57
|
+
attachment: string;
|
|
58
|
+
/** The line beside the buttons naming the shortcut. */
|
|
59
|
+
submitHint: string;
|
|
60
|
+
cancel: string;
|
|
61
|
+
save: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const DEFAULT_FEEDBACK_DIALOG_LABELS: FeedbackDialogTextLabels = {
|
|
65
|
+
title: "Send feedback",
|
|
66
|
+
category: "Category",
|
|
67
|
+
subject: "Subject",
|
|
68
|
+
body: "What happened?",
|
|
69
|
+
attachment: "Screenshot",
|
|
70
|
+
submitHint: "Ctrl/⌘ + Enter to send",
|
|
71
|
+
cancel: "Cancel",
|
|
72
|
+
save: "Send",
|
|
73
|
+
};
|
|
74
|
+
|
|
42
75
|
export interface FeedbackSubmission {
|
|
43
76
|
title: string;
|
|
44
77
|
body: string;
|
|
@@ -67,7 +100,7 @@ export function FeedbackDialog({
|
|
|
67
100
|
categories,
|
|
68
101
|
category,
|
|
69
102
|
onCategoryChange,
|
|
70
|
-
labels,
|
|
103
|
+
labels: labelsProp,
|
|
71
104
|
onSubmit,
|
|
72
105
|
submitting = false,
|
|
73
106
|
contextSlot,
|
|
@@ -81,7 +114,9 @@ export function FeedbackDialog({
|
|
|
81
114
|
categories: FeedbackCategoryOption[];
|
|
82
115
|
category: string;
|
|
83
116
|
onCategoryChange: (value: string) => void;
|
|
84
|
-
labels
|
|
117
|
+
/** Prop > `<UiKitProvider labels={{ feedbackDialog, feedbackAttachment }}>` >
|
|
118
|
+
* English. Optional since 0.12.0; a whole `FeedbackDialogLabels` still fits. */
|
|
119
|
+
labels?: Partial<FeedbackDialogLabels>;
|
|
85
120
|
onSubmit: (data: FeedbackSubmission) => void | Promise<void>;
|
|
86
121
|
submitting?: boolean;
|
|
87
122
|
contextSlot?: ReactNode;
|
|
@@ -99,6 +134,10 @@ export function FeedbackDialog({
|
|
|
99
134
|
const [body, setBody] = useState("");
|
|
100
135
|
const [attachment, setAttachment] = useState<File | null>(null);
|
|
101
136
|
const isMobile = useMediaQuery(PHONE_QUERY, false);
|
|
137
|
+
const labels = useKitLabels("feedbackDialog", DEFAULT_FEEDBACK_DIALOG_LABELS, labelsProp);
|
|
138
|
+
// The attachment field resolves its own keys from `feedbackAttachment`; it gets the
|
|
139
|
+
// prop's (so a caller's `attachmentAdd` still wins) plus the resolved heading.
|
|
140
|
+
const attachmentLabels = { ...labelsProp, attachment: labels.attachment };
|
|
102
141
|
|
|
103
142
|
// Reset the form whenever the dialog is (re)opened.
|
|
104
143
|
useEffect(() => {
|
|
@@ -138,7 +177,7 @@ export function FeedbackDialog({
|
|
|
138
177
|
<FeedbackAttachmentField
|
|
139
178
|
value={attachment}
|
|
140
179
|
onChange={setAttachment}
|
|
141
|
-
labels={
|
|
180
|
+
labels={attachmentLabels}
|
|
142
181
|
accept={attachmentAccept}
|
|
143
182
|
maxBytes={maxAttachmentBytes}
|
|
144
183
|
onError={onAttachmentError}
|