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