@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.
- 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 -51
- 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
package/src/helpers.tsx
CHANGED
|
@@ -2,35 +2,28 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
5
|
+
import { Check, ClipboardText, type Icon, X } from '@phosphor-icons/react';
|
|
6
|
+
import {
|
|
7
|
+
createColumnHelper,
|
|
8
|
+
type ColumnDef,
|
|
9
|
+
type ColumnMeta,
|
|
10
|
+
type RowData,
|
|
11
|
+
type ColumnHelper,
|
|
12
|
+
type CellContext,
|
|
13
|
+
} from '@tanstack/react-table';
|
|
8
14
|
import format from 'date-fns/format';
|
|
9
15
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
|
10
16
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
11
|
-
import React, {
|
|
17
|
+
import React, { useRef, useState } from 'react';
|
|
12
18
|
|
|
13
19
|
import { type PublicKey } from '@dxos/keys';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { descriptionText, getSize, mx } from '@dxos/react-ui-theme';
|
|
20
|
+
import { type ClassNameValue, Input, Tooltip } from '@dxos/react-ui';
|
|
21
|
+
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
17
22
|
import { stripUndefinedValues } from '@dxos/util';
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
container: HTMLElement,
|
|
23
|
-
): HTMLElement | undefined => {
|
|
24
|
-
const next = findFirstFocusable(container as any);
|
|
25
|
-
if (next) {
|
|
26
|
-
if (next?.tagName === 'INPUT') {
|
|
27
|
-
return next;
|
|
28
|
-
}
|
|
29
|
-
if (container.nextElementSibling) {
|
|
30
|
-
return findNextFocusable(findFirstFocusable, container.nextSibling as any);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
24
|
+
import { CellCombobox } from './components';
|
|
25
|
+
import { useTableContext } from './components/Table/TableContext';
|
|
26
|
+
import { textPadding } from './theme';
|
|
34
27
|
|
|
35
28
|
export type ValueUpdater<TData extends RowData, TValue> = (row: TData, id: string, value: TValue) => void;
|
|
36
29
|
|
|
@@ -45,22 +38,23 @@ export const createColumnBuilder = <TData extends RowData>() => ({
|
|
|
45
38
|
export type BaseColumnOptions<TData, TValue> = Partial<ColumnDef<TData, TValue>> & {
|
|
46
39
|
meta?: ColumnMeta<TData, TValue>;
|
|
47
40
|
label?: string;
|
|
48
|
-
|
|
41
|
+
classNames?: ClassNameValue;
|
|
49
42
|
onUpdate?: ValueUpdater<TData, TValue | undefined>;
|
|
50
43
|
};
|
|
51
44
|
|
|
52
45
|
// TODO(burdon): Better abstraction?
|
|
53
|
-
export type
|
|
46
|
+
export type SearchListQueryModel<TData extends RowData> = {
|
|
54
47
|
getId(object: TData): string;
|
|
55
48
|
getText(object: TData): string;
|
|
56
49
|
query(text?: string): Promise<TData[]>;
|
|
57
50
|
};
|
|
58
51
|
|
|
59
|
-
export type
|
|
60
|
-
model:
|
|
52
|
+
export type ComboboxColumnOptions<TData extends RowData> = BaseColumnOptions<TData, any> & {
|
|
53
|
+
model: SearchListQueryModel<TData>;
|
|
61
54
|
};
|
|
62
55
|
|
|
63
56
|
export type StringColumnOptions<TData extends RowData> = BaseColumnOptions<TData, string> & {};
|
|
57
|
+
export type SelectRowColumnOptions<TData extends RowData> = BaseColumnOptions<TData, string> & {};
|
|
64
58
|
|
|
65
59
|
export type NumberColumnOptions<TData extends RowData> = BaseColumnOptions<TData, number> & {
|
|
66
60
|
digits?: number;
|
|
@@ -75,7 +69,7 @@ export type DateColumnOptions<TData extends RowData> = BaseColumnOptions<TData,
|
|
|
75
69
|
relative?: boolean;
|
|
76
70
|
};
|
|
77
71
|
|
|
78
|
-
export type
|
|
72
|
+
export type SwitchColumnOptions<TData extends RowData> = BaseColumnOptions<TData, boolean> & {};
|
|
79
73
|
|
|
80
74
|
export type IconColumnOptions<TData extends RowData> = BaseColumnOptions<TData, boolean> & {
|
|
81
75
|
on?: {
|
|
@@ -95,85 +89,145 @@ const defaults = <TData extends RowData, TValue>(
|
|
|
95
89
|
return stripUndefinedValues(defaultsDeep({}, options, ...sources));
|
|
96
90
|
};
|
|
97
91
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
value
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
acc[model.getId(item)] = {
|
|
126
|
-
label: model.getText(item),
|
|
127
|
-
data: item,
|
|
128
|
-
};
|
|
129
|
-
return acc;
|
|
130
|
-
}, initialItems(model, value)),
|
|
131
|
-
);
|
|
132
|
-
setLoading(false);
|
|
92
|
+
const ComboboxBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, TData>) => {
|
|
93
|
+
const { model, onUpdate } = cellContext.column.columnDef.meta as ColumnMeta<TData, TData>;
|
|
94
|
+
return (
|
|
95
|
+
<CellCombobox<TData>
|
|
96
|
+
model={model!}
|
|
97
|
+
value={cellContext.getValue()}
|
|
98
|
+
onValueChange={(value) => onUpdate?.(cellContext.row.original, cellContext.column.id, value)}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const StringBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, string>) => {
|
|
104
|
+
const { onUpdate } = cellContext.column.columnDef.meta as ColumnMeta<TData, string>;
|
|
105
|
+
// https://tanstack.com/table/v8/docs/examples/react/editable-data
|
|
106
|
+
const initialValue = cellContext.getValue();
|
|
107
|
+
const [value, setValue] = useState(initialValue);
|
|
108
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
109
|
+
|
|
110
|
+
const handleSave = () => {
|
|
111
|
+
if (value === initialValue) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
onUpdate?.(cellContext.row.original, cellContext.column.id, value);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleCancel = () => {
|
|
118
|
+
setValue(initialValue);
|
|
133
119
|
};
|
|
134
120
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
121
|
+
// Check if first input column of last row.
|
|
122
|
+
const rows = cellContext.table.getRowModel().flatRows;
|
|
123
|
+
const columns = cellContext.table.getVisibleFlatColumns();
|
|
124
|
+
const placeholder = cellContext.row.index === rows.length - 1 && columns[0].id === cellContext.column.id;
|
|
125
|
+
|
|
126
|
+
// TODO(burdon): Don't render inputs unless mouse over (Show ellipsis when div)?
|
|
127
|
+
return (
|
|
128
|
+
<Input.Root>
|
|
129
|
+
<Input.TextInput
|
|
130
|
+
ref={inputRef}
|
|
131
|
+
variant='subdued'
|
|
132
|
+
placeholder={placeholder ? 'Add row...' : undefined}
|
|
133
|
+
classNames={['is-full', textPadding]}
|
|
134
|
+
value={value ?? ''}
|
|
135
|
+
onBlur={() => handleSave()}
|
|
136
|
+
onChange={(event) => setValue(event.target.value)}
|
|
137
|
+
onKeyDown={(event) => (event.key === 'Enter' && handleSave()) || (event.key === 'Escape' && handleCancel())}
|
|
138
|
+
/>
|
|
139
|
+
</Input.Root>
|
|
140
140
|
);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const NumberBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, number>) => {
|
|
144
|
+
const { onUpdate, cell, digits } = cellContext.column.columnDef.meta as ColumnMeta<TData, string>;
|
|
145
|
+
const value = cellContext.getValue();
|
|
146
|
+
const [text, setText] = useState<string>();
|
|
147
|
+
|
|
148
|
+
const handleEdit = () => {
|
|
149
|
+
if (onUpdate) {
|
|
150
|
+
setText(value !== undefined ? String(value) : '');
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// TODO(burdon): Property is encoded as float (e.g., 6.1 => 6.099)
|
|
155
|
+
const handleSave = () => {
|
|
156
|
+
const value = text?.trim().length ? Number(text) : NaN;
|
|
157
|
+
onUpdate?.(cellContext.row.original, cellContext.column.id, isNaN(value) ? undefined : value);
|
|
158
|
+
setText(undefined);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const handleCancel = () => {
|
|
162
|
+
setText(undefined);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (text !== undefined) {
|
|
166
|
+
return (
|
|
167
|
+
<Input.Root>
|
|
168
|
+
<Input.TextInput
|
|
169
|
+
value={text}
|
|
170
|
+
classNames={['is-full text-end font-mono']}
|
|
171
|
+
onBlur={handleSave}
|
|
172
|
+
onChange={(event) => setText(event.target.value)}
|
|
173
|
+
onKeyDown={(event) => (event.key === 'Escape' && handleCancel()) || (event.key === 'Enter' && handleSave())}
|
|
174
|
+
/>
|
|
175
|
+
</Input.Root>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
141
178
|
|
|
142
179
|
return (
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
<div
|
|
181
|
+
className={mx('is-full text-end font-mono empty:after:content-[" "]', textPadding, cell?.classNames)}
|
|
182
|
+
onClick={handleEdit}
|
|
183
|
+
>
|
|
184
|
+
{value?.toLocaleString(undefined, {
|
|
185
|
+
minimumFractionDigits: digits ?? 0,
|
|
186
|
+
maximumFractionDigits: digits ?? 2,
|
|
187
|
+
})}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const RowSelectorBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, unknown>) => {
|
|
193
|
+
const { cell } = cellContext.column.columnDef.meta as ColumnMeta<TData, unknown>;
|
|
194
|
+
const { row } = cellContext;
|
|
195
|
+
|
|
196
|
+
const checked = row.getCanSelect()
|
|
197
|
+
? row.getIsSelected()
|
|
198
|
+
: row.getCanSelectSubRows() && (row.getIsSomeSelected() ? 'indeterminate' : row.getIsAllSubRowsSelected());
|
|
199
|
+
return (
|
|
200
|
+
<Input.Root>
|
|
201
|
+
<Input.Checkbox
|
|
202
|
+
size={4}
|
|
203
|
+
classNames={['mli-auto', cell?.classNames]}
|
|
204
|
+
checked={checked}
|
|
205
|
+
onCheckedChange={(event) => {
|
|
206
|
+
if (row.getCanSelect()) {
|
|
207
|
+
row.getToggleSelectedHandler()(event);
|
|
208
|
+
}
|
|
209
|
+
}}
|
|
210
|
+
disabled={!(row.getCanSelect() || row.getCanSelectSubRows())}
|
|
211
|
+
/>
|
|
212
|
+
</Input.Root>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const SwitchBuilderCell = <TData extends RowData>(cellContext: CellContext<TData, boolean>) => {
|
|
217
|
+
const { onUpdate } = cellContext.column.columnDef.meta as ColumnMeta<TData, boolean>;
|
|
218
|
+
const value = cellContext.getValue();
|
|
219
|
+
return (
|
|
220
|
+
<Input.Root>
|
|
221
|
+
<Input.Switch
|
|
222
|
+
classNames='block mli-auto'
|
|
223
|
+
onClick={(event) => event.stopPropagation()}
|
|
224
|
+
disabled={!onUpdate}
|
|
225
|
+
checked={!!value}
|
|
226
|
+
onCheckedChange={(value) => {
|
|
227
|
+
onUpdate?.(cellContext.row.original, cellContext.column.id, !!value);
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
</Input.Root>
|
|
177
231
|
);
|
|
178
232
|
};
|
|
179
233
|
|
|
@@ -182,26 +236,31 @@ const CellSelector = <TData extends RowData>({
|
|
|
182
236
|
*/
|
|
183
237
|
export class ColumnBuilder<TData extends RowData> {
|
|
184
238
|
/**
|
|
185
|
-
*
|
|
239
|
+
* Combobox value
|
|
186
240
|
*/
|
|
187
|
-
|
|
241
|
+
combobox({
|
|
242
|
+
label,
|
|
243
|
+
classNames,
|
|
244
|
+
model,
|
|
245
|
+
onUpdate,
|
|
246
|
+
...props
|
|
247
|
+
}: ComboboxColumnOptions<TData>): Partial<ColumnDef<TData, any>> {
|
|
188
248
|
return defaults(props, {
|
|
189
249
|
minSize: 100,
|
|
250
|
+
meta: {
|
|
251
|
+
model,
|
|
252
|
+
onUpdate,
|
|
253
|
+
cell: { classNames },
|
|
254
|
+
},
|
|
190
255
|
header: (column) => {
|
|
191
|
-
return
|
|
256
|
+
return label ?? column.header.id;
|
|
192
257
|
},
|
|
193
258
|
cell:
|
|
194
259
|
model && onUpdate
|
|
195
|
-
?
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
onValueChange={(value) => onUpdate?.(cell.row.original, cell.column.id, value)}
|
|
200
|
-
/>
|
|
201
|
-
)
|
|
202
|
-
: (cell) => {
|
|
203
|
-
const value = cell.getValue();
|
|
204
|
-
return <div className={mx('truncate', className)}>{value ? model.getText(value) : ''}</div>;
|
|
260
|
+
? ComboboxBuilderCell
|
|
261
|
+
: (cellContext) => {
|
|
262
|
+
const cellValue = cellContext.getValue();
|
|
263
|
+
return <div className={mx('truncate', classNames)}>{cellValue ? model?.getText(cellValue) : ''}</div>;
|
|
205
264
|
},
|
|
206
265
|
});
|
|
207
266
|
}
|
|
@@ -209,69 +268,21 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
209
268
|
/**
|
|
210
269
|
* String formats.
|
|
211
270
|
*/
|
|
212
|
-
string({ label,
|
|
271
|
+
string({ label, classNames, onUpdate, ...props }: StringColumnOptions<TData> = {}): Partial<
|
|
272
|
+
ColumnDef<TData, string>
|
|
273
|
+
> {
|
|
213
274
|
return defaults(props, {
|
|
214
275
|
minSize: 100,
|
|
276
|
+
meta: { onUpdate, cell: { classNames } },
|
|
215
277
|
// TODO(burdon): Default.
|
|
216
278
|
header: (column) => {
|
|
217
|
-
return
|
|
279
|
+
return label ?? column.header.id;
|
|
218
280
|
},
|
|
219
281
|
cell: onUpdate
|
|
220
|
-
?
|
|
221
|
-
// https://tanstack.com/table/v8/docs/examples/react/editable-data
|
|
222
|
-
const initialValue = cell.getValue();
|
|
223
|
-
const [value, setValue] = useState(initialValue);
|
|
224
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
225
|
-
const { findFirstFocusable } = useFocusFinders();
|
|
226
|
-
|
|
227
|
-
const handleSave = (focusNext = false) => {
|
|
228
|
-
if (value === initialValue) {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
onUpdate?.(cell.row.original, cell.column.id, value);
|
|
233
|
-
|
|
234
|
-
// TODO(burdon): More generally support keyboard navigation.
|
|
235
|
-
if (focusNext) {
|
|
236
|
-
const rowElement = inputRef.current?.parentElement?.parentElement;
|
|
237
|
-
// TODO(burdon): Hack to wait for next row to render.
|
|
238
|
-
setTimeout(() => {
|
|
239
|
-
const next = findNextFocusable(findFirstFocusable, rowElement as HTMLElement);
|
|
240
|
-
next?.focus();
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const handleCancel = () => {
|
|
246
|
-
setValue(initialValue);
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// Check if first input column of last row.
|
|
250
|
-
const rows = cell.table.getRowModel().flatRows;
|
|
251
|
-
const columns = cell.table.getVisibleFlatColumns();
|
|
252
|
-
const placeholder = cell.row.index === rows.length - 1 && columns[0].id === cell.column.id;
|
|
253
|
-
|
|
254
|
-
// TODO(burdon): Don't render inputs unless mouse over (Show ellipsis when div)?
|
|
255
|
-
return (
|
|
256
|
-
<Input.Root>
|
|
257
|
-
<Input.TextInput
|
|
258
|
-
ref={inputRef}
|
|
259
|
-
variant='subdued'
|
|
260
|
-
placeholder={placeholder ? 'Add row...' : undefined}
|
|
261
|
-
classNames={'px-2'}
|
|
262
|
-
value={(value as string) ?? ''}
|
|
263
|
-
onBlur={() => handleSave(false)}
|
|
264
|
-
onChange={(event) => setValue(event.target.value)}
|
|
265
|
-
onKeyDown={(event) =>
|
|
266
|
-
(event.key === 'Enter' && handleSave(true)) || (event.key === 'Escape' && handleCancel())
|
|
267
|
-
}
|
|
268
|
-
/>
|
|
269
|
-
</Input.Root>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
282
|
+
? StringBuilderCell
|
|
272
283
|
: (cell) => {
|
|
273
284
|
const value = cell.getValue();
|
|
274
|
-
return <div className={mx('truncate',
|
|
285
|
+
return <div className={mx('truncate', textPadding, classNames)}>{value}</div>;
|
|
275
286
|
},
|
|
276
287
|
});
|
|
277
288
|
}
|
|
@@ -279,65 +290,15 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
279
290
|
/**
|
|
280
291
|
* Number formats.
|
|
281
292
|
*/
|
|
282
|
-
number({ label, minSize,
|
|
293
|
+
number({ label, minSize, classNames, digits, onUpdate, ...props }: NumberColumnOptions<TData> = {}): Partial<
|
|
283
294
|
ColumnDef<TData, number>
|
|
284
295
|
> {
|
|
285
296
|
return defaults(props, {
|
|
286
297
|
size: 100,
|
|
287
298
|
minSize: 100,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const [text, setText] = useState<string>();
|
|
292
|
-
|
|
293
|
-
const handleEdit = () => {
|
|
294
|
-
if (onUpdate) {
|
|
295
|
-
setText(value !== undefined ? String(value) : '');
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// TODO(burdon): Property is encoded as float (e.g., 6.1 => 6.099)
|
|
300
|
-
const handleSave = () => {
|
|
301
|
-
const value = text?.trim().length ? Number(text) : NaN;
|
|
302
|
-
onUpdate?.(cell.row.original, cell.column.id, isNaN(value) ? undefined : value);
|
|
303
|
-
setText(undefined);
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const handleCancel = () => {
|
|
307
|
-
setText(undefined);
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
if (text !== undefined) {
|
|
311
|
-
return (
|
|
312
|
-
<div className={mx('grow text-right font-mono', className)}>
|
|
313
|
-
<Input.Root>
|
|
314
|
-
<Input.TextInput
|
|
315
|
-
autoFocus
|
|
316
|
-
value={text}
|
|
317
|
-
classNames={'px-2'}
|
|
318
|
-
onBlur={handleSave}
|
|
319
|
-
onChange={(event) => setText(event.target.value)}
|
|
320
|
-
onKeyDown={(event) =>
|
|
321
|
-
(event.key === 'Escape' && handleCancel()) || (event.key === 'Enter' && handleSave())
|
|
322
|
-
}
|
|
323
|
-
/>
|
|
324
|
-
</Input.Root>
|
|
325
|
-
</div>
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return (
|
|
330
|
-
<div
|
|
331
|
-
className={mx('grow w-full text-right font-mono empty:after:content-[" "]', className)}
|
|
332
|
-
onClick={handleEdit}
|
|
333
|
-
>
|
|
334
|
-
{value?.toLocaleString(undefined, {
|
|
335
|
-
minimumFractionDigits: digits ?? 0,
|
|
336
|
-
maximumFractionDigits: digits ?? 2,
|
|
337
|
-
})}
|
|
338
|
-
</div>
|
|
339
|
-
);
|
|
340
|
-
},
|
|
299
|
+
meta: { onUpdate, digits, header: { classNames: 'text-end' }, cell: { classNames } },
|
|
300
|
+
header: (cell) => label ?? cell.header.id,
|
|
301
|
+
cell: NumberBuilderCell,
|
|
341
302
|
});
|
|
342
303
|
}
|
|
343
304
|
|
|
@@ -345,13 +306,14 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
345
306
|
* Date formats.
|
|
346
307
|
*/
|
|
347
308
|
// TODO(burdon): Date picker (pluggable renderers?)
|
|
348
|
-
date({ label, format: formatSpec, relative,
|
|
309
|
+
date({ label, format: formatSpec, relative, classNames, ...props }: DateColumnOptions<TData> = {}): Partial<
|
|
349
310
|
ColumnDef<TData, Date>
|
|
350
311
|
> {
|
|
351
312
|
return defaults(props, {
|
|
352
313
|
size: 220, // TODO(burdon): Depends on format.
|
|
353
314
|
minSize: 100,
|
|
354
|
-
|
|
315
|
+
meta: { cell: { classNames } },
|
|
316
|
+
header: (cell) => label ?? cell.header.id,
|
|
355
317
|
cell: (cell) => {
|
|
356
318
|
const value = cell.getValue();
|
|
357
319
|
|
|
@@ -364,7 +326,7 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
364
326
|
: value.toISOString()
|
|
365
327
|
: undefined;
|
|
366
328
|
|
|
367
|
-
return <div className={mx(
|
|
329
|
+
return <div className={mx(textPadding, classNames)}>{str}</div>;
|
|
368
330
|
} catch (err) {
|
|
369
331
|
console.log(value);
|
|
370
332
|
return null;
|
|
@@ -378,9 +340,10 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
378
340
|
*/
|
|
379
341
|
key({ label, tooltip, ...props }: KeyColumnOptions<TData> = {}): Partial<ColumnDef<TData, PublicKey>> {
|
|
380
342
|
return defaults(props, {
|
|
381
|
-
size:
|
|
382
|
-
minSize:
|
|
383
|
-
|
|
343
|
+
size: 94,
|
|
344
|
+
minSize: 94,
|
|
345
|
+
meta: { cell: { classNames: ['font-mono', textPadding] } },
|
|
346
|
+
header: (cell) => label ?? cell.header.id,
|
|
384
347
|
cell: (cell) => {
|
|
385
348
|
const value = cell.getValue();
|
|
386
349
|
if (!value) {
|
|
@@ -388,64 +351,77 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
388
351
|
}
|
|
389
352
|
|
|
390
353
|
// TODO(burdon): Factor out styles.
|
|
391
|
-
const element = (
|
|
392
|
-
<div className='font-mono font-thin text-green-500 dark:text-green-300'>{value.truncate()}</div>
|
|
393
|
-
);
|
|
354
|
+
const element = value.truncate();
|
|
394
355
|
if (!tooltip) {
|
|
395
356
|
return element;
|
|
396
357
|
}
|
|
397
358
|
|
|
398
359
|
return (
|
|
399
|
-
<
|
|
400
|
-
<Tooltip.
|
|
401
|
-
<Tooltip.
|
|
402
|
-
|
|
403
|
-
<Tooltip.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
</Tooltip.Provider>
|
|
414
|
-
</div>
|
|
360
|
+
<Tooltip.Provider>
|
|
361
|
+
<Tooltip.Root>
|
|
362
|
+
<Tooltip.Trigger>{element}</Tooltip.Trigger>
|
|
363
|
+
<Tooltip.Content side='right'>
|
|
364
|
+
<Tooltip.Arrow />
|
|
365
|
+
<ClipboardText
|
|
366
|
+
onClick={(ev) => {
|
|
367
|
+
ev.stopPropagation(); // Prevent select row.
|
|
368
|
+
void navigator.clipboard.writeText(value.toHex());
|
|
369
|
+
}}
|
|
370
|
+
/>
|
|
371
|
+
</Tooltip.Content>
|
|
372
|
+
</Tooltip.Root>
|
|
373
|
+
</Tooltip.Provider>
|
|
415
374
|
);
|
|
416
375
|
},
|
|
417
376
|
});
|
|
418
377
|
}
|
|
419
378
|
|
|
420
379
|
/**
|
|
421
|
-
*
|
|
380
|
+
* Row selector
|
|
422
381
|
*/
|
|
423
|
-
|
|
382
|
+
selectRow({
|
|
383
|
+
label,
|
|
384
|
+
classNames,
|
|
385
|
+
onUpdate,
|
|
386
|
+
id = 'selectRow',
|
|
387
|
+
...props
|
|
388
|
+
}: SelectRowColumnOptions<TData> = {}): Parameters<ColumnHelper<TData>['display']>[0] {
|
|
389
|
+
return {
|
|
390
|
+
id,
|
|
391
|
+
size: 32,
|
|
392
|
+
minSize: 32,
|
|
393
|
+
maxSize: 32,
|
|
394
|
+
meta: { onUpdate, cell: { classNames } },
|
|
395
|
+
header: ({ table }) => {
|
|
396
|
+
const { rowsSelectable } = useTableContext('HELPER_SELECT_ROW_HEADER_CELL');
|
|
397
|
+
const checked = table.getIsSomeRowsSelected() ? 'indeterminate' : table.getIsAllRowsSelected();
|
|
398
|
+
return rowsSelectable === 'multi' ? (
|
|
399
|
+
<Input.Root>
|
|
400
|
+
<Input.Checkbox
|
|
401
|
+
size={4}
|
|
402
|
+
classNames={['mli-auto', classNames]}
|
|
403
|
+
checked={checked}
|
|
404
|
+
onCheckedChange={() => table.toggleAllRowsSelected()}
|
|
405
|
+
/>
|
|
406
|
+
</Input.Root>
|
|
407
|
+
) : null;
|
|
408
|
+
},
|
|
409
|
+
cell: RowSelectorBuilderCell,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Switch
|
|
415
|
+
*/
|
|
416
|
+
switch({ label, classNames, onUpdate, ...props }: SwitchColumnOptions<TData> = {}): Partial<
|
|
424
417
|
ColumnDef<TData, boolean>
|
|
425
418
|
> {
|
|
426
419
|
return defaults(props, {
|
|
427
|
-
size:
|
|
428
|
-
minSize:
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
// TODO(burdon): Center.
|
|
433
|
-
return (
|
|
434
|
-
<div className='flex grow justify-center'>
|
|
435
|
-
<Input.Root>
|
|
436
|
-
<Input.Checkbox
|
|
437
|
-
onClick={(event) => event.stopPropagation()}
|
|
438
|
-
classNames={className}
|
|
439
|
-
disabled={!onUpdate}
|
|
440
|
-
checked={!!value}
|
|
441
|
-
onCheckedChange={(value) => {
|
|
442
|
-
onUpdate?.(cell.row.original, cell.column.id, !!value);
|
|
443
|
-
}}
|
|
444
|
-
/>
|
|
445
|
-
</Input.Root>
|
|
446
|
-
</div>
|
|
447
|
-
);
|
|
448
|
-
},
|
|
420
|
+
size: 50,
|
|
421
|
+
minSize: 50,
|
|
422
|
+
meta: { onUpdate, cell: { classNames: [textPadding, classNames] } },
|
|
423
|
+
header: (column) => label ?? column.header.id,
|
|
424
|
+
cell: SwitchBuilderCell,
|
|
449
425
|
});
|
|
450
426
|
}
|
|
451
427
|
|
|
@@ -461,17 +437,9 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
461
437
|
cell: (cell) => {
|
|
462
438
|
const value = cell.getValue();
|
|
463
439
|
if (value) {
|
|
464
|
-
return (
|
|
465
|
-
<div className='flex grow justify-center'>
|
|
466
|
-
<IconOn className={mx(getSize(6), on?.className ?? 'text-green-700')} />
|
|
467
|
-
</div>
|
|
468
|
-
);
|
|
440
|
+
return <IconOn className={mx('block mli-auto', getSize(6), on?.className ?? 'text-green-700')} />;
|
|
469
441
|
} else if (value === false) {
|
|
470
|
-
return (
|
|
471
|
-
<div className='flex grow justify-center'>
|
|
472
|
-
<IconOff className={mx(getSize(6), off?.className ?? 'text-red-700')} />
|
|
473
|
-
</div>
|
|
474
|
-
);
|
|
442
|
+
return <IconOff className={mx('block mli-auto', getSize(6), off?.className ?? 'text-red-700')} />;
|
|
475
443
|
} else {
|
|
476
444
|
return null;
|
|
477
445
|
}
|