@dxos/react-ui-table 0.8.1-staging.5be625a → 0.8.1-staging.9eaf14f

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 (172) hide show
  1. package/dist/lib/browser/{chunk-5MKYB7NF.mjs → chunk-YWNRUF4J.mjs} +7 -3
  2. package/dist/lib/browser/{chunk-5MKYB7NF.mjs.map → chunk-YWNRUF4J.mjs.map} +3 -3
  3. package/dist/lib/browser/index.mjs +295 -97
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/types/index.mjs +1 -1
  7. package/dist/lib/node/{chunk-NRFKBRPV.cjs → chunk-TGG6OZ54.cjs} +10 -6
  8. package/dist/lib/node/{chunk-NRFKBRPV.cjs.map → chunk-TGG6OZ54.cjs.map} +3 -3
  9. package/dist/lib/node/index.cjs +293 -93
  10. package/dist/lib/node/index.cjs.map +3 -3
  11. package/dist/lib/node/meta.json +1 -1
  12. package/dist/lib/node/types/index.cjs +3 -3
  13. package/dist/lib/node/types/index.cjs.map +1 -1
  14. package/dist/lib/node-esm/{chunk-SU4D55KI.mjs → chunk-KKLP54L3.mjs} +7 -3
  15. package/dist/lib/node-esm/{chunk-SU4D55KI.mjs.map → chunk-KKLP54L3.mjs.map} +3 -3
  16. package/dist/lib/node-esm/index.mjs +295 -97
  17. package/dist/lib/node-esm/index.mjs.map +3 -3
  18. package/dist/lib/node-esm/meta.json +1 -1
  19. package/dist/lib/node-esm/types/index.mjs +1 -1
  20. package/dist/types/src/components/Table/DynamicTable.d.ts +15 -10
  21. package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
  22. package/dist/types/src/components/Table/DynamicTable.stories.d.ts +10 -0
  23. package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -0
  24. package/dist/types/src/components/Table/Table.d.ts +1 -1
  25. package/dist/types/src/components/Table/Table.d.ts.map +1 -1
  26. package/dist/types/src/components/Table/Table.stories.d.ts +2 -3
  27. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Table/relations.stories.d.ts +9 -0
  29. package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -0
  30. package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
  31. package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/testing.d.ts.map +1 -1
  33. package/dist/types/src/hooks/useTableModel.d.ts +6 -10
  34. package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
  35. package/dist/types/src/model/index.d.ts +1 -0
  36. package/dist/types/src/model/index.d.ts.map +1 -1
  37. package/dist/types/src/model/selection-model.d.ts +6 -3
  38. package/dist/types/src/model/selection-model.d.ts.map +1 -1
  39. package/dist/types/src/model/table-model.d.ts +13 -14
  40. package/dist/types/src/model/table-model.d.ts.map +1 -1
  41. package/dist/types/src/model/table-presentation.d.ts +4 -4
  42. package/dist/types/src/model/table-presentation.d.ts.map +1 -1
  43. package/dist/types/src/model/table-sorting.d.ts +2 -2
  44. package/dist/types/src/model/table-sorting.d.ts.map +1 -1
  45. package/dist/types/src/types/table.d.ts +2 -1
  46. package/dist/types/src/types/table.d.ts.map +1 -1
  47. package/dist/types/src/util/dynamic-table.d.ts +21 -30
  48. package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
  49. package/dist/types/src/util/table-controls.d.ts +2 -2
  50. package/dist/types/src/util/table-controls.d.ts.map +1 -1
  51. package/dist/types/src/util/table.d.ts.map +1 -1
  52. package/package.json +33 -32
  53. package/src/components/Table/DynamicTable.stories.tsx +155 -0
  54. package/src/components/Table/DynamicTable.tsx +46 -28
  55. package/src/components/Table/Table.stories.tsx +129 -125
  56. package/src/components/Table/Table.tsx +51 -26
  57. package/src/components/Table/relations.stories.tsx +113 -0
  58. package/src/components/TableCellEditor/TableCellEditor.stories.tsx +8 -3
  59. package/src/components/TableCellEditor/TableCellEditor.tsx +102 -4
  60. package/src/components/testing.ts +0 -1
  61. package/src/hooks/useTableModel.ts +11 -11
  62. package/src/model/index.ts +1 -0
  63. package/src/model/selection-model.ts +12 -2
  64. package/src/model/table-model.test.ts +1 -1
  65. package/src/model/table-model.ts +27 -18
  66. package/src/model/table-presentation.test.ts +1 -1
  67. package/src/model/table-presentation.ts +65 -11
  68. package/src/model/table-sorting.ts +2 -2
  69. package/src/playwright/smoke.spec.ts +2 -3
  70. package/src/types/table.ts +6 -5
  71. package/src/util/dynamic-table.ts +57 -25
  72. package/src/util/table-controls.ts +4 -4
  73. package/src/util/table.ts +2 -1
  74. package/dist/lib/browser/deprecated/index.mjs +0 -1483
  75. package/dist/lib/browser/deprecated/index.mjs.map +0 -7
  76. package/dist/lib/node/deprecated/index.cjs +0 -1511
  77. package/dist/lib/node/deprecated/index.cjs.map +0 -7
  78. package/dist/lib/node-esm/deprecated/index.mjs +0 -1485
  79. package/dist/lib/node-esm/deprecated/index.mjs.map +0 -7
  80. package/dist/types/src/deprecated/components/Cell/Cell.d.ts +0 -12
  81. package/dist/types/src/deprecated/components/Cell/Cell.d.ts.map +0 -1
  82. package/dist/types/src/deprecated/components/Cell/CellCombobox.d.ts +0 -13
  83. package/dist/types/src/deprecated/components/Cell/CellCombobox.d.ts.map +0 -1
  84. package/dist/types/src/deprecated/components/Cell/index.d.ts +0 -2
  85. package/dist/types/src/deprecated/components/Cell/index.d.ts.map +0 -1
  86. package/dist/types/src/deprecated/components/ColumnMenu/ColumnMenu.d.ts +0 -23
  87. package/dist/types/src/deprecated/components/ColumnMenu/ColumnMenu.d.ts.map +0 -1
  88. package/dist/types/src/deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts +0 -36
  89. package/dist/types/src/deprecated/components/ColumnMenu/ColumnMenu.stories.d.ts.map +0 -1
  90. package/dist/types/src/deprecated/components/ColumnMenu/ColumnSettings.d.ts +0 -15
  91. package/dist/types/src/deprecated/components/ColumnMenu/ColumnSettings.d.ts.map +0 -1
  92. package/dist/types/src/deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts +0 -84
  93. package/dist/types/src/deprecated/components/ColumnMenu/ColumnSettings.stories.d.ts.map +0 -1
  94. package/dist/types/src/deprecated/components/ColumnMenu/index.d.ts +0 -3
  95. package/dist/types/src/deprecated/components/ColumnMenu/index.d.ts.map +0 -1
  96. package/dist/types/src/deprecated/components/Table/Table.d.ts +0 -20
  97. package/dist/types/src/deprecated/components/Table/Table.d.ts.map +0 -1
  98. package/dist/types/src/deprecated/components/Table/Table.stories.d.ts +0 -116
  99. package/dist/types/src/deprecated/components/Table/Table.stories.d.ts.map +0 -1
  100. package/dist/types/src/deprecated/components/Table/TableBody.d.ts +0 -12
  101. package/dist/types/src/deprecated/components/Table/TableBody.d.ts.map +0 -1
  102. package/dist/types/src/deprecated/components/Table/TableContext.d.ts +0 -13
  103. package/dist/types/src/deprecated/components/Table/TableContext.d.ts.map +0 -1
  104. package/dist/types/src/deprecated/components/Table/TableHead.d.ts +0 -9
  105. package/dist/types/src/deprecated/components/Table/TableHead.d.ts.map +0 -1
  106. package/dist/types/src/deprecated/components/Table/TableRootContext.d.ts +0 -17
  107. package/dist/types/src/deprecated/components/Table/TableRootContext.d.ts.map +0 -1
  108. package/dist/types/src/deprecated/components/Table/index.d.ts +0 -5
  109. package/dist/types/src/deprecated/components/Table/index.d.ts.map +0 -1
  110. package/dist/types/src/deprecated/components/Table/props.d.ts +0 -42
  111. package/dist/types/src/deprecated/components/Table/props.d.ts.map +0 -1
  112. package/dist/types/src/deprecated/components/index.d.ts +0 -4
  113. package/dist/types/src/deprecated/components/index.d.ts.map +0 -1
  114. package/dist/types/src/deprecated/helpers.d.ts +0 -91
  115. package/dist/types/src/deprecated/helpers.d.ts.map +0 -1
  116. package/dist/types/src/deprecated/hooks/index.d.ts +0 -5
  117. package/dist/types/src/deprecated/hooks/index.d.ts.map +0 -1
  118. package/dist/types/src/deprecated/hooks/useColumnSizing.d.ts +0 -12
  119. package/dist/types/src/deprecated/hooks/useColumnSizing.d.ts.map +0 -1
  120. package/dist/types/src/deprecated/hooks/useColumnSorting.d.ts +0 -15
  121. package/dist/types/src/deprecated/hooks/useColumnSorting.d.ts.map +0 -1
  122. package/dist/types/src/deprecated/hooks/usePinLastRow.d.ts +0 -3
  123. package/dist/types/src/deprecated/hooks/usePinLastRow.d.ts.map +0 -1
  124. package/dist/types/src/deprecated/hooks/useRowSelection.d.ts +0 -7
  125. package/dist/types/src/deprecated/hooks/useRowSelection.d.ts.map +0 -1
  126. package/dist/types/src/deprecated/index.d.ts +0 -8
  127. package/dist/types/src/deprecated/index.d.ts.map +0 -1
  128. package/dist/types/src/deprecated/schema/Schema.stories.d.ts +0 -11
  129. package/dist/types/src/deprecated/schema/Schema.stories.d.ts.map +0 -1
  130. package/dist/types/src/deprecated/schema/column-utils.d.ts +0 -7
  131. package/dist/types/src/deprecated/schema/column-utils.d.ts.map +0 -1
  132. package/dist/types/src/deprecated/schema/index.d.ts +0 -3
  133. package/dist/types/src/deprecated/schema/index.d.ts.map +0 -1
  134. package/dist/types/src/deprecated/schema/types.d.ts +0 -27
  135. package/dist/types/src/deprecated/schema/types.d.ts.map +0 -1
  136. package/dist/types/src/deprecated/theme.d.ts +0 -29
  137. package/dist/types/src/deprecated/theme.d.ts.map +0 -1
  138. package/dist/types/src/deprecated/translations.d.ts +0 -21
  139. package/dist/types/src/deprecated/translations.d.ts.map +0 -1
  140. package/dist/types/src/deprecated/types.d.ts +0 -26
  141. package/dist/types/src/deprecated/types.d.ts.map +0 -1
  142. package/src/deprecated/components/Cell/Cell.tsx +0 -88
  143. package/src/deprecated/components/Cell/CellCombobox.tsx +0 -103
  144. package/src/deprecated/components/Cell/index.ts +0 -5
  145. package/src/deprecated/components/ColumnMenu/ColumnMenu.stories.tsx +0 -129
  146. package/src/deprecated/components/ColumnMenu/ColumnMenu.tsx +0 -182
  147. package/src/deprecated/components/ColumnMenu/ColumnSettings.stories.tsx +0 -108
  148. package/src/deprecated/components/ColumnMenu/ColumnSettings.tsx +0 -207
  149. package/src/deprecated/components/ColumnMenu/index.ts +0 -6
  150. package/src/deprecated/components/Table/Table.stories.tsx +0 -508
  151. package/src/deprecated/components/Table/Table.tsx +0 -301
  152. package/src/deprecated/components/Table/TableBody.tsx +0 -82
  153. package/src/deprecated/components/Table/TableContext.tsx +0 -34
  154. package/src/deprecated/components/Table/TableHead.tsx +0 -78
  155. package/src/deprecated/components/Table/TableRootContext.ts +0 -39
  156. package/src/deprecated/components/Table/index.ts +0 -8
  157. package/src/deprecated/components/Table/props.ts +0 -60
  158. package/src/deprecated/components/index.ts +0 -7
  159. package/src/deprecated/helpers.tsx +0 -526
  160. package/src/deprecated/hooks/index.ts +0 -8
  161. package/src/deprecated/hooks/useColumnSizing.ts +0 -46
  162. package/src/deprecated/hooks/useColumnSorting.ts +0 -64
  163. package/src/deprecated/hooks/usePinLastRow.ts +0 -47
  164. package/src/deprecated/hooks/useRowSelection.ts +0 -40
  165. package/src/deprecated/index.ts +0 -12
  166. package/src/deprecated/schema/Schema.stories.tsx +0 -59
  167. package/src/deprecated/schema/column-utils.ts +0 -54
  168. package/src/deprecated/schema/index.ts +0 -6
  169. package/src/deprecated/schema/types.ts +0 -40
  170. package/src/deprecated/theme.ts +0 -82
  171. package/src/deprecated/translations.ts +0 -26
  172. package/src/deprecated/types.ts +0 -38
@@ -1,526 +0,0 @@
1
- //
2
- // Copyright 2023 DXOS.org
3
- //
4
-
5
- import { useFocusFinders } from '@fluentui/react-tabster';
6
- import { Check, ClipboardText, type Icon, X } from '@phosphor-icons/react';
7
- import {
8
- createColumnHelper,
9
- type ColumnDef,
10
- type ColumnMeta,
11
- type RowData,
12
- type ColumnHelper,
13
- type CellContext,
14
- } from '@tanstack/react-table';
15
- import { format } from 'date-fns/format';
16
- import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
17
- import React, { useCallback, useEffect, useRef, useState } from 'react';
18
-
19
- import { type PublicKey } from '@dxos/keys';
20
- import { log } from '@dxos/log';
21
- import { type ClassNameValue, Input, Tooltip } from '@dxos/react-ui';
22
- import { defaultSizeRow } from '@dxos/react-ui-grid';
23
- import { getSize, mx } from '@dxos/react-ui-theme';
24
-
25
- import { CellCombobox, useTableContext } from './components';
26
- import { textPadding } from './theme';
27
-
28
- export type ValueUpdater<TData extends RowData, TValue> = (row: TData, id: string, value: TValue) => void;
29
-
30
- export const createColumnBuilder = <TData extends RowData>() => ({
31
- helper: createColumnHelper<TData>(),
32
- builder: new ColumnBuilder<TData>(),
33
- });
34
-
35
- // TODO(thure): This interface is redundant, e.g. `classNames` == `meta.cell.classNames`
36
- /**
37
- * NOTE: Can use `meta` for custom properties.
38
- */
39
- export type BaseColumnOptions<TData, TValue> = Partial<ColumnDef<TData, TValue>> & {
40
- meta?: ColumnMeta<TData, TValue>;
41
- label?: string;
42
- classNames?: ClassNameValue;
43
- onUpdate?: ValueUpdater<TData, TValue | undefined>;
44
- };
45
-
46
- // TODO(burdon): Better abstraction?
47
- export type SearchListQueryModel<TData extends RowData> = {
48
- getId(object: TData): string;
49
- getText(object: TData): string;
50
- query(text?: string): Promise<TData[]>;
51
- };
52
-
53
- export type ComboboxColumnOptions<TData extends RowData> = BaseColumnOptions<TData, any> & {
54
- model: SearchListQueryModel<TData>;
55
- };
56
-
57
- export type StringColumnOptions<TData extends RowData> = BaseColumnOptions<TData, string> & {};
58
- export type SelectRowColumnOptions<TData extends RowData> = BaseColumnOptions<TData, string> & {};
59
-
60
- export type NumberColumnOptions<TData extends RowData> = BaseColumnOptions<TData, number> & {
61
- digits?: number;
62
- };
63
-
64
- export type KeyColumnOptions<TData extends RowData> = BaseColumnOptions<TData, PublicKey> & {
65
- tooltip?: boolean;
66
- };
67
-
68
- export type DateColumnOptions<TData extends RowData> = BaseColumnOptions<TData, Date> & {
69
- format?: string;
70
- relative?: boolean;
71
- };
72
-
73
- export type SwitchColumnOptions<TData extends RowData> = BaseColumnOptions<TData, boolean> & {};
74
-
75
- export type IconColumnOptions<TData extends RowData> = BaseColumnOptions<TData, boolean> & {
76
- on?: {
77
- Icon?: Icon;
78
- className?: string;
79
- };
80
- off?: {
81
- Icon?: Icon;
82
- className?: string;
83
- };
84
- };
85
-
86
- const ComboboxBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, TData>) => {
87
- const { model, onUpdate } = cellContext.column.columnDef.meta as ColumnMeta<TData, TData>;
88
- return model ? (
89
- <CellCombobox<TData>
90
- model={model}
91
- value={cellContext.getValue()}
92
- onValueChange={(value) => onUpdate?.(cellContext.row.original, cellContext.column.id, value)}
93
- />
94
- ) : null;
95
- };
96
-
97
- const StringBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, string>) => {
98
- const ref = useRef<HTMLInputElement>(null);
99
- const { findPrevFocusable } = useFocusFinders();
100
-
101
- const { onUpdate, cell } = cellContext.column.columnDef.meta as ColumnMeta<TData, string>;
102
-
103
- // https://tanstack.com/table/v8/docs/examples/react/editable-data
104
- const initialValue = cellContext.getValue();
105
-
106
- const getInitialValue = useCallback(() => {
107
- // TODO(burdon): Temporary shim for automerge.
108
- return (initialValue as any)?.content ?? initialValue;
109
- }, [initialValue]);
110
-
111
- const [value, setValue] = useState(getInitialValue);
112
-
113
- // Update value if initialValue changes externally (cell reactivity).
114
- useEffect(() => setValue(getInitialValue()), [getInitialValue]);
115
-
116
- const handleSave = () => {
117
- ref.current && document.activeElement === ref.current && findPrevFocusable(ref.current)?.focus();
118
-
119
- if (value !== initialValue) {
120
- onUpdate?.(cellContext.row.original, cellContext.column.id, value);
121
- }
122
- };
123
-
124
- const handleCancel = () => setValue(initialValue);
125
-
126
- let placeholder = false;
127
- const columns = cellContext.table.getVisibleFlatColumns();
128
-
129
- // Calling getRowModel is expensive, so only do it when we're a cell in the first position.
130
- if (columns[0].id === cellContext.column.id) {
131
- const rowCount = cellContext.table.getRowModel().rows.length;
132
- placeholder = cellContext.row.index === rowCount - 1 && columns[0].id === cellContext.column.id;
133
- }
134
-
135
- // TODO(burdon): Don't render inputs unless mouse over (Show ellipsis when div)?
136
- return (
137
- <Input.Root>
138
- <Input.TextInput
139
- ref={ref}
140
- variant='subdued'
141
- placeholder={placeholder ? 'Add row...' : undefined}
142
- classNames={['is-full', textPadding, cell?.classNames]}
143
- value={value ?? ''}
144
- onBlur={handleSave}
145
- onChange={(event) => setValue(event.target.value)}
146
- onKeyDown={(event) => (event.key === 'Enter' && handleSave()) || (event.key === 'Escape' && handleCancel())}
147
- autoComplete='off'
148
- />
149
- </Input.Root>
150
- );
151
- };
152
-
153
- const parseNumber = (text?: string) => (text?.trim().length ? Number(text) : NaN);
154
-
155
- const displayNumber = (value?: number, digits?: number) =>
156
- value || value === 0
157
- ? value.toLocaleString(undefined, {
158
- minimumFractionDigits: digits ?? 0,
159
- maximumFractionDigits: digits ?? 2,
160
- })
161
- : '';
162
-
163
- const NumberBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, number>) => {
164
- const { onUpdate, digits } = cellContext.column.columnDef.meta as ColumnMeta<TData, string>;
165
- const value = cellContext.getValue();
166
- const [text, setText] = useState<string>(displayNumber(value, digits));
167
- const { findPrevFocusable } = useFocusFinders();
168
- const ref = useRef<HTMLInputElement | null>(null);
169
-
170
- // Update display value if `digits` changes and input is not in focus.
171
- useEffect(() => {
172
- (!document.activeElement || document.activeElement !== ref.current) && setText(displayNumber(value, digits));
173
- }, [digits, value]);
174
-
175
- const handleEdit = () => setText(value !== undefined ? String(value) : '');
176
-
177
- // TODO(burdon): Property is encoded as float (e.g., 6.1 => 6.099)
178
- const handleSave = () => {
179
- const nextValue = parseNumber(text);
180
-
181
- if (!isNaN(nextValue)) {
182
- onUpdate?.(cellContext.row.original, cellContext.column.id, nextValue);
183
- setText(displayNumber(nextValue, digits));
184
- } else {
185
- // Reset the text to display the original value without updating the model.
186
- setText(displayNumber(value, digits));
187
- }
188
-
189
- // Shift focus only if input is focused on save (e.g. on `Enter`).
190
- ref.current && document.activeElement === ref.current && findPrevFocusable(ref.current)?.focus();
191
- };
192
-
193
- const _handleCancel = () => {
194
- setText(displayNumber(value, digits));
195
- ref.current && findPrevFocusable(ref.current)?.focus();
196
- };
197
-
198
- return (
199
- <Input.Root>
200
- <Input.TextInput
201
- ref={ref}
202
- variant='subdued'
203
- value={text}
204
- classNames={['is-full text-end font-mono', textPadding]}
205
- onBlur={handleSave}
206
- onFocus={handleEdit}
207
- onChange={({ target: { value } }) => setText(value)}
208
- onKeyDown={(event) => {
209
- // TODO(thure): `Escape` is reserved for blurring the input and moving focus to the cell.
210
- return /* (event.key === 'Escape' && handleCancel()) || */ event.key === 'Enter' && handleSave();
211
- }}
212
- autoComplete='off'
213
- />
214
- </Input.Root>
215
- );
216
- };
217
-
218
- const RowSelectorBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, unknown>) => {
219
- const { cell } = cellContext.column.columnDef.meta as ColumnMeta<TData, unknown>;
220
- const { row } = cellContext;
221
-
222
- const checked = row.getCanSelect()
223
- ? row.getIsSelected()
224
- : row.getCanSelectSubRows() && (row.getIsSomeSelected() ? 'indeterminate' : row.getIsAllSubRowsSelected());
225
-
226
- return (
227
- <div className='flex justify-center w-full h-full' role='presentation'>
228
- <Input.Root>
229
- <Input.Checkbox
230
- size={4}
231
- classNames={[cell?.classNames]}
232
- checked={checked}
233
- onCheckedChange={(event) => {
234
- if (row.getCanSelect()) {
235
- row.getToggleSelectedHandler()(event);
236
- }
237
- }}
238
- disabled={!(row.getCanSelect() || row.getCanSelectSubRows())}
239
- />
240
- </Input.Root>
241
- </div>
242
- );
243
- };
244
-
245
- const SwitchBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, boolean>) => {
246
- const { onUpdate } = cellContext.column.columnDef.meta as ColumnMeta<TData, boolean>;
247
- const value = cellContext.getValue();
248
- return (
249
- <Input.Root>
250
- <Input.Switch
251
- classNames='block mli-auto'
252
- onClick={(event) => event.stopPropagation()}
253
- disabled={!onUpdate}
254
- checked={!!value}
255
- onCheckedChange={(value) => {
256
- onUpdate?.(cellContext.row.original, cellContext.column.id, !!value);
257
- }}
258
- />
259
- </Input.Root>
260
- );
261
- };
262
-
263
- /**
264
- * Util to create column definitions.
265
- */
266
- export class ColumnBuilder<TData extends RowData> {
267
- /**
268
- * Combobox value
269
- */
270
- combobox({
271
- label,
272
- classNames,
273
- model,
274
- onUpdate,
275
- ...props
276
- }: ComboboxColumnOptions<TData>): Partial<ColumnDef<TData, any>> {
277
- return {
278
- minSize: 100,
279
- header: (column) => {
280
- return label ?? column.header.id;
281
- },
282
- cell:
283
- model && onUpdate
284
- ? ComboboxBuilderCell
285
- : (cellContext) => {
286
- const cellValue = cellContext.getValue();
287
- return <div className={mx('truncate', classNames)}>{cellValue ? model?.getText(cellValue) : ''}</div>;
288
- },
289
- ...props,
290
- meta: {
291
- ...props.meta,
292
- model,
293
- onUpdate,
294
- cell: { classNames, ...props.meta?.cell },
295
- },
296
- };
297
- }
298
-
299
- /**
300
- * String formats.
301
- */
302
- string({ label, classNames, onUpdate, ...props }: StringColumnOptions<TData> = {}): Partial<
303
- ColumnDef<TData, string>
304
- > {
305
- return {
306
- minSize: 100,
307
- // TODO(burdon): Default.
308
- header: (column) => {
309
- return label ?? column.header.id;
310
- },
311
- cell: onUpdate
312
- ? StringBuilderCell
313
- : (cell) => {
314
- let value = cell.getValue();
315
- // TODO(burdon): Hack to support automerge.
316
- if (typeof value === 'object' && (value as any)?.type === 'dxos.Text.v0') {
317
- value = (value as any).content;
318
- }
319
-
320
- return <div className={mx('truncate', textPadding, classNames)}>{value}</div>;
321
- },
322
- ...props,
323
- meta: {
324
- ...props.meta,
325
- onUpdate,
326
- cell: { ...props.meta?.cell, classNames },
327
- },
328
- };
329
- }
330
-
331
- /**
332
- * Number formats.
333
- */
334
- number({ label, minSize, classNames, digits, onUpdate, ...props }: NumberColumnOptions<TData> = {}): Partial<
335
- ColumnDef<TData, number>
336
- > {
337
- return {
338
- size: 100,
339
- minSize: 100,
340
- header: (cell) => label ?? cell.header.id,
341
- cell: NumberBuilderCell,
342
- ...props,
343
- meta: {
344
- ...props.meta,
345
- onUpdate,
346
- digits,
347
- header: { ...props.meta?.header },
348
- cell: { ...props.meta?.cell, classNames },
349
- },
350
- };
351
- }
352
-
353
- /**
354
- * Date formats.
355
- */
356
- // TODO(burdon): Date picker (pluggable renderers?)
357
- date({ label, format: formatSpec, relative, classNames, ...props }: DateColumnOptions<TData> = {}): Partial<
358
- ColumnDef<TData, Date>
359
- > {
360
- return {
361
- size: 220, // TODO(burdon): Depends on format.
362
- minSize: 100,
363
- header: (cell) => label ?? cell.header.id,
364
- cell: (cell) => {
365
- const value = cell.getValue();
366
-
367
- try {
368
- const str = value
369
- ? formatSpec
370
- ? format(value, formatSpec)
371
- : relative
372
- ? formatDistanceToNow(value, { addSuffix: true })
373
- : value.toISOString()
374
- : undefined;
375
-
376
- return <div className={mx(textPadding, classNames)}>{str}</div>;
377
- } catch (err) {
378
- log.info('formatError', { value });
379
- return null;
380
- }
381
- },
382
- ...props,
383
- meta: { ...props.meta, cell: { ...props.meta?.cell, classNames } },
384
- };
385
- }
386
-
387
- /**
388
- * PublicKey with tooltip.
389
- */
390
- key({ label, tooltip, classNames, ...props }: KeyColumnOptions<TData> = {}): Partial<ColumnDef<TData, PublicKey>> {
391
- return {
392
- size: 94,
393
- minSize: 94,
394
- header: (cell) => label ?? cell.header.id,
395
- cell: (cell) => {
396
- const value = cell.getValue();
397
- if (!value) {
398
- return;
399
- }
400
-
401
- // TODO(burdon): Factor out styles.
402
- const element = value.truncate();
403
- if (!tooltip) {
404
- return element;
405
- }
406
-
407
- return (
408
- <Tooltip.Provider>
409
- <Tooltip.Root>
410
- <Tooltip.Trigger>{element}</Tooltip.Trigger>
411
- <Tooltip.Portal>
412
- <Tooltip.Content side='right'>
413
- <Tooltip.Arrow />
414
- <ClipboardText
415
- onClick={(ev) => {
416
- ev.stopPropagation(); // Prevent select row.
417
- void navigator.clipboard.writeText(value.toHex());
418
- }}
419
- />
420
- </Tooltip.Content>
421
- </Tooltip.Portal>
422
- </Tooltip.Root>
423
- </Tooltip.Provider>
424
- );
425
- },
426
- ...props,
427
- meta: { ...props.meta, cell: { ...props.meta?.cell, classNames: ['font-mono', textPadding, classNames] } },
428
- };
429
- }
430
-
431
- /**
432
- * Row selector
433
- */
434
- selectRow({ classNames, onUpdate, id = 'selectRow' }: SelectRowColumnOptions<TData> = {}): Parameters<
435
- ColumnHelper<TData>['display']
436
- >[0] {
437
- return {
438
- id,
439
- size: defaultSizeRow,
440
- enableResizing: false,
441
- meta: { onUpdate, cell: { classNames } },
442
- header: ({ table }) => {
443
- const { rowsSelectable } = useTableContext();
444
- const checked = table.getIsSomeRowsSelected() ? 'indeterminate' : table.getIsAllRowsSelected();
445
-
446
- return rowsSelectable === 'multi' ? (
447
- <div className='flex justify-center w-full h-full' role='presentation'>
448
- <Input.Root>
449
- <Input.Checkbox
450
- size={4}
451
- classNames={['mli-auto', classNames]}
452
- checked={checked}
453
- onCheckedChange={() => table.toggleAllRowsSelected()}
454
- />
455
- </Input.Root>
456
- </div>
457
- ) : null;
458
- },
459
- cell: RowSelectorBuilderCell,
460
- };
461
- }
462
-
463
- /**
464
- * Switch
465
- */
466
- switch({ label, classNames, onUpdate, ...props }: SwitchColumnOptions<TData> = {}): Partial<
467
- ColumnDef<TData, boolean>
468
- > {
469
- return {
470
- size: 50,
471
- minSize: 50,
472
- header: (column) => label ?? column.header.id,
473
- // TODO(zan): Implement sort algorithm.
474
- enableSorting: true,
475
- cell: SwitchBuilderCell,
476
- ...props,
477
- meta: { ...props.meta, onUpdate, cell: { ...props.meta?.cell, classNames: [textPadding, classNames] } },
478
- };
479
- }
480
-
481
- /**
482
- * Icon based on boolean value.
483
- */
484
- icon({ label, size, on, off, ...props }: IconColumnOptions<TData> = {}): Partial<ColumnDef<TData, boolean>> {
485
- const IconOn = on?.Icon ?? Check;
486
- const IconOff = off?.Icon ?? X;
487
- return {
488
- size: size ?? defaultSizeRow,
489
- // TODO(zan): Implement sort algorithm.
490
- enableSorting: false,
491
- header: (column) => <div className={'justify-center'}>{label ?? column.header.id}</div>,
492
- cell: (cell) => {
493
- const value = cell.getValue();
494
- if (value) {
495
- return <IconOn className={mx('block mli-auto', getSize(6), on?.className ?? 'text-green-700')} />;
496
- } else if (value === false) {
497
- return <IconOff className={mx('block mli-auto', getSize(6), off?.className ?? 'text-red-700')} />;
498
- } else {
499
- return null;
500
- }
501
- },
502
- ...props,
503
- };
504
- }
505
-
506
- /**
507
- * This display column type is for nested/unhandled data to appear as JSON.
508
- */
509
- json({ label, classNames, ...props }: KeyColumnOptions<TData> = {}): Partial<ColumnDef<TData, PublicKey>> {
510
- return {
511
- size: 120,
512
- minSize: 120,
513
- header: (cell) => label ?? cell.header.id,
514
- cell: (cell) => {
515
- const value = cell.getValue();
516
- return (
517
- <div className='overflow-scroll'>
518
- <code className='text-xs'>{JSON.stringify(value)}</code>
519
- </div>
520
- );
521
- },
522
- ...props,
523
- meta: { ...props.meta, cell: { ...props.meta?.cell, classNames: ['font-mono', textPadding, classNames] } },
524
- };
525
- }
526
- }
@@ -1,8 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- export * from './useColumnSizing';
6
- export * from './useColumnSorting';
7
- export * from './usePinLastRow';
8
- export * from './useRowSelection';
@@ -1,46 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type ColumnSizingInfoState, type ColumnSizingState } from '@tanstack/react-table';
6
- import { useCallback, useEffect, useState } from 'react';
7
-
8
- import { useOnTransition } from '@dxos/react-ui';
9
-
10
- import { type TableColumnDef } from '../types';
11
-
12
- export const useColumnResizing = ({
13
- columns,
14
- onColumnResize,
15
- }: {
16
- columns: TableColumnDef<any>[];
17
- onColumnResize: ((state: Record<string, number>) => void) | undefined;
18
- }) => {
19
- const [columnsInitialised, setColumnsInitialised] = useState(false);
20
- const [columnSizing, setColumnSizing] = useState<ColumnSizingState>({});
21
-
22
- useEffect(() => {
23
- if (columnsInitialised) {
24
- return;
25
- }
26
-
27
- setColumnSizing(
28
- columns
29
- .filter((column) => !!column.size && (column as any).prop !== undefined)
30
- .reduce<ColumnSizingState>((state, column) => {
31
- state[(column as any).prop] = column.size!;
32
- return state;
33
- }, {}),
34
- );
35
-
36
- setColumnsInitialised(true);
37
- }, [columns, setColumnSizing]);
38
-
39
- const [columnSizingInfo, setColumnSizingInfo] = useState<ColumnSizingInfoState>({} as ColumnSizingInfoState);
40
-
41
- // Notify on column resize.
42
- const notifyColumnResize = useCallback(() => onColumnResize?.(columnSizing), [onColumnResize, columnSizing]);
43
- useOnTransition(columnSizingInfo.isResizingColumn, (v) => typeof v === 'string', false, notifyColumnResize);
44
-
45
- return { columnSizing, columnSizingInfo, setColumnSizing, setColumnSizingInfo };
46
- };
@@ -1,64 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type SortDirection, type Column, type ColumnSort } from '@tanstack/react-table';
6
- import { useState } from 'react';
7
-
8
- import { useTableContext } from '../components/Table/TableContext';
9
-
10
- export const useColumnSorting = () => {
11
- const [columnSorting, setColumnSorting] = useState<ColumnSort[]>([]);
12
-
13
- const sortColumn = (columnId: string, sortDirection: SortDirection) => {
14
- setColumnSorting([{ id: columnId, desc: sortDirection === 'desc' }]);
15
- };
16
-
17
- const clearColumnSorting = () => {
18
- setColumnSorting([]);
19
- };
20
-
21
- return { sorting: columnSorting, sortColumn, clearColumnSorting };
22
- };
23
-
24
- export type ColumnSorting = ReturnType<typeof useColumnSorting>;
25
-
26
- export const useSortColumn = (column: Column<any>) => {
27
- const { sorting, sortColumn, clearColumnSorting } = useTableContext();
28
- const [initialSortDirection, setInitialSortDirection] = useState<SortDirection | undefined>();
29
-
30
- const columnSort = sorting.find((s) => s.id === column.id);
31
- const sortDirection = columnSort ? (columnSort.desc ? ('desc' as const) : ('asc' as const)) : undefined;
32
-
33
- const canSort = column.getCanSort();
34
-
35
- const updateColumnSort = (direction: SortDirection) => {
36
- sortColumn(column.id, direction);
37
- };
38
-
39
- // Whichever sort is explicitly set first, will be the first sort direction
40
- // When toggling, we always go to the other direction, and then to unsorted.
41
- const onToggleSort = () => {
42
- if (sortDirection === undefined) {
43
- return;
44
- }
45
-
46
- if (sortDirection === initialSortDirection) {
47
- updateColumnSort(initialSortDirection === 'asc' ? 'desc' : 'asc');
48
- } else {
49
- clearColumnSorting();
50
- }
51
- };
52
-
53
- const onSelectSort = (direction: SortDirection) => {
54
- clearColumnSorting();
55
- setInitialSortDirection(direction);
56
- updateColumnSort(direction);
57
- };
58
-
59
- const onClearSort = () => {
60
- clearColumnSorting();
61
- };
62
-
63
- return { canSort, sortDirection, onSelectSort, onToggleSort, onClearSort };
64
- };
@@ -1,47 +0,0 @@
1
- //
2
- // Copyright 2024 DXOS.org
3
- //
4
-
5
- import { type Row, type Table } from '@tanstack/react-table';
6
- import { useCallback, useEffect, useState } from 'react';
7
-
8
- export const usePinLastRow = (pinLastRow: boolean | undefined, table: Table<any>, data: any[]) => {
9
- const [pinnedRowKey, setPinnedRowKey] = useState<string>();
10
-
11
- const pinRow = useCallback(
12
- (rowToPin: Row<any>) => {
13
- table.resetRowPinning();
14
- rowToPin.pin('bottom');
15
- setPinnedRowKey(rowToPin.id);
16
- },
17
- [table],
18
- );
19
-
20
- const clearRowPinning = useCallback(() => {
21
- table.resetRowPinning();
22
- setPinnedRowKey(undefined);
23
- }, [table]);
24
-
25
- // Define the logic for pinning the last row in a callback.
26
- const pinLastRowCallback = useCallback(() => {
27
- const rows = table.getRowModel().rows;
28
-
29
- if (rows.length === 0) {
30
- return clearRowPinning();
31
- }
32
-
33
- const lastRow = rows[rows.length - 1];
34
- if (lastRow.id === pinnedRowKey) {
35
- return;
36
- }
37
-
38
- pinRow(lastRow);
39
- }, [table, pinnedRowKey]);
40
-
41
- // Effect to handle pinning logic when data changes.
42
- useEffect(() => {
43
- if (pinLastRow) {
44
- pinLastRowCallback();
45
- }
46
- }, [data, pinLastRow, pinLastRowCallback]);
47
- };