@eifi1/ui-kit 0.10.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 +66 -37
- 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/alert-banner.d.ts +26 -1
- package/dist/components/alert-banner.js +10 -7
- package/dist/components/alert-banner.js.map +1 -1
- package/dist/components/amount-input.d.ts +26 -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/bulk-action-bar.d.ts +51 -5
- package/dist/components/bulk-action-bar.js +88 -41
- package/dist/components/bulk-action-bar.js.map +1 -1
- package/dist/components/button-group.d.ts +107 -3
- package/dist/components/button-group.js +60 -5
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/calculator.d.ts +17 -4
- package/dist/components/calendar-heatmap.d.ts +162 -0
- package/dist/components/calendar-heatmap.js +336 -0
- package/dist/components/calendar-heatmap.js.map +1 -0
- 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 +66 -2
- package/dist/components/choice-card.js +108 -1
- 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 +52 -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 +104 -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 +69 -0
- package/dist/components/field.js +75 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/file-button.d.ts +51 -208
- package/dist/components/file-dropzone.d.ts +50 -2
- package/dist/components/floating-panel.d.ts +156 -4
- package/dist/components/floating-panel.js +193 -29
- 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 +37 -3
- package/dist/components/list.js +39 -14
- 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 +13 -2
- package/dist/components/menu-item.js +11 -2
- package/dist/components/menu-item.js.map +1 -1
- package/dist/components/mini-calendar.d.ts +82 -5
- package/dist/components/mini-calendar.js +212 -64
- 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 +67 -0
- package/dist/components/nav-pills.js +69 -0
- package/dist/components/nav-pills.js.map +1 -0
- package/dist/components/number-field.d.ts +26 -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 +27 -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 +17 -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 +73 -3
- package/dist/components/progress-bar.js +110 -8
- 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/series-chart.d.ts +16 -0
- package/dist/components/series-chart.js +4 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/settings-fields.d.ts +50 -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/status-dot.d.ts +6 -2
- package/dist/components/status-dot.js +6 -1
- package/dist/components/status-dot.js.map +1 -1
- package/dist/components/table.d.ts +77 -11
- package/dist/components/table.js +85 -17
- 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/text.d.ts +8 -1
- package/dist/components/text.js +1 -0
- package/dist/components/text.js.map +1 -1
- package/dist/components/time-input.d.ts +50 -1
- package/dist/components/toggle-group.d.ts +28 -0
- package/dist/components/toggle-group.js +58 -9
- 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/tooltip.d.ts +11 -3
- package/dist/components/tooltip.js +2 -0
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/ui.d.ts +52 -729
- package/dist/components/ui.js +296 -27
- 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/components/user-avatar.d.ts +25 -3
- package/dist/components/user-avatar.js +32 -3
- package/dist/components/user-avatar.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 +50 -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 +17 -4
- package/dist/i18n/defaults.js +33 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +47 -310
- 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 +17 -4
- package/dist/i18n/locales/de-CH.d.ts +17 -4
- package/dist/i18n/locales/de-informal.d.ts +17 -4
- package/dist/i18n/locales/de.d.ts +17 -4
- package/dist/i18n/locales/de.js +165 -4
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +17 -4
- package/dist/i18n/locales/es.js +162 -4
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +17 -4
- package/dist/i18n/locales/fr.js +162 -4
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +17 -4
- package/dist/i18n/locales/hu.js +162 -4
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +17 -4
- package/dist/i18n/locales/it.js +162 -4
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +17 -4
- package/dist/i18n/locales/zh.js +162 -4
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +45 -21
- package/dist/index.js +43 -1
- package/dist/index.js.map +1 -1
- package/dist/kit-labels-v3biUF1L.d.ts +1645 -0
- package/dist/lib/clipping.d.ts +21 -5
- package/dist/lib/clipping.js +3 -0
- package/dist/lib/clipping.js.map +1 -1
- package/dist/lib/dates.d.ts +17 -1
- package/dist/lib/dates.js +19 -0
- package/dist/lib/dates.js.map +1 -1
- 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 +50 -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/use-rhf-wizard-step.d.ts +22 -0
- package/dist/rhf/use-rhf-wizard-step.js +38 -0
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -0
- package/dist/rhf.d.ts +63 -1
- package/dist/rhf.js +3 -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 +84 -3
- package/dist/shell/topbar-action-menu.js +115 -27
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +58 -2
- package/dist/shell.js +2 -0
- package/dist/shell.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +49 -1
- package/dist/wizard/wizard-context.d.ts +30 -1
- package/dist/wizard/wizard-context.js +23 -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 +51 -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/alert-banner.tsx +36 -7
- 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/bulk-action-bar.tsx +163 -57
- package/src/components/button-group.tsx +129 -4
- package/src/components/calendar-heatmap.tsx +599 -0
- package/src/components/chip.tsx +8 -4
- package/src/components/choice-card.tsx +208 -1
- 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 +231 -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 +188 -0
- package/src/components/floating-panel.tsx +380 -16
- 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 +93 -18
- package/src/components/loading-state.tsx +47 -0
- package/src/components/menu-item.tsx +29 -3
- package/src/components/mini-calendar.tsx +317 -57
- 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 +152 -0
- 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 +241 -13
- package/src/components/qr-code.tsx +83 -0
- package/src/components/search-field.tsx +20 -4
- package/src/components/series-chart.tsx +21 -0
- 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/status-dot.tsx +10 -2
- package/src/components/table.tsx +186 -23
- package/src/components/text-link.tsx +248 -0
- package/src/components/text.tsx +9 -1
- package/src/components/toggle-group.tsx +106 -13
- package/src/components/toggle-legend.tsx +87 -2
- package/src/components/tooltip.tsx +17 -3
- package/src/components/ui.tsx +572 -44
- package/src/components/use-table-state.ts +265 -33
- package/src/components/user-avatar.tsx +57 -3
- 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 +32 -0
- package/src/i18n/kit-labels.tsx +104 -8
- package/src/i18n/locales/de.ts +162 -0
- package/src/i18n/locales/es.ts +159 -0
- package/src/i18n/locales/fr.ts +162 -0
- package/src/i18n/locales/hu.ts +160 -0
- package/src/i18n/locales/it.ts +160 -0
- package/src/i18n/locales/zh.ts +159 -0
- package/src/index.ts +64 -0
- package/src/lib/clipping.ts +22 -4
- package/src/lib/dates.ts +37 -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/use-rhf-wizard-step.ts +113 -0
- package/src/rhf.ts +8 -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 +260 -49
- package/src/shell.ts +2 -0
- package/src/wizard/wizard-context.tsx +50 -1
- package/src/wizard/wizard-step.tsx +43 -6
- package/tokens.css +12 -0
|
@@ -1,9 +1,253 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, TdHTMLAttributes, ThHTMLAttributes } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { SortState, SortDir, SortCycle } from './components/data-table-sort.js';
|
|
4
4
|
import { SwipeAction } from './components/swipeable-row.js';
|
|
5
5
|
import { DataTableLabels } from './components/data-table-labels.js';
|
|
6
6
|
|
|
7
|
+
/** How a column is filtered. `text`/`select`/`date`/`number` each read a value
|
|
8
|
+
* off the row and compare it against the user's {@link FilterValue}. */
|
|
9
|
+
type ColumnFilter<T> = {
|
|
10
|
+
type: "text";
|
|
11
|
+
getValue: (row: T) => string;
|
|
12
|
+
} | {
|
|
13
|
+
type: "select";
|
|
14
|
+
getValue: (row: T) => string;
|
|
15
|
+
options?: {
|
|
16
|
+
value: string;
|
|
17
|
+
label?: string;
|
|
18
|
+
}[];
|
|
19
|
+
} | {
|
|
20
|
+
type: "date";
|
|
21
|
+
getValue: (row: T) => string | null | undefined;
|
|
22
|
+
} | {
|
|
23
|
+
type: "number";
|
|
24
|
+
getValue: (row: T) => number | null | undefined;
|
|
25
|
+
};
|
|
26
|
+
/** The current state of a column's filter (what the user has entered). */
|
|
27
|
+
type FilterValue = {
|
|
28
|
+
type: "text";
|
|
29
|
+
q: string;
|
|
30
|
+
} | {
|
|
31
|
+
type: "select";
|
|
32
|
+
values: string[];
|
|
33
|
+
} | {
|
|
34
|
+
type: "date";
|
|
35
|
+
from: string;
|
|
36
|
+
to: string;
|
|
37
|
+
} | {
|
|
38
|
+
type: "number";
|
|
39
|
+
min: string;
|
|
40
|
+
max: string;
|
|
41
|
+
abs: boolean;
|
|
42
|
+
};
|
|
43
|
+
/** A column's filter config, falling back to a plain text filter over `filterBy`. */
|
|
44
|
+
declare function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null;
|
|
45
|
+
/** Whether a filter value is actually narrowing anything (vs its empty default). */
|
|
46
|
+
declare function isFilterActive(v: FilterValue | undefined): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Whether `row` passes column `col`'s filter given the user's `state`.
|
|
49
|
+
*
|
|
50
|
+
* `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the
|
|
51
|
+
* root rules, under which a Turkish user typing "istanbul" does not find "İSTANBUL";
|
|
52
|
+
* `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.
|
|
53
|
+
*/
|
|
54
|
+
declare function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValue, locale?: string): boolean;
|
|
55
|
+
/** The empty filter state for a column's filter type. */
|
|
56
|
+
declare function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue;
|
|
57
|
+
/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */
|
|
58
|
+
declare function encodeFilterValue(v: FilterValue): string | null;
|
|
59
|
+
/** Inverse of {@link encodeFilterValue} given just the filter type — for
|
|
60
|
+
* callers that know a column's type without holding its full config. */
|
|
61
|
+
declare function decodeFilterValueOfType(type: FilterValue["type"], raw: string): FilterValue;
|
|
62
|
+
/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */
|
|
63
|
+
declare function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue;
|
|
64
|
+
/** A free-text filter over `getValue`. */
|
|
65
|
+
declare function textFilter<T>(getValue: (row: T) => string): ColumnFilter<T>;
|
|
66
|
+
/**
|
|
67
|
+
* A multi-select filter. `options` is an options array, or a `{ value: label }` record
|
|
68
|
+
* (a translated status map) — kept in its key order. Left out, the table derives the
|
|
69
|
+
* options from the rows it holds.
|
|
70
|
+
*/
|
|
71
|
+
declare function selectFilter<T>(getValue: (row: T) => string, options?: {
|
|
72
|
+
value: string;
|
|
73
|
+
label?: string;
|
|
74
|
+
}[] | Record<string, string>): ColumnFilter<T>;
|
|
75
|
+
/** A from/to date filter over an ISO date (or date-time) string. */
|
|
76
|
+
declare function dateFilter<T>(getValue: (row: T) => string | null | undefined): ColumnFilter<T>;
|
|
77
|
+
/** A min/max (and absolute-value) filter over a number. */
|
|
78
|
+
declare function numberFilter<T>(getValue: (row: T) => number | null | undefined): ColumnFilter<T>;
|
|
79
|
+
/**
|
|
80
|
+
* A link to a list page with one column filter already applied — in the table's own URL
|
|
81
|
+
* scheme (`f.<column>=<encoded>`), which a table with `urlSync` (or
|
|
82
|
+
* {@link useTableUrlState}) reads on mount. kastlan's `buildListUrl`, which had to
|
|
83
|
+
* restate the `f.` prefix to do it.
|
|
84
|
+
*
|
|
85
|
+
* `value` is a string for a text filter, an array for a select filter, or any
|
|
86
|
+
* {@link FilterValue}. A filter that narrows nothing returns `path` unchanged. A `path`
|
|
87
|
+
* that already carries a query keeps it; a `#fragment` stays at the end.
|
|
88
|
+
*
|
|
89
|
+
* filterHref("/units", "status", ["vacant"]) // "/units?f.status=vacant"
|
|
90
|
+
*/
|
|
91
|
+
declare function filterHref(path: string, column: string, value: string | string[] | FilterValue,
|
|
92
|
+
/** The target table's `urlSync` prefix, if it has one (`{ prefix: "inv." }` →
|
|
93
|
+
* `inv.f.<column>`). */
|
|
94
|
+
options?: {
|
|
95
|
+
prefix?: string;
|
|
96
|
+
}): string;
|
|
97
|
+
|
|
98
|
+
declare const DEFAULT_PERSIST_PREFIX = "hbui-table:";
|
|
99
|
+
/**
|
|
100
|
+
* How a table is mirrored into the URL: `true`, or `{ prefix }` for a table that shares
|
|
101
|
+
* its page with another URL-synced one. The prefix is put verbatim in front of EVERY key
|
|
102
|
+
* the table writes and reads — `{ prefix: "inv." }` gives `inv.f.<column>`, `inv.sort`,
|
|
103
|
+
* `inv.p`, `inv.ps` (and `inv.q`) — so two tables on one page stop sharing a sort and a
|
|
104
|
+
* page. Without one the keys are unprefixed, as they always were.
|
|
105
|
+
*/
|
|
106
|
+
type TableUrlSync = boolean | {
|
|
107
|
+
prefix?: string;
|
|
108
|
+
};
|
|
109
|
+
/** What {@link readTableUrlState} found in a query string. */
|
|
110
|
+
interface TableUrlState {
|
|
111
|
+
sorts?: SortState[];
|
|
112
|
+
filters?: FilterState;
|
|
113
|
+
/** `Infinity` for `ps=all`. */
|
|
114
|
+
pageSize?: number;
|
|
115
|
+
/** 0-based (the URL's `p` is 1-based). */
|
|
116
|
+
page?: number;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* What a table's URL says, read out of a query string: the column filters (`f.<key>`),
|
|
120
|
+
* the sort, the page size and the 0-based page. Absent keys are absent here too, so a
|
|
121
|
+
* caller can tell "not in the link" from "the default". The same reader `urlSync` uses
|
|
122
|
+
* on mount, exported for an owner that keeps the state itself — see
|
|
123
|
+
* {@link useTableUrlState}.
|
|
124
|
+
*
|
|
125
|
+
* Pass the ROUTER's query string — the one `setSearchParams` writes — and the table's
|
|
126
|
+
* `prefix` when it has one (see {@link TableUrlSync}).
|
|
127
|
+
*
|
|
128
|
+
* This read `window.location.search` until 0.7.0, which is the same string only under a
|
|
129
|
+
* BrowserRouter. Under a HashRouter the router's query lives after the `#`
|
|
130
|
+
* (`/#/tx?f.payee=Rewe`) and `window.location.search` is empty: a shared link opened
|
|
131
|
+
* unfiltered, and the table's first write then replaced the link's filter with its own
|
|
132
|
+
* empty state. Reading and writing through `useSearchParams` is one source for both
|
|
133
|
+
* directions under every router, MemoryRouter included.
|
|
134
|
+
*/
|
|
135
|
+
declare function readTableUrlState<T>(sp: URLSearchParams, columns: DataTableColumn<T>[], prefix?: string): TableUrlState;
|
|
136
|
+
/**
|
|
137
|
+
* Where a table's view lives: what it is sorted and filtered by, which page, how wide
|
|
138
|
+
* the columns are and which are hidden — plus the two places that outlives a render.
|
|
139
|
+
*
|
|
140
|
+
* Lifted out of `DataTable`, which was 984 lines and twenty hooks in one function. This
|
|
141
|
+
* is the part that is genuinely about STATE rather than about drawing, and the three
|
|
142
|
+
* rules it encodes are easy to lose among the markup:
|
|
143
|
+
*
|
|
144
|
+
* * **The URL is read once, on mount.** A shared or deep link populates the initial view;
|
|
145
|
+
* after that the state is local, or the browser's own history would fight the user's
|
|
146
|
+
* next click.
|
|
147
|
+
* * **Sorting and filtering are optionally CONTROLLED.** A server-driven table passes
|
|
148
|
+
* state and a callback and owns them; everything else is self-owned. That is the
|
|
149
|
+
* `sortsProp ?? internalSorts` pair, and it is why the setters are exposed separately.
|
|
150
|
+
* * **Persistence writes on every change, the URL only when asked.** `storageKey` is the
|
|
151
|
+
* per-table identity; `urlSync` is opt-in because only some tables want their view in
|
|
152
|
+
* a shareable address.
|
|
153
|
+
* * **A controlled filter or sort that changes sends the table back to page 1.** See
|
|
154
|
+
* the note at the reset below.
|
|
155
|
+
*/
|
|
156
|
+
declare function useTableState<T>({ columns, storageKey, storageKeyPrefix, urlSync: urlSyncOption, defaultPageSize, sortsProp, filtersProp, }: {
|
|
157
|
+
columns: DataTableColumn<T>[];
|
|
158
|
+
storageKey?: string;
|
|
159
|
+
storageKeyPrefix: string;
|
|
160
|
+
urlSync?: TableUrlSync;
|
|
161
|
+
defaultPageSize: number;
|
|
162
|
+
sortsProp?: SortState[];
|
|
163
|
+
filtersProp?: FilterState;
|
|
164
|
+
}): {
|
|
165
|
+
sorts: SortState[];
|
|
166
|
+
filters: FilterState;
|
|
167
|
+
setInternalSorts: react.Dispatch<react.SetStateAction<SortState[]>>;
|
|
168
|
+
setInternalFilters: react.Dispatch<react.SetStateAction<FilterState>>;
|
|
169
|
+
page: number;
|
|
170
|
+
setPage: react.Dispatch<react.SetStateAction<number>>;
|
|
171
|
+
pageSize: number;
|
|
172
|
+
setPageSize: react.Dispatch<react.SetStateAction<number>>;
|
|
173
|
+
widths: Record<string, number>;
|
|
174
|
+
setWidths: react.Dispatch<react.SetStateAction<Record<string, number>>>;
|
|
175
|
+
hiddenCols: Set<string>;
|
|
176
|
+
setHiddenCols: react.Dispatch<react.SetStateAction<Set<string>>>;
|
|
177
|
+
showSettings: boolean;
|
|
178
|
+
setShowSettings: react.Dispatch<react.SetStateAction<boolean>>;
|
|
179
|
+
headRefs: Map<string, HTMLTableCellElement>;
|
|
180
|
+
};
|
|
181
|
+
interface UseTableUrlStateOptions<T> {
|
|
182
|
+
/** The table's columns — read for their keys and filter types only. */
|
|
183
|
+
columns: DataTableColumn<T>[];
|
|
184
|
+
/** Default 25. A page size equal to it is left out of the URL. */
|
|
185
|
+
defaultPageSize?: number;
|
|
186
|
+
/** Mirror the state into the URL and seed it from there on mount. Default true.
|
|
187
|
+
* `{ prefix }` puts a prefix in front of every key, for a page with two URL-synced
|
|
188
|
+
* tables — see {@link TableUrlSync}. */
|
|
189
|
+
urlSync?: TableUrlSync;
|
|
190
|
+
/** Shorthand for `urlSync: { prefix }`; wins over a prefix given there. */
|
|
191
|
+
urlPrefix?: string;
|
|
192
|
+
/** Also hold a free-text search term, under `q`. Off by default, so a `q` that
|
|
193
|
+
* belongs to someone else on the page is never touched. */
|
|
194
|
+
search?: boolean;
|
|
195
|
+
}
|
|
196
|
+
/** The part of {@link DataTableProps} a server-driven table takes from its owner. */
|
|
197
|
+
interface TableUrlStateProps {
|
|
198
|
+
serverPagination: ServerPagination;
|
|
199
|
+
filters: FilterState;
|
|
200
|
+
onFiltersChange: (next: FilterState) => void;
|
|
201
|
+
sorts: SortState[];
|
|
202
|
+
onSortsChange: (next: SortState[]) => void;
|
|
203
|
+
}
|
|
204
|
+
interface UseTableUrlStateReturn {
|
|
205
|
+
/** 0-based. */
|
|
206
|
+
page: number;
|
|
207
|
+
setPage: (page: number) => void;
|
|
208
|
+
pageSize: number;
|
|
209
|
+
/** Also back to the first page. */
|
|
210
|
+
setPageSize: (pageSize: number) => void;
|
|
211
|
+
filters: FilterState;
|
|
212
|
+
/** Also back to the first page. */
|
|
213
|
+
setFilters: (next: FilterState) => void;
|
|
214
|
+
sorts: SortState[];
|
|
215
|
+
/** Also back to the first page. */
|
|
216
|
+
setSorts: (next: SortState[]) => void;
|
|
217
|
+
/** The search term (`""` unless `search: true`). */
|
|
218
|
+
search: string;
|
|
219
|
+
/** Also back to the first page. */
|
|
220
|
+
setSearch: (next: string) => void;
|
|
221
|
+
/**
|
|
222
|
+
* Everything the table needs, to spread onto it:
|
|
223
|
+
* `<DataTable {...table.tableProps(data?.total ?? 0, { isLoading })} rows={…} … />`.
|
|
224
|
+
*/
|
|
225
|
+
tableProps: (total: number, options?: {
|
|
226
|
+
isLoading?: boolean;
|
|
227
|
+
}) => TableUrlStateProps;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The controlled state of a `serverPagination` table — filters, sorts, page, page size
|
|
231
|
+
* and optionally a search term — kept by the OWNER and mirrored into the URL in the
|
|
232
|
+
* table's own scheme (`f.<key>`, `sort`, `p`, `ps`, and `q`).
|
|
233
|
+
*
|
|
234
|
+
* A server-driven table owns none of this: the owner has to hold it to turn it into
|
|
235
|
+
* the query that fetches `rows`, and until now it also had to restate the table's URL
|
|
236
|
+
* codec to keep the view bookmarkable (kastlan's use-server-table.ts copied
|
|
237
|
+
* `readUrlState` / `writeUrlState` to do it). Same rules as `urlSync`:
|
|
238
|
+
*
|
|
239
|
+
* * **The URL is read once, on mount** — a shared or deep link (see {@link filterHref})
|
|
240
|
+
* seeds the view; after that the state is local and written out with `replace`.
|
|
241
|
+
* * **Any change but the page itself goes back to page 1** — a narrower filter must not
|
|
242
|
+
* leave the user on page 7 of 2.
|
|
243
|
+
* * **Written by value**: an unchanged view writes nothing, however often the owner
|
|
244
|
+
* renders.
|
|
245
|
+
*
|
|
246
|
+
* Mapping the state onto the backend's query parameters stays the owner's: that is the
|
|
247
|
+
* part the kit cannot know.
|
|
248
|
+
*/
|
|
249
|
+
declare function useTableUrlState<T>({ columns, defaultPageSize, urlSync: urlSyncOption, urlPrefix: urlPrefixOption, search: manageSearch, }: UseTableUrlStateOptions<T>): UseTableUrlStateReturn;
|
|
250
|
+
|
|
7
251
|
interface DataTableColumn<T> {
|
|
8
252
|
key: string;
|
|
9
253
|
header: ReactNode;
|
|
@@ -144,8 +388,11 @@ interface DataTableProps<T> {
|
|
|
144
388
|
* shareable and bookmarkable. URL params used: `f.<column>`, `sort`, `p`, `ps`.
|
|
145
389
|
* URL state takes precedence over localStorage on initial load. Requires the
|
|
146
390
|
* consuming app to render inside a react-router Router.
|
|
391
|
+
*
|
|
392
|
+
* `{ prefix }` namespaces them, for two URL-synced tables on one page:
|
|
393
|
+
* `urlSync={{ prefix: "inv." }}` uses `inv.f.<column>`, `inv.sort`, `inv.p`, `inv.ps`.
|
|
147
394
|
*/
|
|
148
|
-
urlSync?:
|
|
395
|
+
urlSync?: TableUrlSync;
|
|
149
396
|
/**
|
|
150
397
|
* When true (desktop only), the table fills its parent's height and scrolls
|
|
151
398
|
* INTERNALLY, with the header pinned — so the page itself doesn't add a second,
|
|
@@ -336,6 +583,60 @@ interface DataTableProps<T> {
|
|
|
336
583
|
/** Extra classes for the table's root element (the card, or with `frame={false}`
|
|
337
584
|
* the plain wrapper). Merged last, so a caller's margin or width wins. */
|
|
338
585
|
className?: string;
|
|
586
|
+
/**
|
|
587
|
+
* Per-row actions: an actions column of icon buttons at the row's end on desktop,
|
|
588
|
+
* and the same actions as swipe actions on a phone card (where the column is not
|
|
589
|
+
* shown). kastlan's `ResourceListPage` appended this column by hand to every CRUD
|
|
590
|
+
* list — the edit pencil, the delete bin, a `useConfirm` before the delete, and a
|
|
591
|
+
* `stopPropagation` on each so a press did not also open the row.
|
|
592
|
+
*
|
|
593
|
+
* `kind: "edit"` / `"delete"` bring the icon, the label (`labels.edit` /
|
|
594
|
+
* `labels.delete`), the tone and the swipe side; any other action states its own
|
|
595
|
+
* `label` and `icon`. `confirm` is the hook-in for a confirmation: pass the kit's
|
|
596
|
+
* `useConfirm()` (or anything answering a boolean) and the action runs only on a yes.
|
|
597
|
+
*
|
|
598
|
+
* const confirm = useConfirm();
|
|
599
|
+
* rowActions={[
|
|
600
|
+
* { kind: "edit", onAction: openEditor },
|
|
601
|
+
* { kind: "delete", onAction: (r) => remove(r.id),
|
|
602
|
+
* confirm: (r) => confirm({ title: t("delete", { name: r.name }), tone: "danger" }) },
|
|
603
|
+
* ]}
|
|
604
|
+
*
|
|
605
|
+
* A function receives the row, for actions that differ per row. An explicit
|
|
606
|
+
* `mobileSwipeActions` wins over the swipes derived from here.
|
|
607
|
+
*/
|
|
608
|
+
rowActions?: DataTableRowAction<T>[] | ((row: T) => DataTableRowAction<T>[]);
|
|
609
|
+
/**
|
|
610
|
+
* The bar above the table — a search field, filter chips, a create button. Inside
|
|
611
|
+
* the table's frame, above the header, on desktop and phone alike (resource-list
|
|
612
|
+
* pages set a search box in a `div` above the card, outside the frame it filters).
|
|
613
|
+
*/
|
|
614
|
+
toolbar?: ReactNode;
|
|
615
|
+
}
|
|
616
|
+
/** One entry of {@link DataTableProps.rowActions}. */
|
|
617
|
+
interface DataTableRowAction<T> {
|
|
618
|
+
/** A preset: `"edit"` (pencil, `labels.edit`, swipes toward the END) or `"delete"`
|
|
619
|
+
* (bin, `labels.delete`, danger tone, swipes toward the START). */
|
|
620
|
+
kind?: "edit" | "delete";
|
|
621
|
+
/** The action's name — its tooltip, accessible name and swipe label. Required
|
|
622
|
+
* without a `kind`. */
|
|
623
|
+
label?: string;
|
|
624
|
+
/** The glyph. Required without a `kind`. */
|
|
625
|
+
icon?: ReactNode;
|
|
626
|
+
/** Runs after `confirm` (when given) has said yes. */
|
|
627
|
+
onAction: (row: T) => void;
|
|
628
|
+
/** Asked before `onAction`; a `false` (or a promise of one) cancels. */
|
|
629
|
+
confirm?: (row: T) => boolean | Promise<boolean>;
|
|
630
|
+
/** Default: `"danger"` for `kind: "delete"`, else `"default"`. */
|
|
631
|
+
tone?: "default" | "danger";
|
|
632
|
+
/** Leave the action out for this row. */
|
|
633
|
+
hidden?: (row: T) => boolean;
|
|
634
|
+
/** Why the action is locked for this row — the button stays focusable and says so,
|
|
635
|
+
* and the row gets no swipe for it. See `IconButton`'s `disabledReason`. */
|
|
636
|
+
disabledReason?: (row: T) => ReactNode;
|
|
637
|
+
/** Which way the phone card is dragged to commit it, or `false` for no swipe.
|
|
638
|
+
* Default: `"start"` for a danger action, `"end"` otherwise. */
|
|
639
|
+
swipe?: "start" | "end" | false;
|
|
339
640
|
}
|
|
340
641
|
/** See {@link DataTableProps.chrome}. */
|
|
341
642
|
type DataTableChrome = "full" | "minimal";
|
|
@@ -354,64 +655,7 @@ interface MobileSwipeActions {
|
|
|
354
655
|
/** Committed by dragging physically RIGHT, whatever the direction. */
|
|
355
656
|
right?: SwipeAction[];
|
|
356
657
|
}
|
|
357
|
-
declare function DataTable<T>({ rows, columns, rowKey, defaultPageSize, onRowClick, rowHref, expandedRow, isExpanded, rowClassName, rowAttributes, paginated, empty, storageKey, urlSync, fillHeight, maxBodyHeight, serverPagination, filters: filtersProp, onFiltersChange, sorts: sortsProp, onSortsChange, selection, labels: labelsProp, locale: localeProp, storageKeyPrefix, mobileExpandAsDialog, mobileGroupBy, mobileGroupLabel, mobileCard, mobileSwipeActions, leadingRow, density, sortCycle, chrome, columnSettings: columnSettingsProp, resizable: resizableProp, multiSort: multiSortProp, frame, className, }: DataTableProps<T>): react.JSX.Element;
|
|
358
658
|
|
|
359
|
-
|
|
360
|
-
* off the row and compare it against the user's {@link FilterValue}. */
|
|
361
|
-
type ColumnFilter<T> = {
|
|
362
|
-
type: "text";
|
|
363
|
-
getValue: (row: T) => string;
|
|
364
|
-
} | {
|
|
365
|
-
type: "select";
|
|
366
|
-
getValue: (row: T) => string;
|
|
367
|
-
options?: {
|
|
368
|
-
value: string;
|
|
369
|
-
label?: string;
|
|
370
|
-
}[];
|
|
371
|
-
} | {
|
|
372
|
-
type: "date";
|
|
373
|
-
getValue: (row: T) => string | null | undefined;
|
|
374
|
-
} | {
|
|
375
|
-
type: "number";
|
|
376
|
-
getValue: (row: T) => number | null | undefined;
|
|
377
|
-
};
|
|
378
|
-
/** The current state of a column's filter (what the user has entered). */
|
|
379
|
-
type FilterValue = {
|
|
380
|
-
type: "text";
|
|
381
|
-
q: string;
|
|
382
|
-
} | {
|
|
383
|
-
type: "select";
|
|
384
|
-
values: string[];
|
|
385
|
-
} | {
|
|
386
|
-
type: "date";
|
|
387
|
-
from: string;
|
|
388
|
-
to: string;
|
|
389
|
-
} | {
|
|
390
|
-
type: "number";
|
|
391
|
-
min: string;
|
|
392
|
-
max: string;
|
|
393
|
-
abs: boolean;
|
|
394
|
-
};
|
|
395
|
-
/** A column's filter config, falling back to a plain text filter over `filterBy`. */
|
|
396
|
-
declare function resolveFilter<T>(col: DataTableColumn<T>): ColumnFilter<T> | null;
|
|
397
|
-
/** Whether a filter value is actually narrowing anything (vs its empty default). */
|
|
398
|
-
declare function isFilterActive(v: FilterValue | undefined): boolean;
|
|
399
|
-
/**
|
|
400
|
-
* Whether `row` passes column `col`'s filter given the user's `state`.
|
|
401
|
-
*
|
|
402
|
-
* `locale` is for the text filter's case folding. Plain `toLowerCase` folds by the
|
|
403
|
-
* root rules, under which a Turkish user typing "istanbul" does not find "İSTANBUL";
|
|
404
|
-
* `toLocaleLowerCase(locale)` does. Optional, and `undefined` is the runtime's default.
|
|
405
|
-
*/
|
|
406
|
-
declare function rowMatches<T>(col: DataTableColumn<T>, row: T, state: FilterValue, locale?: string): boolean;
|
|
407
|
-
/** The empty filter state for a column's filter type. */
|
|
408
|
-
declare function defaultFilterState<T>(filter: ColumnFilter<T>): FilterValue;
|
|
409
|
-
/** URL-safe encoding of a filter value (`f.<column>` param), null when empty. */
|
|
410
|
-
declare function encodeFilterValue(v: FilterValue): string | null;
|
|
411
|
-
/** Inverse of {@link encodeFilterValue} given just the filter type — for
|
|
412
|
-
* callers that know a column's type without holding its full config. */
|
|
413
|
-
declare function decodeFilterValueOfType(type: FilterValue["type"], raw: string): FilterValue;
|
|
414
|
-
/** Inverse of {@link encodeFilterValue}, typed by the column's filter config. */
|
|
415
|
-
declare function decodeFilterValue<T>(filter: ColumnFilter<T>, raw: string): FilterValue;
|
|
659
|
+
declare function DataTable<T>({ rows, columns: columnsProp, rowKey, defaultPageSize, onRowClick, rowHref, expandedRow, isExpanded, rowClassName, rowAttributes, paginated, empty, storageKey, urlSync, fillHeight, maxBodyHeight, serverPagination, filters: filtersProp, onFiltersChange, sorts: sortsProp, onSortsChange, selection, labels: labelsProp, locale: localeProp, storageKeyPrefix, mobileExpandAsDialog, mobileGroupBy, mobileGroupLabel, mobileCard, mobileSwipeActions, leadingRow, density, sortCycle, chrome, columnSettings: columnSettingsProp, resizable: resizableProp, multiSort: multiSortProp, frame, className, rowActions, toolbar, }: DataTableProps<T>): react.JSX.Element;
|
|
416
660
|
|
|
417
|
-
export { type ColumnFilter as C, DataTable as D, type FilterState as F, type MobileSwipeActions as M, type ServerPagination as S, type DataTableChrome as a, type DataTableColumn as b, type DataTableDensity as c, type DataTableProps as d, type
|
|
661
|
+
export { useTableState as A, type ColumnFilter as C, DataTable as D, type FilterState as F, type MobileSwipeActions as M, type ServerPagination as S, type TableUrlState as T, type UseTableUrlStateOptions as U, type DataTableChrome as a, type DataTableColumn as b, type DataTableDensity as c, type DataTableProps as d, type DataTableRowAction as e, type FilterValue as f, type TableUrlStateProps as g, type TableUrlSync as h, type UseTableUrlStateReturn as i, dateFilter as j, decodeFilterValue as k, decodeFilterValueOfType as l, defaultFilterState as m, encodeFilterValue as n, filterHref as o, isFilterActive as p, numberFilter as q, readTableUrlState as r, resolveFilter as s, rowMatches as t, selectFilter as u, textFilter as v, useTableUrlState as w, type DataTableCellProps as x, type DataTableHeadProps as y, DEFAULT_PERSIST_PREFIX as z };
|
package/dist/data-table.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { DEFAULT_DATA_TABLE_LABELS, DataTableLabels, missingDataTableLabels, resolveDataTableLabels } from './components/data-table-labels.js';
|
|
2
2
|
export { SortCycle, SortDir, SortState, SortStepOptions, decodeSorts, encodeSorts, nextSorts, normalizeSorts } from './components/data-table-sort.js';
|
|
3
|
-
export { C as ColumnFilter, D as DataTable, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, d as DataTableProps, F as FilterState,
|
|
3
|
+
export { C as ColumnFilter, D as DataTable, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, d as DataTableProps, e as DataTableRowAction, F as FilterState, f as FilterValue, M as MobileSwipeActions, S as ServerPagination, T as TableUrlState, g as TableUrlStateProps, h as TableUrlSync, U as UseTableUrlStateOptions, i as UseTableUrlStateReturn, j as dateFilter, k as decodeFilterValue, l as decodeFilterValueOfType, m as defaultFilterState, n as encodeFilterValue, o as filterHref, p as isFilterActive, q as numberFilter, r as readTableUrlState, s as resolveFilter, t as rowMatches, u as selectFilter, v as textFilter, w as useTableUrlState } from './data-table-Drun8Fk9.js';
|
|
4
4
|
export { PAGE_SIZE_OPTIONS, Pagination } from './components/data-table-pagination.js';
|
|
5
5
|
export { FilterPopover } from './components/data-table-filter-popover.js';
|
|
6
|
+
export { BooleanColumnOptions, BooleanMark, BooleanMarkProps, booleanColumn } from './components/data-table-cells.js';
|
|
6
7
|
import 'react';
|
|
7
8
|
import './components/swipeable-row.js';
|
package/dist/data-table.js
CHANGED
|
@@ -5,8 +5,12 @@ export * from "./components/data-table-filters.js";
|
|
|
5
5
|
export * from "./components/data-table-pagination.js";
|
|
6
6
|
import { FilterPopover } from "./components/data-table-filter-popover.js";
|
|
7
7
|
import { DataTable } from "./components/data-table.js";
|
|
8
|
+
export * from "./components/data-table-cells.js";
|
|
9
|
+
import { useTableUrlState, readTableUrlState } from "./components/use-table-state.js";
|
|
8
10
|
export {
|
|
9
11
|
DataTable,
|
|
10
|
-
FilterPopover
|
|
12
|
+
FilterPopover,
|
|
13
|
+
readTableUrlState,
|
|
14
|
+
useTableUrlState
|
|
11
15
|
};
|
|
12
16
|
//# sourceMappingURL=data-table.js.map
|
package/dist/data-table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/data-table.ts"],"sourcesContent":["// `@eifi1/ui-kit/data-table` — the table suite.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: DataTable calls useSearchParams unconditionally, above any `urlSync`\n// branch, so a Router is needed even with URL sync switched off.\nexport * from \"./components/data-table-labels\";\nexport * from \"./components/data-table-sort\";\nexport * from \"./components/data-table-filters\";\nexport * from \"./components/data-table-pagination\";\nexport { FilterPopover } from \"./components/data-table-filter-popover\";\n// `SortState` comes from data-table-sort above; re-exporting the module wholesale\n// would collide with it, which is why the main barrel names these explicitly too.\nexport { DataTable } from \"./components/data-table\";\nexport type {\n DataTableColumn,\n DataTableProps,\n ServerPagination,\n FilterState,\n MobileSwipeActions,\n DataTableDensity,\n DataTableChrome,\n} from \"./components/data-table\";\n"],"mappings":";AAKA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,qBAAqB;AAG9B,SAAS,iBAAiB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/data-table.ts"],"sourcesContent":["// `@eifi1/ui-kit/data-table` — the table suite.\n//\n// A re-slicing of the main barrel, not a new API. This entry point requires\n// `react-router`: DataTable calls useSearchParams unconditionally, above any `urlSync`\n// branch, so a Router is needed even with URL sync switched off.\nexport * from \"./components/data-table-labels\";\nexport * from \"./components/data-table-sort\";\nexport * from \"./components/data-table-filters\";\nexport * from \"./components/data-table-pagination\";\nexport { FilterPopover } from \"./components/data-table-filter-popover\";\n// `SortState` comes from data-table-sort above; re-exporting the module wholesale\n// would collide with it, which is why the main barrel names these explicitly too.\nexport { DataTable } from \"./components/data-table\";\nexport type {\n DataTableColumn,\n DataTableProps,\n ServerPagination,\n FilterState,\n MobileSwipeActions,\n DataTableDensity,\n DataTableChrome,\n DataTableRowAction,\n} from \"./components/data-table\";\nexport * from \"./components/data-table-cells\";\nexport { useTableUrlState, readTableUrlState } from \"./components/use-table-state\";\nexport type {\n TableUrlState,\n TableUrlSync,\n TableUrlStateProps,\n UseTableUrlStateOptions,\n UseTableUrlStateReturn,\n} from \"./components/use-table-state\";\n"],"mappings":";AAKA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,qBAAqB;AAG9B,SAAS,iBAAiB;AAW1B,cAAc;AACd,SAAS,kBAAkB,yBAAyB;","names":[]}
|
|
@@ -28,6 +28,27 @@ interface FeedbackDialogLabels extends FeedbackAttachmentLabels {
|
|
|
28
28
|
cancel: string;
|
|
29
29
|
save: string;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The dialog's own strings — the `feedbackDialog` namespace of `<UiKitProvider
|
|
33
|
+
* labels>` (0.12.0). The attachment keys are not repeated here: they are the
|
|
34
|
+
* `feedbackAttachment` namespace's, which the field inside already reads.
|
|
35
|
+
*
|
|
36
|
+
* Before, every key was required at the call site, so each app restated ten strings
|
|
37
|
+
* wherever it mounted the dialog (kastlan feedback-button.tsx built the object inline).
|
|
38
|
+
*/
|
|
39
|
+
interface FeedbackDialogTextLabels {
|
|
40
|
+
title: string;
|
|
41
|
+
category: string;
|
|
42
|
+
subject: string;
|
|
43
|
+
body: string;
|
|
44
|
+
/** The heading over the attachment buttons. */
|
|
45
|
+
attachment: string;
|
|
46
|
+
/** The line beside the buttons naming the shortcut. */
|
|
47
|
+
submitHint: string;
|
|
48
|
+
cancel: string;
|
|
49
|
+
save: string;
|
|
50
|
+
}
|
|
51
|
+
declare const DEFAULT_FEEDBACK_DIALOG_LABELS: FeedbackDialogTextLabels;
|
|
31
52
|
interface FeedbackSubmission {
|
|
32
53
|
title: string;
|
|
33
54
|
body: string;
|
|
@@ -49,13 +70,15 @@ interface FeedbackSubmission {
|
|
|
49
70
|
* (Steering Design feedback #39). The clipboard is where a region snip already
|
|
50
71
|
* is on every platform.
|
|
51
72
|
*/
|
|
52
|
-
declare function FeedbackDialog({ open, onClose, categories, category, onCategoryChange, labels, onSubmit, submitting, contextSlot, attachmentAccept, maxAttachmentBytes, onAttachmentError, onCaptureScreenshot, }: {
|
|
73
|
+
declare function FeedbackDialog({ open, onClose, categories, category, onCategoryChange, labels: labelsProp, onSubmit, submitting, contextSlot, attachmentAccept, maxAttachmentBytes, onAttachmentError, onCaptureScreenshot, }: {
|
|
53
74
|
open: boolean;
|
|
54
75
|
onClose: () => void;
|
|
55
76
|
categories: FeedbackCategoryOption[];
|
|
56
77
|
category: string;
|
|
57
78
|
onCategoryChange: (value: string) => void;
|
|
58
|
-
labels
|
|
79
|
+
/** Prop > `<UiKitProvider labels={{ feedbackDialog, feedbackAttachment }}>` >
|
|
80
|
+
* English. Optional since 0.12.0; a whole `FeedbackDialogLabels` still fits. */
|
|
81
|
+
labels?: Partial<FeedbackDialogLabels>;
|
|
59
82
|
onSubmit: (data: FeedbackSubmission) => void | Promise<void>;
|
|
60
83
|
submitting?: boolean;
|
|
61
84
|
contextSlot?: ReactNode;
|
|
@@ -70,4 +93,4 @@ declare function FeedbackDialog({ open, onClose, categories, category, onCategor
|
|
|
70
93
|
onCaptureScreenshot?: () => Promise<File | null>;
|
|
71
94
|
}): react.JSX.Element | null;
|
|
72
95
|
|
|
73
|
-
export { type FeedbackAttachmentLabels, type FeedbackCategoryOption, FeedbackDialog, type FeedbackDialogLabels, type FeedbackSubmission };
|
|
96
|
+
export { DEFAULT_FEEDBACK_DIALOG_LABELS, type FeedbackAttachmentLabels, type FeedbackCategoryOption, FeedbackDialog, type FeedbackDialogLabels, type FeedbackDialogTextLabels, type FeedbackSubmission };
|
|
@@ -4,18 +4,29 @@ import { useEffect, useState } from "react";
|
|
|
4
4
|
import { Button, Input, PHONE_QUERY, Select, Textarea } from "../components/ui.js";
|
|
5
5
|
import { Modal } from "../components/modal.js";
|
|
6
6
|
import { useMediaQuery } from "../hooks/use-media-query.js";
|
|
7
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
7
8
|
import {
|
|
8
9
|
DEFAULT_ATTACHMENT_ACCEPT,
|
|
9
10
|
DEFAULT_MAX_ATTACHMENT_BYTES,
|
|
10
11
|
FeedbackAttachmentField
|
|
11
12
|
} from "./feedback-attachment.js";
|
|
13
|
+
const DEFAULT_FEEDBACK_DIALOG_LABELS = {
|
|
14
|
+
title: "Send feedback",
|
|
15
|
+
category: "Category",
|
|
16
|
+
subject: "Subject",
|
|
17
|
+
body: "What happened?",
|
|
18
|
+
attachment: "Screenshot",
|
|
19
|
+
submitHint: "Ctrl/\u2318 + Enter to send",
|
|
20
|
+
cancel: "Cancel",
|
|
21
|
+
save: "Send"
|
|
22
|
+
};
|
|
12
23
|
function FeedbackDialog({
|
|
13
24
|
open,
|
|
14
25
|
onClose,
|
|
15
26
|
categories,
|
|
16
27
|
category,
|
|
17
28
|
onCategoryChange,
|
|
18
|
-
labels,
|
|
29
|
+
labels: labelsProp,
|
|
19
30
|
onSubmit,
|
|
20
31
|
submitting = false,
|
|
21
32
|
contextSlot,
|
|
@@ -28,6 +39,8 @@ function FeedbackDialog({
|
|
|
28
39
|
const [body, setBody] = useState("");
|
|
29
40
|
const [attachment, setAttachment] = useState(null);
|
|
30
41
|
const isMobile = useMediaQuery(PHONE_QUERY, false);
|
|
42
|
+
const labels = useKitLabels("feedbackDialog", DEFAULT_FEEDBACK_DIALOG_LABELS, labelsProp);
|
|
43
|
+
const attachmentLabels = { ...labelsProp, attachment: labels.attachment };
|
|
31
44
|
useEffect(() => {
|
|
32
45
|
if (open) {
|
|
33
46
|
setTitle("");
|
|
@@ -48,7 +61,7 @@ function FeedbackDialog({
|
|
|
48
61
|
{
|
|
49
62
|
value: attachment,
|
|
50
63
|
onChange: setAttachment,
|
|
51
|
-
labels,
|
|
64
|
+
labels: attachmentLabels,
|
|
52
65
|
accept: attachmentAccept,
|
|
53
66
|
maxBytes: maxAttachmentBytes,
|
|
54
67
|
onError: onAttachmentError,
|
|
@@ -105,6 +118,7 @@ function FeedbackDialog({
|
|
|
105
118
|
);
|
|
106
119
|
}
|
|
107
120
|
export {
|
|
121
|
+
DEFAULT_FEEDBACK_DIALOG_LABELS,
|
|
108
122
|
FeedbackDialog
|
|
109
123
|
};
|
|
110
124
|
//# sourceMappingURL=feedback-dialog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/feedback/feedback-dialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Button, Input, PHONE_QUERY, Select, Textarea } from \"../components/ui\";\nimport { Modal } from \"../components/modal\";\nimport { useMediaQuery } from \"../hooks/use-media-query\";\nimport {\n DEFAULT_ATTACHMENT_ACCEPT,\n DEFAULT_MAX_ATTACHMENT_BYTES,\n FeedbackAttachmentField,\n} from \"./feedback-attachment\";\n\nexport interface FeedbackCategoryOption {\n value: string;\n label: string;\n}\n\nexport interface FeedbackAttachmentLabels {\n /** The heading over the field. Optional: a note editor puts the buttons\n * straight under its textarea, where a second heading is noise. */\n attachment?: string;\n attachmentAdd: string;\n /** Label for the \"capture screenshot\" button. Optional — falls back to the provider's\n * `feedbackAttachment.attachmentCapture`, then English. */\n attachmentCapture?: string;\n /** The line under the attachment buttons saying a screenshot can be pasted\n * straight in. Optional — falls back to the provider's `feedbackAttachment`, then English. */\n attachmentPaste?: string;\n attachmentRemove: string;\n}\n\nexport interface FeedbackDialogLabels extends FeedbackAttachmentLabels {\n title: string;\n category: string;\n subject: string;\n body: string;\n attachment: string;\n submitHint: string;\n cancel: string;\n save: string;\n}\n\nexport interface FeedbackSubmission {\n title: string;\n body: string;\n category: string;\n attachment: File | null;\n}\n\n/**\n * The generic feedback form dialog: category + subject + body + an optional image\n * attachment, with Ctrl/Cmd+Enter to submit. Domain-free — the app supplies the\n * category options, labels and an `onSubmit` that talks to its own backend, plus\n * an optional `contextSlot` for app-specific context (user, current URL, …).\n *\n * **A screenshot can be pasted straight in.** Ctrl/Cmd+V anywhere in the dialog\n * takes an image off the clipboard and makes it the attachment, through the same\n * validation and the same preview as the file picker. It is the gesture the two\n * ways in did not cover: `onCaptureScreenshot` snapshots the *whole* app view,\n * and the file picker needs a file — so somebody who wanted to show one panel,\n * or one region of one, had to save a crop to disk first and then find it again\n * (Steering Design feedback #39). The clipboard is where a region snip already\n * is on every platform.\n */\nexport function FeedbackDialog({\n open,\n onClose,\n categories,\n category,\n onCategoryChange,\n labels,\n onSubmit,\n submitting = false,\n contextSlot,\n attachmentAccept = DEFAULT_ATTACHMENT_ACCEPT,\n maxAttachmentBytes = DEFAULT_MAX_ATTACHMENT_BYTES,\n onAttachmentError,\n onCaptureScreenshot,\n}: {\n open: boolean;\n onClose: () => void;\n categories: FeedbackCategoryOption[];\n category: string;\n onCategoryChange: (value: string) => void;\n labels: FeedbackDialogLabels;\n onSubmit: (data: FeedbackSubmission) => void | Promise<void>;\n submitting?: boolean;\n contextSlot?: ReactNode;\n attachmentAccept?: string[];\n maxAttachmentBytes?: number;\n onAttachmentError?: (kind: \"type\" | \"size\") => void;\n /**\n * Optional: capture a screenshot of the underlying app view and return it as a\n * File. When provided, a \"Capture screenshot\" button is shown next to \"Add\n * attachment\"; the returned file is fed through the same validation + preview.\n */\n onCaptureScreenshot?: () => Promise<File | null>;\n}) {\n const [title, setTitle] = useState(\"\");\n const [body, setBody] = useState(\"\");\n const [attachment, setAttachment] = useState<File | null>(null);\n const isMobile = useMediaQuery(PHONE_QUERY, false);\n\n // Reset the form whenever the dialog is (re)opened.\n useEffect(() => {\n if (open) {\n setTitle(\"\");\n setBody(\"\");\n setAttachment(null);\n }\n }, [open]);\n\n const canSubmit = !!title && !!body && !submitting;\n const trySubmit = () => {\n if (canSubmit) void onSubmit({ title, body, category, attachment });\n };\n\n if (!open) return null;\n\n const categoryField = (\n <Select label={labels.category} value={category} onChange={(e) => onCategoryChange(e.target.value)}>\n {categories.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </Select>\n );\n // On a phone the subject leads as a heading rather than as the second of two\n // identical boxes under a select (Keksdose feedback #179). `variant=\"display\"`\n // is a no-op above the phone breakpoint, so the desktop dialog is unchanged.\n const subjectField = (\n <Input label={labels.subject} value={title} onChange={(e) => setTitle(e.target.value)} variant=\"display\" />\n );\n const bodyField = (\n <Textarea rows={5} label={labels.body} value={body} onChange={(e) => setBody(e.target.value)} />\n );\n const attachmentField = (\n <FeedbackAttachmentField\n value={attachment}\n onChange={setAttachment}\n labels={labels}\n accept={attachmentAccept}\n maxBytes={maxAttachmentBytes}\n onError={onAttachmentError}\n onCaptureScreenshot={onCaptureScreenshot}\n // On `document`, not on the panel: the Modal focuses its own panel on open\n // and traps Tab inside it, so while this dialog is up every paste in the\n // page is meant for it — including the one made with nothing in\n // particular focused, which never reaches a React `onPaste` on a child.\n documentPaste={open}\n />\n );\n\n return (\n <Modal\n onClose={onClose}\n // The one dialog whose subject is the page behind it (Keksdose dev#460: \"Make\n // the feedback dialog draggable so I can see behind it if it blocks\n // something\") — you are describing what is under it while you type.\n draggable\n onKeyDown={(e) => {\n if ((e.ctrlKey || e.metaKey) && e.key === \"Enter\") {\n e.preventDefault();\n trySubmit();\n }\n }}\n >\n {/* Also the drag handle: pressing anywhere on the panel's own chrome moves it,\n and the heading is the strip a user reaches for. */}\n <h2 className=\"mb-3 text-lg font-semibold\">{labels.title}</h2>\n <div className=\"space-y-3\">\n {isMobile ? (\n // Phone shape, the same reasoning as the transaction editor's (#177):\n // the desktop order is the DATA MODEL's order — category, then subject,\n // then body — but nobody opens this dialog to pick a category. What they\n // came to do is say the thing, so that leads; the classification and the\n // evidence follow, a tier down.\n <>\n {subjectField}\n {bodyField}\n <div className=\"space-y-3 border-t border-[var(--border)] pt-3\">\n {categoryField}\n {attachmentField}\n {contextSlot}\n </div>\n </>\n ) : (\n <>\n {categoryField}\n {subjectField}\n {bodyField}\n {attachmentField}\n {contextSlot}\n </>\n )}\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-xs text-[var(--text-placeholder)]\">{labels.submitHint}</div>\n <div className=\"flex gap-2\">\n <Button variant=\"ghost\" onClick={onClose}>\n {labels.cancel}\n </Button>\n <Button onClick={trySubmit} disabled={!canSubmit}>\n {labels.save}\n </Button>\n </div>\n </div>\n </div>\n </Modal>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/feedback/feedback-dialog.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { Button, Input, PHONE_QUERY, Select, Textarea } from \"../components/ui\";\nimport { Modal } from \"../components/modal\";\nimport { useMediaQuery } from \"../hooks/use-media-query\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport {\n DEFAULT_ATTACHMENT_ACCEPT,\n DEFAULT_MAX_ATTACHMENT_BYTES,\n FeedbackAttachmentField,\n} from \"./feedback-attachment\";\n\nexport interface FeedbackCategoryOption {\n value: string;\n label: string;\n}\n\nexport interface FeedbackAttachmentLabels {\n /** The heading over the field. Optional: a note editor puts the buttons\n * straight under its textarea, where a second heading is noise. */\n attachment?: string;\n attachmentAdd: string;\n /** Label for the \"capture screenshot\" button. Optional — falls back to the provider's\n * `feedbackAttachment.attachmentCapture`, then English. */\n attachmentCapture?: string;\n /** The line under the attachment buttons saying a screenshot can be pasted\n * straight in. Optional — falls back to the provider's `feedbackAttachment`, then English. */\n attachmentPaste?: string;\n attachmentRemove: string;\n}\n\nexport interface FeedbackDialogLabels extends FeedbackAttachmentLabels {\n title: string;\n category: string;\n subject: string;\n body: string;\n attachment: string;\n submitHint: string;\n cancel: string;\n save: string;\n}\n\n/**\n * The dialog's own strings — the `feedbackDialog` namespace of `<UiKitProvider\n * labels>` (0.12.0). The attachment keys are not repeated here: they are the\n * `feedbackAttachment` namespace's, which the field inside already reads.\n *\n * Before, every key was required at the call site, so each app restated ten strings\n * wherever it mounted the dialog (kastlan feedback-button.tsx built the object inline).\n */\nexport interface FeedbackDialogTextLabels {\n title: string;\n category: string;\n subject: string;\n body: string;\n /** The heading over the attachment buttons. */\n attachment: string;\n /** The line beside the buttons naming the shortcut. */\n submitHint: string;\n cancel: string;\n save: string;\n}\n\nexport const DEFAULT_FEEDBACK_DIALOG_LABELS: FeedbackDialogTextLabels = {\n title: \"Send feedback\",\n category: \"Category\",\n subject: \"Subject\",\n body: \"What happened?\",\n attachment: \"Screenshot\",\n submitHint: \"Ctrl/⌘ + Enter to send\",\n cancel: \"Cancel\",\n save: \"Send\",\n};\n\nexport interface FeedbackSubmission {\n title: string;\n body: string;\n category: string;\n attachment: File | null;\n}\n\n/**\n * The generic feedback form dialog: category + subject + body + an optional image\n * attachment, with Ctrl/Cmd+Enter to submit. Domain-free — the app supplies the\n * category options, labels and an `onSubmit` that talks to its own backend, plus\n * an optional `contextSlot` for app-specific context (user, current URL, …).\n *\n * **A screenshot can be pasted straight in.** Ctrl/Cmd+V anywhere in the dialog\n * takes an image off the clipboard and makes it the attachment, through the same\n * validation and the same preview as the file picker. It is the gesture the two\n * ways in did not cover: `onCaptureScreenshot` snapshots the *whole* app view,\n * and the file picker needs a file — so somebody who wanted to show one panel,\n * or one region of one, had to save a crop to disk first and then find it again\n * (Steering Design feedback #39). The clipboard is where a region snip already\n * is on every platform.\n */\nexport function FeedbackDialog({\n open,\n onClose,\n categories,\n category,\n onCategoryChange,\n labels: labelsProp,\n onSubmit,\n submitting = false,\n contextSlot,\n attachmentAccept = DEFAULT_ATTACHMENT_ACCEPT,\n maxAttachmentBytes = DEFAULT_MAX_ATTACHMENT_BYTES,\n onAttachmentError,\n onCaptureScreenshot,\n}: {\n open: boolean;\n onClose: () => void;\n categories: FeedbackCategoryOption[];\n category: string;\n onCategoryChange: (value: string) => void;\n /** Prop > `<UiKitProvider labels={{ feedbackDialog, feedbackAttachment }}>` >\n * English. Optional since 0.12.0; a whole `FeedbackDialogLabels` still fits. */\n labels?: Partial<FeedbackDialogLabels>;\n onSubmit: (data: FeedbackSubmission) => void | Promise<void>;\n submitting?: boolean;\n contextSlot?: ReactNode;\n attachmentAccept?: string[];\n maxAttachmentBytes?: number;\n onAttachmentError?: (kind: \"type\" | \"size\") => void;\n /**\n * Optional: capture a screenshot of the underlying app view and return it as a\n * File. When provided, a \"Capture screenshot\" button is shown next to \"Add\n * attachment\"; the returned file is fed through the same validation + preview.\n */\n onCaptureScreenshot?: () => Promise<File | null>;\n}) {\n const [title, setTitle] = useState(\"\");\n const [body, setBody] = useState(\"\");\n const [attachment, setAttachment] = useState<File | null>(null);\n const isMobile = useMediaQuery(PHONE_QUERY, false);\n const labels = useKitLabels(\"feedbackDialog\", DEFAULT_FEEDBACK_DIALOG_LABELS, labelsProp);\n // The attachment field resolves its own keys from `feedbackAttachment`; it gets the\n // prop's (so a caller's `attachmentAdd` still wins) plus the resolved heading.\n const attachmentLabels = { ...labelsProp, attachment: labels.attachment };\n\n // Reset the form whenever the dialog is (re)opened.\n useEffect(() => {\n if (open) {\n setTitle(\"\");\n setBody(\"\");\n setAttachment(null);\n }\n }, [open]);\n\n const canSubmit = !!title && !!body && !submitting;\n const trySubmit = () => {\n if (canSubmit) void onSubmit({ title, body, category, attachment });\n };\n\n if (!open) return null;\n\n const categoryField = (\n <Select label={labels.category} value={category} onChange={(e) => onCategoryChange(e.target.value)}>\n {categories.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </Select>\n );\n // On a phone the subject leads as a heading rather than as the second of two\n // identical boxes under a select (Keksdose feedback #179). `variant=\"display\"`\n // is a no-op above the phone breakpoint, so the desktop dialog is unchanged.\n const subjectField = (\n <Input label={labels.subject} value={title} onChange={(e) => setTitle(e.target.value)} variant=\"display\" />\n );\n const bodyField = (\n <Textarea rows={5} label={labels.body} value={body} onChange={(e) => setBody(e.target.value)} />\n );\n const attachmentField = (\n <FeedbackAttachmentField\n value={attachment}\n onChange={setAttachment}\n labels={attachmentLabels}\n accept={attachmentAccept}\n maxBytes={maxAttachmentBytes}\n onError={onAttachmentError}\n onCaptureScreenshot={onCaptureScreenshot}\n // On `document`, not on the panel: the Modal focuses its own panel on open\n // and traps Tab inside it, so while this dialog is up every paste in the\n // page is meant for it — including the one made with nothing in\n // particular focused, which never reaches a React `onPaste` on a child.\n documentPaste={open}\n />\n );\n\n return (\n <Modal\n onClose={onClose}\n // The one dialog whose subject is the page behind it (Keksdose dev#460: \"Make\n // the feedback dialog draggable so I can see behind it if it blocks\n // something\") — you are describing what is under it while you type.\n draggable\n onKeyDown={(e) => {\n if ((e.ctrlKey || e.metaKey) && e.key === \"Enter\") {\n e.preventDefault();\n trySubmit();\n }\n }}\n >\n {/* Also the drag handle: pressing anywhere on the panel's own chrome moves it,\n and the heading is the strip a user reaches for. */}\n <h2 className=\"mb-3 text-lg font-semibold\">{labels.title}</h2>\n <div className=\"space-y-3\">\n {isMobile ? (\n // Phone shape, the same reasoning as the transaction editor's (#177):\n // the desktop order is the DATA MODEL's order — category, then subject,\n // then body — but nobody opens this dialog to pick a category. What they\n // came to do is say the thing, so that leads; the classification and the\n // evidence follow, a tier down.\n <>\n {subjectField}\n {bodyField}\n <div className=\"space-y-3 border-t border-[var(--border)] pt-3\">\n {categoryField}\n {attachmentField}\n {contextSlot}\n </div>\n </>\n ) : (\n <>\n {categoryField}\n {subjectField}\n {bodyField}\n {attachmentField}\n {contextSlot}\n </>\n )}\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"text-xs text-[var(--text-placeholder)]\">{labels.submitHint}</div>\n <div className=\"flex gap-2\">\n <Button variant=\"ghost\" onClick={onClose}>\n {labels.cancel}\n </Button>\n <Button onClick={trySubmit} disabled={!canSubmit}>\n {labels.save}\n </Button>\n </div>\n </div>\n </div>\n </Modal>\n );\n}\n"],"mappings":"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","names":[]}
|