@dxos/react-ui-table 0.3.8-next.f4e0086 → 0.3.8
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 +511 -462
- 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 +37 -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 +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/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 +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 -1
- package/src/helpers.tsx +255 -206
- package/src/theme.ts +95 -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,28 @@
|
|
|
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
16
|
import defaultsDeep from 'lodash.defaultsdeep';
|
|
11
17
|
import React, { useRef, useState } from 'react';
|
|
12
18
|
|
|
13
19
|
import { type PublicKey } from '@dxos/keys';
|
|
14
|
-
import { Input, Tooltip } from '@dxos/react-ui';
|
|
20
|
+
import { type ClassNameValue, Input, Tooltip } from '@dxos/react-ui';
|
|
15
21
|
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
16
22
|
import { stripUndefinedValues } from '@dxos/util';
|
|
17
23
|
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
};
|
|
24
|
+
import { CellCombobox } from './components';
|
|
25
|
+
import { useTableContext } from './components/Table/TableContext';
|
|
26
|
+
import { textPadding } from './theme';
|
|
35
27
|
|
|
36
28
|
export type ValueUpdater<TData extends RowData, TValue> = (row: TData, id: string, value: TValue) => void;
|
|
37
29
|
|
|
@@ -46,22 +38,23 @@ export const createColumnBuilder = <TData extends RowData>() => ({
|
|
|
46
38
|
export type BaseColumnOptions<TData, TValue> = Partial<ColumnDef<TData, TValue>> & {
|
|
47
39
|
meta?: ColumnMeta<TData, TValue>;
|
|
48
40
|
label?: string;
|
|
49
|
-
|
|
41
|
+
classNames?: ClassNameValue;
|
|
50
42
|
onUpdate?: ValueUpdater<TData, TValue | undefined>;
|
|
51
43
|
};
|
|
52
44
|
|
|
53
45
|
// TODO(burdon): Better abstraction?
|
|
54
|
-
export type
|
|
46
|
+
export type SearchListQueryModel<TData extends RowData> = {
|
|
55
47
|
getId(object: TData): string;
|
|
56
48
|
getText(object: TData): string;
|
|
57
49
|
query(text?: string): Promise<TData[]>;
|
|
58
50
|
};
|
|
59
51
|
|
|
60
|
-
export type
|
|
61
|
-
model:
|
|
52
|
+
export type ComboboxColumnOptions<TData extends RowData> = BaseColumnOptions<TData, any> & {
|
|
53
|
+
model: SearchListQueryModel<TData>;
|
|
62
54
|
};
|
|
63
55
|
|
|
64
56
|
export type StringColumnOptions<TData extends RowData> = BaseColumnOptions<TData, string> & {};
|
|
57
|
+
export type SelectRowColumnOptions<TData extends RowData> = BaseColumnOptions<TData, string> & {};
|
|
65
58
|
|
|
66
59
|
export type NumberColumnOptions<TData extends RowData> = BaseColumnOptions<TData, number> & {
|
|
67
60
|
digits?: number;
|
|
@@ -76,7 +69,7 @@ export type DateColumnOptions<TData extends RowData> = BaseColumnOptions<TData,
|
|
|
76
69
|
relative?: boolean;
|
|
77
70
|
};
|
|
78
71
|
|
|
79
|
-
export type
|
|
72
|
+
export type SwitchColumnOptions<TData extends RowData> = BaseColumnOptions<TData, boolean> & {};
|
|
80
73
|
|
|
81
74
|
export type IconColumnOptions<TData extends RowData> = BaseColumnOptions<TData, boolean> & {
|
|
82
75
|
on?: {
|
|
@@ -96,31 +89,178 @@ const defaults = <TData extends RowData, TValue>(
|
|
|
96
89
|
return stripUndefinedValues(defaultsDeep({}, options, ...sources));
|
|
97
90
|
};
|
|
98
91
|
|
|
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);
|
|
119
|
+
};
|
|
120
|
+
|
|
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
|
+
);
|
|
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
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
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>
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
99
234
|
/**
|
|
100
235
|
* Util to create column definitions.
|
|
101
236
|
*/
|
|
102
237
|
export class ColumnBuilder<TData extends RowData> {
|
|
103
238
|
/**
|
|
104
|
-
*
|
|
239
|
+
* Combobox value
|
|
105
240
|
*/
|
|
106
|
-
|
|
241
|
+
combobox({
|
|
242
|
+
label,
|
|
243
|
+
classNames,
|
|
244
|
+
model,
|
|
245
|
+
onUpdate,
|
|
246
|
+
...props
|
|
247
|
+
}: ComboboxColumnOptions<TData>): Partial<ColumnDef<TData, any>> {
|
|
107
248
|
return defaults(props, {
|
|
108
249
|
minSize: 100,
|
|
250
|
+
meta: {
|
|
251
|
+
model,
|
|
252
|
+
onUpdate,
|
|
253
|
+
cell: { classNames },
|
|
254
|
+
},
|
|
109
255
|
header: (column) => {
|
|
110
|
-
return
|
|
256
|
+
return label ?? column.header.id;
|
|
111
257
|
},
|
|
112
258
|
cell:
|
|
113
259
|
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>;
|
|
260
|
+
? ComboboxBuilderCell
|
|
261
|
+
: (cellContext) => {
|
|
262
|
+
const cellValue = cellContext.getValue();
|
|
263
|
+
return <div className={mx('truncate', classNames)}>{cellValue ? model?.getText(cellValue) : ''}</div>;
|
|
124
264
|
},
|
|
125
265
|
});
|
|
126
266
|
}
|
|
@@ -128,69 +268,21 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
128
268
|
/**
|
|
129
269
|
* String formats.
|
|
130
270
|
*/
|
|
131
|
-
string({ label,
|
|
271
|
+
string({ label, classNames, onUpdate, ...props }: StringColumnOptions<TData> = {}): Partial<
|
|
272
|
+
ColumnDef<TData, string>
|
|
273
|
+
> {
|
|
132
274
|
return defaults(props, {
|
|
133
275
|
minSize: 100,
|
|
276
|
+
meta: { onUpdate, cell: { classNames } },
|
|
134
277
|
// TODO(burdon): Default.
|
|
135
278
|
header: (column) => {
|
|
136
|
-
return
|
|
279
|
+
return label ?? column.header.id;
|
|
137
280
|
},
|
|
138
281
|
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
|
-
}
|
|
282
|
+
? StringBuilderCell
|
|
191
283
|
: (cell) => {
|
|
192
284
|
const value = cell.getValue();
|
|
193
|
-
return <div className={mx('truncate',
|
|
285
|
+
return <div className={mx('truncate', textPadding, classNames)}>{value}</div>;
|
|
194
286
|
},
|
|
195
287
|
});
|
|
196
288
|
}
|
|
@@ -198,65 +290,15 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
198
290
|
/**
|
|
199
291
|
* Number formats.
|
|
200
292
|
*/
|
|
201
|
-
number({ label, minSize,
|
|
293
|
+
number({ label, minSize, classNames, digits, onUpdate, ...props }: NumberColumnOptions<TData> = {}): Partial<
|
|
202
294
|
ColumnDef<TData, number>
|
|
203
295
|
> {
|
|
204
296
|
return defaults(props, {
|
|
205
297
|
size: 100,
|
|
206
298
|
minSize: 100,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const [text, setText] = useState<string>();
|
|
211
|
-
|
|
212
|
-
const handleEdit = () => {
|
|
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
|
-
);
|
|
259
|
-
},
|
|
299
|
+
meta: { onUpdate, digits, header: { classNames: 'text-end' }, cell: { classNames } },
|
|
300
|
+
header: (cell) => label ?? cell.header.id,
|
|
301
|
+
cell: NumberBuilderCell,
|
|
260
302
|
});
|
|
261
303
|
}
|
|
262
304
|
|
|
@@ -264,13 +306,14 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
264
306
|
* Date formats.
|
|
265
307
|
*/
|
|
266
308
|
// TODO(burdon): Date picker (pluggable renderers?)
|
|
267
|
-
date({ label, format: formatSpec, relative,
|
|
309
|
+
date({ label, format: formatSpec, relative, classNames, ...props }: DateColumnOptions<TData> = {}): Partial<
|
|
268
310
|
ColumnDef<TData, Date>
|
|
269
311
|
> {
|
|
270
312
|
return defaults(props, {
|
|
271
313
|
size: 220, // TODO(burdon): Depends on format.
|
|
272
314
|
minSize: 100,
|
|
273
|
-
|
|
315
|
+
meta: { cell: { classNames } },
|
|
316
|
+
header: (cell) => label ?? cell.header.id,
|
|
274
317
|
cell: (cell) => {
|
|
275
318
|
const value = cell.getValue();
|
|
276
319
|
|
|
@@ -283,7 +326,7 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
283
326
|
: value.toISOString()
|
|
284
327
|
: undefined;
|
|
285
328
|
|
|
286
|
-
return <div className={mx(
|
|
329
|
+
return <div className={mx(textPadding, classNames)}>{str}</div>;
|
|
287
330
|
} catch (err) {
|
|
288
331
|
console.log(value);
|
|
289
332
|
return null;
|
|
@@ -297,9 +340,10 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
297
340
|
*/
|
|
298
341
|
key({ label, tooltip, ...props }: KeyColumnOptions<TData> = {}): Partial<ColumnDef<TData, PublicKey>> {
|
|
299
342
|
return defaults(props, {
|
|
300
|
-
size:
|
|
301
|
-
minSize:
|
|
302
|
-
|
|
343
|
+
size: 94,
|
|
344
|
+
minSize: 94,
|
|
345
|
+
meta: { cell: { classNames: ['font-mono', textPadding] } },
|
|
346
|
+
header: (cell) => label ?? cell.header.id,
|
|
303
347
|
cell: (cell) => {
|
|
304
348
|
const value = cell.getValue();
|
|
305
349
|
if (!value) {
|
|
@@ -307,64 +351,77 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
307
351
|
}
|
|
308
352
|
|
|
309
353
|
// 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
|
-
);
|
|
354
|
+
const element = value.truncate();
|
|
313
355
|
if (!tooltip) {
|
|
314
356
|
return element;
|
|
315
357
|
}
|
|
316
358
|
|
|
317
359
|
return (
|
|
318
|
-
<
|
|
319
|
-
<Tooltip.
|
|
320
|
-
<Tooltip.
|
|
321
|
-
|
|
322
|
-
<Tooltip.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
</Tooltip.Provider>
|
|
333
|
-
</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>
|
|
334
374
|
);
|
|
335
375
|
},
|
|
336
376
|
});
|
|
337
377
|
}
|
|
338
378
|
|
|
339
379
|
/**
|
|
340
|
-
*
|
|
380
|
+
* Row selector
|
|
381
|
+
*/
|
|
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
|
|
341
415
|
*/
|
|
342
|
-
|
|
416
|
+
switch({ label, classNames, onUpdate, ...props }: SwitchColumnOptions<TData> = {}): Partial<
|
|
343
417
|
ColumnDef<TData, boolean>
|
|
344
418
|
> {
|
|
345
419
|
return defaults(props, {
|
|
346
|
-
size:
|
|
347
|
-
minSize:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
// TODO(burdon): Center.
|
|
352
|
-
return (
|
|
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
|
-
},
|
|
420
|
+
size: 50,
|
|
421
|
+
minSize: 50,
|
|
422
|
+
meta: { onUpdate, cell: { classNames: [textPadding, classNames] } },
|
|
423
|
+
header: (column) => label ?? column.header.id,
|
|
424
|
+
cell: SwitchBuilderCell,
|
|
368
425
|
});
|
|
369
426
|
}
|
|
370
427
|
|
|
@@ -380,17 +437,9 @@ export class ColumnBuilder<TData extends RowData> {
|
|
|
380
437
|
cell: (cell) => {
|
|
381
438
|
const value = cell.getValue();
|
|
382
439
|
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
|
-
);
|
|
440
|
+
return <IconOn className={mx('block mli-auto', getSize(6), on?.className ?? 'text-green-700')} />;
|
|
388
441
|
} 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
|
-
);
|
|
442
|
+
return <IconOff className={mx('block mli-auto', getSize(6), off?.className ?? 'text-red-700')} />;
|
|
394
443
|
} else {
|
|
395
444
|
return null;
|
|
396
445
|
}
|