@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.
Files changed (57) hide show
  1. package/dist/lib/browser/index.mjs +547 -480
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/components/Cell/Cell.d.ts +12 -0
  5. package/dist/types/src/components/Cell/Cell.d.ts.map +1 -0
  6. package/dist/types/src/components/{ComboboxCell.d.ts → Cell/CellCombobox.d.ts} +4 -4
  7. package/dist/types/src/components/Cell/CellCombobox.d.ts.map +1 -0
  8. package/dist/types/src/components/Cell/index.d.ts +2 -0
  9. package/dist/types/src/components/Cell/index.d.ts.map +1 -0
  10. package/dist/types/src/components/Table/Table.d.ts +3 -0
  11. package/dist/types/src/components/Table/Table.d.ts.map +1 -0
  12. package/dist/types/src/components/{Table.stories.d.ts → Table/Table.stories.d.ts} +4 -7
  13. package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -0
  14. package/dist/types/src/components/Table/TableBody.d.ts +11 -0
  15. package/dist/types/src/components/Table/TableBody.d.ts.map +1 -0
  16. package/dist/types/src/components/Table/TableContext.d.ts +14 -0
  17. package/dist/types/src/components/Table/TableContext.d.ts.map +1 -0
  18. package/dist/types/src/components/Table/TableFooter.d.ts +7 -0
  19. package/dist/types/src/components/Table/TableFooter.d.ts.map +1 -0
  20. package/dist/types/src/components/Table/TableHead.d.ts +8 -0
  21. package/dist/types/src/components/Table/TableHead.d.ts.map +1 -0
  22. package/dist/types/src/components/Table/index.d.ts +3 -0
  23. package/dist/types/src/components/Table/index.d.ts.map +1 -0
  24. package/dist/types/src/components/Table/props.d.ts +39 -0
  25. package/dist/types/src/components/Table/props.d.ts.map +1 -0
  26. package/dist/types/src/components/index.d.ts +1 -1
  27. package/dist/types/src/components/index.d.ts.map +1 -1
  28. package/dist/types/src/helpers.d.ts +21 -15
  29. package/dist/types/src/helpers.d.ts.map +1 -1
  30. package/dist/types/src/index.d.ts +1 -0
  31. package/dist/types/src/index.d.ts.map +1 -1
  32. package/dist/types/src/theme.d.ts +30 -33
  33. package/dist/types/src/theme.d.ts.map +1 -1
  34. package/dist/types/src/types.d.ts +13 -10
  35. package/dist/types/src/types.d.ts.map +1 -1
  36. package/package.json +12 -10
  37. package/src/components/Cell/Cell.tsx +43 -0
  38. package/src/components/{ComboboxCell.tsx → Cell/CellCombobox.tsx} +6 -6
  39. package/src/components/Cell/index.ts +5 -0
  40. package/src/components/{Table.stories.tsx → Table/Table.stories.tsx} +51 -51
  41. package/src/components/Table/Table.tsx +184 -0
  42. package/src/components/Table/TableBody.tsx +70 -0
  43. package/src/components/Table/TableContext.tsx +28 -0
  44. package/src/components/Table/TableFooter.tsx +42 -0
  45. package/src/components/Table/TableHead.tsx +79 -0
  46. package/src/components/Table/index.ts +6 -0
  47. package/src/components/Table/props.ts +57 -0
  48. package/src/components/index.ts +1 -1
  49. package/src/helpers.tsx +276 -219
  50. package/src/index.ts +1 -0
  51. package/src/theme.ts +102 -63
  52. package/src/types.ts +15 -10
  53. package/dist/types/src/components/ComboboxCell.d.ts.map +0 -1
  54. package/dist/types/src/components/Table.d.ts +0 -29
  55. package/dist/types/src/components/Table.d.ts.map +0 -1
  56. package/dist/types/src/components/Table.stories.d.ts.map +0 -1
  57. 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 { createColumnHelper, type ColumnDef, type ColumnMeta, type RowData } from '@tanstack/react-table';
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
- import { stripUndefinedValues } from '@dxos/util';
17
-
18
- import { ComboboxCell } from './components';
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
- className?: string;
40
+ classNames?: ClassNameValue;
50
41
  onUpdate?: ValueUpdater<TData, TValue | undefined>;
51
42
  };
52
43
 
53
44
  // TODO(burdon): Better abstraction?
54
- export type SelectQueryModel<TData extends RowData> = {
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 SelectColumnOptions<TData extends RowData> = BaseColumnOptions<TData, any> & {
61
- model: SelectQueryModel<TData>;
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 BooleanColumnOptions<TData extends RowData> = BaseColumnOptions<TData, boolean> & {};
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 defaults = <TData extends RowData, TValue>(
93
- options: Partial<ColumnDef<TData, TValue>>,
94
- ...sources: Partial<ColumnDef<TData, TValue>>[]
95
- ): Partial<ColumnDef<TData, TValue>> => {
96
- return stripUndefinedValues(defaultsDeep({}, options, ...sources));
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
- * Select value
231
+ * Combobox value
105
232
  */
106
- select({ label, className, model, onUpdate, ...props }: SelectColumnOptions<TData>): Partial<ColumnDef<TData, any>> {
107
- return defaults(props, {
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 <div className={'truncate'}>{label ?? column.header.id}</div>;
250
+ return label ?? column.header.id;
111
251
  },
112
252
  cell:
113
253
  model && onUpdate
114
- ? (cell) => (
115
- <ComboboxCell<any>
116
- model={model}
117
- value={cell.getValue()}
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, className, onUpdate, ...props }: StringColumnOptions<TData> = {}): Partial<ColumnDef<TData, string>> {
132
- return defaults(props, {
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 <div className={'truncate'}>{label ?? column.header.id}</div>;
278
+ return label ?? column.header.id;
137
279
  },
138
280
  cell: onUpdate
139
- ? (cell) => {
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', className)}>{value}</div>;
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, className, digits, onUpdate, ...props }: NumberColumnOptions<TData> = {}): Partial<
292
+ number({ label, minSize, classNames, digits, onUpdate, ...props }: NumberColumnOptions<TData> = {}): Partial<
202
293
  ColumnDef<TData, number>
203
294
  > {
204
- return defaults(props, {
295
+ return {
296
+ ...props,
205
297
  size: 100,
206
298
  minSize: 100,
207
- header: (cell) => <div className='w-full truncate text-right'>{label ?? cell.header.id}</div>,
208
- cell: (cell) => {
209
- const value = cell.getValue();
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
- );
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, className, ...props }: DateColumnOptions<TData> = {}): Partial<
315
+ date({ label, format: formatSpec, relative, classNames, ...props }: DateColumnOptions<TData> = {}): Partial<
268
316
  ColumnDef<TData, Date>
269
317
  > {
270
- return defaults(props, {
318
+ return {
319
+ ...props,
271
320
  size: 220, // TODO(burdon): Depends on format.
272
321
  minSize: 100,
273
- header: (cell) => <div>{label ?? cell.header.id}</div>,
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('font-mono', className)}>{str}</div>;
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 defaults(props, {
300
- size: 86,
301
- minSize: 86,
302
- header: (cell) => <div>{label ?? cell.header.id}</div>,
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
- <div className='group inline-flex gap-2 items-center'>
319
- <Tooltip.Provider>
320
- <Tooltip.Root>
321
- <Tooltip.Trigger asChild>{element}</Tooltip.Trigger>
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.Root>
332
- </Tooltip.Provider>
333
- </div>
381
+ </Tooltip.Portal>
382
+ </Tooltip.Root>
383
+ </Tooltip.Provider>
334
384
  );
335
385
  },
336
- });
386
+ };
337
387
  }
338
388
 
339
389
  /**
340
- * Checkbox.
390
+ * Row selector
341
391
  */
342
- checkbox({ label, className, onUpdate, ...props }: BooleanColumnOptions<TData> = {}): Partial<
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 defaults(props, {
346
- size: 40,
347
- minSize: 40,
348
- header: (column) => <div className={'flex grow justify-center'}>{label ?? column.header.id}</div>,
349
- cell: (cell) => {
350
- const value = cell.getValue();
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
- },
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 defaults(props, {
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
  }
package/src/index.ts CHANGED
@@ -6,4 +6,5 @@ export * from './components';
6
6
  export * from './helpers';
7
7
  export * from './schema';
8
8
  export * from './theme';
9
+ export { default as translations } from './translations';
9
10
  export * from './types';