@dxos/react-ui-table 0.4.10-main.0302e13 → 0.4.10-main.05dbd64
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 +775 -384
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/ColumnMenu/ColumnMenu.d.ts +19 -0
- package/dist/types/src/components/ColumnMenu/ColumnMenu.d.ts.map +1 -0
- package/dist/types/src/components/{ColumnMenu.stories.d.ts → ColumnMenu/ColumnMenu.stories.d.ts} +10 -2
- package/dist/types/src/components/ColumnMenu/ColumnMenu.stories.d.ts.map +1 -0
- package/dist/types/src/components/ColumnMenu/ColumnSettingsForm.d.ts +11 -0
- package/dist/types/src/components/ColumnMenu/ColumnSettingsForm.d.ts.map +1 -0
- package/dist/types/src/components/ColumnMenu/index.d.ts +3 -0
- package/dist/types/src/components/ColumnMenu/index.d.ts.map +1 -0
- package/dist/types/src/components/Table/Table.d.ts +17 -4
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +32 -1
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableBody.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableRootContext.d.ts +17 -0
- package/dist/types/src/components/Table/TableRootContext.d.ts.map +1 -0
- package/dist/types/src/components/Table/index.d.ts +1 -0
- package/dist/types/src/components/Table/index.d.ts.map +1 -1
- package/dist/types/src/components/Table/props.d.ts +2 -3
- package/dist/types/src/components/Table/props.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/helpers.d.ts +4 -0
- package/dist/types/src/helpers.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +5 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useColumnSizing.d.ts +12 -0
- package/dist/types/src/hooks/useColumnSizing.d.ts.map +1 -0
- package/dist/types/src/hooks/useColumnSorting.d.ts +9 -0
- package/dist/types/src/hooks/useColumnSorting.d.ts.map +1 -0
- package/dist/types/src/hooks/usePinLastRow.d.ts +3 -0
- package/dist/types/src/hooks/usePinLastRow.d.ts.map +1 -0
- package/dist/types/src/hooks/useRowSelection.d.ts +7 -0
- package/dist/types/src/hooks/useRowSelection.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/schema/SchemaToTable.stories.d.ts +11 -0
- package/dist/types/src/schema/SchemaToTable.stories.d.ts.map +1 -0
- package/dist/types/src/schema/index.d.ts +3 -0
- package/dist/types/src/schema/index.d.ts.map +1 -0
- package/dist/types/src/schema/schema.d.ts +5 -0
- package/dist/types/src/schema/schema.d.ts.map +1 -0
- package/dist/types/src/schema/schema.test.d.ts +2 -0
- package/dist/types/src/schema/schema.test.d.ts.map +1 -0
- package/dist/types/src/schema/schemaToColumns.d.ts +4 -0
- package/dist/types/src/schema/schemaToColumns.d.ts.map +1 -0
- package/dist/types/src/{schema.d.ts → schema/types.d.ts} +1 -1
- package/dist/types/src/schema/types.d.ts.map +1 -0
- package/package.json +22 -16
- package/src/components/ColumnMenu/ColumnMenu.stories.tsx +127 -0
- package/src/components/ColumnMenu/ColumnMenu.tsx +168 -0
- package/src/components/ColumnMenu/ColumnSettingsForm.tsx +214 -0
- package/src/components/ColumnMenu/index.ts +6 -0
- package/src/components/Table/Table.stories.tsx +244 -74
- package/src/components/Table/Table.tsx +79 -96
- package/src/components/Table/TableBody.tsx +6 -1
- package/src/components/Table/TableRootContext.ts +39 -0
- package/src/components/Table/index.ts +1 -0
- package/src/components/Table/props.ts +17 -24
- package/src/components/index.ts +1 -1
- package/src/helpers.tsx +32 -8
- package/src/hooks/index.ts +8 -0
- package/src/hooks/useColumnSizing.ts +45 -0
- package/src/hooks/useColumnSorting.ts +54 -0
- package/src/hooks/usePinLastRow.ts +64 -0
- package/src/hooks/useRowSelection.ts +39 -0
- package/src/index.ts +3 -1
- package/src/schema/SchemaToTable.stories.tsx +71 -0
- package/src/schema/index.ts +6 -0
- package/src/schema/schema.test.ts +148 -0
- package/src/schema/schema.ts +92 -0
- package/src/schema/schemaToColumns.ts +48 -0
- package/src/theme.ts +1 -1
- package/dist/types/src/components/ColumnMenu.d.ts +0 -13
- package/dist/types/src/components/ColumnMenu.d.ts.map +0 -1
- package/dist/types/src/components/ColumnMenu.stories.d.ts.map +0 -1
- package/dist/types/src/components/SortButton.d.ts +0 -8
- package/dist/types/src/components/SortButton.d.ts.map +0 -1
- package/dist/types/src/components/hooks/usePinLastRow.d.ts +0 -3
- package/dist/types/src/components/hooks/usePinLastRow.d.ts.map +0 -1
- package/dist/types/src/schema.d.ts.map +0 -1
- package/src/components/ColumnMenu.stories.tsx +0 -46
- package/src/components/ColumnMenu.tsx +0 -248
- package/src/components/SortButton.tsx +0 -34
- package/src/components/hooks/usePinLastRow.ts +0 -43
- /package/src/{schema.ts → schema/types.ts} +0 -0
|
@@ -5,12 +5,13 @@
|
|
|
5
5
|
import '@dxosTheme';
|
|
6
6
|
|
|
7
7
|
import { Plugs, PlugsConnected } from '@phosphor-icons/react';
|
|
8
|
-
import React, { useCallback, useEffect, useMemo,
|
|
8
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import { create } from '@dxos/echo-schema';
|
|
11
|
+
import { registerSignalRuntime } from '@dxos/echo-signals/react';
|
|
11
12
|
import { PublicKey } from '@dxos/keys';
|
|
12
13
|
import { faker } from '@dxos/random';
|
|
13
|
-
import {
|
|
14
|
+
import { Button, DensityProvider } from '@dxos/react-ui';
|
|
14
15
|
import { withTheme } from '@dxos/storybook-utils';
|
|
15
16
|
import { range } from '@dxos/util';
|
|
16
17
|
|
|
@@ -35,7 +36,7 @@ faker.seed(911);
|
|
|
35
36
|
const createItems = (count: number) =>
|
|
36
37
|
range(count).map(
|
|
37
38
|
() =>
|
|
38
|
-
|
|
39
|
+
create<Item>({
|
|
39
40
|
publicKey: PublicKey.random(),
|
|
40
41
|
name: faker.commerce.productName(),
|
|
41
42
|
count: faker.datatype.boolean({ probability: 0.9 }) ? faker.number.int({ min: 0, max: 10_000 }) : undefined,
|
|
@@ -115,9 +116,19 @@ const makeColumns = (onUpdate?: ValueUpdater<Item, any>): TableColumnDef<Item, a
|
|
|
115
116
|
// Tests
|
|
116
117
|
//
|
|
117
118
|
|
|
119
|
+
const MinimalTable = (props: any) => {
|
|
120
|
+
return (
|
|
121
|
+
<Table.Root>
|
|
122
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
123
|
+
<Table.Table<any> {...props} />
|
|
124
|
+
</Table.Viewport>
|
|
125
|
+
</Table.Root>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
118
129
|
export default {
|
|
119
130
|
title: 'react-ui-table/Table',
|
|
120
|
-
component:
|
|
131
|
+
component: MinimalTable,
|
|
121
132
|
args: {
|
|
122
133
|
header: true,
|
|
123
134
|
keyAccessor: (item: Item) => item.publicKey.toHex(),
|
|
@@ -186,7 +197,6 @@ export const Empty = {
|
|
|
186
197
|
|
|
187
198
|
export const Dynamic = {
|
|
188
199
|
render: () => {
|
|
189
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
190
200
|
const [items, setItems] = useState<Item[]>(createItems(50));
|
|
191
201
|
|
|
192
202
|
useEffect(() => {
|
|
@@ -204,26 +214,27 @@ export const Dynamic = {
|
|
|
204
214
|
const columns = useMemo(() => makeColumns(), []);
|
|
205
215
|
|
|
206
216
|
return (
|
|
207
|
-
<
|
|
208
|
-
<Table
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
<Table.Root>
|
|
218
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
219
|
+
<Table.Table<Item>
|
|
220
|
+
role='grid'
|
|
221
|
+
rowsSelectable='multi'
|
|
222
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
223
|
+
columns={columns}
|
|
224
|
+
data={items}
|
|
225
|
+
fullWidth
|
|
226
|
+
stickyHeader
|
|
227
|
+
border
|
|
228
|
+
pinLastRow
|
|
229
|
+
/>
|
|
230
|
+
</Table.Viewport>
|
|
231
|
+
</Table.Root>
|
|
220
232
|
);
|
|
221
233
|
},
|
|
222
234
|
};
|
|
223
235
|
|
|
224
236
|
export const Editable = {
|
|
225
237
|
render: () => {
|
|
226
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
227
238
|
const [items, setItems] = useState<Item[]>(createItems(200));
|
|
228
239
|
|
|
229
240
|
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
@@ -234,26 +245,27 @@ export const Editable = {
|
|
|
234
245
|
const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
|
|
235
246
|
|
|
236
247
|
return (
|
|
237
|
-
<
|
|
238
|
-
<Table
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
<Table.Root>
|
|
249
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
250
|
+
<Table.Table<Item>
|
|
251
|
+
role='grid'
|
|
252
|
+
rowsSelectable='multi'
|
|
253
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
254
|
+
columns={columns}
|
|
255
|
+
data={items}
|
|
256
|
+
fullWidth
|
|
257
|
+
stickyHeader
|
|
258
|
+
border
|
|
259
|
+
pinLastRow
|
|
260
|
+
/>
|
|
261
|
+
</Table.Viewport>
|
|
262
|
+
</Table.Root>
|
|
250
263
|
);
|
|
251
264
|
},
|
|
252
265
|
};
|
|
253
266
|
|
|
254
267
|
export const PinnedLastRow = {
|
|
255
268
|
render: () => {
|
|
256
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
257
269
|
const [items, setItems] = useState<Item[]>(createItems(200));
|
|
258
270
|
|
|
259
271
|
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
@@ -264,19 +276,75 @@ export const PinnedLastRow = {
|
|
|
264
276
|
const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
|
|
265
277
|
|
|
266
278
|
return (
|
|
267
|
-
<
|
|
268
|
-
<Table
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
279
|
+
<Table.Root>
|
|
280
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
281
|
+
<Table.Table<Item>
|
|
282
|
+
role='grid'
|
|
283
|
+
rowsSelectable='multi'
|
|
284
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
285
|
+
columns={columns}
|
|
286
|
+
data={items}
|
|
287
|
+
fullWidth
|
|
288
|
+
stickyHeader
|
|
289
|
+
border
|
|
290
|
+
pinLastRow
|
|
291
|
+
/>
|
|
292
|
+
</Table.Viewport>
|
|
293
|
+
</Table.Root>
|
|
294
|
+
);
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const InsertDelete = {
|
|
299
|
+
render: () => {
|
|
300
|
+
const [items, setItems] = useState<Item[]>(createItems(10));
|
|
301
|
+
|
|
302
|
+
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
303
|
+
(item, prop, value) => setItems((items) => updateItems(items, item.publicKey, prop, value)),
|
|
304
|
+
[setItems],
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
|
|
308
|
+
|
|
309
|
+
const onInsertFirst = () => {
|
|
310
|
+
setItems((items) => [...createItems(1), ...items]);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const onInsertLast = () => {
|
|
314
|
+
setItems((items) => [...items, ...createItems(1)]);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const onDeleteFirst = () => {
|
|
318
|
+
setItems(([_first, ...rest]) => rest);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const onDeleteLast = () => {
|
|
322
|
+
setItems((items) => [...items.slice(0, items.length - 1)]);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div>
|
|
327
|
+
<div className='flex flex-row gap-2'>
|
|
328
|
+
<Button onClick={onInsertFirst}>Insert first</Button>
|
|
329
|
+
<Button onClick={onInsertLast}>Insert last</Button>
|
|
330
|
+
<Button onClick={onDeleteFirst}>Delete first</Button>
|
|
331
|
+
<Button onClick={onDeleteLast}>Delete last</Button>
|
|
332
|
+
</div>
|
|
333
|
+
<Table.Root>
|
|
334
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
335
|
+
<Table.Table<Item>
|
|
336
|
+
role='grid'
|
|
337
|
+
rowsSelectable='multi'
|
|
338
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
339
|
+
columns={columns}
|
|
340
|
+
data={items}
|
|
341
|
+
fullWidth
|
|
342
|
+
stickyHeader
|
|
343
|
+
border
|
|
344
|
+
pinLastRow
|
|
345
|
+
/>
|
|
346
|
+
</Table.Viewport>
|
|
347
|
+
</Table.Root>
|
|
280
348
|
</div>
|
|
281
349
|
);
|
|
282
350
|
},
|
|
@@ -284,7 +352,6 @@ export const PinnedLastRow = {
|
|
|
284
352
|
|
|
285
353
|
export const Resizable = {
|
|
286
354
|
render: () => {
|
|
287
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
288
355
|
const [items, setItems] = useState<Item[]>(createItems(10));
|
|
289
356
|
|
|
290
357
|
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
@@ -293,25 +360,24 @@ export const Resizable = {
|
|
|
293
360
|
);
|
|
294
361
|
|
|
295
362
|
return (
|
|
296
|
-
<
|
|
297
|
-
<Table
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
</
|
|
363
|
+
<Table.Root>
|
|
364
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
365
|
+
<Table.Table<Item>
|
|
366
|
+
rowsSelectable='multi'
|
|
367
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
368
|
+
columns={makeColumns(onUpdate)}
|
|
369
|
+
data={items}
|
|
370
|
+
fullWidth
|
|
371
|
+
stickyHeader
|
|
372
|
+
/>
|
|
373
|
+
</Table.Viewport>
|
|
374
|
+
</Table.Root>
|
|
308
375
|
);
|
|
309
376
|
},
|
|
310
377
|
};
|
|
311
378
|
|
|
312
379
|
export const TenThousandRows = {
|
|
313
380
|
render: () => {
|
|
314
|
-
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
315
381
|
const [items, setItems] = useState<Item[]>(createItems(10000));
|
|
316
382
|
|
|
317
383
|
const onUpdate: ValueUpdater<Item, any> = useCallback(
|
|
@@ -322,19 +388,123 @@ export const TenThousandRows = {
|
|
|
322
388
|
const columns = useMemo(() => makeColumns(onUpdate), [onUpdate]);
|
|
323
389
|
|
|
324
390
|
return (
|
|
325
|
-
<
|
|
326
|
-
<Table
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
391
|
+
<Table.Root>
|
|
392
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
393
|
+
<Table.Table<Item>
|
|
394
|
+
role='grid'
|
|
395
|
+
rowsSelectable='multi'
|
|
396
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
397
|
+
columns={columns}
|
|
398
|
+
data={items}
|
|
399
|
+
fullWidth
|
|
400
|
+
stickyHeader
|
|
401
|
+
border
|
|
402
|
+
/>{' '}
|
|
403
|
+
</Table.Viewport>
|
|
404
|
+
</Table.Root>
|
|
405
|
+
);
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
registerSignalRuntime();
|
|
410
|
+
const state = create({ items: createItems(10) });
|
|
411
|
+
|
|
412
|
+
export const RealTimeUpdates = {
|
|
413
|
+
args: {
|
|
414
|
+
periodicMutations: true,
|
|
415
|
+
mutationInterval: 1000,
|
|
416
|
+
periodicDeletions: false,
|
|
417
|
+
deletionInterval: 5000,
|
|
418
|
+
periodicInsertions: false,
|
|
419
|
+
insertionInterval: 3000,
|
|
420
|
+
},
|
|
421
|
+
argTypes: {
|
|
422
|
+
mutationInterval: { control: 'number' },
|
|
423
|
+
periodicDeletions: { control: 'boolean' },
|
|
424
|
+
deletionInterval: { control: 'number' },
|
|
425
|
+
periodicInsertions: { control: 'boolean' },
|
|
426
|
+
insertionInterval: { control: 'number' },
|
|
427
|
+
},
|
|
428
|
+
render: ({
|
|
429
|
+
periodicMutations,
|
|
430
|
+
mutationInterval,
|
|
431
|
+
periodicDeletions,
|
|
432
|
+
deletionInterval,
|
|
433
|
+
periodicInsertions,
|
|
434
|
+
insertionInterval,
|
|
435
|
+
}: any) => {
|
|
436
|
+
useEffect(() => {
|
|
437
|
+
if (!periodicMutations) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const interval = setInterval(() => {
|
|
442
|
+
if (state.items.length === 0) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const randomIndex = Math.floor(Math.random() * state.items.length);
|
|
447
|
+
console.log('Mutating row', randomIndex);
|
|
448
|
+
state.items[randomIndex].name = faker.commerce.productName();
|
|
449
|
+
state.items[randomIndex].count = Math.floor(Math.random() * 1000);
|
|
450
|
+
state.items[randomIndex].started = new Date();
|
|
451
|
+
}, mutationInterval);
|
|
452
|
+
|
|
453
|
+
return () => clearInterval(interval);
|
|
454
|
+
}, [periodicMutations, mutationInterval, state.items]);
|
|
455
|
+
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
if (!periodicInsertions) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const interval = setInterval(() => {
|
|
462
|
+
console.log('Inserting...');
|
|
463
|
+
state.items.push(createItems(1)[0]);
|
|
464
|
+
}, insertionInterval);
|
|
465
|
+
|
|
466
|
+
return () => clearInterval(interval);
|
|
467
|
+
}, [periodicInsertions, insertionInterval, state.items]);
|
|
468
|
+
|
|
469
|
+
useEffect(() => {
|
|
470
|
+
if (!periodicDeletions) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const interval = setInterval(() => {
|
|
475
|
+
if (state.items.length === 0) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
console.log('Deleting');
|
|
480
|
+
|
|
481
|
+
// Randomly delete a row from state.items
|
|
482
|
+
const randomIndex = Math.floor(Math.random() * state.items.length);
|
|
483
|
+
state.items.splice(randomIndex, 1);
|
|
484
|
+
}, deletionInterval);
|
|
485
|
+
|
|
486
|
+
return () => clearInterval(interval);
|
|
487
|
+
}, [periodicDeletions, deletionInterval, state.items]);
|
|
488
|
+
|
|
489
|
+
const onUpdate = useCallback((...args: any[]) => {}, []);
|
|
490
|
+
const columns = useMemo(() => makeColumns(onUpdate), []);
|
|
491
|
+
|
|
492
|
+
return (
|
|
493
|
+
<Table.Root>
|
|
494
|
+
<Table.Viewport classNames='fixed inset-0 overflow-auto'>
|
|
495
|
+
<Table.Table<Item>
|
|
496
|
+
role='grid'
|
|
497
|
+
rowsSelectable='multi'
|
|
498
|
+
keyAccessor={(row) => row.publicKey.toHex()}
|
|
499
|
+
columns={columns}
|
|
500
|
+
data={state.items}
|
|
501
|
+
fullWidth
|
|
502
|
+
stickyHeader
|
|
503
|
+
border
|
|
504
|
+
pinLastRow
|
|
505
|
+
/>
|
|
506
|
+
</Table.Viewport>
|
|
507
|
+
</Table.Root>
|
|
338
508
|
);
|
|
339
509
|
},
|
|
340
510
|
};
|
|
@@ -1,35 +1,60 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
|
-
import {
|
|
4
|
+
import { type Primitive } from '@radix-ui/react-primitive';
|
|
5
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
5
6
|
import {
|
|
6
7
|
getCoreRowModel,
|
|
7
8
|
getSortedRowModel,
|
|
8
9
|
useReactTable,
|
|
9
|
-
type ColumnSizingInfoState,
|
|
10
10
|
type GroupingState,
|
|
11
11
|
type RowData,
|
|
12
|
-
type ColumnSizingState,
|
|
13
|
-
type OnChangeFn,
|
|
14
|
-
type RowSelectionState,
|
|
15
12
|
getGroupedRowModel,
|
|
16
13
|
getExpandedRowModel,
|
|
14
|
+
type Row,
|
|
17
15
|
} from '@tanstack/react-table';
|
|
18
|
-
import {
|
|
19
|
-
import React, { Fragment, useCallback, useEffect, useState } from 'react';
|
|
16
|
+
import React, { type ComponentPropsWithoutRef, Fragment, useCallback, useEffect, useState, useContext } from 'react';
|
|
20
17
|
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
18
|
+
import { type ThemedClassName, useDefaultValue } from '@dxos/react-ui';
|
|
19
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
23
20
|
|
|
24
21
|
import { TableBody } from './TableBody';
|
|
25
22
|
import { type TypedTableProvider, TableProvider as UntypedTableProvider, useTableContext } from './TableContext';
|
|
26
23
|
import { TableFooter } from './TableFooter';
|
|
27
24
|
import { TableHead } from './TableHead';
|
|
25
|
+
import { TableRootContext, useTableRootContext } from './TableRootContext';
|
|
28
26
|
import { type TableProps } from './props';
|
|
27
|
+
import { useColumnResizing, usePinLastRow, useRowSelection } from '../../hooks';
|
|
29
28
|
import { groupTh, tableRoot } from '../../theme';
|
|
30
|
-
import { usePinLastRow } from '../hooks/usePinLastRow';
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
type TableRootProps = { children: React.ReactNode };
|
|
31
|
+
|
|
32
|
+
const TableRoot = ({ children }: TableRootProps) => {
|
|
33
|
+
const contextValue = useTableRootContext();
|
|
34
|
+
return <TableRootContext.Provider value={contextValue}>{children}</TableRootContext.Provider>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type TableViewportProps = ThemedClassName<ComponentPropsWithoutRef<typeof Primitive.div>> & {
|
|
38
|
+
asChild?: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const TableViewport = ({ children, classNames, asChild, ...props }: TableViewportProps) => {
|
|
42
|
+
const { scrollContextRef } = useContext(TableRootContext);
|
|
43
|
+
|
|
44
|
+
const classes = mx(classNames);
|
|
45
|
+
|
|
46
|
+
return asChild ? (
|
|
47
|
+
<Slot ref={scrollContextRef} {...props}>
|
|
48
|
+
{children}
|
|
49
|
+
</Slot>
|
|
50
|
+
) : (
|
|
51
|
+
<div role='none' className={classes} ref={scrollContextRef} {...props}>
|
|
52
|
+
{children}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const TablePrimitive = <TData extends RowData>(props: TableProps<TData>) => {
|
|
33
58
|
const {
|
|
34
59
|
role,
|
|
35
60
|
onColumnResize,
|
|
@@ -38,62 +63,26 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
38
63
|
rowsSelectable,
|
|
39
64
|
debug,
|
|
40
65
|
onDataSelectionChange,
|
|
41
|
-
getScrollElement,
|
|
42
66
|
pinLastRow,
|
|
43
67
|
} = props;
|
|
44
68
|
|
|
45
69
|
const columns = useDefaultValue(props.columns, []);
|
|
46
|
-
const
|
|
70
|
+
const incomingData = useDefaultValue(props.data, []);
|
|
47
71
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const [columnsInitialised, setColumnsInitialised] = useState(false);
|
|
51
|
-
const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});
|
|
52
|
-
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (columnsInitialised) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
setColumnSizing(
|
|
59
|
-
columns
|
|
60
|
-
.filter((column) => !!column.size && (column as any).prop !== undefined)
|
|
61
|
-
.reduce<ColumnSizingState>((state, column) => {
|
|
62
|
-
state[(column as any).prop] = column.size!;
|
|
63
|
-
return state;
|
|
64
|
-
}, {}),
|
|
65
|
-
);
|
|
72
|
+
const [data, setData] = useState([...incomingData]);
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
// Reactivity workaround: https://github.com/dxos/dxos/issues/6376.
|
|
75
|
+
const forceUpdate = useCallback(() => setData([...incomingData]), [incomingData]);
|
|
76
|
+
useEffect(() => forceUpdate(), [JSON.stringify(incomingData), forceUpdate]);
|
|
69
77
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
// Notify on column resize.
|
|
73
|
-
const notifyColumnResize = useCallback(() => onColumnResize?.(columnSizing), [onColumnResize, columnSizing]);
|
|
74
|
-
useOnTransition(columnSizingInfo.isResizingColumn, (v) => typeof v === 'string', false, notifyColumnResize);
|
|
78
|
+
const TableProvider = UntypedTableProvider as TypedTableProvider<TData>;
|
|
75
79
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
defaultProp: props.defaultRowSelection,
|
|
80
|
+
const { columnSizing, setColumnSizing, columnSizingInfo, setColumnSizingInfo } = useColumnResizing({
|
|
81
|
+
columns,
|
|
82
|
+
onColumnResize,
|
|
80
83
|
});
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
const handleRowSelectionChange = useCallback<OnChangeFn<RowSelectionState>>(
|
|
84
|
-
(updaterOrValue) => {
|
|
85
|
-
const nextRowSelection = typeof updaterOrValue === 'function' ? updaterOrValue(rowSelection) : updaterOrValue;
|
|
86
|
-
if (rowsSelectable === 'multi') {
|
|
87
|
-
setRowSelection(nextRowSelection);
|
|
88
|
-
} else if (rowsSelectable) {
|
|
89
|
-
const nextRowSelectionKey = Object.keys(nextRowSelection).filter((id) => !rowSelection[id])[0];
|
|
90
|
-
setRowSelection(nextRowSelectionKey ? { [nextRowSelectionKey]: true } : {});
|
|
91
|
-
} else {
|
|
92
|
-
setRowSelection({});
|
|
93
|
-
}
|
|
94
|
-
},
|
|
95
|
-
[rowsSelectable, setRowSelection, rowSelection],
|
|
96
|
-
);
|
|
85
|
+
const { rowSelection, handleRowSelectionChange } = useRowSelection(props);
|
|
97
86
|
|
|
98
87
|
const [grouping, handleGroupingChange] = useState<GroupingState>(props.grouping ?? []);
|
|
99
88
|
useEffect(() => handleGroupingChange(props.grouping ?? []), [handleGroupingChange, props.grouping]);
|
|
@@ -150,15 +139,11 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
150
139
|
onDataSelectionChange?.(Object.keys(rowSelection).map((id) => table.getRowModel().rowsById[id].original));
|
|
151
140
|
}, [onDataSelectionChange, rowSelection, table]);
|
|
152
141
|
|
|
153
|
-
usePinLastRow(pinLastRow, table, data
|
|
142
|
+
usePinLastRow(pinLastRow, table, data);
|
|
154
143
|
|
|
155
144
|
// Create additional expansion column if all columns have fixed width.
|
|
156
145
|
const expand = false; // columns.map((column) => column.size).filter(Boolean).length === columns?.length;
|
|
157
146
|
|
|
158
|
-
if (!getScrollElement) {
|
|
159
|
-
log.warn('Table: getScrollElement is not set. This is required for virtualized tables.');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
147
|
return (
|
|
163
148
|
<TableProvider
|
|
164
149
|
{...props}
|
|
@@ -167,22 +152,18 @@ export const Table = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
167
152
|
expand={expand}
|
|
168
153
|
isGrid={role === 'grid' || role === 'treegrid'}
|
|
169
154
|
>
|
|
170
|
-
<TableImpl<TData> debug={false}
|
|
155
|
+
<TableImpl<TData> debug={false} {...props} />
|
|
171
156
|
</TableProvider>
|
|
172
157
|
);
|
|
173
158
|
};
|
|
174
159
|
|
|
160
|
+
// TODO(Zan): Smush this into the Table component.
|
|
175
161
|
/**
|
|
176
162
|
* Pure implementation of table outside of context set-up.
|
|
177
163
|
*/
|
|
178
164
|
const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
|
|
179
|
-
const { debug, classNames,
|
|
180
|
-
const { table } = useTableContext();
|
|
181
|
-
|
|
182
|
-
const centerRows = table.getCenterRows();
|
|
183
|
-
const bottomRows = table.getBottomRows();
|
|
184
|
-
|
|
185
|
-
const rows = [...centerRows, ...bottomRows];
|
|
165
|
+
const { debug, classNames, role, footer, grouping, fullWidth } = props;
|
|
166
|
+
const { table } = useTableContext<TData>();
|
|
186
167
|
|
|
187
168
|
if (debug) {
|
|
188
169
|
return (
|
|
@@ -192,9 +173,10 @@ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
192
173
|
);
|
|
193
174
|
}
|
|
194
175
|
|
|
195
|
-
const virtualisable = getScrollElement !== undefined;
|
|
196
176
|
const isResizingColumn = table.getState().columnSizingInfo.isResizingColumn;
|
|
197
177
|
|
|
178
|
+
const TableComponent = isResizingColumn ? MemoizedVirtualisedTableContent : VirtualizedTableContent;
|
|
179
|
+
|
|
198
180
|
return (
|
|
199
181
|
<table
|
|
200
182
|
role={role}
|
|
@@ -203,39 +185,34 @@ const TableImpl = <TData extends RowData>(props: TableProps<TData>) => {
|
|
|
203
185
|
>
|
|
204
186
|
<TableHead />
|
|
205
187
|
|
|
206
|
-
{grouping?.length !== 0 ?
|
|
207
|
-
virtualisable ? (
|
|
208
|
-
isResizingColumn ? (
|
|
209
|
-
<MemoizedVirtualisedTableContent getScrollElement={getScrollElement} />
|
|
210
|
-
) : (
|
|
211
|
-
<VirtualizedTableContent getScrollElement={getScrollElement} />
|
|
212
|
-
)
|
|
213
|
-
) : (
|
|
214
|
-
<TableBody rows={rows} />
|
|
215
|
-
)
|
|
216
|
-
) : (
|
|
217
|
-
<GroupedTableContent />
|
|
218
|
-
)}
|
|
188
|
+
{grouping?.length !== 0 ? <TableComponent /> : <GroupedTableContent />}
|
|
219
189
|
|
|
220
190
|
{footer && <TableFooter />}
|
|
221
191
|
</table>
|
|
222
192
|
);
|
|
223
193
|
};
|
|
224
194
|
|
|
225
|
-
const VirtualizedTableContent = ({
|
|
226
|
-
getScrollElement,
|
|
227
|
-
}: Pick<VirtualizerOptions<Element, Element>, 'getScrollElement'>) => {
|
|
195
|
+
const VirtualizedTableContent = () => {
|
|
228
196
|
const { table } = useTableContext();
|
|
197
|
+
const { virtualizer, dispatch } = useContext(TableRootContext);
|
|
229
198
|
|
|
230
199
|
const centerRows = table.getCenterRows();
|
|
231
|
-
const pinnedRows = table.getBottomRows();
|
|
232
200
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
dispatch({ type: 'updateTableCount', count: centerRows.length });
|
|
203
|
+
}, [centerRows.length]);
|
|
204
|
+
|
|
205
|
+
let pinnedRows = [] as Row<unknown>[];
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
// TODO(zan): Work out how to sync the row pinning with rendering
|
|
209
|
+
// This is a hack because the first call to this function after a row is deleted will throw an error
|
|
210
|
+
// because the row index is no longer in the table
|
|
211
|
+
// The pin last row hook updates the pinned row key after the row is deleted
|
|
212
|
+
pinnedRows = table.getBottomRows();
|
|
213
|
+
} catch (_) {} // Ignore error
|
|
214
|
+
|
|
215
|
+
const { getTotalSize, getVirtualItems } = virtualizer;
|
|
239
216
|
|
|
240
217
|
const virtualRows = getVirtualItems();
|
|
241
218
|
const totalSize = getTotalSize();
|
|
@@ -246,7 +223,7 @@ const VirtualizedTableContent = ({
|
|
|
246
223
|
const rowsToRender = [...virtualRows.map((virtualRow) => centerRows[virtualRow.index]), ...pinnedRows];
|
|
247
224
|
|
|
248
225
|
return (
|
|
249
|
-
|
|
226
|
+
<Fragment>
|
|
250
227
|
{paddingTop > 0 && (
|
|
251
228
|
<tbody role='none'>
|
|
252
229
|
<tr role='none'>
|
|
@@ -262,7 +239,7 @@ const VirtualizedTableContent = ({
|
|
|
262
239
|
</tr>
|
|
263
240
|
</tbody>
|
|
264
241
|
)}
|
|
265
|
-
|
|
242
|
+
</Fragment>
|
|
266
243
|
);
|
|
267
244
|
};
|
|
268
245
|
|
|
@@ -299,3 +276,9 @@ const GroupedTableContent = () => {
|
|
|
299
276
|
</>
|
|
300
277
|
);
|
|
301
278
|
};
|
|
279
|
+
|
|
280
|
+
export const Table = {
|
|
281
|
+
Root: TableRoot,
|
|
282
|
+
Table: TablePrimitive,
|
|
283
|
+
Viewport: TableViewport,
|
|
284
|
+
};
|