@eifi1/ui-kit 0.11.0 → 0.13.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 +59 -34
- 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 +40 -4
- package/dist/components/amount-input.js +50 -12
- 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 +10 -2
- package/dist/components/bulk-action-bar.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/chart.js +14 -8
- package/dist/components/chart.js.map +1 -1
- package/dist/components/chip.d.ts +2 -1
- package/dist/components/chip.js +26 -18
- 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.d.ts +3 -1
- package/dist/components/combobox-core.js +18 -5
- 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-filter-popover.js +8 -1
- package/dist/components/data-table-filter-popover.js.map +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 +125 -27
- 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 +40 -20
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/error-boundary.d.ts +281 -0
- package/dist/components/error-boundary.js +302 -0
- package/dist/components/error-boundary.js.map +1 -0
- package/dist/components/field-sync.js +90 -83
- package/dist/components/field-sync.js.map +1 -1
- 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/full-bleed-dialog.js +4 -0
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/hover-menu.js +64 -56
- package/dist/components/hover-menu.js.map +1 -1
- 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 -4
- package/dist/components/list.js +35 -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 +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 +42 -20
- 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 +20 -5
- package/dist/components/numpad-sheet.js +3 -2
- package/dist/components/numpad-sheet.js.map +1 -1
- package/dist/components/page-header.d.ts +35 -6
- package/dist/components/page-header.js +7 -1
- package/dist/components/page-header.js.map +1 -1
- 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 +5 -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/popover.js +3 -0
- package/dist/components/popover.js.map +1 -1
- 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 +122 -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 +6 -3
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swipeable-row.js +4 -3
- package/dist/components/swipeable-row.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 +238 -0
- package/dist/components/text-link.js +144 -0
- package/dist/components/text-link.js.map +1 -0
- package/dist/components/text.d.ts +26 -2
- package/dist/components/text.js +16 -2
- package/dist/components/text.js.map +1 -1
- package/dist/components/time-input.d.ts +49 -1
- package/dist/components/toggle-group.d.ts +35 -0
- package/dist/components/toggle-group.js +43 -14
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle-legend.d.ts +53 -3
- package/dist/components/toggle-legend.js +54 -1
- package/dist/components/toggle-legend.js.map +1 -1
- package/dist/components/tooltip.d.ts +51 -1
- package/dist/components/tooltip.js +59 -35
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/ui.d.ts +51 -779
- package/dist/components/ui.js +364 -34
- 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-attachment.js +4 -6
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +26 -3
- package/dist/feedback/feedback-dialog.js +21 -5
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.js +86 -75
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +173 -0
- package/dist/feedback/feedback-thread.js +264 -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 +164 -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 +161 -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 +161 -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 +161 -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 +161 -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 +161 -3
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.d.ts +39 -19
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/kit-labels-Mz8zZHIF.d.ts +1744 -0
- package/dist/lib/format.d.ts +162 -0
- package/dist/lib/format.js +151 -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 +283 -0
- package/dist/rhf/fields.js +631 -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/search/command-palette.js +20 -14
- package/dist/search/command-palette.js.map +1 -1
- package/dist/shell/app-shell.d.ts +99 -2
- package/dist/shell/app-shell.js +120 -57
- 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 +21 -4
- package/dist/shell/topbar-action-menu.js +18 -2
- 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/tour/tour.js +10 -4
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +48 -1
- package/dist/wizard/use-wizard.js +4 -2
- package/dist/wizard/use-wizard.js.map +1 -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 +30 -5
- package/dist/wizard/wizard-step.js +34 -3
- package/dist/wizard/wizard-step.js.map +1 -1
- package/dist/wizard.d.ts +50 -3
- package/package.json +6 -6
- 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 +109 -10
- package/src/components/authed-image.tsx +190 -0
- package/src/components/breadcrumbs.tsx +8 -2
- package/src/components/bulk-action-bar.tsx +16 -3
- package/src/components/button-group.tsx +7 -2
- package/src/components/calendar-heatmap.tsx +105 -10
- package/src/components/chart.tsx +19 -8
- package/src/components/chip.tsx +14 -4
- package/src/components/choice-card.tsx +136 -25
- package/src/components/combobox-core.tsx +33 -9
- package/src/components/combobox.tsx +33 -9
- package/src/components/copy-button.tsx +3 -2
- package/src/components/data-table-cells.tsx +119 -0
- package/src/components/data-table-filter-popover.tsx +13 -5
- package/src/components/data-table-filters.ts +77 -0
- package/src/components/data-table-labels.ts +23 -0
- package/src/components/data-table.tsx +230 -13
- package/src/components/date-picker.tsx +19 -3
- package/src/components/description-list.tsx +52 -3
- package/src/components/entity-combobox.tsx +25 -7
- package/src/components/error-boundary.tsx +640 -0
- package/src/components/field-sync.tsx +48 -40
- 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/full-bleed-dialog.tsx +4 -0
- package/src/components/hover-menu.tsx +26 -17
- 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 +82 -19
- 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 +65 -3
- package/src/components/month-picker.tsx +181 -26
- package/src/components/multi-entity-combobox.tsx +27 -7
- package/src/components/nav-pills.tsx +8 -1
- package/src/components/number-field.tsx +15 -10
- package/src/components/number-input.tsx +17 -1
- package/src/components/numpad-sheet.tsx +6 -1
- package/src/components/page-header.tsx +42 -5
- package/src/components/passkeys-setting.tsx +331 -0
- package/src/components/picker-sheet.tsx +10 -1
- package/src/components/pie-chart-labels.ts +32 -0
- package/src/components/pie-chart.tsx +479 -0
- package/src/components/popover.tsx +3 -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 +295 -0
- package/src/components/skeleton.tsx +21 -3
- package/src/components/stat-tile.tsx +11 -3
- package/src/components/swipeable-row.tsx +7 -7
- package/src/components/table.tsx +102 -13
- package/src/components/text-link.tsx +356 -0
- package/src/components/text.tsx +46 -2
- package/src/components/toggle-group.tsx +93 -20
- package/src/components/toggle-legend.tsx +108 -2
- package/src/components/tooltip.tsx +79 -8
- package/src/components/ui.tsx +707 -49
- package/src/components/use-table-state.ts +265 -33
- package/src/data-table.ts +10 -0
- package/src/feedback/feedback-attachment.tsx +10 -7
- package/src/feedback/feedback-dialog.tsx +49 -7
- package/src/feedback/feedback-inbox.tsx +7 -0
- package/src/feedback/feedback-thread.tsx +510 -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 +118 -8
- package/src/i18n/locales/de.ts +161 -0
- package/src/i18n/locales/es.ts +158 -0
- package/src/i18n/locales/fr.ts +161 -0
- package/src/i18n/locales/hu.ts +159 -0
- package/src/i18n/locales/it.ts +159 -0
- package/src/i18n/locales/zh.ts +158 -0
- package/src/index.ts +59 -0
- package/src/lib/format.ts +348 -0
- package/src/lib/qr-encode.ts +420 -0
- package/src/rhf/fields.tsx +1135 -0
- package/src/rhf/line-items.tsx +130 -0
- package/src/rhf.ts +6 -0
- package/src/search/command-palette.tsx +29 -19
- package/src/shell/app-shell.tsx +153 -3
- package/src/shell/auth-layout.tsx +190 -0
- package/src/shell/top-bar-brand.tsx +76 -0
- package/src/shell/topbar-action-menu.tsx +32 -4
- package/src/shell.ts +2 -0
- package/src/tour/tour.tsx +16 -5
- package/src/wizard/use-wizard.ts +8 -4
- package/src/wizard/wizard-context.tsx +41 -1
- package/src/wizard/wizard-step.tsx +75 -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":[]}
|
|
@@ -25,18 +25,16 @@ function FeedbackAttachmentField({
|
|
|
25
25
|
pasteFrom,
|
|
26
26
|
className
|
|
27
27
|
}) {
|
|
28
|
-
const [
|
|
28
|
+
const [previewFor, setPreviewFor] = useState(null);
|
|
29
|
+
const preview = value && previewFor?.file === value ? previewFor.url : null;
|
|
29
30
|
const [capturing, setCapturing] = useState(false);
|
|
30
31
|
const fileInputRef = useRef(null);
|
|
31
32
|
const fileText = useKitFileLabels();
|
|
32
33
|
const text = useKitLabels("feedbackAttachment", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);
|
|
33
34
|
useEffect(() => {
|
|
34
|
-
if (!value)
|
|
35
|
-
setPreview(null);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
35
|
+
if (!value) return;
|
|
38
36
|
const url = URL.createObjectURL(value);
|
|
39
|
-
|
|
37
|
+
setPreviewFor({ file: value, url });
|
|
40
38
|
return () => URL.revokeObjectURL(url);
|
|
41
39
|
}, [value]);
|
|
42
40
|
const pick = (file) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/feedback/feedback-attachment.tsx"],"sourcesContent":["import { cn } from \"../lib/cn\";\nimport { useEffect, useRef, useState, type RefObject } from \"react\";\nimport { Camera, FileText, Paperclip, X } from \"lucide-react\";\nimport { Button } from \"../components/ui\";\nimport { useKitFileLabels, useKitLabels } from \"../i18n/kit-labels\";\nimport type { FeedbackAttachmentLabels } from \"./feedback-dialog\";\n\nexport const DEFAULT_ATTACHMENT_ACCEPT = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"];\nexport const DEFAULT_MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;\n\n/**\n * The field's own strings — the `feedbackAttachment` namespace of `<UiKitProvider\n * labels>`. The keys are {@link FeedbackAttachmentLabels}' own, so the `labels` prop\n * (and a whole `FeedbackDialogLabels` handed down by the dialog) merges over them\n * one to one. The optional heading, `attachment`, stays a prop: a note editor puts\n * the buttons straight under its textarea and wants none.\n *\n * Before 0.7.0 the two optional keys fell back to hard-coded English, so a German app\n * that relied on its provider got \"Capture screenshot\" under a German form.\n */\nexport interface FeedbackAttachmentFieldLabels {\n attachmentAdd: string;\n attachmentCapture: string;\n attachmentPaste: string;\n attachmentRemove: string;\n}\n\nexport const DEFAULT_FEEDBACK_ATTACHMENT_LABELS: FeedbackAttachmentFieldLabels = {\n attachmentAdd: \"Attach image\",\n attachmentCapture: \"Capture screenshot\",\n attachmentPaste: \"…or paste a screenshot from the clipboard.\",\n attachmentRemove: \"Remove attachment\",\n};\n\n/**\n * Picking one picture: the file dialog, a capture of the app view, or a paste.\n *\n * Lifted out of {@link FeedbackDialog} so the *reply* half of the feature can\n * have it too (Steering Design feedback #128). A report is a conversation — it\n * is filed, it is answered, and the reporter sends it back saying that is not\n * what they meant — and the screenshot showing what they mean is taken at\n * whichever of those points they looked. Only the first of them had a way to\n * attach one, so everything after it had to be described in words.\n *\n * **Three ways in, and they are three because no one of them covers the others.**\n * The file dialog needs a file on disk; `onCaptureScreenshot` snapshots the\n * *whole* app view; and the clipboard is where a region snip already is on every\n * platform (Steering Design feedback #39). Somebody who wanted to show one panel\n * had to save a crop and then find it again.\n *\n * `documentPaste` and `pasteFrom` decide where the paste is listened for, and\n * it is a real choice rather than a flag with a default. A modal traps focus,\n * so while it is up every paste in the page is meant for it — including one\n * made with nothing in particular focused, which never reaches a React\n * `onPaste` on a child: that is `documentPaste`. An editor **inline on a\n * page** is not that: the page around it has its own fields, so it listens\n * within its own subtree and a paste elsewhere stays where it was aimed. But\n * \"its own subtree\" has to include the text box the paste is actually made in,\n * and that box is this field's *sibling*, not its child — a paste in it bubbles\n * to their common parent and never through here. `pasteFrom` is that parent\n * (Steering Design feedback #140): the element whose subtree is listened to,\n * handed in by whoever renders both the box and this field side by side.\n */\nexport function FeedbackAttachmentField({\n value,\n onChange,\n labels: labelsProp,\n accept = DEFAULT_ATTACHMENT_ACCEPT,\n maxBytes = DEFAULT_MAX_ATTACHMENT_BYTES,\n onError,\n onCaptureScreenshot,\n documentPaste = false,\n pasteFrom,\n className,\n}: {\n value: File | null;\n onChange: (file: File | null) => void;\n /** Prop > `<UiKitProvider labels={{ feedbackAttachment }}>` > English. Optional\n * since 0.7.0; `attachment` (the heading) is only ever read from here. */\n labels?: Partial<FeedbackAttachmentLabels>;\n accept?: string[];\n maxBytes?: number;\n onError?: (kind: \"type\" | \"size\") => void;\n /** Snapshot the app view behind this and return it as a File. A \"Capture\n * screenshot\" button appears only when it is given. */\n onCaptureScreenshot?: () => Promise<File | null>;\n /** Listen for the paste on `document` rather than on this field's own\n * subtree. For a modal, which owns the whole page while it is up. */\n documentPaste?: boolean;\n /** Listen for the paste within this element's subtree rather than this\n * field's own — for a field standing *beside* the text box a paste is made\n * in, whose common parent is where the event bubbles to. Ignored when\n * `documentPaste` is set. */\n pasteFrom?: RefObject<HTMLElement | null>;\n className?: string;\n}) {\n const [preview, setPreview] = useState<string | null>(null);\n const [capturing, setCapturing] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n // `file.size` from `<UiKitProvider labels>`, formatted in its locale — see FileDropzone.\n const fileText = useKitFileLabels();\n const text = useKitLabels(\"feedbackAttachment\", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);\n\n // Object-URL preview lifecycle (create on change, revoke on cleanup).\n useEffect(() => {\n if (!value) {\n setPreview(null);\n return;\n }\n const url = URL.createObjectURL(value);\n setPreview(url);\n return () => URL.revokeObjectURL(url);\n }, [value]);\n\n const pick = (file: File | undefined | null) => {\n if (!file) return;\n if (!accept.includes(file.type)) {\n onError?.(\"type\");\n return;\n }\n if (file.size > maxBytes) {\n onError?.(\"size\");\n return;\n }\n onChange(file);\n };\n\n // The paste handler is registered once rather than per render, so it reads\n // `pick` — which closes over props that change identity on every render — out\n // of a ref rather than out of its own dependency list.\n const latest = useRef(pick);\n useEffect(() => {\n latest.current = pick;\n });\n\n const takeImage = (clipboard: DataTransfer | null, stop: () => void) => {\n const items = Array.from(clipboard?.items ?? []);\n const image = items.find((item) => item.kind === \"file\" && item.type.startsWith(\"image/\"));\n const file = image?.getAsFile();\n if (!file) return;\n // Only once there IS an image: a paste of text into a field must stay a\n // paste of text, and a clipboard holding both is a copy whose text half is\n // what the field was focused for.\n stop();\n // Clipboard images arrive named \"image.png\" at best and unnamed at worst,\n // and the name is what the inbox shows beside the thumbnail. A name that\n // says where it came from is more use than the browser's.\n latest.current(new File([file], pastedName(file.type), { type: file.type }));\n };\n\n // The element listened on, where it is not this field's own subtree: the\n // document for a modal, the given parent for an inline editor. Read inside\n // the effect rather than in render, because a ref's `current` is only set\n // once the parent has mounted — which it has by the time effects run.\n useEffect(() => {\n const target: EventTarget | null = documentPaste ? document : (pasteFrom?.current ?? null);\n if (!target) return;\n const onPaste = (event: Event) => {\n const clipboard = (event as ClipboardEvent).clipboardData;\n takeImage(clipboard, () => event.preventDefault());\n };\n target.addEventListener(\"paste\", onPaste);\n return () => target.removeEventListener(\"paste\", onPaste);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- takeImage reads `latest`, which is a ref\n }, [documentPaste, pasteFrom]);\n const listensElsewhere = documentPaste || pasteFrom !== undefined;\n\n return (\n <div\n // `relative` for the same reason as FileDropzone: the `sr-only` file input below\n // is `position: absolute`, and without a positioned ancestor it is laid out\n // against the initial containing block — extending the DOCUMENT height to its own\n // offset and producing a phantom second scrollbar on any long page.\n className={cn(\"relative\", className)}\n onPaste={\n listensElsewhere\n ? undefined\n : (event) => takeImage(event.clipboardData, () => event.preventDefault())\n }\n >\n {labelsProp?.attachment && (\n <div className=\"mb-1 text-xs font-medium uppercase tracking-wide text-[var(--text-muted)]\">\n {labelsProp.attachment}\n </div>\n )}\n {value ? (\n <div className=\"flex items-start gap-2\">\n {value.type.startsWith(\"image/\") && preview ? (\n <img\n src={preview}\n alt={value.name}\n className=\"h-20 w-20 rounded border border-[var(--border)] object-cover\"\n />\n ) : (\n // Non-image attachments (PDF, text) can't preview as an <img>, so\n // show a neutral file tile with the name/size beside it instead.\n <div className=\"flex h-20 w-20 items-center justify-center rounded border border-[var(--border)] text-[var(--text-placeholder)]\">\n <FileText className=\"size-8\" />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm text-[var(--text-secondary)]\">{value.name}</div>\n <div className=\"text-xs text-[var(--text-muted)]\">\n {fileText.size(value.size)}\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => onChange(null)}\n aria-label={text.attachmentRemove}\n className=\"rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n ) : (\n <div className=\"space-y-1.5\">\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"secondary\" onClick={() => fileInputRef.current?.click()}>\n <Paperclip className=\"size-4\" /> {text.attachmentAdd}\n </Button>\n {onCaptureScreenshot && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n disabled={capturing}\n onClick={() => {\n if (capturing) return;\n setCapturing(true);\n void onCaptureScreenshot()\n .then((file) => {\n if (file) pick(file);\n })\n .finally(() => setCapturing(false));\n }}\n >\n <Camera className=\"size-4\" />{\" \"}\n {capturing ? \"…\" : text.attachmentCapture}\n </Button>\n )}\n </div>\n {/* Said out loud, because a gesture with no affordance is a gesture\n nobody finds. */}\n <p className=\"text-xs text-[var(--text-muted)]\">\n {text.attachmentPaste}\n </p>\n </div>\n )}\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={accept.join(\",\")}\n className=\"sr-only\"\n onChange={(e) => {\n pick(e.target.files?.[0]);\n e.target.value = \"\";\n }}\n />\n </div>\n );\n}\n\n/** What a pasted image is called once it is an attachment.\n *\n * The extension is read off the mime type rather than assumed to be `.png`:\n * Safari puts TIFF on the clipboard and a file called `pasted.png` that is not\n * a PNG is one the receiving end opens wrong. */\nexport function pastedName(type: string): string {\n const subtype = type.split(\"/\")[1] ?? \"png\";\n // `image/svg+xml` and friends carry a suffix that is not part of the\n // extension, and none of them are in the accepted list anyway.\n return `pasted.${subtype.split(\"+\")[0]}`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/feedback/feedback-attachment.tsx"],"sourcesContent":["import { cn } from \"../lib/cn\";\nimport { useEffect, useRef, useState, type RefObject } from \"react\";\nimport { Camera, FileText, Paperclip, X } from \"lucide-react\";\nimport { Button } from \"../components/ui\";\nimport { useKitFileLabels, useKitLabels } from \"../i18n/kit-labels\";\nimport type { FeedbackAttachmentLabels } from \"./feedback-dialog\";\n\nexport const DEFAULT_ATTACHMENT_ACCEPT = [\"image/png\", \"image/jpeg\", \"image/webp\", \"image/gif\"];\nexport const DEFAULT_MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;\n\n/**\n * The field's own strings — the `feedbackAttachment` namespace of `<UiKitProvider\n * labels>`. The keys are {@link FeedbackAttachmentLabels}' own, so the `labels` prop\n * (and a whole `FeedbackDialogLabels` handed down by the dialog) merges over them\n * one to one. The optional heading, `attachment`, stays a prop: a note editor puts\n * the buttons straight under its textarea and wants none.\n *\n * Before 0.7.0 the two optional keys fell back to hard-coded English, so a German app\n * that relied on its provider got \"Capture screenshot\" under a German form.\n */\nexport interface FeedbackAttachmentFieldLabels {\n attachmentAdd: string;\n attachmentCapture: string;\n attachmentPaste: string;\n attachmentRemove: string;\n}\n\nexport const DEFAULT_FEEDBACK_ATTACHMENT_LABELS: FeedbackAttachmentFieldLabels = {\n attachmentAdd: \"Attach image\",\n attachmentCapture: \"Capture screenshot\",\n attachmentPaste: \"…or paste a screenshot from the clipboard.\",\n attachmentRemove: \"Remove attachment\",\n};\n\n/**\n * Picking one picture: the file dialog, a capture of the app view, or a paste.\n *\n * Lifted out of {@link FeedbackDialog} so the *reply* half of the feature can\n * have it too (Steering Design feedback #128). A report is a conversation — it\n * is filed, it is answered, and the reporter sends it back saying that is not\n * what they meant — and the screenshot showing what they mean is taken at\n * whichever of those points they looked. Only the first of them had a way to\n * attach one, so everything after it had to be described in words.\n *\n * **Three ways in, and they are three because no one of them covers the others.**\n * The file dialog needs a file on disk; `onCaptureScreenshot` snapshots the\n * *whole* app view; and the clipboard is where a region snip already is on every\n * platform (Steering Design feedback #39). Somebody who wanted to show one panel\n * had to save a crop and then find it again.\n *\n * `documentPaste` and `pasteFrom` decide where the paste is listened for, and\n * it is a real choice rather than a flag with a default. A modal traps focus,\n * so while it is up every paste in the page is meant for it — including one\n * made with nothing in particular focused, which never reaches a React\n * `onPaste` on a child: that is `documentPaste`. An editor **inline on a\n * page** is not that: the page around it has its own fields, so it listens\n * within its own subtree and a paste elsewhere stays where it was aimed. But\n * \"its own subtree\" has to include the text box the paste is actually made in,\n * and that box is this field's *sibling*, not its child — a paste in it bubbles\n * to their common parent and never through here. `pasteFrom` is that parent\n * (Steering Design feedback #140): the element whose subtree is listened to,\n * handed in by whoever renders both the box and this field side by side.\n */\nexport function FeedbackAttachmentField({\n value,\n onChange,\n labels: labelsProp,\n accept = DEFAULT_ATTACHMENT_ACCEPT,\n maxBytes = DEFAULT_MAX_ATTACHMENT_BYTES,\n onError,\n onCaptureScreenshot,\n documentPaste = false,\n pasteFrom,\n className,\n}: {\n value: File | null;\n onChange: (file: File | null) => void;\n /** Prop > `<UiKitProvider labels={{ feedbackAttachment }}>` > English. Optional\n * since 0.7.0; `attachment` (the heading) is only ever read from here. */\n labels?: Partial<FeedbackAttachmentLabels>;\n accept?: string[];\n maxBytes?: number;\n onError?: (kind: \"type\" | \"size\") => void;\n /** Snapshot the app view behind this and return it as a File. A \"Capture\n * screenshot\" button appears only when it is given. */\n onCaptureScreenshot?: () => Promise<File | null>;\n /** Listen for the paste on `document` rather than on this field's own\n * subtree. For a modal, which owns the whole page while it is up. */\n documentPaste?: boolean;\n /** Listen for the paste within this element's subtree rather than this\n * field's own — for a field standing *beside* the text box a paste is made\n * in, whose common parent is where the event bubbles to. Ignored when\n * `documentPaste` is set. */\n pasteFrom?: RefObject<HTMLElement | null>;\n className?: string;\n}) {\n // The object URL is keyed to the file it was made for, so a stale one (from the\n // previous file, or after `value` is cleared) is never shown — no reset needed.\n const [previewFor, setPreviewFor] = useState<{ file: File; url: string } | null>(null);\n const preview = value && previewFor?.file === value ? previewFor.url : null;\n const [capturing, setCapturing] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n // `file.size` from `<UiKitProvider labels>`, formatted in its locale — see FileDropzone.\n const fileText = useKitFileLabels();\n const text = useKitLabels(\"feedbackAttachment\", DEFAULT_FEEDBACK_ATTACHMENT_LABELS, labelsProp);\n\n // Object-URL preview lifecycle (create on change, revoke on cleanup).\n useEffect(() => {\n if (!value) return;\n const url = URL.createObjectURL(value);\n // The URL is an external resource that must be created and revoked in step\n // with the effect (a render-time `createObjectURL` would leak, and StrictMode's\n // remount would revoke a memoised one out from under the <img>).\n // eslint-disable-next-line react-hooks/set-state-in-effect -- publishing an external resource's handle\n setPreviewFor({ file: value, url });\n return () => URL.revokeObjectURL(url);\n }, [value]);\n\n const pick = (file: File | undefined | null) => {\n if (!file) return;\n if (!accept.includes(file.type)) {\n onError?.(\"type\");\n return;\n }\n if (file.size > maxBytes) {\n onError?.(\"size\");\n return;\n }\n onChange(file);\n };\n\n // The paste handler is registered once rather than per render, so it reads\n // `pick` — which closes over props that change identity on every render — out\n // of a ref rather than out of its own dependency list.\n const latest = useRef(pick);\n useEffect(() => {\n latest.current = pick;\n });\n\n const takeImage = (clipboard: DataTransfer | null, stop: () => void) => {\n const items = Array.from(clipboard?.items ?? []);\n const image = items.find((item) => item.kind === \"file\" && item.type.startsWith(\"image/\"));\n const file = image?.getAsFile();\n if (!file) return;\n // Only once there IS an image: a paste of text into a field must stay a\n // paste of text, and a clipboard holding both is a copy whose text half is\n // what the field was focused for.\n stop();\n // Clipboard images arrive named \"image.png\" at best and unnamed at worst,\n // and the name is what the inbox shows beside the thumbnail. A name that\n // says where it came from is more use than the browser's.\n latest.current(new File([file], pastedName(file.type), { type: file.type }));\n };\n\n // The element listened on, where it is not this field's own subtree: the\n // document for a modal, the given parent for an inline editor. Read inside\n // the effect rather than in render, because a ref's `current` is only set\n // once the parent has mounted — which it has by the time effects run.\n useEffect(() => {\n const target: EventTarget | null = documentPaste ? document : (pasteFrom?.current ?? null);\n if (!target) return;\n const onPaste = (event: Event) => {\n const clipboard = (event as ClipboardEvent).clipboardData;\n takeImage(clipboard, () => event.preventDefault());\n };\n target.addEventListener(\"paste\", onPaste);\n return () => target.removeEventListener(\"paste\", onPaste);\n }, [documentPaste, pasteFrom]);\n const listensElsewhere = documentPaste || pasteFrom !== undefined;\n\n return (\n <div\n // `relative` for the same reason as FileDropzone: the `sr-only` file input below\n // is `position: absolute`, and without a positioned ancestor it is laid out\n // against the initial containing block — extending the DOCUMENT height to its own\n // offset and producing a phantom second scrollbar on any long page.\n className={cn(\"relative\", className)}\n onPaste={\n listensElsewhere\n ? undefined\n : (event) => takeImage(event.clipboardData, () => event.preventDefault())\n }\n >\n {labelsProp?.attachment && (\n <div className=\"mb-1 text-xs font-medium uppercase tracking-wide text-[var(--text-muted)]\">\n {labelsProp.attachment}\n </div>\n )}\n {value ? (\n <div className=\"flex items-start gap-2\">\n {value.type.startsWith(\"image/\") && preview ? (\n <img\n src={preview}\n alt={value.name}\n className=\"h-20 w-20 rounded border border-[var(--border)] object-cover\"\n />\n ) : (\n // Non-image attachments (PDF, text) can't preview as an <img>, so\n // show a neutral file tile with the name/size beside it instead.\n <div className=\"flex h-20 w-20 items-center justify-center rounded border border-[var(--border)] text-[var(--text-placeholder)]\">\n <FileText className=\"size-8\" />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm text-[var(--text-secondary)]\">{value.name}</div>\n <div className=\"text-xs text-[var(--text-muted)]\">\n {fileText.size(value.size)}\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => onChange(null)}\n aria-label={text.attachmentRemove}\n className=\"rounded p-1.5 text-[var(--text-placeholder)] hover:bg-[var(--bg-hover)] hover:text-[var(--text-secondary)]\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n ) : (\n <div className=\"space-y-1.5\">\n <div className=\"flex flex-wrap gap-2\">\n <Button type=\"button\" variant=\"secondary\" onClick={() => fileInputRef.current?.click()}>\n <Paperclip className=\"size-4\" /> {text.attachmentAdd}\n </Button>\n {onCaptureScreenshot && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n disabled={capturing}\n onClick={() => {\n if (capturing) return;\n setCapturing(true);\n void onCaptureScreenshot()\n .then((file) => {\n if (file) pick(file);\n })\n .finally(() => setCapturing(false));\n }}\n >\n <Camera className=\"size-4\" />{\" \"}\n {capturing ? \"…\" : text.attachmentCapture}\n </Button>\n )}\n </div>\n {/* Said out loud, because a gesture with no affordance is a gesture\n nobody finds. */}\n <p className=\"text-xs text-[var(--text-muted)]\">\n {text.attachmentPaste}\n </p>\n </div>\n )}\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={accept.join(\",\")}\n className=\"sr-only\"\n onChange={(e) => {\n pick(e.target.files?.[0]);\n e.target.value = \"\";\n }}\n />\n </div>\n );\n}\n\n/** What a pasted image is called once it is an attachment.\n *\n * The extension is read off the mime type rather than assumed to be `.png`:\n * Safari puts TIFF on the clipboard and a file called `pasted.png` that is not\n * a PNG is one the receiving end opens wrong. */\nexport function pastedName(type: string): string {\n const subtype = type.split(\"/\")[1] ?? \"png\";\n // `image/svg+xml` and friends carry a suffix that is not part of the\n // extension, and none of them are in the accepted list anyway.\n return `pasted.${subtype.split(\"+\")[0]}`;\n}\n"],"mappings":"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","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 };
|