@dxos/react-ui-table 0.3.7 → 0.3.8-main.0ab6c73

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.
Files changed (56) hide show
  1. package/dist/lib/browser/index.mjs +546 -475
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/Cell/Cell.d.ts +12 -0
  5. package/dist/types/src/components/Cell/Cell.d.ts.map +1 -0
  6. package/dist/types/src/components/Cell/CellCombobox.d.ts +12 -0
  7. package/dist/types/src/components/Cell/CellCombobox.d.ts.map +1 -0
  8. package/dist/types/src/components/Cell/index.d.ts +2 -0
  9. package/dist/types/src/components/Cell/index.d.ts.map +1 -0
  10. package/dist/types/src/components/Table/Table.d.ts +3 -0
  11. package/dist/types/src/components/Table/Table.d.ts.map +1 -0
  12. package/dist/types/src/components/{Table.stories.d.ts → Table/Table.stories.d.ts} +4 -7
  13. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -0
  14. package/dist/types/src/components/Table/TableBody.d.ts +11 -0
  15. package/dist/types/src/components/Table/TableBody.d.ts.map +1 -0
  16. package/dist/types/src/components/Table/TableContext.d.ts +14 -0
  17. package/dist/types/src/components/Table/TableContext.d.ts.map +1 -0
  18. package/dist/types/src/components/Table/TableFooter.d.ts +7 -0
  19. package/dist/types/src/components/Table/TableFooter.d.ts.map +1 -0
  20. package/dist/types/src/components/Table/TableHead.d.ts +8 -0
  21. package/dist/types/src/components/Table/TableHead.d.ts.map +1 -0
  22. package/dist/types/src/components/Table/index.d.ts +3 -0
  23. package/dist/types/src/components/Table/index.d.ts.map +1 -0
  24. package/dist/types/src/components/Table/props.d.ts +37 -0
  25. package/dist/types/src/components/Table/props.d.ts.map +1 -0
  26. package/dist/types/src/components/index.d.ts +1 -0
  27. package/dist/types/src/components/index.d.ts.map +1 -1
  28. package/dist/types/src/helpers.d.ts +20 -14
  29. package/dist/types/src/helpers.d.ts.map +1 -1
  30. package/dist/types/src/theme.d.ts +32 -33
  31. package/dist/types/src/theme.d.ts.map +1 -1
  32. package/dist/types/src/translations.d.ts +10 -0
  33. package/dist/types/src/translations.d.ts.map +1 -0
  34. package/dist/types/src/types.d.ts +13 -10
  35. package/dist/types/src/types.d.ts.map +1 -1
  36. package/package.json +12 -10
  37. package/src/components/Cell/Cell.tsx +43 -0
  38. package/src/components/Cell/CellCombobox.tsx +97 -0
  39. package/src/components/Cell/index.ts +5 -0
  40. package/src/components/{Table.stories.tsx → Table/Table.stories.tsx} +51 -51
  41. package/src/components/Table/Table.tsx +179 -0
  42. package/src/components/Table/TableBody.tsx +69 -0
  43. package/src/components/Table/TableContext.tsx +28 -0
  44. package/src/components/Table/TableFooter.tsx +42 -0
  45. package/src/components/Table/TableHead.tsx +79 -0
  46. package/src/components/Table/index.ts +6 -0
  47. package/src/components/Table/props.ts +53 -0
  48. package/src/components/index.ts +1 -0
  49. package/src/helpers.tsx +250 -282
  50. package/src/theme.ts +95 -63
  51. package/src/translations.ts +15 -0
  52. package/src/types.ts +15 -10
  53. package/dist/types/src/components/Table.d.ts +0 -29
  54. package/dist/types/src/components/Table.d.ts.map +0 -1
  55. package/dist/types/src/components/Table.stories.d.ts.map +0 -1
  56. 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 SelectQueryModel, type ValueUpdater } from '../helpers';
18
- import { type TableColumnDef } from '../types';
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.
@@ -29,7 +29,6 @@ type Item = {
29
29
  complete?: boolean;
30
30
  };
31
31
 
32
- // TODO(thure): …y’all okay?
33
32
  faker.seed(911);
34
33
 
35
34
  const createItems = (count: number) =>
@@ -59,7 +58,7 @@ const tableStorySelectItems: Record<string, Item> = range(128).reduce((acc: Reco
59
58
 
60
59
  const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
61
60
 
62
- const tableStorySelectModel: SelectQueryModel<Item> = {
61
+ const tableStorySelectModel: SearchListQueryModel<Item> = {
63
62
  getId: (object) => object?.publicKey?.toHex() ?? 'never',
64
63
  getText: (object) => object?.name ?? 'never',
65
64
  query: async (search: string) => {
@@ -71,36 +70,44 @@ const tableStorySelectModel: SelectQueryModel<Item> = {
71
70
  };
72
71
 
73
72
  const { helper, builder } = createColumnBuilder<Item>();
73
+
74
74
  const columns = (onUpdate?: ValueUpdater<Item, any>): TableColumnDef<Item, any>[] => [
75
+ helper.display(builder.selectRow()),
76
+ helper.accessor((item) => item.publicKey, { id: 'key', ...builder.key({ tooltip: true }) }),
75
77
  helper.accessor(
76
- 'complete',
77
- builder.checkbox({
78
- // enableGrouping: true,
79
- getGroupingValue: (row) => row.complete === true,
80
- label: '',
78
+ 'name',
79
+ builder.string({
80
+ label: 'Name',
81
81
  onUpdate,
82
+ meta: { expand: true },
83
+ footer: (props) => props.table.getRowModel().rows.length,
82
84
  }),
83
85
  ),
84
- helper.accessor((item) => item.publicKey, { id: 'key', ...builder.key({ tooltip: true }) }),
86
+ helper.accessor('started', builder.date({ label: 'Started', relative: true, meta: { resizable: true } })),
85
87
  helper.accessor(
86
- 'name',
87
- builder.string({ onUpdate, meta: { expand: true }, footer: (props) => props.table.getRowModel().rows.length }),
88
+ 'count',
89
+ builder.number({
90
+ label: 'Count',
91
+ meta: { resizable: true },
92
+ // TODO(burdon): Sorting.
93
+ getGroupingValue: (row) => (row.count ? (row.count < 2_000 ? 'A' : row.count < 5_000 ? 'B' : 'C') : 'D'),
94
+ }),
88
95
  ),
89
96
  helper.accessor(
90
97
  'company',
91
- builder.select({
98
+ builder.combobox({
92
99
  label: 'Company',
93
100
  model: tableStorySelectModel,
94
101
  onUpdate,
95
102
  }),
96
103
  ),
97
- helper.accessor('started', builder.date({ relative: true, meta: { resizable: true } })),
98
104
  helper.accessor(
99
- 'count',
100
- builder.number({
101
- meta: { resizable: true },
102
- // TODO(burdon): Sorting.
103
- getGroupingValue: (row) => (row.count ? (row.count < 2_000 ? 'A' : row.count < 5_000 ? 'B' : 'C') : 'D'),
105
+ 'complete',
106
+ builder.switch({
107
+ // enableGrouping: true,
108
+ getGroupingValue: (row) => row.complete === true,
109
+ label: '',
110
+ onUpdate,
104
111
  }),
105
112
  ),
106
113
  helper.accessor('complete', builder.icon({ id: 'done', label: '' })),
@@ -138,9 +145,6 @@ export default {
138
145
  fullWidth: {
139
146
  control: 'boolean',
140
147
  },
141
- pinToBottom: {
142
- control: 'boolean',
143
- },
144
148
  grouping: {
145
149
  control: 'select',
146
150
  options: ['none', 'complete', 'count'],
@@ -158,9 +162,9 @@ export default {
158
162
  limited: { key: false, started: false },
159
163
  },
160
164
  },
161
- select: {
165
+ rowsSelectable: {
162
166
  control: 'select',
163
- options: ['single', 'single-toggle', 'multiple', 'multiple-toggle'],
167
+ options: [false, true, 'multi'],
164
168
  },
165
169
  },
166
170
  decorators: [
@@ -209,25 +213,23 @@ export const Dynamic = {
209
213
  if (items.length >= 200) {
210
214
  clearInterval(interval);
211
215
  }
212
-
213
216
  return [...items, ...createItems(1)];
214
217
  });
215
218
  }, 500);
216
219
  return () => clearInterval(interval);
217
220
  }, []);
218
-
219
221
  return (
220
- <div className='flex grow overflow-hidden'>
221
- {/* prettier-ignore */}
222
+ <AnchoredOverflow.Root classNames='max-bs-[80dvh]'>
222
223
  <Table<Item>
223
- keyAccessor={row => row.publicKey.toHex()}
224
+ rowsSelectable='multi'
225
+ keyAccessor={(row) => row.publicKey.toHex()}
224
226
  columns={columns()}
225
227
  data={items}
226
228
  fullWidth
227
- pinToBottom
228
229
  footer
229
230
  />
230
- </div>
231
+ <AnchoredOverflow.Anchor />
232
+ </AnchoredOverflow.Root>
231
233
  );
232
234
  },
233
235
  };
@@ -240,15 +242,15 @@ export const Editable = {
240
242
  };
241
243
 
242
244
  return (
243
- <div className='flex grow overflow-hidden'>
244
- {/* prettier-ignore */}
245
- <Table<Item>
246
- keyAccessor={row => row.publicKey.toHex()}
247
- columns={columns(onUpdate)}
248
- data={items}
249
- fullWidth
250
- />
251
- </div>
245
+ <Table<Item>
246
+ role='grid'
247
+ rowsSelectable='multi'
248
+ keyAccessor={(row) => row.publicKey.toHex()}
249
+ columns={columns(onUpdate)}
250
+ data={items}
251
+ fullWidth
252
+ border
253
+ />
252
254
  );
253
255
  },
254
256
  };
@@ -265,16 +267,14 @@ export const Resizable = {
265
267
  // };
266
268
 
267
269
  return (
268
- <div className='flex grow overflow-hidden'>
269
- {/* prettier-ignore */}
270
- <Table<Item>
271
- keyAccessor={row => row.publicKey.toHex()}
272
- columns={columns(onUpdate)}
273
- data={items}
274
- fullWidth
275
- // onColumnResize={handleColumnResize}
276
- />
277
- </div>
270
+ <Table<Item>
271
+ rowsSelectable='multi'
272
+ keyAccessor={(row) => row.publicKey.toHex()}
273
+ columns={columns(onUpdate)}
274
+ data={items}
275
+ fullWidth
276
+ // onColumnResize={handleColumnResize}
277
+ />
278
278
  );
279
279
  },
280
280
  };
@@ -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,6 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ export * from './Table';
6
+ export * from './props';
@@ -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
+ };
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './ColumnMenu';
6
6
  export * from './Table';
7
+ export * from './Cell';