@dxos/react-ui-table 0.3.7 → 0.3.8-main.0694b57
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/lib/browser/index.mjs +546 -475
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/Cell/Cell.d.ts +12 -0
- package/dist/types/src/components/Cell/Cell.d.ts.map +1 -0
- package/dist/types/src/components/Cell/CellCombobox.d.ts +12 -0
- package/dist/types/src/components/Cell/CellCombobox.d.ts.map +1 -0
- package/dist/types/src/components/Cell/index.d.ts +2 -0
- package/dist/types/src/components/Cell/index.d.ts.map +1 -0
- package/dist/types/src/components/Table/Table.d.ts +3 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -0
- package/dist/types/src/components/{Table.stories.d.ts → Table/Table.stories.d.ts} +4 -7
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableBody.d.ts +11 -0
- package/dist/types/src/components/Table/TableBody.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableContext.d.ts +14 -0
- package/dist/types/src/components/Table/TableContext.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableFooter.d.ts +7 -0
- package/dist/types/src/components/Table/TableFooter.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableHead.d.ts +8 -0
- package/dist/types/src/components/Table/TableHead.d.ts.map +1 -0
- package/dist/types/src/components/Table/index.d.ts +3 -0
- package/dist/types/src/components/Table/index.d.ts.map +1 -0
- package/dist/types/src/components/Table/props.d.ts +37 -0
- package/dist/types/src/components/Table/props.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts +20 -14
- package/dist/types/src/helpers.d.ts.map +1 -1
- package/dist/types/src/theme.d.ts +32 -33
- package/dist/types/src/theme.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +10 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +13 -10
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +12 -10
- package/src/components/Cell/Cell.tsx +43 -0
- package/src/components/Cell/CellCombobox.tsx +97 -0
- package/src/components/Cell/index.ts +5 -0
- package/src/components/{Table.stories.tsx → Table/Table.stories.tsx} +51 -50
- package/src/components/Table/Table.tsx +179 -0
- package/src/components/Table/TableBody.tsx +69 -0
- package/src/components/Table/TableContext.tsx +28 -0
- package/src/components/Table/TableFooter.tsx +42 -0
- package/src/components/Table/TableHead.tsx +79 -0
- package/src/components/Table/index.ts +6 -0
- package/src/components/Table/props.ts +53 -0
- package/src/components/index.ts +1 -0
- package/src/helpers.tsx +250 -282
- package/src/theme.ts +95 -63
- package/src/translations.ts +15 -0
- package/src/types.ts +15 -10
- package/dist/types/src/components/Table.d.ts +0 -29
- package/dist/types/src/components/Table.d.ts.map +0 -1
- package/dist/types/src/components/Table.stories.d.ts.map +0 -1
- package/src/components/Table.tsx +0 -551
|
@@ -10,12 +10,12 @@ import { deepSignal } from 'deepsignal';
|
|
|
10
10
|
import React, { useEffect, useState } from 'react';
|
|
11
11
|
|
|
12
12
|
import { PublicKey } from '@dxos/keys';
|
|
13
|
-
import { DensityProvider } from '@dxos/react-ui';
|
|
13
|
+
import { DensityProvider, AnchoredOverflow } from '@dxos/react-ui';
|
|
14
14
|
import { range } from '@dxos/util';
|
|
15
15
|
|
|
16
16
|
import { Table } from './Table';
|
|
17
|
-
import { createColumnBuilder, type
|
|
18
|
-
import { type TableColumnDef } from '
|
|
17
|
+
import { createColumnBuilder, type SearchListQueryModel, type ValueUpdater } from '../../helpers';
|
|
18
|
+
import { type TableColumnDef } from '../../types';
|
|
19
19
|
|
|
20
20
|
// TODO(burdon): Header menu builder.
|
|
21
21
|
// TODO(burdon): Expand width if not all columns have explicit size.
|
|
@@ -59,7 +59,7 @@ const tableStorySelectItems: Record<string, Item> = range(128).reduce((acc: Reco
|
|
|
59
59
|
|
|
60
60
|
const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
61
|
|
|
62
|
-
const tableStorySelectModel:
|
|
62
|
+
const tableStorySelectModel: SearchListQueryModel<Item> = {
|
|
63
63
|
getId: (object) => object?.publicKey?.toHex() ?? 'never',
|
|
64
64
|
getText: (object) => object?.name ?? 'never',
|
|
65
65
|
query: async (search: string) => {
|
|
@@ -71,36 +71,44 @@ const tableStorySelectModel: SelectQueryModel<Item> = {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
const { helper, builder } = createColumnBuilder<Item>();
|
|
74
|
+
|
|
74
75
|
const columns = (onUpdate?: ValueUpdater<Item, any>): TableColumnDef<Item, any>[] => [
|
|
76
|
+
helper.display(builder.selectRow()),
|
|
77
|
+
helper.accessor((item) => item.publicKey, { id: 'key', ...builder.key({ tooltip: true }) }),
|
|
75
78
|
helper.accessor(
|
|
76
|
-
'
|
|
77
|
-
builder.
|
|
78
|
-
|
|
79
|
-
getGroupingValue: (row) => row.complete === true,
|
|
80
|
-
label: '',
|
|
79
|
+
'name',
|
|
80
|
+
builder.string({
|
|
81
|
+
label: 'Name',
|
|
81
82
|
onUpdate,
|
|
83
|
+
meta: { expand: true },
|
|
84
|
+
footer: (props) => props.table.getRowModel().rows.length,
|
|
82
85
|
}),
|
|
83
86
|
),
|
|
84
|
-
helper.accessor(
|
|
87
|
+
helper.accessor('started', builder.date({ label: 'Started', relative: true, meta: { resizable: true } })),
|
|
85
88
|
helper.accessor(
|
|
86
|
-
'
|
|
87
|
-
builder.
|
|
89
|
+
'count',
|
|
90
|
+
builder.number({
|
|
91
|
+
label: 'Count',
|
|
92
|
+
meta: { resizable: true },
|
|
93
|
+
// TODO(burdon): Sorting.
|
|
94
|
+
getGroupingValue: (row) => (row.count ? (row.count < 2_000 ? 'A' : row.count < 5_000 ? 'B' : 'C') : 'D'),
|
|
95
|
+
}),
|
|
88
96
|
),
|
|
89
97
|
helper.accessor(
|
|
90
98
|
'company',
|
|
91
|
-
builder.
|
|
99
|
+
builder.combobox({
|
|
92
100
|
label: 'Company',
|
|
93
101
|
model: tableStorySelectModel,
|
|
94
102
|
onUpdate,
|
|
95
103
|
}),
|
|
96
104
|
),
|
|
97
|
-
helper.accessor('started', builder.date({ relative: true, meta: { resizable: true } })),
|
|
98
105
|
helper.accessor(
|
|
99
|
-
'
|
|
100
|
-
builder.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
106
|
+
'complete',
|
|
107
|
+
builder.switch({
|
|
108
|
+
// enableGrouping: true,
|
|
109
|
+
getGroupingValue: (row) => row.complete === true,
|
|
110
|
+
label: '',
|
|
111
|
+
onUpdate,
|
|
104
112
|
}),
|
|
105
113
|
),
|
|
106
114
|
helper.accessor('complete', builder.icon({ id: 'done', label: '' })),
|
|
@@ -138,9 +146,6 @@ export default {
|
|
|
138
146
|
fullWidth: {
|
|
139
147
|
control: 'boolean',
|
|
140
148
|
},
|
|
141
|
-
pinToBottom: {
|
|
142
|
-
control: 'boolean',
|
|
143
|
-
},
|
|
144
149
|
grouping: {
|
|
145
150
|
control: 'select',
|
|
146
151
|
options: ['none', 'complete', 'count'],
|
|
@@ -158,9 +163,9 @@ export default {
|
|
|
158
163
|
limited: { key: false, started: false },
|
|
159
164
|
},
|
|
160
165
|
},
|
|
161
|
-
|
|
166
|
+
rowsSelectable: {
|
|
162
167
|
control: 'select',
|
|
163
|
-
options: [
|
|
168
|
+
options: [false, true, 'multi'],
|
|
164
169
|
},
|
|
165
170
|
},
|
|
166
171
|
decorators: [
|
|
@@ -209,25 +214,23 @@ export const Dynamic = {
|
|
|
209
214
|
if (items.length >= 200) {
|
|
210
215
|
clearInterval(interval);
|
|
211
216
|
}
|
|
212
|
-
|
|
213
217
|
return [...items, ...createItems(1)];
|
|
214
218
|
});
|
|
215
219
|
}, 500);
|
|
216
220
|
return () => clearInterval(interval);
|
|
217
221
|
}, []);
|
|
218
|
-
|
|
219
222
|
return (
|
|
220
|
-
<
|
|
221
|
-
{/* prettier-ignore */}
|
|
223
|
+
<AnchoredOverflow.Root classNames='max-bs-[80dvh]'>
|
|
222
224
|
<Table<Item>
|
|
223
|
-
|
|
225
|
+
rowsSelectable='multi'
|
|
226
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
224
227
|
columns={columns()}
|
|
225
228
|
data={items}
|
|
226
229
|
fullWidth
|
|
227
|
-
pinToBottom
|
|
228
230
|
footer
|
|
229
231
|
/>
|
|
230
|
-
|
|
232
|
+
<AnchoredOverflow.Anchor />
|
|
233
|
+
</AnchoredOverflow.Root>
|
|
231
234
|
);
|
|
232
235
|
},
|
|
233
236
|
};
|
|
@@ -240,15 +243,15 @@ export const Editable = {
|
|
|
240
243
|
};
|
|
241
244
|
|
|
242
245
|
return (
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
<Table<Item>
|
|
247
|
+
role='grid'
|
|
248
|
+
rowsSelectable='multi'
|
|
249
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
250
|
+
columns={columns(onUpdate)}
|
|
251
|
+
data={items}
|
|
252
|
+
fullWidth
|
|
253
|
+
border
|
|
254
|
+
/>
|
|
252
255
|
);
|
|
253
256
|
},
|
|
254
257
|
};
|
|
@@ -265,16 +268,14 @@ export const Resizable = {
|
|
|
265
268
|
// };
|
|
266
269
|
|
|
267
270
|
return (
|
|
268
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
/>
|
|
277
|
-
</div>
|
|
271
|
+
<Table<Item>
|
|
272
|
+
rowsSelectable='multi'
|
|
273
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
274
|
+
columns={columns(onUpdate)}
|
|
275
|
+
data={items}
|
|
276
|
+
fullWidth
|
|
277
|
+
// onColumnResize={handleColumnResize}
|
|
278
|
+
/>
|
|
278
279
|
);
|
|
279
280
|
},
|
|
280
281
|
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
6
|
+
import {
|
|
7
|
+
getCoreRowModel,
|
|
8
|
+
getGroupedRowModel,
|
|
9
|
+
useReactTable,
|
|
10
|
+
type ColumnSizingInfoState,
|
|
11
|
+
type ColumnSizingState,
|
|
12
|
+
type GroupingState,
|
|
13
|
+
type RowData,
|
|
14
|
+
type RowSelectionState,
|
|
15
|
+
type OnChangeFn,
|
|
16
|
+
} from '@tanstack/react-table';
|
|
17
|
+
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
import { debounce } from '@dxos/async';
|
|
20
|
+
|
|
21
|
+
import { TableBody } from './TableBody';
|
|
22
|
+
import { TableProvider as UntypedTableProvider, type TypedTableProvider } from './TableContext';
|
|
23
|
+
import { TableFooter } from './TableFooter';
|
|
24
|
+
import { TableHead } from './TableHead';
|
|
25
|
+
import { type TableProps } from './props';
|
|
26
|
+
import { groupTh, tableRoot } from '../../theme';
|
|
27
|
+
|
|
28
|
+
export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
29
|
+
const {
|
|
30
|
+
role,
|
|
31
|
+
data = [],
|
|
32
|
+
columns = [],
|
|
33
|
+
onColumnResize,
|
|
34
|
+
columnVisibility,
|
|
35
|
+
header = true,
|
|
36
|
+
footer,
|
|
37
|
+
rowsSelectable,
|
|
38
|
+
fullWidth,
|
|
39
|
+
debug,
|
|
40
|
+
onDataSelectionChange,
|
|
41
|
+
} = props;
|
|
42
|
+
|
|
43
|
+
const TableProvider = UntypedTableProvider as TypedTableProvider<TData>;
|
|
44
|
+
|
|
45
|
+
// Row selection
|
|
46
|
+
const [rowSelection = {}, directlySetRowSelection] = useControllableState({
|
|
47
|
+
prop: props.rowSelection,
|
|
48
|
+
onChange: props.onRowSelectionChange,
|
|
49
|
+
defaultProp: props.defaultRowSelection,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// TODO(thure): Does @tanstack/react-table really need this intervention? It did seem necessary to enforce single-selection...
|
|
53
|
+
const handleRowSelectionChange = useCallback<OnChangeFn<RowSelectionState>>(
|
|
54
|
+
(updaterOrValue) => {
|
|
55
|
+
const nextRowSelection = typeof updaterOrValue === 'function' ? updaterOrValue(rowSelection) : updaterOrValue;
|
|
56
|
+
if (rowsSelectable === 'multi') {
|
|
57
|
+
directlySetRowSelection(nextRowSelection);
|
|
58
|
+
} else if (rowsSelectable) {
|
|
59
|
+
const nextRowSelectionKey = Object.keys(nextRowSelection).filter((id) => !rowSelection[id])[0];
|
|
60
|
+
directlySetRowSelection(nextRowSelectionKey ? { [nextRowSelectionKey]: true } : {});
|
|
61
|
+
} else {
|
|
62
|
+
directlySetRowSelection({});
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
[rowSelection, directlySetRowSelection],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Resizing
|
|
69
|
+
const [columnSizingInfo, setColumnSizingInfo] = useState<ColumnSizingInfoState>({} as ColumnSizingInfoState);
|
|
70
|
+
const onColumnResizeDebounced = debounce<ColumnSizingState>((info) => onColumnResize?.(info), 500);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (columnSizingInfo.columnSizingStart?.length === 0) {
|
|
73
|
+
onColumnResizeDebounced(table.getState().columnSizing);
|
|
74
|
+
}
|
|
75
|
+
}, [columnSizingInfo]);
|
|
76
|
+
|
|
77
|
+
const [grouping, setGrouping] = useState<GroupingState>(props.grouping ?? []);
|
|
78
|
+
useEffect(() => setGrouping(props.grouping ?? []), [props.grouping]);
|
|
79
|
+
|
|
80
|
+
const table = useReactTable({
|
|
81
|
+
// Data
|
|
82
|
+
meta: {},
|
|
83
|
+
data,
|
|
84
|
+
|
|
85
|
+
// Columns
|
|
86
|
+
columns,
|
|
87
|
+
defaultColumn: {
|
|
88
|
+
size: 256, // Required in order remove default width.
|
|
89
|
+
maxSize: 1024,
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Rows
|
|
93
|
+
getCoreRowModel: getCoreRowModel(),
|
|
94
|
+
|
|
95
|
+
// State
|
|
96
|
+
state: {
|
|
97
|
+
columnVisibility,
|
|
98
|
+
columnSizingInfo,
|
|
99
|
+
rowSelection,
|
|
100
|
+
grouping,
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Grouping
|
|
104
|
+
getGroupedRowModel: getGroupedRowModel(),
|
|
105
|
+
onGroupingChange: setGrouping,
|
|
106
|
+
|
|
107
|
+
// Selection
|
|
108
|
+
...(rowsSelectable === 'multi'
|
|
109
|
+
? { enableMultiRowSelection: true }
|
|
110
|
+
: rowsSelectable
|
|
111
|
+
? { enableRowSelection: true }
|
|
112
|
+
: {}),
|
|
113
|
+
onRowSelectionChange: handleRowSelectionChange,
|
|
114
|
+
|
|
115
|
+
// Resize columns
|
|
116
|
+
columnResizeMode: 'onChange',
|
|
117
|
+
enableColumnResizing: true,
|
|
118
|
+
onColumnSizingInfoChange: setColumnSizingInfo,
|
|
119
|
+
|
|
120
|
+
// Debug
|
|
121
|
+
debugTable: debug,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (onDataSelectionChange) {
|
|
126
|
+
onDataSelectionChange(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
|
|
127
|
+
}
|
|
128
|
+
}, [onDataSelectionChange, rowSelection, table]);
|
|
129
|
+
|
|
130
|
+
// Create additional expansion column if all columns have fixed width.
|
|
131
|
+
const expand = false; // columns.map((column) => column.size).filter(Boolean).length === columns?.length;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<TableProvider
|
|
135
|
+
{...props}
|
|
136
|
+
header={header}
|
|
137
|
+
expand={expand}
|
|
138
|
+
table={table}
|
|
139
|
+
isGrid={role === 'grid' || role === 'treegrid'}
|
|
140
|
+
>
|
|
141
|
+
<table role={role} className={tableRoot(props)} {...(!fullWidth && { style: { width: table.getTotalSize() } })}>
|
|
142
|
+
<TableHead />
|
|
143
|
+
|
|
144
|
+
{grouping.length === 0 && <TableBody rows={table.getRowModel().rows} />}
|
|
145
|
+
|
|
146
|
+
{grouping.length !== 0 &&
|
|
147
|
+
table.getGroupedRowModel().rows.map((row, i) => {
|
|
148
|
+
return (
|
|
149
|
+
<Fragment key={i}>
|
|
150
|
+
{/* TODO(burdon): Customize group header renderer. */}
|
|
151
|
+
<thead>
|
|
152
|
+
<tr>
|
|
153
|
+
{debug && <th />}
|
|
154
|
+
<th
|
|
155
|
+
// TODO(burdon): Calculate row span.
|
|
156
|
+
colSpan={table.getHeaderGroups()[0].headers.length}
|
|
157
|
+
className={groupTh(props)}
|
|
158
|
+
>
|
|
159
|
+
{table.getState().grouping[0]}[{String(row.getGroupingValue(table.getState().grouping[0]))}]
|
|
160
|
+
</th>
|
|
161
|
+
</tr>
|
|
162
|
+
</thead>
|
|
163
|
+
|
|
164
|
+
<TableBody rows={row.subRows} />
|
|
165
|
+
</Fragment>
|
|
166
|
+
);
|
|
167
|
+
})}
|
|
168
|
+
|
|
169
|
+
{footer && <TableFooter />}
|
|
170
|
+
</table>
|
|
171
|
+
|
|
172
|
+
{debug && (
|
|
173
|
+
<pre className='font-mono text-xs text-neutral-500 my-4 p-2 ring'>
|
|
174
|
+
<code>{JSON.stringify(table.getState(), undefined, 2)}</code>
|
|
175
|
+
</pre>
|
|
176
|
+
)}
|
|
177
|
+
</TableProvider>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useArrowNavigationGroup } from '@fluentui/react-tabster';
|
|
6
|
+
import { flexRender, type Row, type RowData } from '@tanstack/react-table';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
import { useTableContext } from './TableContext';
|
|
10
|
+
import { tbodyTr } from '../../theme';
|
|
11
|
+
import { Cell } from '../Cell/Cell';
|
|
12
|
+
|
|
13
|
+
type TableBodyProps<TData extends RowData> = {
|
|
14
|
+
rows: Row<TData>[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const TABLE_BODY_NAME = 'TableBody';
|
|
18
|
+
|
|
19
|
+
const TableBody = <TData extends RowData>({ rows }: TableBodyProps<TData>) => {
|
|
20
|
+
const tableContext = useTableContext<TData>(TABLE_BODY_NAME);
|
|
21
|
+
const { table, keyAccessor, debug, expand, isGrid, onDatumClick, currentDatum } = tableContext;
|
|
22
|
+
const rowSelection = table.getState().rowSelection;
|
|
23
|
+
const domAttributes = useArrowNavigationGroup({ axis: 'grid' });
|
|
24
|
+
const canBeCurrent = !isGrid && !!onDatumClick;
|
|
25
|
+
return (
|
|
26
|
+
<tbody {...(isGrid && domAttributes)}>
|
|
27
|
+
{rows.map((row) => {
|
|
28
|
+
const isCurrent = currentDatum === row.original;
|
|
29
|
+
const isSelected = rowSelection?.[row.id];
|
|
30
|
+
return (
|
|
31
|
+
<tr
|
|
32
|
+
key={keyAccessor ? keyAccessor(row.original) : row.id}
|
|
33
|
+
className={tbodyTr({ isSelected, isCurrent, canBeCurrent })}
|
|
34
|
+
{...(isCurrent && { 'aria-current': 'location' })}
|
|
35
|
+
{...(canBeCurrent && {
|
|
36
|
+
tabIndex: 0,
|
|
37
|
+
onClick: () => {
|
|
38
|
+
onDatumClick(row.original);
|
|
39
|
+
},
|
|
40
|
+
onKeyUp: ({ key }) => {
|
|
41
|
+
if (key === 'Enter' || key === ' ') {
|
|
42
|
+
onDatumClick(row.original);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
})}
|
|
46
|
+
>
|
|
47
|
+
{debug && <td>{row.id}</td>}
|
|
48
|
+
|
|
49
|
+
{row.getVisibleCells().map((cell) => {
|
|
50
|
+
return (
|
|
51
|
+
<Cell key={cell.id} cell={cell}>
|
|
52
|
+
{flexRender(cell.column.columnDef.cell, { className: 'pli-2', ...cell.getContext() })}
|
|
53
|
+
</Cell>
|
|
54
|
+
);
|
|
55
|
+
})}
|
|
56
|
+
|
|
57
|
+
{expand && <td />}
|
|
58
|
+
</tr>
|
|
59
|
+
);
|
|
60
|
+
})}
|
|
61
|
+
</tbody>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
TableBody.displayName = TABLE_BODY_NAME;
|
|
66
|
+
|
|
67
|
+
export { TableBody };
|
|
68
|
+
|
|
69
|
+
export type { TableBodyProps };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import { type RowData } from '@tanstack/react-table';
|
|
7
|
+
import { type ReactNode } from 'react';
|
|
8
|
+
|
|
9
|
+
import { type TableContextValue } from './props';
|
|
10
|
+
|
|
11
|
+
export const TABLE_NAME = 'Table';
|
|
12
|
+
|
|
13
|
+
type CreateContextResult<TData extends RowData> = Readonly<
|
|
14
|
+
[TypedTableProvider<unknown>, (consumerName: string) => TableContextValue<TData>]
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
export type TypedTableProvider<TData extends RowData> = {
|
|
18
|
+
(props: TableContextValue<TData> & { children: ReactNode }): JSX.Element;
|
|
19
|
+
displayName: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const [TableProvider, useUntypedTableContext]: CreateContextResult<unknown> =
|
|
23
|
+
createContext<TableContextValue<unknown>>(TABLE_NAME);
|
|
24
|
+
|
|
25
|
+
export const useTableContext = <TData extends RowData>(consumerName: string) =>
|
|
26
|
+
useUntypedTableContext(consumerName) as TableContextValue<TData>;
|
|
27
|
+
|
|
28
|
+
export { TableProvider };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { flexRender, type RowData } from '@tanstack/react-table';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { useTableContext } from './TableContext';
|
|
9
|
+
import { tfootRoot, tfootTr, tfootTh } from '../../theme';
|
|
10
|
+
|
|
11
|
+
export type TableFooterProps = {};
|
|
12
|
+
|
|
13
|
+
const TABLE_FOOTER_NAME = 'TableFooter';
|
|
14
|
+
|
|
15
|
+
const TableFooter = <TData extends RowData>(props: TableFooterProps) => {
|
|
16
|
+
const tableContext = useTableContext<TData>(TABLE_FOOTER_NAME);
|
|
17
|
+
const { table, expand, debug } = tableContext;
|
|
18
|
+
const footers = table.getFooterGroups();
|
|
19
|
+
return (
|
|
20
|
+
<tfoot className={tfootRoot(props)}>
|
|
21
|
+
{footers.map((footerGroup) => (
|
|
22
|
+
<tr key={footerGroup.id} className={tfootTr(tableContext)}>
|
|
23
|
+
{debug && <th />}
|
|
24
|
+
|
|
25
|
+
{footerGroup.headers.map((footer) => {
|
|
26
|
+
return (
|
|
27
|
+
<th key={footer.id} className={tfootTh(tableContext, footer.column.columnDef.meta?.footer?.classNames)}>
|
|
28
|
+
{footer.isPlaceholder ? null : flexRender(footer.column.columnDef.footer, footer.getContext())}
|
|
29
|
+
</th>
|
|
30
|
+
);
|
|
31
|
+
})}
|
|
32
|
+
|
|
33
|
+
{expand && <th />}
|
|
34
|
+
</tr>
|
|
35
|
+
))}
|
|
36
|
+
</tfoot>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
TableFooter.displayName = TABLE_FOOTER_NAME;
|
|
41
|
+
|
|
42
|
+
export { TableFooter };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { flexRender, type RowData } from '@tanstack/react-table';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
9
|
+
|
|
10
|
+
import { useTableContext } from './TableContext';
|
|
11
|
+
import { theadResizeRoot, theadResizeThumb, theadRoot, theadTh, theadTr } from '../../theme';
|
|
12
|
+
|
|
13
|
+
const TABLE_HEAD_NAME = 'TableHead';
|
|
14
|
+
|
|
15
|
+
type TableHeadProps = {};
|
|
16
|
+
|
|
17
|
+
const TableHead = <TData extends RowData>(_props: TableHeadProps) => {
|
|
18
|
+
const tableContext = useTableContext<TData>(TABLE_HEAD_NAME);
|
|
19
|
+
const headerGroups = tableContext.table.getHeaderGroups();
|
|
20
|
+
const state = tableContext.table.getState();
|
|
21
|
+
return (
|
|
22
|
+
<thead className={theadRoot(tableContext)}>
|
|
23
|
+
{headerGroups.map((headerGroup) => {
|
|
24
|
+
return (
|
|
25
|
+
// Group element to hover resize handles.
|
|
26
|
+
<tr key={headerGroup.id} className={theadTr(tableContext)}>
|
|
27
|
+
{/* TODO(burdon): Calc. width. */}
|
|
28
|
+
{tableContext.debug && (
|
|
29
|
+
<th className='font-mono' style={{ width: 32 }}>
|
|
30
|
+
#
|
|
31
|
+
</th>
|
|
32
|
+
)}
|
|
33
|
+
|
|
34
|
+
{headerGroup.headers.map((header) => {
|
|
35
|
+
const isResizing = header.column.getIsResizing();
|
|
36
|
+
return (
|
|
37
|
+
<th
|
|
38
|
+
key={header.id}
|
|
39
|
+
style={{
|
|
40
|
+
width: header.getSize(),
|
|
41
|
+
}}
|
|
42
|
+
className={theadTh(tableContext, header.column.columnDef.meta?.header?.classNames)}
|
|
43
|
+
>
|
|
44
|
+
{!header || header.isPlaceholder
|
|
45
|
+
? null
|
|
46
|
+
: flexRender(header.column.columnDef.header, header.getContext())}
|
|
47
|
+
|
|
48
|
+
{/*
|
|
49
|
+
* Resize handle.
|
|
50
|
+
* https://codesandbox.io/p/sandbox/github/tanstack/table/tree/main/examples/react/column-sizing
|
|
51
|
+
*/}
|
|
52
|
+
{header.column.columnDef.meta?.resizable && (
|
|
53
|
+
<div
|
|
54
|
+
className={theadResizeRoot(tableContext, isResizing && 'hidden')}
|
|
55
|
+
style={{
|
|
56
|
+
transform: `translateX(${isResizing ? state.columnSizingInfo.deltaOffset : 0}px)`,
|
|
57
|
+
}}
|
|
58
|
+
onMouseDown={header.getResizeHandler()}
|
|
59
|
+
onTouchStart={header.getResizeHandler()}
|
|
60
|
+
>
|
|
61
|
+
<div className={mx(theadResizeThumb(tableContext))} />
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
</th>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
{tableContext.expand && <th />}
|
|
68
|
+
</tr>
|
|
69
|
+
);
|
|
70
|
+
})}
|
|
71
|
+
</thead>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
TableHead.displayName = TABLE_HEAD_NAME;
|
|
76
|
+
|
|
77
|
+
export { TableHead };
|
|
78
|
+
|
|
79
|
+
export type { TableHeadProps };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type RowData, type RowSelectionState, type Table, type VisibilityState } from '@tanstack/react-table';
|
|
6
|
+
|
|
7
|
+
import { type KeyValue, type TableColumnDef } from '../../types';
|
|
8
|
+
|
|
9
|
+
export type TableFlags = Partial<{
|
|
10
|
+
role: 'table' | 'grid' | 'treegrid';
|
|
11
|
+
grouping: string[];
|
|
12
|
+
header: boolean;
|
|
13
|
+
footer: boolean;
|
|
14
|
+
border: boolean;
|
|
15
|
+
fullWidth: boolean;
|
|
16
|
+
debug: boolean;
|
|
17
|
+
expand: boolean;
|
|
18
|
+
rowsSelectable: boolean | 'multi';
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* This is ‘current’ in the `aria-current` sense.
|
|
23
|
+
*/
|
|
24
|
+
export type TableCurrent<TData extends RowData> = Partial<{
|
|
25
|
+
// App state
|
|
26
|
+
currentDatum: TData;
|
|
27
|
+
onDatumClick: (datum: TData) => void;
|
|
28
|
+
}>;
|
|
29
|
+
|
|
30
|
+
export type TableProps<TData extends RowData> = TableFlags &
|
|
31
|
+
TableCurrent<TData> &
|
|
32
|
+
Partial<{
|
|
33
|
+
keyAccessor: KeyValue<TData>;
|
|
34
|
+
data: TData[];
|
|
35
|
+
columns: TableColumnDef<TData>[];
|
|
36
|
+
onColumnResize: (state: Record<string, number>) => void;
|
|
37
|
+
columnVisibility: VisibilityState;
|
|
38
|
+
// Controllable row selection
|
|
39
|
+
rowSelection: RowSelectionState;
|
|
40
|
+
defaultRowSelection: RowSelectionState;
|
|
41
|
+
onRowSelectionChange: (rowSelection: RowSelectionState) => void;
|
|
42
|
+
// Derived from row selection
|
|
43
|
+
onDataSelectionChange: (dataSelection: TData[]) => void;
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
export type { RowSelectionState, VisibilityState, TableColumnDef, KeyValue };
|
|
47
|
+
|
|
48
|
+
export type TableContextValue<TData extends RowData> = TableFlags &
|
|
49
|
+
TableCurrent<TData> &
|
|
50
|
+
Pick<TableProps<TData>, 'keyAccessor'> & {
|
|
51
|
+
table: Table<TData>;
|
|
52
|
+
isGrid: boolean;
|
|
53
|
+
};
|
package/src/components/index.ts
CHANGED