@lerianstudio/sindarian-ui 2.0.0-beta.25 → 2.0.0-beta.27
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/dist/charts/chart.js +4 -231
- package/dist/charts/index.js +1 -11
- package/dist/components/breadcrumb/get-breadcrumb-paths.js +1 -18
- package/dist/components/breadcrumb/index.js +1 -37
- package/dist/components/card/account-card/index.js +1 -69
- package/dist/components/card/basic-card/index.js +1 -20
- package/dist/components/card/dashboard-card/index.js +1 -20
- package/dist/components/card/entity-card/index.js +1 -68
- package/dist/components/card/index.js +1 -20
- package/dist/components/dialog/confirmation-dialog/index.js +1 -96
- package/dist/components/dialog/confirmation-dialog/use-confirm-dialog.js +1 -55
- package/dist/components/dialog/index.js +1 -19
- package/dist/components/dialog/large-dialog/index.js +1 -21
- package/dist/components/entity-box/index.js +1 -67
- package/dist/components/entity-data-table/index.js +1 -20
- package/dist/components/form/combo-box-field/index.js +1 -35
- package/dist/components/form/country-field/index.js +1 -11
- package/dist/components/form/date-picker-field/index.js +1 -83
- package/dist/components/form/date-range-field/index.js +1 -58
- package/dist/components/form/date-time-picker-field/index.js +1 -114
- package/dist/components/form/file-upload-field/index.js +1 -69
- package/dist/components/form/index.js +1 -31
- package/dist/components/form/input-field/index.js +1 -96
- package/dist/components/form/otp-field/index.js +1 -19
- package/dist/components/form/pagination-limit-field/index.js +1 -10
- package/dist/components/form/password-field/index.js +1 -16
- package/dist/components/form/radio-group-field/index.js +1 -36
- package/dist/components/form/renderable-label.js +1 -57
- package/dist/components/form/select-field/index.js +1 -68
- package/dist/components/form/state-field/index.js +1 -42
- package/dist/components/form/switch-field/index.js +1 -16
- package/dist/components/form/textarea-field/index.js +1 -23
- package/dist/components/page/index.js +1 -20
- package/dist/components/page-header/index.js +1 -80
- package/dist/components/pagination/index.js +1 -10
- package/dist/components/table/id-table-cell.js +1 -109
- package/dist/components/table/index.js +1 -19
- package/dist/components/table/locked-table-actions.js +1 -41
- package/dist/components/table/name-table-cell.js +1 -58
- package/dist/components/ui/accordion/index.js +1 -62
- package/dist/components/ui/alert/index.js +1 -45
- package/dist/components/ui/alert-dialog/index.js +1 -84
- package/dist/components/ui/autocomplete/index.js +1 -440
- package/dist/components/ui/autosize-textarea/index.js +1 -89
- package/dist/components/ui/avatar/index.js +1 -51
- package/dist/components/ui/badge/index.js +1 -83
- package/dist/components/ui/breadcrumb/index.js +1 -35
- package/dist/components/ui/button/index.js +1 -65
- package/dist/components/ui/calendar/index.js +1 -118
- package/dist/components/ui/card/index.js +1 -32
- package/dist/components/ui/checkbox/index.js +1 -44
- package/dist/components/ui/collapsible/index.js +1 -50
- package/dist/components/ui/command/index.js +1 -95
- package/dist/components/ui/copy-field/index.js +1 -163
- package/dist/components/ui/dialog/index.js +1 -80
- package/dist/components/ui/dialog/styles.css +1 -1
- package/dist/components/ui/dropdown-menu/index.js +1 -110
- package/dist/components/ui/field/index.js +1 -82
- package/dist/components/ui/file-upload/index.js +1 -574
- package/dist/components/ui/form.js +1 -162
- package/dist/components/ui/hover-card/index.js +1 -51
- package/dist/components/ui/icon-button/index.js +1 -35
- package/dist/components/ui/input/adornment.js +1 -21
- package/dist/components/ui/input/index.js +1 -99
- package/dist/components/ui/label/index.js +1 -45
- package/dist/components/ui/lerian-wordmark/index.js +1 -7
- package/dist/components/ui/loading-button/index.js +1 -9
- package/dist/components/ui/multiple-select/index.js +1 -235
- package/dist/components/ui/otp-input/index.js +1 -62
- package/dist/components/ui/paper/index.js +1 -8
- package/dist/components/ui/paper-collapsible/index.js +1 -24
- package/dist/components/ui/popover/index.js +1 -55
- package/dist/components/ui/progress/index.js +1 -66
- package/dist/components/ui/qr-code/index.js +1 -27
- package/dist/components/ui/radio-group/index.js +1 -48
- package/dist/components/ui/scroll-area/index.js +1 -49
- package/dist/components/ui/select/index.js +1 -119
- package/dist/components/ui/separator/index.js +1 -43
- package/dist/components/ui/sheet/index.js +1 -136
- package/dist/components/ui/sidebar/index.js +1 -27
- package/dist/components/ui/sidebar/next-router.cjs +1 -119
- package/dist/components/ui/sidebar/sidebar-back-button.js +1 -8
- package/dist/components/ui/sidebar/sidebar-components.js +1 -23
- package/dist/components/ui/sidebar/sidebar-expand-button.js +1 -49
- package/dist/components/ui/sidebar/sidebar-item-button.js +1 -21
- package/dist/components/ui/sidebar/sidebar-item-collapsible.js +1 -52
- package/dist/components/ui/sidebar/sidebar-item-icon-button.js +1 -18
- package/dist/components/ui/sidebar/sidebar-item.js +1 -44
- package/dist/components/ui/sidebar/sidebar-provider.js +1 -201
- package/dist/components/ui/sidebar/sidebar-root.js +1 -130
- package/dist/components/ui/sidebar/sidebar-router.js +1 -126
- package/dist/components/ui/sidebar/sidebar-trigger.js +1 -57
- package/dist/components/ui/skeleton/index.js +1 -8
- package/dist/components/ui/stepper/index.js +1 -29
- package/dist/components/ui/switch/index.js +1 -43
- package/dist/components/ui/table/index.js +1 -56
- package/dist/components/ui/tabs/index.js +1 -55
- package/dist/components/ui/tabs/styles.css +1 -1
- package/dist/components/ui/textarea/index.js +1 -8
- package/dist/components/ui/time-picker/index.js +1 -85
- package/dist/components/ui/toast/index.js +1 -3
- package/dist/components/ui/toast/toaster.js +1 -16
- package/dist/components/ui/toggle-group/index.js +1 -156
- package/dist/components/ui/tooltip/index.js +1 -81
- package/dist/domain/blotter/index.js +1 -20
- package/dist/domain/delinquency-aging/aging-buckets.js +1 -120
- package/dist/domain/delinquency-aging/index.js +1 -222
- package/dist/domain/figure/index.js +1 -19
- package/dist/domain/format.js +1 -137
- package/dist/domain/index.js +1 -43
- package/dist/domain/key-id/index.js +1 -174
- package/dist/domain/ledger-sheet/index.js +1 -28
- package/dist/domain/money-diff.js +1 -48
- package/dist/domain/money-math.js +1 -104
- package/dist/domain/money-text/index.js +1 -123
- package/dist/domain/section-label/index.js +1 -17
- package/dist/domain/status-rail/index.js +1 -76
- package/dist/domain/threshold-gauge/index.js +1 -161
- package/dist/enterprise/alert-banner/index.js +1 -24
- package/dist/enterprise/app-shell/index.js +1 -87
- package/dist/enterprise/cursor-pager/index.js +1 -26
- package/dist/enterprise/data-table/column-size.js +1 -103
- package/dist/enterprise/data-table/index.js +1 -345
- package/dist/enterprise/date-range-picker/index.js +1 -115
- package/dist/enterprise/detail-panel/index.js +1 -20
- package/dist/enterprise/empty-state/index.js +1 -14
- package/dist/enterprise/index.js +1 -39
- package/dist/enterprise/number-input/index.js +1 -252
- package/dist/enterprise/search-input/index.js +1 -121
- package/dist/enterprise/stat-card/index.js +1 -75
- package/dist/enterprise/status-badge/index.js +1 -120
- package/dist/enterprise/use-is-mobile.js +1 -68
- package/dist/enterprise/virtualized-table/index.js +1 -141
- package/dist/esm/charts/chart.js +4 -190
- package/dist/esm/charts/index.js +1 -3
- package/dist/esm/components/breadcrumb/get-breadcrumb-paths.js +1 -12
- package/dist/esm/components/breadcrumb/index.js +1 -30
- package/dist/esm/components/card/account-card/index.js +1 -53
- package/dist/esm/components/card/basic-card/index.js +1 -14
- package/dist/esm/components/card/dashboard-card/index.js +1 -14
- package/dist/esm/components/card/entity-card/index.js +1 -52
- package/dist/esm/components/card/index.js +1 -4
- package/dist/esm/components/dialog/confirmation-dialog/index.js +1 -60
- package/dist/esm/components/dialog/confirmation-dialog/use-confirm-dialog.js +1 -49
- package/dist/esm/components/dialog/index.js +1 -3
- package/dist/esm/components/dialog/large-dialog/index.js +1 -16
- package/dist/esm/components/entity-box/index.js +1 -56
- package/dist/esm/components/entity-data-table/index.js +1 -17
- package/dist/esm/components/form/combo-box-field/index.js +1 -28
- package/dist/esm/components/form/country-field/index.js +1 -7
- package/dist/esm/components/form/date-picker-field/index.js +1 -43
- package/dist/esm/components/form/date-range-field/index.js +1 -18
- package/dist/esm/components/form/date-time-picker-field/index.js +1 -74
- package/dist/esm/components/form/file-upload-field/index.js +1 -65
- package/dist/esm/components/form/index.js +1 -15
- package/dist/esm/components/form/input-field/index.js +1 -92
- package/dist/esm/components/form/otp-field/index.js +1 -15
- package/dist/esm/components/form/pagination-limit-field/index.js +1 -6
- package/dist/esm/components/form/password-field/index.js +1 -10
- package/dist/esm/components/form/radio-group-field/index.js +1 -32
- package/dist/esm/components/form/renderable-label.js +1 -54
- package/dist/esm/components/form/select-field/index.js +1 -64
- package/dist/esm/components/form/state-field/index.js +1 -35
- package/dist/esm/components/form/switch-field/index.js +1 -12
- package/dist/esm/components/form/textarea-field/index.js +1 -19
- package/dist/esm/components/page/index.js +1 -15
- package/dist/esm/components/page-header/index.js +1 -71
- package/dist/esm/components/pagination/index.js +1 -6
- package/dist/esm/components/table/id-table-cell.js +1 -105
- package/dist/esm/components/table/index.js +1 -3
- package/dist/esm/components/table/locked-table-actions.js +1 -37
- package/dist/esm/components/table/name-table-cell.js +1 -54
- package/dist/esm/components/ui/accordion/index.js +1 -24
- package/dist/esm/components/ui/alert/index.js +1 -38
- package/dist/esm/components/ui/alert-dialog/index.js +1 -39
- package/dist/esm/components/ui/autocomplete/index.js +1 -392
- package/dist/esm/components/ui/autosize-textarea/index.js +1 -51
- package/dist/esm/components/ui/avatar/index.js +1 -14
- package/dist/esm/components/ui/badge/index.js +1 -79
- package/dist/esm/components/ui/breadcrumb/index.js +1 -27
- package/dist/esm/components/ui/button/index.js +1 -60
- package/dist/esm/components/ui/calendar/index.js +1 -82
- package/dist/esm/components/ui/card/index.js +1 -24
- package/dist/esm/components/ui/checkbox/index.js +1 -9
- package/dist/esm/components/ui/collapsible/index.js +1 -13
- package/dist/esm/components/ui/command/index.js +1 -51
- package/dist/esm/components/ui/copy-field/index.js +1 -127
- package/dist/esm/components/ui/dialog/index.js +1 -36
- package/dist/esm/components/ui/dropdown-menu/index.js +1 -60
- package/dist/esm/components/ui/field/index.js +1 -71
- package/dist/esm/components/ui/file-upload/index.js +1 -536
- package/dist/esm/components/ui/form.js +1 -118
- package/dist/esm/components/ui/hover-card/index.js +1 -14
- package/dist/esm/components/ui/icon-button/index.js +1 -31
- package/dist/esm/components/ui/input/adornment.js +1 -17
- package/dist/esm/components/ui/input/index.js +1 -60
- package/dist/esm/components/ui/label/index.js +1 -10
- package/dist/esm/components/ui/lerian-wordmark/index.js +1 -4
- package/dist/esm/components/ui/loading-button/index.js +1 -7
- package/dist/esm/components/ui/multiple-select/index.js +1 -196
- package/dist/esm/components/ui/otp-input/index.js +1 -21
- package/dist/esm/components/ui/paper/index.js +1 -5
- package/dist/esm/components/ui/paper-collapsible/index.js +1 -19
- package/dist/esm/components/ui/popover/index.js +1 -17
- package/dist/esm/components/ui/progress/index.js +1 -31
- package/dist/esm/components/ui/qr-code/index.js +1 -24
- package/dist/esm/components/ui/radio-group/index.js +1 -12
- package/dist/esm/components/ui/scroll-area/index.js +1 -13
- package/dist/esm/components/ui/select/index.js +1 -74
- package/dist/esm/components/ui/separator/index.js +1 -8
- package/dist/esm/components/ui/sheet/index.js +1 -92
- package/dist/esm/components/ui/sidebar/index.js +1 -11
- package/dist/esm/components/ui/sidebar/next-router.cjs +1 -119
- package/dist/esm/components/ui/sidebar/sidebar-back-button.js +1 -5
- package/dist/esm/components/ui/sidebar/sidebar-components.js +1 -15
- package/dist/esm/components/ui/sidebar/sidebar-expand-button.js +1 -42
- package/dist/esm/components/ui/sidebar/sidebar-item-button.js +1 -18
- package/dist/esm/components/ui/sidebar/sidebar-item-collapsible.js +1 -44
- package/dist/esm/components/ui/sidebar/sidebar-item-icon-button.js +1 -14
- package/dist/esm/components/ui/sidebar/sidebar-item.js +1 -40
- package/dist/esm/components/ui/sidebar/sidebar-provider.js +1 -193
- package/dist/esm/components/ui/sidebar/sidebar-root.js +1 -126
- package/dist/esm/components/ui/sidebar/sidebar-router.js +1 -89
- package/dist/esm/components/ui/sidebar/sidebar-trigger.js +1 -53
- package/dist/esm/components/ui/skeleton/index.js +1 -6
- package/dist/esm/components/ui/stepper/index.js +1 -20
- package/dist/esm/components/ui/switch/index.js +1 -8
- package/dist/esm/components/ui/table/index.js +1 -44
- package/dist/esm/components/ui/tabs/index.js +1 -17
- package/dist/esm/components/ui/textarea/index.js +1 -6
- package/dist/esm/components/ui/time-picker/index.js +1 -49
- package/dist/esm/components/ui/toast/index.js +1 -2
- package/dist/esm/components/ui/toast/toaster.js +1 -13
- package/dist/esm/components/ui/toggle-group/index.js +1 -120
- package/dist/esm/components/ui/tooltip/index.js +1 -43
- package/dist/esm/domain/blotter/index.js +1 -16
- package/dist/esm/domain/delinquency-aging/aging-buckets.js +1 -115
- package/dist/esm/domain/delinquency-aging/index.js +1 -218
- package/dist/esm/domain/figure/index.js +1 -15
- package/dist/esm/domain/format.js +1 -128
- package/dist/esm/domain/index.js +1 -19
- package/dist/esm/domain/key-id/index.js +1 -169
- package/dist/esm/domain/ledger-sheet/index.js +1 -24
- package/dist/esm/domain/money-diff.js +1 -45
- package/dist/esm/domain/money-math.js +1 -98
- package/dist/esm/domain/money-text/index.js +1 -118
- package/dist/esm/domain/section-label/index.js +1 -14
- package/dist/esm/domain/status-rail/index.js +1 -71
- package/dist/esm/domain/threshold-gauge/index.js +1 -157
- package/dist/esm/enterprise/alert-banner/index.js +1 -21
- package/dist/esm/enterprise/app-shell/index.js +1 -51
- package/dist/esm/enterprise/cursor-pager/index.js +1 -23
- package/dist/esm/enterprise/data-table/column-size.js +1 -97
- package/dist/esm/enterprise/data-table/index.js +1 -342
- package/dist/esm/enterprise/date-range-picker/index.js +1 -112
- package/dist/esm/enterprise/detail-panel/index.js +1 -17
- package/dist/esm/enterprise/empty-state/index.js +1 -11
- package/dist/esm/enterprise/index.js +1 -22
- package/dist/esm/enterprise/number-input/index.js +1 -214
- package/dist/esm/enterprise/search-input/index.js +1 -85
- package/dist/esm/enterprise/stat-card/index.js +1 -72
- package/dist/esm/enterprise/status-badge/index.js +1 -116
- package/dist/esm/enterprise/use-is-mobile.js +1 -31
- package/dist/esm/enterprise/virtualized-table/index.js +1 -138
- package/dist/esm/hooks/use-click-away.js +1 -22
- package/dist/esm/hooks/use-pagination.js +1 -38
- package/dist/esm/hooks/use-stepper.js +1 -22
- package/dist/esm/hooks/use-time.js +1 -13
- package/dist/esm/hooks/use-toast.js +1 -107
- package/dist/esm/index.js +1 -77
- package/dist/esm/lib/storage/get-storage-object.js +1 -17
- package/dist/esm/lib/storage/get-storage.js +1 -14
- package/dist/esm/lib/storage/index.js +1 -2
- package/dist/esm/lib/typography.js +1 -31
- package/dist/esm/lib/utils.js +1 -41
- package/dist/esm/theme/get-theme-script.js +1 -41
- package/dist/esm/theme/index.js +1 -4
- package/dist/esm/theme/mode-toggle.js +1 -45
- package/dist/esm/theme/theme-provider.js +1 -122
- package/dist/esm/toast/helpers.js +1 -36
- package/dist/esm/toast/index.js +1 -4
- package/dist/esm/types/metadata-type.js +1 -1
- package/dist/esm/utils/countries.cjs +1 -17
- package/dist/esm/utils/country-utils.js +1 -18
- package/dist/esm/utils/currency-codes.js +1 -181
- package/dist/hooks/use-click-away.js +1 -29
- package/dist/hooks/use-pagination.js +1 -41
- package/dist/hooks/use-stepper.js +1 -26
- package/dist/hooks/use-time.js +1 -20
- package/dist/hooks/use-toast.js +1 -111
- package/dist/index.js +1 -96
- package/dist/lib/storage/get-storage-object.js +1 -23
- package/dist/lib/storage/get-storage.js +1 -17
- package/dist/lib/storage/index.js +1 -18
- package/dist/lib/typography.js +1 -34
- package/dist/lib/utils.js +1 -45
- package/dist/theme/get-theme-script.js +1 -44
- package/dist/theme/index.js +1 -11
- package/dist/theme/mode-toggle.js +1 -48
- package/dist/theme/theme-provider.js +1 -160
- package/dist/toast/helpers.js +1 -41
- package/dist/toast/index.js +1 -10
- package/dist/types/metadata-type.js +1 -2
- package/dist/utils/countries.cjs +1 -17
- package/dist/utils/country-utils.js +1 -25
- package/dist/utils/currency-codes.js +1 -184
- package/package.json +4 -3
|
@@ -1,97 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* The column sizing a ColumnDef actually DECLARED, or `undefined` when it
|
|
3
|
-
* declared none. This is VirtualizedTable's reading: flex rows honour all
|
|
4
|
-
* three, so `size`, `minSize` and `maxSize` all PIN. DataTable's `<th>`/`<td>`
|
|
5
|
-
* path reads the same declaration through {@link readPreferredColumnSize},
|
|
6
|
-
* which drops the ceiling CSS auto table layout cannot enforce.
|
|
7
|
-
*
|
|
8
|
-
* Reading the raw `columnDef` is only honest because both tables pass
|
|
9
|
-
* `defaultColumn: { size: undefined, minSize: undefined, maxSize: undefined }`
|
|
10
|
-
* to `useReactTable`. TanStack builds every columnDef as
|
|
11
|
-
* `{ ...featureDefaults, ...table.options.defaultColumn, ...userDef }`, so
|
|
12
|
-
* those three overrides blank out the `defaultColumnSizing` the ColumnSizing
|
|
13
|
-
* feature would otherwise stamp on every column (150 / 20 / MAX_SAFE_INTEGER).
|
|
14
|
-
* Without them a column asking for nothing reports `size: 150` and a column
|
|
15
|
-
* asking for exactly 150 is indistinguishable from it. `column.getSize()` is
|
|
16
|
-
* unaffected: it falls back to `defaultColumnSizing` with `??`, so column
|
|
17
|
-
* offsets and the table's total size are byte-identical either way.
|
|
18
|
-
*
|
|
19
|
-
* Width comes from `getSize()` rather than the raw `size` so it lands already
|
|
20
|
-
* clamped by the column's own min/max.
|
|
21
|
-
*/
|
|
22
|
-
export function readDeclaredColumnSize(column) {
|
|
23
|
-
const { size, minSize, maxSize } = column.columnDef;
|
|
24
|
-
const width = size === undefined ? undefined : column.getSize();
|
|
25
|
-
if (width === undefined && minSize === undefined && maxSize === undefined) {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
return { width, minWidth: minSize, maxWidth: maxSize };
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* A GROUP header cell is exactly as wide as the leaf columns it spans, so it
|
|
32
|
-
* can only be pinned when every one of them is: a group whose leaves are
|
|
33
|
-
* partly sized has no honest fixed width, and keeps its colSpan flex share so
|
|
34
|
-
* the header rule still tracks the cells beneath it. Reading the group's own
|
|
35
|
-
* ColumnDef instead would size the header against a declaration its body cells
|
|
36
|
-
* never see.
|
|
37
|
-
*
|
|
38
|
-
* Every dimension the leaves agree on carries up, CEILING INCLUDED. This is
|
|
39
|
-
* VirtualizedTable's path, where flex rows honour `maxSize` on a leaf — so a
|
|
40
|
-
* group of capped leaves whose header carried no `maxWidth` was free to grow
|
|
41
|
-
* past every column it names, which is the same header-vs-cells drift the
|
|
42
|
-
* width sum exists to prevent. Each dimension is decided on its own: a group
|
|
43
|
-
* can be uncapped and still have a floor.
|
|
44
|
-
*/
|
|
45
|
-
export function readDeclaredGroupSize(leaves) {
|
|
46
|
-
if (leaves.length === 0)
|
|
47
|
-
return undefined;
|
|
48
|
-
const sizes = leaves.map(readDeclaredColumnSize);
|
|
49
|
-
const width = sum(sizes, 'width');
|
|
50
|
-
const minWidth = sum(sizes, 'minWidth');
|
|
51
|
-
const maxWidth = sum(sizes, 'maxWidth');
|
|
52
|
-
if (width === undefined && minWidth === undefined && maxWidth === undefined) {
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
return { width, minWidth, maxWidth };
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* The total of one dimension, or `undefined` unless EVERY leaf declared it. One
|
|
59
|
-
* leaf short and the group has no honest total: an unsized leaf flexes, an
|
|
60
|
-
* unfloored one can shrink, and an uncapped one grows without limit.
|
|
61
|
-
*/
|
|
62
|
-
function sum(sizes, key) {
|
|
63
|
-
let total = 0;
|
|
64
|
-
for (const size of sizes) {
|
|
65
|
-
const value = size?.[key];
|
|
66
|
-
if (value === undefined)
|
|
67
|
-
return undefined;
|
|
68
|
-
total += value;
|
|
69
|
-
}
|
|
70
|
-
return total;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* The same declaration as {@link readDeclaredColumnSize}, minus `maxWidth`.
|
|
74
|
-
*
|
|
75
|
-
* DataTable renders real `<th>`/`<td>` under CSS AUTO table layout, where
|
|
76
|
-
* Chrome and Firefox ignore `max-width` on a cell. Emitting it would advertise
|
|
77
|
-
* a ceiling that never holds, so this path keeps only what the layout obeys:
|
|
78
|
-
* `size` as a PREFERRED width and `minSize` as a floor. A column that must not
|
|
79
|
-
* grow past a width belongs in VirtualizedTable.
|
|
80
|
-
*/
|
|
81
|
-
export function readPreferredColumnSize(column) {
|
|
82
|
-
const { width, minWidth } = readDeclaredColumnSize(column) ?? {};
|
|
83
|
-
if (width === undefined && minWidth === undefined) {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
return { width, minWidth };
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Blanks out the `defaultColumnSizing` TanStack stamps onto every ColumnDef, so
|
|
90
|
-
* `columnDef.size` / `minSize` / `maxSize` mean "the consumer asked for this"
|
|
91
|
-
* and nothing else. Both tables pass it to `useReactTable`.
|
|
92
|
-
*/
|
|
93
|
-
export const UNSIZED_DEFAULT_COLUMN = {
|
|
94
|
-
size: undefined,
|
|
95
|
-
minSize: undefined,
|
|
96
|
-
maxSize: undefined
|
|
97
|
-
};
|
|
1
|
+
export function readDeclaredColumnSize(i){const{size:n,minSize:e,maxSize:t}=i.columnDef;const d=n===void 0?void 0:i.getSize();if(d===void 0&&e===void 0&&t===void 0){return void 0}return{width:d,minWidth:e,maxWidth:t}}export function readDeclaredGroupSize(i){if(i.length===0)return void 0;const n=i.map(readDeclaredColumnSize);const e=u(n,"width");const t=u(n,"minWidth");const d=u(n,"maxWidth");if(e===void 0&&t===void 0&&d===void 0){return void 0}return{width:e,minWidth:t,maxWidth:d}}function u(i,n){let e=0;for(const t of i){const d=t?.[n];if(d===void 0)return void 0;e+=d}return e}export function readPreferredColumnSize(i){const{width:n,minWidth:e}=readDeclaredColumnSize(i)??{};if(n===void 0&&e===void 0){return void 0}return{width:n,minWidth:e}}export const UNSIZED_DEFAULT_COLUMN={size:void 0,minSize:void 0,maxSize:void 0};
|
|
@@ -1,342 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* DataTable — a typed, generic wrapper over TanStack Table v8.
|
|
5
|
-
*
|
|
6
|
-
* Pairs with CursorPager (server-driven pagination): pass the page's `items`
|
|
7
|
-
* as `data`; this component does no client-side paging or sorting by default.
|
|
8
|
-
* It owns the three render states the list screens need:
|
|
9
|
-
* - loading → header + skeleton rows
|
|
10
|
-
* - empty → EmptyState (data resolved, zero rows)
|
|
11
|
-
* - data → semantic table rows; cells own any links/actions
|
|
12
|
-
*
|
|
13
|
-
* Column heads speak the kit's LABEL_VOICE_CLASS; `headClassName` overrides it
|
|
14
|
-
* per table. An optional `footer` node renders in a `tfoot` for footed totals.
|
|
15
|
-
*
|
|
16
|
-
* Columns are standard TanStack `ColumnDef<T>`; cells receive the row's
|
|
17
|
-
* `original` value, so screens compose money / date / status cells directly in
|
|
18
|
-
* their column `cell` renderers.
|
|
19
|
-
*
|
|
20
|
-
* Declared column sizing (see `./column-size`) does NOT mean the same thing in
|
|
21
|
-
* the kit's two table paths, because their layouts differ:
|
|
22
|
-
* - DataTable renders real `<th>`/`<td>` under CSS AUTO table layout (the
|
|
23
|
-
* `Table` primitive sets no `table-fixed`). There, `size` is a PREFERRED
|
|
24
|
-
* width the browser may exceed to fit content, `minSize` acts as a floor,
|
|
25
|
-
* and `maxSize` is ignored outright — Chrome and Firefox do not apply
|
|
26
|
-
* `max-width` to table cells. A column declaring none keeps auto width.
|
|
27
|
-
* - VirtualizedTable renders flex rows, where all three PIN: `size`,
|
|
28
|
-
* `minSize` and `maxSize` are honoured exactly as declared.
|
|
29
|
-
* So a column that must not grow past a width belongs in VirtualizedTable, or
|
|
30
|
-
* in a cell renderer that truncates its own content.
|
|
31
|
-
*
|
|
32
|
-
* Row selection is opt-in and controlled: pass `enableRowSelection` plus
|
|
33
|
-
* `rowSelection` / `onRowSelectionChange` (TanStack-native types). When
|
|
34
|
-
* enabled, a checkbox column is prepended — header checkbox selects the
|
|
35
|
-
* page (indeterminate for partial), checkbox-cell clicks never bubble to
|
|
36
|
-
* row-level handlers. With the flag off (the default), nothing changes.
|
|
37
|
-
*
|
|
38
|
-
* Sorting is opt-in and controlled too: pass `enableSorting` plus `sorting` /
|
|
39
|
-
* `onSortingChange`. Rows are never reordered client-side (`manualSorting`, no
|
|
40
|
-
* sorted row model); what the flag buys is `aria-sort` on each sortable `th`.
|
|
41
|
-
* With the flag off (the default), no header carries the attribute.
|
|
42
|
-
*
|
|
43
|
-
* Keyboard navigation is opt-in via `onRowActivate`: when present, rows get
|
|
44
|
-
* a roving tabindex (Tab enters the table once at the anchor row; the next
|
|
45
|
-
* Tab moves to the row's first tabbable child if it has one, otherwise out
|
|
46
|
-
* of the table — no trap), ArrowUp/Down move row focus clamped to the page,
|
|
47
|
-
* Home/End jump to the page edges, Enter activates the row through the
|
|
48
|
-
* callback, and Space toggles selection when `enableRowSelection` is on.
|
|
49
|
-
* Cmd/Ctrl+Enter opens the row's destination in a new tab when the optional
|
|
50
|
-
* `rowHref` builder is provided (window.open with noopener,noreferrer);
|
|
51
|
-
* without it, modified Enter stays a deliberate no-op — never a same-tab
|
|
52
|
-
* activation. Keydown events originating from interactive children (links,
|
|
53
|
-
* buttons, checkboxes, form fields) are never hijacked.
|
|
54
|
-
*/
|
|
55
|
-
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
|
56
|
-
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
|
57
|
-
import { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '../../components/ui/table/index.js';
|
|
58
|
-
import { Skeleton } from '../../components/ui/skeleton/index.js';
|
|
59
|
-
import { LABEL_VOICE_CLASS } from '../../lib/typography.js';
|
|
60
|
-
import { cn } from '../../lib/utils.js';
|
|
61
|
-
import { EmptyState } from '../empty-state/index.js';
|
|
62
|
-
import { readPreferredColumnSize, UNSIZED_DEFAULT_COLUMN } from './column-size.js';
|
|
63
|
-
const SELECTION_COLUMN_ID = '__select__';
|
|
64
|
-
const CHECKBOX_CLASS = 'accent-primary block size-4';
|
|
65
|
-
/** Figure treatment for `meta: { numeric: true }` columns; the alignment half
|
|
66
|
-
* lives in ALIGN_CELL_CLASS so `meta.align` can override one without the other. */
|
|
67
|
-
const NUMERIC_FIGURE_CLASS = 'font-mono tabular-nums';
|
|
68
|
-
/**
|
|
69
|
-
* Body-cell alignment, one class per direction rather than a single string with
|
|
70
|
-
* `text-right` baked in: an explicit `meta.align` has to beat `numeric` by
|
|
71
|
-
* choosing the class, not by out-ordering it — tailwind-merge resolving two
|
|
72
|
-
* conflicting alignment utilities by source order is not a contract worth
|
|
73
|
-
* relying on.
|
|
74
|
-
*/
|
|
75
|
-
const ALIGN_CELL_CLASS = {
|
|
76
|
-
left: 'text-left',
|
|
77
|
-
center: 'text-center',
|
|
78
|
-
right: 'text-right'
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* Keydown events whose target sits inside one of these are never hijacked
|
|
82
|
-
* by the row-level keyboard handler — Space in a checkbox and Enter on an
|
|
83
|
-
* in-cell link keep their native behavior.
|
|
84
|
-
*/
|
|
85
|
-
const INTERACTIVE_CHILD_SELECTOR = 'a, button, input, select, textarea, [role="checkbox"]';
|
|
86
|
-
/**
|
|
87
|
-
* Inset focus outline for keyboard-navigable rows. Outline, not Tailwind
|
|
88
|
-
* ring: ring is box-shadow, which renders unreliably on display: table-row.
|
|
89
|
-
* `focus-visible`-gated, so mouse users (and visual baselines) see nothing.
|
|
90
|
-
*/
|
|
91
|
-
const ROW_FOCUS_CLASS = 'focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring';
|
|
92
|
-
function stopPropagation(event) {
|
|
93
|
-
event.stopPropagation();
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* ARIA sort state for one header cell. `undefined` (attribute omitted) on a
|
|
97
|
-
* column that cannot sort, which covers both sorting-off tables and columns
|
|
98
|
-
* that opted out, so the attribute never claims a control the user does not
|
|
99
|
-
* have.
|
|
100
|
-
*/
|
|
101
|
-
function ariaSortFor(column) {
|
|
102
|
-
if (!column.getCanSort())
|
|
103
|
-
return undefined;
|
|
104
|
-
const sorted = column.getIsSorted();
|
|
105
|
-
if (sorted === 'asc')
|
|
106
|
-
return 'ascending';
|
|
107
|
-
if (sorted === 'desc')
|
|
108
|
-
return 'descending';
|
|
109
|
-
return 'none';
|
|
110
|
-
}
|
|
111
|
-
function SelectAllCheckbox({ table }) {
|
|
112
|
-
const allSelected = table.getIsAllPageRowsSelected();
|
|
113
|
-
const someSelected = table.getIsSomePageRowsSelected();
|
|
114
|
-
return (_jsx("input", { type: "checkbox", className: CHECKBOX_CLASS, "aria-label": "Select all rows on this page", checked: allSelected, ref: (el) => {
|
|
115
|
-
if (el)
|
|
116
|
-
el.indeterminate = !allSelected && someSelected;
|
|
117
|
-
}, onChange: table.getToggleAllPageRowsSelectedHandler() }));
|
|
118
|
-
}
|
|
119
|
-
function RowSelectionCheckbox({ row, getLabel }) {
|
|
120
|
-
return (_jsx("input", { type: "checkbox", className: CHECKBOX_CLASS, "aria-label": getLabel ? getLabel(row.original) : `Select row ${row.id}`, checked: row.getIsSelected(), disabled: !row.getCanSelect(), onChange: row.getToggleSelectedHandler() }));
|
|
121
|
-
}
|
|
122
|
-
function createSelectionColumn(getLabel) {
|
|
123
|
-
return {
|
|
124
|
-
id: SELECTION_COLUMN_ID,
|
|
125
|
-
header: ({ table }) => _jsx(SelectAllCheckbox, { table: table }),
|
|
126
|
-
cell: ({ row }) => _jsx(RowSelectionCheckbox, { row: row, getLabel: getLabel })
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
export function DataTable({ columns, data, loading = false, skeletonRows = 8, loadingLabel = 'Loading table rows', empty, getRowId, flush = false, density = 'comfortable', enableRowSelection = false, rowSelection, onRowSelectionChange, getRowSelectionLabel, enableSorting = false, sorting, onSortingChange, columnVisibility, onColumnVisibilityChange, onRowActivate, rowHref, className, headClassName, footer }) {
|
|
130
|
-
const tableColumns = useMemo(() => enableRowSelection
|
|
131
|
-
? [createSelectionColumn(getRowSelectionLabel), ...columns]
|
|
132
|
-
: columns, [columns, enableRowSelection, getRowSelectionLabel]);
|
|
133
|
-
const table = useReactTable({
|
|
134
|
-
data,
|
|
135
|
-
columns: tableColumns,
|
|
136
|
-
// Keeps `columnDef.size`/`minSize`/`maxSize` meaning "declared" — see
|
|
137
|
-
// ./column-size. `getSize()` is unaffected; it falls back to the library
|
|
138
|
-
// defaults itself.
|
|
139
|
-
defaultColumn: UNSIZED_DEFAULT_COLUMN,
|
|
140
|
-
getCoreRowModel: getCoreRowModel(),
|
|
141
|
-
getRowId,
|
|
142
|
-
enableRowSelection,
|
|
143
|
-
// Controlled-only, exactly as sindarian-x shipped it: this component holds
|
|
144
|
-
// no selection state. The `?? {}` is the JS-consumer safety net; the prop
|
|
145
|
-
// types make the TS path require the controlled pair whenever selection is
|
|
146
|
-
// enabled, so the dead-checkbox configuration is now unrepresentable.
|
|
147
|
-
onRowSelectionChange: enableRowSelection ? onRowSelectionChange : undefined,
|
|
148
|
-
// Controlled-only as well, and deliberately without `getSortedRowModel`:
|
|
149
|
-
// `manualSorting` tells TanStack the rows arrive in the order the consumer
|
|
150
|
-
// already chose, so enabling sorting changes what the header ANNOUNCES,
|
|
151
|
-
// never what the body shows.
|
|
152
|
-
manualSorting: true,
|
|
153
|
-
enableSorting,
|
|
154
|
-
onSortingChange: enableSorting ? onSortingChange : undefined,
|
|
155
|
-
// ⛔ EACH VISIBILITY ENTRY IS GUARDED ON ITS OWN PROP, NEVER ON THE OTHER.
|
|
156
|
-
//
|
|
157
|
-
// TanStack merges options as `{...defaultOptions, ...options}` and state as
|
|
158
|
-
// `{...internalState, ...options.state}`, both plain spreads — so a key
|
|
159
|
-
// present and holding `undefined` OVERWRITES what it was meant to leave
|
|
160
|
-
// alone. `onColumnVisibilityChange: undefined` replaces the feature's own
|
|
161
|
-
// `makeStateUpdater`, and `setColumnVisibility` reads
|
|
162
|
-
// `options.onColumnVisibilityChange == null ? void 0 : ...`, so it (and
|
|
163
|
-
// every `column.toggleVisibility` behind it) becomes a silent no-op.
|
|
164
|
-
// `columnVisibility: undefined` replaces the table's initial `{}`, so a
|
|
165
|
-
// consumer reading the slice gets `undefined` instead of an empty object.
|
|
166
|
-
//
|
|
167
|
-
// Guarding the UPDATER on the STATE put that `undefined` back for the
|
|
168
|
-
// union's first arm — state with no updater, which is the shipped console
|
|
169
|
-
// usage, where a separate dropdown owns the state. A table that passes
|
|
170
|
-
// neither prop is uncontrolled, exactly as before.
|
|
171
|
-
...(onColumnVisibilityChange !== undefined && { onColumnVisibilityChange }),
|
|
172
|
-
state: {
|
|
173
|
-
rowSelection: enableRowSelection ? (rowSelection ?? {}) : undefined,
|
|
174
|
-
sorting: enableSorting ? (sorting ?? []) : undefined,
|
|
175
|
-
...(columnVisibility !== undefined && { columnVisibility })
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
// Visible, not declared: the skeleton grid and the empty row's colSpan have
|
|
179
|
-
// to match the header the table actually rendered, and a hidden column is in
|
|
180
|
-
// `tableColumns` but in no `<th>`.
|
|
181
|
-
const colCount = table.getVisibleLeafColumns().length;
|
|
182
|
-
const dataRows = table.getRowModel().rows;
|
|
183
|
-
const rowCount = dataRows.length;
|
|
184
|
-
// Compact density overrides only THIS table's cell padding (tailwind-merge
|
|
185
|
-
// lets the later utility win over TableHead/TableCell's defaults).
|
|
186
|
-
// Comfortable passes nothing, so the default render is unchanged.
|
|
187
|
-
const compact = density === 'compact';
|
|
188
|
-
const headDensityClass = compact ? 'h-8 px-2' : undefined;
|
|
189
|
-
const cellDensityClass = compact ? 'px-2 py-1.5' : undefined;
|
|
190
|
-
// --- Roving row focus (active only when onRowActivate is provided) ---
|
|
191
|
-
const keyboardNav = onRowActivate !== undefined;
|
|
192
|
-
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
193
|
-
// ponytail: rowRefs is keyed by render index, while rows are id-keyed
|
|
194
|
-
// (`key={row.id}`). This holds only because DataTable is server-driven with
|
|
195
|
-
// no in-place client reorder, and React 19's per-render ref cleanup defuses
|
|
196
|
-
// stale entries. If client-side reorder is ever added, re-key this Map by
|
|
197
|
-
// row.id to keep focus targets aligned with their rows.
|
|
198
|
-
const rowRefs = useRef(new Map());
|
|
199
|
-
const focusWithinRef = useRef(false);
|
|
200
|
-
const containerRef = useRef(null);
|
|
201
|
-
// Render-time clamp: exactly one anchor row (tabIndex=0) even on the
|
|
202
|
-
// frame where data shrank but state has not caught up yet.
|
|
203
|
-
const anchorIndex = rowCount > 0 ? Math.min(focusedIndex, rowCount - 1) : 0;
|
|
204
|
-
// Data changes clamp the anchor but never steal focus: DOM focus moves
|
|
205
|
-
// only if focus was already inside the table and the focused row vanished
|
|
206
|
-
// (e.g. page flip removed it). Never autofocus on mount.
|
|
207
|
-
useEffect(() => {
|
|
208
|
-
if (!keyboardNav)
|
|
209
|
-
return;
|
|
210
|
-
// Clamp the focus target ONCE and drive both the state setter and the
|
|
211
|
-
// .focus() call from the same value — reading `focusedIndex` separately
|
|
212
|
-
// after the functional update would use the stale pre-clamp closure value
|
|
213
|
-
// and try to focus an unmounted row, dropping focus to <body>.
|
|
214
|
-
const max = Math.max(rowCount - 1, 0);
|
|
215
|
-
const target = Math.min(focusedIndex, max);
|
|
216
|
-
setFocusedIndex(target);
|
|
217
|
-
if (!focusWithinRef.current)
|
|
218
|
-
return;
|
|
219
|
-
const active = document.activeElement;
|
|
220
|
-
if (active && containerRef.current?.contains(active))
|
|
221
|
-
return;
|
|
222
|
-
// The target row may have unmounted on a data-shrink frame; walk down to
|
|
223
|
-
// the nearest existing lower index, falling back to the container, so
|
|
224
|
-
// focus never escapes the table.
|
|
225
|
-
for (let i = target; i >= 0; i--) {
|
|
226
|
-
const rowEl = rowRefs.current.get(i);
|
|
227
|
-
if (rowEl) {
|
|
228
|
-
rowEl.focus();
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
containerRef.current?.focus();
|
|
233
|
-
}, [keyboardNav, rowCount, focusedIndex, data]);
|
|
234
|
-
const focusRow = (index) => {
|
|
235
|
-
rowRefs.current.get(index)?.focus();
|
|
236
|
-
};
|
|
237
|
-
const handleRowBlur = (event) => {
|
|
238
|
-
const next = event.relatedTarget;
|
|
239
|
-
if (!next || !containerRef.current?.contains(next)) {
|
|
240
|
-
focusWithinRef.current = false;
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
const handleRowKeyDown = (event, row, rowIndex) => {
|
|
244
|
-
const target = event.target;
|
|
245
|
-
if (target !== event.currentTarget &&
|
|
246
|
-
target.closest(INTERACTIVE_CHILD_SELECTOR) !== null) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
switch (event.key) {
|
|
250
|
-
case 'ArrowDown':
|
|
251
|
-
event.preventDefault();
|
|
252
|
-
focusRow(Math.min(rowIndex + 1, rowCount - 1));
|
|
253
|
-
break;
|
|
254
|
-
case 'ArrowUp':
|
|
255
|
-
event.preventDefault();
|
|
256
|
-
focusRow(Math.max(rowIndex - 1, 0));
|
|
257
|
-
break;
|
|
258
|
-
case 'Home':
|
|
259
|
-
event.preventDefault();
|
|
260
|
-
focusRow(0);
|
|
261
|
-
break;
|
|
262
|
-
case 'End':
|
|
263
|
-
event.preventDefault();
|
|
264
|
-
focusRow(rowCount - 1);
|
|
265
|
-
break;
|
|
266
|
-
case 'Enter':
|
|
267
|
-
// Modified Enter (Cmd/Ctrl) means "open elsewhere" — a same-tab
|
|
268
|
-
// activation would destroy the user's context. With rowHref it
|
|
269
|
-
// opens the row's destination in a new tab; without it (sheet-based
|
|
270
|
-
// tables), it stays a deliberate no-op.
|
|
271
|
-
if (event.metaKey || event.ctrlKey) {
|
|
272
|
-
if (rowHref) {
|
|
273
|
-
window.open(rowHref(row.original), '_blank', 'noopener,noreferrer');
|
|
274
|
-
}
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
onRowActivate?.(row.original);
|
|
278
|
-
break;
|
|
279
|
-
case ' ':
|
|
280
|
-
if (enableRowSelection && row.getCanSelect()) {
|
|
281
|
-
event.preventDefault();
|
|
282
|
-
row.getToggleSelectedHandler()(event);
|
|
283
|
-
}
|
|
284
|
-
break;
|
|
285
|
-
default:
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
|
-
return (_jsxs("div", { ref: containerRef,
|
|
290
|
-
// tabIndex={-1} (keyboard-nav only) makes the container programmatically
|
|
291
|
-
// focusable so the focus-restoration fallback can land here when every
|
|
292
|
-
// row has unmounted, keeping focus inside the table rather than on <body>.
|
|
293
|
-
tabIndex: keyboardNav ? -1 : undefined, className: cn(!flush &&
|
|
294
|
-
'border-border bg-card text-card-foreground rounded-lg border', className), children: [_jsx("span", { className: "sr-only", role: "status", children: loading
|
|
295
|
-
? loadingLabel
|
|
296
|
-
: rowCount === 0
|
|
297
|
-
? 'No rows'
|
|
298
|
-
: `${rowCount} rows` }), _jsxs(Table, { "aria-busy": loading ? 'true' : undefined, children: [_jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { className: "hover:bg-transparent", children: headerGroup.headers.map((header) => {
|
|
299
|
-
const meta = header.column.columnDef.meta;
|
|
300
|
-
return (_jsx(TableHead, { align: meta?.align ?? (meta?.numeric ? 'right' : undefined), "aria-sort": header.isPlaceholder
|
|
301
|
-
? undefined
|
|
302
|
-
: ariaSortFor(header.column),
|
|
303
|
-
// Only a column that DECLARED sizing gets an inline width;
|
|
304
|
-
// everything else keeps the auto table layout it has today.
|
|
305
|
-
style: readPreferredColumnSize(header.column), className: cn(LABEL_VOICE_CLASS, headDensityClass, header.column.id === SELECTION_COLUMN_ID && 'w-10', headClassName, meta?.headerClassName), children: header.isPlaceholder
|
|
306
|
-
? null
|
|
307
|
-
: flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
|
|
308
|
-
}) }, headerGroup.id))) }), _jsx(TableBody, { children: loading ? (Array.from({ length: skeletonRows }).map((_, rowIndex) => (_jsx(TableRow, { className: "hover:bg-transparent", children: Array.from({ length: colCount }).map((__, cellIndex) => (_jsx(TableCell, { className: cellDensityClass, children: _jsx(Skeleton, { className: "h-4 w-full max-w-[160px]" }) }, `skeleton-${rowIndex}-${cellIndex}`))) }, `skeleton-${rowIndex}`)))) : rowCount === 0 ? (_jsx(TableRow, { className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: colCount, className: "p-0", children: _jsx(EmptyState, { title: empty?.title ?? 'Nothing here yet', titleAs: empty?.titleAs, description: empty?.description, icon: empty?.icon, action: empty?.action,
|
|
309
|
-
// The DataTable renders the ledger register grammar, so its
|
|
310
|
-
// empty surface reads as a ruled page rather than a centered
|
|
311
|
-
// void. Callers can still override via `empty.ruled = false`.
|
|
312
|
-
ruled: empty?.ruled ?? true }) }) })) : (dataRows.map((row, rowIndex) => (_jsx(TableRow, { ref: keyboardNav
|
|
313
|
-
? (el) => {
|
|
314
|
-
if (el)
|
|
315
|
-
rowRefs.current.set(rowIndex, el);
|
|
316
|
-
else
|
|
317
|
-
rowRefs.current.delete(rowIndex);
|
|
318
|
-
}
|
|
319
|
-
: undefined, tabIndex: keyboardNav ? (rowIndex === anchorIndex ? 0 : -1) : undefined, onFocus: keyboardNav
|
|
320
|
-
? () => {
|
|
321
|
-
focusWithinRef.current = true;
|
|
322
|
-
setFocusedIndex(rowIndex);
|
|
323
|
-
}
|
|
324
|
-
: undefined, onBlur: keyboardNav ? handleRowBlur : undefined, onKeyDown: keyboardNav
|
|
325
|
-
? (event) => handleRowKeyDown(event, row, rowIndex)
|
|
326
|
-
: undefined, className: keyboardNav ? ROW_FOCUS_CLASS : undefined, "data-state": enableRowSelection && row.getIsSelected()
|
|
327
|
-
? 'selected'
|
|
328
|
-
: undefined, children: row.getVisibleCells().map((cell) => {
|
|
329
|
-
const meta = cell.column.columnDef.meta;
|
|
330
|
-
const content = flexRender(cell.column.columnDef.cell, cell.getContext());
|
|
331
|
-
// The column owns its `<td>`: render the consumer's cell bare.
|
|
332
|
-
// The kit's selection column is built here and carries no
|
|
333
|
-
// meta, so it can never take this path.
|
|
334
|
-
if (meta?.renderOwnCell) {
|
|
335
|
-
return _jsx(Fragment, { children: content }, cell.id);
|
|
336
|
-
}
|
|
337
|
-
const align = meta?.align ?? (meta?.numeric ? 'right' : undefined);
|
|
338
|
-
return (_jsx(TableCell, { style: readPreferredColumnSize(cell.column), className: cn(meta?.numeric && NUMERIC_FIGURE_CLASS, align && ALIGN_CELL_CLASS[align], cellDensityClass, cell.column.id === SELECTION_COLUMN_ID && 'w-10', meta?.className), onClick: cell.column.id === SELECTION_COLUMN_ID
|
|
339
|
-
? stopPropagation
|
|
340
|
-
: undefined, children: content }, cell.id));
|
|
341
|
-
}) }, row.id)))) }), footer ? _jsx(TableFooter, { children: footer }) : null] })] }));
|
|
342
|
-
}
|
|
1
|
+
"use client";import{jsx as o,jsxs as U}from"react/jsx-runtime";import{Fragment as re,useEffect as oe,useMemo as ae,useRef as k,useState as se}from"react";import{flexRender as v,getCoreRowModel as ce,useReactTable as le}from"@tanstack/react-table";import{Table as ie,TableBody as ue,TableCell as E,TableFooter as de,TableHead as fe,TableHeader as me,TableRow as C}from"../../components/ui/table/index.js";import{Skeleton as ge}from"../../components/ui/skeleton/index.js";import{LABEL_VOICE_CLASS as he}from"../../lib/typography.js";import{cn as D}from"../../lib/utils.js";import{EmptyState as pe}from"../empty-state/index.js";import{readPreferredColumnSize as B,UNSIZED_DEFAULT_COLUMN as Ce}from"./column-size.js";const b="__select__";const K="accent-primary block size-4";const be="font-mono tabular-nums";const Se={left:"text-left",center:"text-center",right:"text-right"};const xe='a, button, input, select, textarea, [role="checkbox"]';const Ne="focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring";function _e(r){r.stopPropagation()}function ke(r){if(!r.getCanSort())return void 0;const a=r.getIsSorted();if(a==="asc")return"ascending";if(a==="desc")return"descending";return"none"}function Ee({table:r}){const a=r.getIsAllPageRowsSelected();const d=r.getIsSomePageRowsSelected();return o("input",{type:"checkbox",className:K,"aria-label":"Select all rows on this page",checked:a,ref:m=>{if(m)m.indeterminate=!a&&d},onChange:r.getToggleAllPageRowsSelectedHandler()})}function De({row:r,getLabel:a}){return o("input",{type:"checkbox",className:K,"aria-label":a?a(r.original):`Select row ${r.id}`,checked:r.getIsSelected(),disabled:!r.getCanSelect(),onChange:r.getToggleSelectedHandler()})}function Te(r){return{id:b,header:({table:a})=>o(Ee,{table:a}),cell:({row:a})=>o(De,{row:a,getLabel:r})}}export function DataTable({columns:r,data:a,loading:d=false,skeletonRows:m=8,loadingLabel:$="Loading table rows",empty:u,getRowId:j,flush:z=false,density:G="comfortable",enableRowSelection:i=false,rowSelection:V,onRowSelectionChange:W,getRowSelectionLabel:T,enableSorting:S=false,sorting:X,onSortingChange:Z,columnVisibility:A,onColumnVisibilityChange:L,onRowActivate:R,rowHref:y,className:q,headClassName:J,footer:I}){const Q=ae(()=>i?[Te(T),...r]:r,[r,i,T]);const x=le({data:a,columns:Q,defaultColumn:Ce,getCoreRowModel:ce(),getRowId:j,enableRowSelection:i,onRowSelectionChange:i?W:void 0,manualSorting:true,enableSorting:S,onSortingChange:S?Z:void 0,...L!==void 0&&{onColumnVisibilityChange:L},state:{rowSelection:i?V??{}:void 0,sorting:S?X??[]:void 0,...A!==void 0&&{columnVisibility:A}}});const w=x.getVisibleLeafColumns().length;const M=x.getRowModel().rows;const c=M.length;const O=G==="compact";const Y=O?"h-8 px-2":void 0;const F=O?"px-2 py-1.5":void 0;const l=R!==void 0;const[N,H]=se(0);const g=k(new Map);const _=k(false);const h=k(null);const ee=c>0?Math.min(N,c-1):0;oe(()=>{if(!l)return;const n=Math.max(c-1,0);const e=Math.min(N,n);H(e);if(!_.current)return;const t=document.activeElement;if(t&&h.current?.contains(t))return;for(let s=e;s>=0;s--){const f=g.current.get(s);if(f){f.focus();return}}h.current?.focus()},[l,c,N,a]);const p=n=>{g.current.get(n)?.focus()};const ne=n=>{const e=n.relatedTarget;if(!e||!h.current?.contains(e)){_.current=false}};const te=(n,e,t)=>{const s=n.target;if(s!==n.currentTarget&&s.closest(xe)!==null){return}switch(n.key){case"ArrowDown":n.preventDefault();p(Math.min(t+1,c-1));break;case"ArrowUp":n.preventDefault();p(Math.max(t-1,0));break;case"Home":n.preventDefault();p(0);break;case"End":n.preventDefault();p(c-1);break;case"Enter":if(n.metaKey||n.ctrlKey){if(y){window.open(y(e.original),"_blank","noopener,noreferrer")}break}R?.(e.original);break;case" ":if(i&&e.getCanSelect()){n.preventDefault();e.getToggleSelectedHandler()(n)}break;default:break}};return U("div",{ref:h,tabIndex:l?-1:void 0,className:D(!z&&"border-border bg-card text-card-foreground rounded-lg border",q),children:[o("span",{className:"sr-only",role:"status",children:d?$:c===0?"No rows":`${c} rows`}),U(ie,{"aria-busy":d?"true":void 0,children:[o(me,{children:x.getHeaderGroups().map(n=>o(C,{className:"hover:bg-transparent",children:n.headers.map(e=>{const t=e.column.columnDef.meta;return o(fe,{align:t?.align??(t?.numeric?"right":void 0),"aria-sort":e.isPlaceholder?void 0:ke(e.column),style:B(e.column),className:D(he,Y,e.column.id===b&&"w-10",J,t?.headerClassName),children:e.isPlaceholder?null:v(e.column.columnDef.header,e.getContext())},e.id)})},n.id))}),o(ue,{children:d?Array.from({length:m}).map((n,e)=>o(C,{className:"hover:bg-transparent",children:Array.from({length:w}).map((t,s)=>o(E,{className:F,children:o(ge,{className:"h-4 w-full max-w-[160px]"})},`skeleton-${e}-${s}`))},`skeleton-${e}`)):c===0?o(C,{className:"hover:bg-transparent",children:o(E,{colSpan:w,className:"p-0",children:o(pe,{title:u?.title??"Nothing here yet",titleAs:u?.titleAs,description:u?.description,icon:u?.icon,action:u?.action,ruled:u?.ruled??true})})}):M.map((n,e)=>o(C,{ref:l?t=>{if(t)g.current.set(e,t);else g.current.delete(e)}:void 0,tabIndex:l?e===ee?0:-1:void 0,onFocus:l?()=>{_.current=true;H(e)}:void 0,onBlur:l?ne:void 0,onKeyDown:l?t=>te(t,n,e):void 0,className:l?Ne:void 0,"data-state":i&&n.getIsSelected()?"selected":void 0,children:n.getVisibleCells().map(t=>{const s=t.column.columnDef.meta;const f=v(t.column.columnDef.cell,t.getContext());if(s?.renderOwnCell){return o(re,{children:f},t.id)}const P=s?.align??(s?.numeric?"right":void 0);return o(E,{style:B(t.column),className:D(s?.numeric&&be,P&&Se[P],F,t.column.id===b&&"w-10",s?.className),onClick:t.column.id===b?_e:void 0,children:f},t.id)})},n.id))}),I?o(de,{children:I}):null]})]})}
|
|
@@ -1,112 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* DateRangePicker — a two-segment popover range control.
|
|
5
|
-
*
|
|
6
|
-
* Two labelled trigger segments (from / to) each open the SAME two-month
|
|
7
|
-
* react-day-picker range calendar over the same range selection. The external
|
|
8
|
-
* contract is plain YYYY-MM-DD strings (empty string = unset) so state shapes
|
|
9
|
-
* that speak `date_from`/`date_to` stay untouched. The picker guarantees
|
|
10
|
-
* from <= to (react-day-picker reorders inverted picks);
|
|
11
|
-
* aria-invalid/aria-describedby pass-throughs keep external validation wiring
|
|
12
|
-
* alive. i18n is via plain string/locale props.
|
|
13
|
-
*
|
|
14
|
-
* ONE POPOVER ROOT PER SEGMENT, deliberately. A single `Popover.Root` holds ONE
|
|
15
|
-
* trigger ref and ONE open state, so wrapping the whole two-button row in a
|
|
16
|
-
* single `PopoverTrigger asChild` put Radix's dialog attributes
|
|
17
|
-
* (`aria-haspopup`, `aria-expanded`, `aria-controls`) on a plain, non-focusable
|
|
18
|
-
* `div` — an aria-allowed-attr violation, two buttons advertising the same
|
|
19
|
-
* expanded state, and focus that Radix could drop on `document.body` when the
|
|
20
|
-
* popover closed. Each segment is now a real `PopoverTrigger` button: focusable,
|
|
21
|
-
* correctly named, and the element Radix returns focus to on dismissal.
|
|
22
|
-
*/
|
|
23
|
-
import { useEffect, useState } from 'react';
|
|
24
|
-
import { CalendarDays } from 'lucide-react';
|
|
25
|
-
import { enUS } from 'react-day-picker/locale';
|
|
26
|
-
import { Button } from '../../components/ui/button/index.js';
|
|
27
|
-
import { Calendar } from '../../components/ui/calendar/index.js';
|
|
28
|
-
import { Label } from '../../components/ui/label/index.js';
|
|
29
|
-
import { Popover, PopoverContent, PopoverTrigger } from '../../components/ui/popover/index.js';
|
|
30
|
-
import { cn } from '../../lib/utils.js';
|
|
31
|
-
/** Leading YYYY-MM-DD of an ISO date (or date-time) string. */
|
|
32
|
-
const ISO_DAY = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
33
|
-
/**
|
|
34
|
-
* Parse a YYYY-MM-DD day into a LOCAL-midnight Date — the calendar works in
|
|
35
|
-
* local time, so building from parts avoids the UTC shift `new Date(string)`
|
|
36
|
-
* applies to date-only input (which can land the picker a day early).
|
|
37
|
-
* Rejects impossible calendar days (e.g. 2024-02-31, which would roll over).
|
|
38
|
-
*/
|
|
39
|
-
function parseDay(value) {
|
|
40
|
-
const match = ISO_DAY.exec(value);
|
|
41
|
-
if (!match)
|
|
42
|
-
return undefined;
|
|
43
|
-
const year = Number(match[1]);
|
|
44
|
-
const month = Number(match[2]);
|
|
45
|
-
const day = Number(match[3]);
|
|
46
|
-
const parsed = new Date(year, month - 1, day);
|
|
47
|
-
return parsed.getFullYear() === year &&
|
|
48
|
-
parsed.getMonth() === month - 1 &&
|
|
49
|
-
parsed.getDate() === day
|
|
50
|
-
? parsed
|
|
51
|
-
: undefined;
|
|
52
|
-
}
|
|
53
|
-
/** Format a Date back to YYYY-MM-DD in LOCAL time (never via toISOString). */
|
|
54
|
-
function toDayString(value) {
|
|
55
|
-
if (!value)
|
|
56
|
-
return '';
|
|
57
|
-
const year = String(value.getFullYear()).padStart(4, '0');
|
|
58
|
-
const month = String(value.getMonth() + 1).padStart(2, '0');
|
|
59
|
-
const day = String(value.getDate()).padStart(2, '0');
|
|
60
|
-
return `${year}-${month}-${day}`;
|
|
61
|
-
}
|
|
62
|
-
export function DateRangePicker({ value, onValueChange, fromId, toId, fromLabel, toLabel, invalid = false, required = false, errorId, placeholder = 'Any date', ariaLabel = 'Select date range', clearLabel = 'Clear', locale = enUS, disabled = false }) {
|
|
63
|
-
// Which segment's popover is open, if any. One root per segment means one open
|
|
64
|
-
// state per segment; holding the identity of the open one here keeps them
|
|
65
|
-
// mutually exclusive, so clicking "to" while "from" is open swaps the anchor
|
|
66
|
-
// instead of stacking two calendars.
|
|
67
|
-
const [openSegment, setOpenSegment] = useState(null);
|
|
68
|
-
// Suspending the control must FORGET which segment was open, not just hide
|
|
69
|
-
// it. The `open` prop below already shuts the popover in the same frame
|
|
70
|
-
// `disabled` flips, but a controlled Radix root never calls `onOpenChange`
|
|
71
|
-
// for a prop-driven close, so the identity survived: lifting `disabled`
|
|
72
|
-
// re-satisfied the same condition and the portaled calendar reopened with no
|
|
73
|
-
// user gesture, pulling focus into it.
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
if (disabled)
|
|
76
|
-
setOpenSegment(null);
|
|
77
|
-
}, [disabled]);
|
|
78
|
-
const fromDate = parseDay(value.from);
|
|
79
|
-
const toDate = parseDay(value.to);
|
|
80
|
-
const selected = fromDate || toDate ? { from: fromDate, to: toDate } : undefined;
|
|
81
|
-
// Derived from the RAW strings, not the parsed dates: a value the calendar
|
|
82
|
-
// cannot parse still needs to be clearable, and gating Clear on `fromDate`
|
|
83
|
-
// would strand the field holding text the user can no longer remove.
|
|
84
|
-
const hasValue = value.from !== '' || value.to !== '';
|
|
85
|
-
const onSelect = (range) => {
|
|
86
|
-
onValueChange({
|
|
87
|
-
from: toDayString(range?.from),
|
|
88
|
-
to: toDayString(range?.to)
|
|
89
|
-
});
|
|
90
|
-
};
|
|
91
|
-
const triggerClass = cn(
|
|
92
|
-
// border-input-border, not border-input: `--input` is a SURFACE token and
|
|
93
|
-
// is white in the light theme, so the trigger had no visible edge next to
|
|
94
|
-
// the Input and Select it sits with. font-sans, not font-mono: the dates are
|
|
95
|
-
// figures in the body face with tabular figures, like every other figure in
|
|
96
|
-
// the console.
|
|
97
|
-
'border-input-border bg-card hover:bg-secondary focus-visible:ring-ring flex h-9 items-center gap-2 rounded-md border px-3 text-left font-sans text-sm tabular-nums transition-colors duration-150 ease-out focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none sm:w-40', invalid && 'border-destructive',
|
|
98
|
-
// Conditional rather than `disabled:` variants, matching how this file
|
|
99
|
-
// already applies `border-destructive`: with the prop absent the class
|
|
100
|
-
// string stays byte-identical to what consumers render today.
|
|
101
|
-
disabled && 'cursor-not-allowed opacity-50');
|
|
102
|
-
// The one calendar, rendered inside whichever segment's popover is open. Both
|
|
103
|
-
// segments read and write the SAME range selection, so which one you opened
|
|
104
|
-
// changes only the anchor, never the state.
|
|
105
|
-
const calendar = (_jsxs(_Fragment, { children: [_jsx(Calendar, { mode: "range", numberOfMonths: 2, defaultMonth: fromDate ?? toDate ?? new Date(), selected: selected, onSelect: onSelect, locale: locale }), _jsx("div", { className: "border-border flex items-center justify-end border-t px-3 py-2", children: _jsx(Button, { type: "button", variant: "plain", size: "small", disabled: !hasValue, onClick: () => onValueChange({ from: '', to: '' }), children: clearLabel }) })] }));
|
|
106
|
-
const renderSegment = (segment, id, label, dateValue) => (_jsxs(Popover, {
|
|
107
|
-
// A disabled trigger cannot open a popover, but a surface that suspends
|
|
108
|
-
// itself WHILE one is open (a filter bar flipping to "show all") would
|
|
109
|
-
// otherwise leave the portaled calendar on screen with no way back to it.
|
|
110
|
-
open: !disabled && openSegment === segment, onOpenChange: (next) => setOpenSegment(next ? segment : null), children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsxs(Label, { htmlFor: id, children: [label, required ? ' *' : ''] }), _jsxs(PopoverTrigger, { type: "button", id: id, className: triggerClass, disabled: disabled, "aria-invalid": invalid || undefined, "aria-describedby": invalid ? errorId : undefined, children: [_jsx(CalendarDays, { className: "text-muted-foreground size-4 shrink-0", "aria-hidden": true }), dateValue ? (_jsx("span", { children: dateValue })) : (_jsx("span", { className: "text-muted-foreground", children: placeholder }))] })] }), _jsx(PopoverContent, { align: "start", className: "w-auto p-0", "aria-label": ariaLabel, children: calendar })] }, segment));
|
|
111
|
-
return (_jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-end", children: [renderSegment('from', fromId, fromLabel, value.from), renderSegment('to', toId, toLabel, value.to)] }));
|
|
112
|
-
}
|
|
1
|
+
"use client";import{jsx as r,Fragment as Y,jsxs as i}from"react/jsx-runtime";import{useEffect as $,useState as z}from"react";import{CalendarDays as A}from"lucide-react";import{enUS as B}from"react-day-picker/locale";import{Button as E}from"../../components/ui/button/index.js";import{Calendar as I}from"../../components/ui/calendar/index.js";import{Label as L}from"../../components/ui/label/index.js";import{Popover as R,PopoverContent as T,PopoverTrigger as U}from"../../components/ui/popover/index.js";import{cn as q}from"../../lib/utils.js";const G=/^(\d{4})-(\d{2})-(\d{2})/;function b(e){const t=G.exec(e);if(!t)return void 0;const n=Number(t[1]);const o=Number(t[2]);const d=Number(t[3]);const s=new Date(n,o-1,d);return s.getFullYear()===n&&s.getMonth()===o-1&&s.getDate()===d?s:void 0}function x(e){if(!e)return"";const t=String(e.getFullYear()).padStart(4,"0");const n=String(e.getMonth()+1).padStart(2,"0");const o=String(e.getDate()).padStart(2,"0");return`${t}-${n}-${o}`}export function DateRangePicker({value:e,onValueChange:t,fromId:n,toId:o,fromLabel:d,toLabel:s,invalid:l=false,required:S=false,errorId:y,placeholder:D="Any date",ariaLabel:N="Select date range",clearLabel:w="Clear",locale:C=B,disabled:c=false}){const[j,u]=z(null);$(()=>{if(c)u(null)},[c]);const m=b(e.from);const f=b(e.to);const F=m||f?{from:m,to:f}:void 0;const v=e.from!==""||e.to!=="";const M=a=>{t({from:x(a?.from),to:x(a?.to)})};const O=q("border-input-border bg-card hover:bg-secondary focus-visible:ring-ring flex h-9 items-center gap-2 rounded-md border px-3 text-left font-sans text-sm tabular-nums transition-colors duration-150 ease-out focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:outline-none sm:w-40",l&&"border-destructive",c&&"cursor-not-allowed opacity-50");const P=i(Y,{children:[r(I,{mode:"range",numberOfMonths:2,defaultMonth:m??f??new Date,selected:F,onSelect:M,locale:C}),r("div",{className:"border-border flex items-center justify-end border-t px-3 py-2",children:r(E,{type:"button",variant:"plain",size:"small",disabled:!v,onClick:()=>t({from:"",to:""}),children:w})})]});const p=(a,g,_,h)=>i(R,{open:!c&&j===a,onOpenChange:k=>u(k?a:null),children:[i("div",{className:"flex flex-col gap-1.5",children:[i(L,{htmlFor:g,children:[_,S?" *":""]}),i(U,{type:"button",id:g,className:O,disabled:c,"aria-invalid":l||void 0,"aria-describedby":l?y:void 0,children:[r(A,{className:"text-muted-foreground size-4 shrink-0","aria-hidden":true}),h?r("span",{children:h}):r("span",{className:"text-muted-foreground",children:D})]})]}),r(T,{align:"start",className:"w-auto p-0","aria-label":N,children:P})]},a);return i("div",{className:"flex flex-col gap-3 sm:flex-row sm:items-end",children:[p("from",n,d,e.from),p("to",o,s,e.to)]})}
|
|
@@ -1,17 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { X } from 'lucide-react';
|
|
4
|
-
import { Sheet, SheetClose, SheetContent, SheetTitle } from '../../components/ui/sheet/index.js';
|
|
5
|
-
import { cn } from '../../lib/utils.js';
|
|
6
|
-
/**
|
|
7
|
-
* The slide-over inspector. The header and footer stay pinned; only the body
|
|
8
|
-
* scrolls. Sheet already renders an overlay, focus trap, and Esc/overlay
|
|
9
|
-
* dismissal — we suppress its built-in corner close button (`[&>button.absolute]`)
|
|
10
|
-
* and provide our own inside the header so the layout stays deliberate.
|
|
11
|
-
*/
|
|
12
|
-
export function DetailPanel({ open, onOpenChange, title, side = 'right', actions, footer, closeLabel = 'Close', children, className }) {
|
|
13
|
-
return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, children: _jsxs(SheetContent, { side: side, className: cn(
|
|
14
|
-
// Reset Sheet's padding/gap and hide its built-in close button; we
|
|
15
|
-
// own the three-slot layout and the header close affordance here.
|
|
16
|
-
'flex flex-col gap-0 overflow-hidden p-0', '[&>button.absolute]:hidden', className), children: [_jsxs("header", { className: "border-border bg-card sticky top-0 z-10 flex shrink-0 items-start justify-between gap-3 border-b px-6 py-4", children: [_jsx(SheetTitle, { className: "min-w-0 flex-1 truncate text-base font-semibold tracking-tight", children: title }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5", children: [actions, _jsx(SheetClose, { "data-slot": "detail-panel-close", className: "ring-offset-background focus:ring-ring rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:outline-none disabled:pointer-events-none", "aria-label": closeLabel, children: _jsx(X, { className: "size-4", "aria-hidden": true }) })] })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: _jsx("div", { className: "px-6 py-5", children: children }) }), footer ? (_jsx("footer", { className: "border-border bg-card sticky bottom-0 z-10 flex shrink-0 flex-col-reverse gap-2 border-t px-6 py-4 sm:flex-row sm:justify-end", children: footer })) : null] }) }));
|
|
17
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as r}from"react/jsx-runtime";import{X as m}from"lucide-react";import{Sheet as h,SheetClose as f,SheetContent as p,SheetTitle as b}from"../../components/ui/sheet/index.js";import{cn as x}from"../../lib/utils.js";export function DetailPanel({open:t,onOpenChange:i,title:n,side:o="right",actions:a,footer:s,closeLabel:l="Close",children:c,className:d}){return e(h,{open:t,onOpenChange:i,children:r(p,{side:o,className:x("flex flex-col gap-0 overflow-hidden p-0","[&>button.absolute]:hidden",d),children:[r("header",{className:"border-border bg-card sticky top-0 z-10 flex shrink-0 items-start justify-between gap-3 border-b px-6 py-4",children:[e(b,{className:"min-w-0 flex-1 truncate text-base font-semibold tracking-tight",children:n}),r("div",{className:"flex shrink-0 items-center gap-1.5",children:[a,e(f,{"data-slot":"detail-panel-close",className:"ring-offset-background focus:ring-ring rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:outline-none disabled:pointer-events-none","aria-label":l,children:e(m,{className:"size-4","aria-hidden":true})})]})]}),e("div",{className:"min-h-0 flex-1 overflow-y-auto",children:e("div",{className:"px-6 py-5",children:c})}),s?e("footer",{className:"border-border bg-card sticky bottom-0 z-10 flex shrink-0 flex-col-reverse gap-2 border-t px-6 py-4 sm:flex-row sm:justify-end",children:s}):null]})})}
|
|
@@ -1,11 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Inbox } from 'lucide-react';
|
|
3
|
-
import { LABEL_VOICE_CLASS } from '../../lib/typography.js';
|
|
4
|
-
import { cn } from '../../lib/utils.js';
|
|
5
|
-
export function EmptyState({ icon: Icon = Inbox, title, description, action, ruled = false, titleAs, className }) {
|
|
6
|
-
const Title = titleAs ?? (ruled ? 'span' : 'p');
|
|
7
|
-
if (ruled) {
|
|
8
|
-
return (_jsxs("div", { "data-variant": "ruled", className: cn('border-border flex flex-col items-start gap-2 border-t px-3 py-6 text-left', className), children: [_jsxs("div", { className: "text-muted-foreground flex items-center gap-2", children: [_jsx(Icon, { className: "size-4", "aria-hidden": true }), _jsx(Title, { className: LABEL_VOICE_CLASS, children: title })] }), description ? (_jsx("p", { className: "text-muted-foreground max-w-prose text-sm", children: description })) : null, action ? _jsx("div", { className: "mt-1", children: action }) : null] }));
|
|
9
|
-
}
|
|
10
|
-
return (_jsxs("div", { className: cn('flex flex-col items-center justify-center gap-3 px-6 py-16 text-center', className), children: [_jsx("div", { className: "bg-muted flex size-11 items-center justify-center rounded-full", children: _jsx(Icon, { className: "text-muted-foreground size-5", "aria-hidden": true }) }), _jsxs("div", { className: "space-y-1", children: [_jsx(Title, { className: "text-foreground text-sm font-medium", children: title }), description ? (_jsx("p", { className: "text-muted-foreground mx-auto max-w-sm text-sm", children: description })) : null] }), action ? _jsx("div", { className: "mt-2", children: action }) : null] }));
|
|
11
|
-
}
|
|
1
|
+
import{jsx as e,jsxs as s}from"react/jsx-runtime";import{Inbox as x}from"lucide-react";import{LABEL_VOICE_CLASS as o}from"../../lib/typography.js";import{cn as i}from"../../lib/utils.js";export function EmptyState({icon:l=x,title:a,description:t,action:r,ruled:m=false,titleAs:c,className:d}){const n=c??(m?"span":"p");if(m){return s("div",{"data-variant":"ruled",className:i("border-border flex flex-col items-start gap-2 border-t px-3 py-6 text-left",d),children:[s("div",{className:"text-muted-foreground flex items-center gap-2",children:[e(l,{className:"size-4","aria-hidden":true}),e(n,{className:o,children:a})]}),t?e("p",{className:"text-muted-foreground max-w-prose text-sm",children:t}):null,r?e("div",{className:"mt-1",children:r}):null]})}return s("div",{className:i("flex flex-col items-center justify-center gap-3 px-6 py-16 text-center",d),children:[e("div",{className:"bg-muted flex size-11 items-center justify-center rounded-full",children:e(l,{className:"text-muted-foreground size-5","aria-hidden":true})}),s("div",{className:"space-y-1",children:[e(n,{className:"text-foreground text-sm font-medium",children:a}),t?e("p",{className:"text-muted-foreground mx-auto max-w-sm text-sm",children:t}):null]}),r?e("div",{className:"mt-2",children:r}):null]})}
|