@react-fabric/data 1.0.0-beta.5 → 1.0.0-beta.51

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 (88) hide show
  1. package/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
  2. package/css/styles.css +24 -0
  3. package/dist/cjs/checklist/CheckList.js +25 -16
  4. package/dist/cjs/histogram/Histogram.js +20 -10
  5. package/dist/cjs/hooks/usePagination.js +7 -4
  6. package/dist/cjs/json/JsonViewer.js +40 -27
  7. package/dist/cjs/table/AddColumn.js +1 -1
  8. package/dist/cjs/table/BodyCell.js +38 -5
  9. package/dist/cjs/table/BodyRow.js +107 -0
  10. package/dist/cjs/table/CheckboxCell.js +1 -1
  11. package/dist/cjs/table/ColumnFilters.js +97 -0
  12. package/dist/cjs/table/HeaderCell.js +70 -37
  13. package/dist/cjs/table/Table.js +113 -71
  14. package/dist/cjs/table/useResizer.js +4 -4
  15. package/dist/cjs/table/useVirtualTable.js +122 -18
  16. package/dist/cjs/tree/TreeNode.js +45 -25
  17. package/dist/cjs/tree/TreePanel.js +28 -10
  18. package/dist/cjs/tree/useTree.js +97 -15
  19. package/dist/cjs/tree/utils.js +20 -5
  20. package/dist/cjs/virtual/Gallery.js +51 -35
  21. package/dist/cjs/virtual/List.js +44 -31
  22. package/dist/esm/checklist/CheckList.js +27 -18
  23. package/dist/esm/histogram/Histogram.js +21 -11
  24. package/dist/esm/hooks/usePagination.js +7 -4
  25. package/dist/esm/json/JsonViewer.js +41 -28
  26. package/dist/esm/table/AddColumn.js +1 -1
  27. package/dist/esm/table/BodyCell.js +38 -5
  28. package/dist/esm/table/BodyRow.js +107 -0
  29. package/dist/esm/table/CheckboxCell.js +1 -1
  30. package/dist/esm/table/ColumnFilters.js +97 -0
  31. package/dist/esm/table/HeaderCell.js +71 -38
  32. package/dist/esm/table/Table.js +115 -73
  33. package/dist/esm/table/useResizer.js +4 -4
  34. package/dist/esm/table/useVirtualTable.js +124 -20
  35. package/dist/esm/tree/TreeNode.js +47 -27
  36. package/dist/esm/tree/TreePanel.js +28 -10
  37. package/dist/esm/tree/useTree.js +99 -17
  38. package/dist/esm/tree/utils.js +20 -5
  39. package/dist/esm/virtual/Gallery.js +51 -35
  40. package/dist/esm/virtual/List.js +44 -31
  41. package/dist/types/checklist/CheckList.d.ts +2 -2
  42. package/dist/types/histogram/Histogram.d.ts +3 -4
  43. package/dist/types/index.d.ts +1 -1
  44. package/dist/types/json/JsonViewer.d.ts +0 -1
  45. package/dist/types/table/AddColumn.d.ts +2 -2
  46. package/dist/types/table/BodyCell.d.ts +4 -3
  47. package/dist/types/table/BodyRow.d.ts +20 -0
  48. package/dist/types/table/ColumnFilters.d.ts +9 -0
  49. package/dist/types/table/EmptyCell.d.ts +2 -2
  50. package/dist/types/table/HeaderCell.d.ts +2 -2
  51. package/dist/types/table/Table.d.ts +1 -1
  52. package/dist/types/table/types.d.ts +20 -7
  53. package/dist/types/table/useTableColumns.d.ts +6 -6
  54. package/dist/types/table/useVirtualTable.d.ts +23 -7
  55. package/dist/types/tree/TreeNode.d.ts +1 -1
  56. package/dist/types/tree/TreePanel.d.ts +1 -1
  57. package/dist/types/tree/types.d.ts +25 -5
  58. package/dist/types/tree/useTree.d.ts +2 -9
  59. package/dist/types/tree/utils.d.ts +4 -1
  60. package/dist/types/virtual/Gallery.d.ts +12 -4
  61. package/dist/types/virtual/List.d.ts +8 -4
  62. package/package.json +13 -10
  63. package/src/checklist/CheckList.tsx +81 -77
  64. package/src/histogram/Histogram.tsx +17 -11
  65. package/src/hooks/usePagination.tsx +13 -7
  66. package/src/index.ts +1 -1
  67. package/src/json/JsonViewer.tsx +45 -35
  68. package/src/table/AddColumn.tsx +3 -3
  69. package/src/table/BodyCell.tsx +34 -5
  70. package/src/table/BodyRow.tsx +143 -0
  71. package/src/table/CheckboxCell.tsx +1 -1
  72. package/src/table/ColumnFilters.tsx +105 -0
  73. package/src/table/EmptyCell.tsx +2 -2
  74. package/src/table/HeaderCell.tsx +72 -14
  75. package/src/table/Table.tsx +164 -77
  76. package/src/table/types.ts +24 -6
  77. package/src/table/useResizer.ts +5 -3
  78. package/src/table/useTableColumns.ts +7 -7
  79. package/src/table/useVirtualTable.ts +146 -24
  80. package/src/tree/TreeNode.tsx +98 -56
  81. package/src/tree/TreePanel.tsx +23 -3
  82. package/src/tree/types.ts +32 -4
  83. package/src/tree/useTree.ts +109 -26
  84. package/src/tree/utils.ts +23 -8
  85. package/src/virtual/Gallery.tsx +63 -42
  86. package/src/virtual/List.tsx +44 -32
  87. package/dist/cjs/histogram/Histogram.module.css +0 -1
  88. package/dist/esm/histogram/Histogram.module.css +0 -1
@@ -21,12 +21,25 @@
21
21
  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  */
23
23
 
24
- import { EmptyContent, Loading, useDebounce } from "@react-fabric/core";
24
+ import {
25
+ CoreIcons,
26
+ EmptyContent,
27
+ Icon,
28
+ Loading,
29
+ useDebounce,
30
+ } from "@react-fabric/core";
31
+ import { debounce } from "@react-fabric/utilities";
25
32
  import classNames from "classnames";
26
- import { useImperativeHandle, useMemo, useRef } from "react";
33
+ import {
34
+ useEffect,
35
+ useImperativeHandle,
36
+ useLayoutEffect,
37
+ useMemo,
38
+ useRef,
39
+ } from "react";
27
40
  import { useTranslation } from "react-i18next";
28
41
  import { AddColumn } from "./AddColumn";
29
- import { BodyCell } from "./BodyCell";
42
+ import { BodyRow } from "./BodyRow";
30
43
  import { CheckboxCell } from "./CheckboxCell";
31
44
  import { TableProvider } from "./Context";
32
45
  import { EmptyCell } from "./EmptyCell";
@@ -39,12 +52,21 @@ export const Table = <T extends KeyValue = KeyValue>({
39
52
  ref,
40
53
  data,
41
54
  columns,
42
- keyProperty,
55
+ keyProperty = "id",
56
+ groupProperty,
43
57
  checkableRows,
58
+ initialScroll,
44
59
  hideableColumns,
45
60
  sort,
46
61
  loading,
62
+ checked,
63
+ emptyDisplay,
64
+ groupRenderer,
65
+ canExpand,
66
+ children,
47
67
  onSort,
68
+ onFilter,
69
+ onScroll,
48
70
  onRowClick,
49
71
  onCheckedChanged,
50
72
  }: TableProps<T>) => {
@@ -58,13 +80,24 @@ export const Table = <T extends KeyValue = KeyValue>({
58
80
  const {
59
81
  scrollerRef,
60
82
  items,
83
+ top,
61
84
  checkState,
85
+ virtualizer,
86
+ hasActiveSticky,
87
+ toggleExpand,
62
88
  toggleChecked,
63
89
  toggleAllChecked,
90
+ toggleGroupExpand,
64
91
  getData,
65
92
  totalSize,
93
+ isActiveSticky,
66
94
  measureElement,
67
- } = useVirtualTable(data, keyProperty, fireCheckChanged);
95
+ } = useVirtualTable(data, {
96
+ checked,
97
+ keyProperty,
98
+ groupProperty,
99
+ onCheckChanged: fireCheckChanged,
100
+ });
68
101
  const refBody = useRef<HTMLDivElement>(null);
69
102
 
70
103
  const wrapperStart = useMemo(
@@ -76,32 +109,51 @@ export const Table = <T extends KeyValue = KeyValue>({
76
109
  [],
77
110
  );
78
111
 
112
+ useLayoutEffect(() => {
113
+ setTimeout(() => {
114
+ initialScroll &&
115
+ virtualizer.scrollToIndex(initialScroll, { align: "start" });
116
+ }, 100);
117
+ }, []);
118
+
79
119
  useImperativeHandle(
80
120
  ref,
81
121
  () => ({
82
- hilight: (row: number) => {
83
- refBody.current
84
- ?.querySelectorAll<HTMLElement>(`.datatable-row[data-hilight="true"]`)
85
- .forEach((el) => el && (el.dataset.hilight = undefined));
86
- const el = refBody.current?.querySelector<HTMLElement>(
87
- `[data-row="${row}"]`,
88
- );
89
- el?.scrollIntoView({ behavior: "smooth", block: "center" });
90
- el && (el.dataset.hilight = "true");
122
+ scrollTo: (row: number) => {
123
+ virtualizer.scrollToIndex(row, { align: "start" });
91
124
  },
92
- unhilight: () => {
93
- refBody.current
94
- ?.querySelectorAll<HTMLElement>(`.datatable-row[data-hilight="true"]`)
95
- .forEach((el) => el && (el.dataset.hilight = undefined));
125
+ hilight(row) {
126
+ scrollerRef.current
127
+ ?.querySelector(".hilight")
128
+ ?.classList.remove("hilight", "ring-1");
129
+ virtualizer.scrollToIndex(Math.floor(row), {
130
+ align: "start",
131
+ });
132
+ setTimeout(() => {
133
+ scrollerRef.current
134
+ ?.querySelector(`[data-index="${row}"]`)
135
+ ?.classList.add("hilight", "ring-1");
136
+ }, 100);
96
137
  },
97
- scrollTo: (row: number) => {
98
- const el = refBody.current?.querySelector(`[data-row="${row}"]`);
99
- el?.scrollIntoView({ behavior: "smooth" });
138
+ unhilight() {
139
+ scrollerRef.current
140
+ ?.querySelector(".hilight")
141
+ ?.classList.remove("hilight", "ring-1");
100
142
  },
101
143
  }),
102
144
  [],
103
145
  );
104
146
 
147
+ useEffect(() => {
148
+ const handle = debounce((top: number) => {
149
+ onScroll?.(top ?? 0);
150
+ });
151
+ handle(top);
152
+ return () => {
153
+ handle.cancel();
154
+ };
155
+ }, [top]);
156
+
105
157
  return (
106
158
  <TableProvider>
107
159
  <div
@@ -109,10 +161,15 @@ export const Table = <T extends KeyValue = KeyValue>({
109
161
  style={{
110
162
  gridTemplate: `"head" auto "loader" auto "content" 1fr "foot" auto/1fr`,
111
163
  }}
164
+ data-ref="tableScroller"
112
165
  ref={scrollerRef}
113
166
  >
114
- <div className="bg-base font-medium sticky top-0 area-head border-b select-none flex flex-nowrap z-2">
167
+ <div
168
+ data-ref="tableHeader"
169
+ className="bg-base font-medium sticky top-0 h-8 area-head border-b select-none flex flex-nowrap z-5"
170
+ >
115
171
  <div className={wrapperStart}>
172
+ {canExpand && <div className="w-6" />}
116
173
  {checkableRows && (
117
174
  <CheckboxCell
118
175
  state={checkState.allChecked}
@@ -120,15 +177,33 @@ export const Table = <T extends KeyValue = KeyValue>({
120
177
  />
121
178
  )}
122
179
  {state.start?.map((col, idx) => (
123
- <HeaderCell key={idx} {...col} sort={sort} onSort={onSort} />
180
+ <HeaderCell
181
+ key={idx}
182
+ {...col}
183
+ sort={sort}
184
+ onSort={onSort}
185
+ onFilter={onFilter}
186
+ />
124
187
  ))}
125
188
  </div>
126
189
  {state.cols?.map((col, idx) => (
127
- <HeaderCell key={idx} {...col} sort={sort} onSort={onSort} />
190
+ <HeaderCell
191
+ key={idx}
192
+ {...col}
193
+ sort={sort}
194
+ onSort={onSort}
195
+ onFilter={onFilter}
196
+ />
128
197
  ))}
129
198
  <div className={wrapperEnd}>
130
199
  {state.end?.map((col, idx) => (
131
- <HeaderCell key={idx} {...col} sort={sort} onSort={onSort} />
200
+ <HeaderCell
201
+ key={idx}
202
+ {...col}
203
+ sort={sort}
204
+ onSort={onSort}
205
+ onFilter={onFilter}
206
+ />
132
207
  ))}
133
208
 
134
209
  {hideableColumns && (
@@ -142,66 +217,76 @@ export const Table = <T extends KeyValue = KeyValue>({
142
217
  {loading && <Loading />}
143
218
  {data.length > 0 && (
144
219
  <div
145
- className="area-content flex flex-col flex-nowrap"
220
+ className="area-content flex flex-col flex-nowrap min-h-full"
146
221
  style={{ height: totalSize() }}
147
222
  ref={refBody}
148
223
  >
149
- <div style={{ height: items[0]?.start }} />
224
+ <div
225
+ style={{
226
+ height: items[hasActiveSticky ? 1 : 0]?.start,
227
+ }}
228
+ />
150
229
  {items.map(({ index, key }) => {
151
230
  const data = getData(index);
152
- return (
231
+ return "__GROUP__" in data ? (
153
232
  <div
154
233
  key={key}
155
- role="none"
156
- data-row={index}
234
+ ref={measureElement}
235
+ data-index={index}
157
236
  className={classNames(
158
- "flex flex-nowrap datatable-row data-[hilight]:outline-1 -outline-offset-1 outline-primary-500",
159
- index % 2 ? "bg-even" : "bg-odd",
160
- onRowClick && "hover:bg-primary-50 active:bg-primary-100",
237
+ "px-2 py-1 text-md bg-base border-y border-tint-100 cursor-pointer flex items-center gap-1 z-3",
238
+ isActiveSticky(index) && "sticky top-8",
161
239
  )}
162
- onClick={() =>
163
- checkableRows && keyProperty && !onRowClick
164
- ? toggleChecked(data[keyProperty])
165
- : onRowClick?.(data)
166
- }
167
- data-index={index}
168
- ref={measureElement}
240
+ role="none"
241
+ onClick={() => toggleGroupExpand(data.key)}
169
242
  >
170
- <div className={wrapperStart}>
171
- {checkableRows && keyProperty && (
172
- <CheckboxCell
173
- onClick={() => toggleChecked(data[keyProperty])}
174
- state={
175
- checkState.checked.includes(data[keyProperty]) ? 1 : 0
176
- }
177
- />
178
- )}
179
- {state.start?.map((col, ids) => (
180
- <BodyCell
181
- key={`${key}:${ids}`}
182
- column={col}
183
- item={data}
184
- />
185
- ))}
186
- </div>
187
- {state.cols?.map((col, idc) => (
188
- <BodyCell key={`${key}:${idc}`} column={col} item={data} />
189
- ))}
190
- <div className={wrapperEnd}>
191
- {state.end?.map((col, ide) => (
192
- <BodyCell
193
- key={`${key}:${ide}`}
194
- column={col}
195
- item={data}
196
- />
197
- ))}
198
- {hideableColumns && <div className="w-6" />}
199
- </div>
243
+ <Icon
244
+ rtlFlip
245
+ icon={
246
+ data.open ? CoreIcons.chevronDown : CoreIcons.chevronRight
247
+ }
248
+ />
249
+ {groupRenderer?.(data) ?? (
250
+ <div className="flex gap-1 items-center">
251
+ <span>{data.key}</span>
252
+ <span className="text-sm">({data.itemCount})</span>
253
+ </div>
254
+ )}
200
255
  </div>
256
+ ) : (
257
+ <BodyRow
258
+ rowKey={key.toString()}
259
+ index={index}
260
+ checkableRows={checkableRows}
261
+ checked={
262
+ keyProperty &&
263
+ checkState.checked.includes(data[keyProperty])
264
+ ? 1
265
+ : 0
266
+ }
267
+ expanded={
268
+ !!keyProperty &&
269
+ checkState.expanded.includes(data[keyProperty])
270
+ }
271
+ data={data}
272
+ hideableColumns={hideableColumns}
273
+ keyProperty={keyProperty}
274
+ measureElement={measureElement}
275
+ onRowClick={onRowClick}
276
+ state={state}
277
+ canExpand={canExpand}
278
+ toggleExpand={toggleExpand}
279
+ expandRow={children}
280
+ toggleChecked={toggleChecked}
281
+ wrapperEnd={wrapperEnd}
282
+ wrapperStart={wrapperStart}
283
+ key={key}
284
+ />
201
285
  );
202
286
  })}
203
287
  <div className="flex-1 flex flex-nowrap">
204
288
  <div className={wrapperStart}>
289
+ {canExpand && <div className="w-6" />}
205
290
  {checkableRows && <div className="w-6" />}
206
291
  {state.start?.map((col) => (
207
292
  <EmptyCell key={String(col.id)} {...col} />
@@ -220,13 +305,15 @@ export const Table = <T extends KeyValue = KeyValue>({
220
305
  </div>
221
306
  </div>
222
307
  )}
223
- {!loading && data.length === 0 && (
224
- <EmptyContent
225
- size="sm"
226
- className="bg-dimmed"
227
- message={t("datagrid.empty")}
228
- />
229
- )}
308
+ {!loading &&
309
+ data.length === 0 &&
310
+ (emptyDisplay ?? (
311
+ <EmptyContent
312
+ size="sm"
313
+ className="bg-dimmed"
314
+ message={t("datagrid.empty")}
315
+ />
316
+ ))}
230
317
  </div>
231
318
  </TableProvider>
232
319
  );
@@ -23,6 +23,7 @@
23
23
 
24
24
  import {
25
25
  type CssProp,
26
+ type Elements,
26
27
  type IconProps,
27
28
  type RefProp,
28
29
  type TestProps,
@@ -33,21 +34,24 @@ export const COL_MIN_WIDTH = "3rem";
33
34
  export const COL_MAX_WIDTH = "64rem";
34
35
  export const COL_DEFAULT_WIDTH = "12rem";
35
36
 
36
- export interface ColumnType<T = KeyValue>
37
+ export interface TableColumn<T = KeyValue>
37
38
  extends CssProp,
38
39
  IconProps,
39
40
  TestProps {
40
41
  id: keyof T | string;
41
42
  label?: string;
42
- actions?: ReactElement;
43
+ tooltip?: string;
44
+ actions?: ReactElement[];
43
45
  align?: "start" | "center" | "end";
44
46
  locked?: "start" | "end";
45
47
  hidden?: boolean;
48
+ truncate?: boolean;
46
49
  hideable?: boolean;
47
50
  resizable?: boolean;
48
51
  sortable?: boolean;
49
52
  filterable?: boolean;
50
- filterList?: AnyObject[];
53
+ filter?: AnyObject;
54
+ filterOptions?: AnyObject[];
51
55
  width?: string | number;
52
56
  minWidth?: string | number;
53
57
  maxWidth?: string | number;
@@ -60,7 +64,8 @@ export interface ColumnType<T = KeyValue>
60
64
  | "time"
61
65
  | "unknown";
62
66
  format?: string;
63
- renderer?: (value: AnyObject, data: T) => void;
67
+ valueMap?: KeyValue;
68
+ renderer?: (value: AnyObject, data: T, index: number) => Elements<AnyObject>;
64
69
  }
65
70
 
66
71
  export interface TableRef {
@@ -70,14 +75,19 @@ export interface TableRef {
70
75
  }
71
76
 
72
77
  export interface TableProps<T = KeyValue> extends RefProp<TableRef> {
73
- columns: Array<ColumnType<T>>;
78
+ columns: Array<TableColumn<T>>;
74
79
  data: T[];
75
80
  keyProperty?: keyof T;
81
+ groupProperty?: keyof T;
82
+ groupRenderer?: (key: AnyObject) => ReactElement;
83
+ emptyDisplay?: ReactElement;
84
+
76
85
  /**
77
86
  * checkable rows
78
87
  */
88
+ checked?: AnyObject[];
79
89
  checkableRows?: boolean;
80
- onCheckedChanged?: (rows: Array<keyof T>) => void;
90
+ onCheckedChanged?: (rows: AnyObject[]) => void;
81
91
  /**
82
92
  * sorting
83
93
  */
@@ -88,4 +98,12 @@ export interface TableProps<T = KeyValue> extends RefProp<TableRef> {
88
98
  hideableColumns?: boolean;
89
99
 
90
100
  onRowClick?: (data: T) => void;
101
+
102
+ initialScroll?: number;
103
+ onScroll?: (firstItem: number) => void;
104
+
105
+ canExpand?: (record: T) => boolean;
106
+ children?: (record: T) => Elements<AnyObject>;
107
+
108
+ onFilter?: (id: keyof T | string, value?: AnyObject) => void;
91
109
  }
@@ -47,13 +47,15 @@ export const useResizer = (
47
47
  /** ***************** attach handlers *******************/
48
48
  const box = colEl.getBoundingClientRect();
49
49
  if (colEl.parentElement != null) {
50
- const parentBox = colEl.parentElement?.getBoundingClientRect();
50
+ const parentBox = colEl
51
+ .closest("[data-ref='tableScroller']")
52
+ ?.getBoundingClientRect() as unknown as DOMRect;
51
53
  if (isRtl) {
52
54
  placeholder.style.left = "unset";
53
- placeholder.style.right = `${box.right - parentBox.right}px`;
55
+ placeholder.style.right = `${parentBox.width - box.right}px`;
54
56
  } else {
55
57
  placeholder.style.right = "unset";
56
- placeholder.style.left = `${box.left - parentBox.left}px`;
58
+ placeholder.style.left = `${box.left}px`;
57
59
  }
58
60
  }
59
61
  placeholder.style.width = `${colEl.offsetWidth}px`;
@@ -23,20 +23,20 @@
23
23
 
24
24
  import { groupBy } from "@react-fabric/utilities";
25
25
  import { useEffect, useReducer } from "react";
26
- import { type ColumnType } from "./types";
26
+ import { type TableColumn } from "./types";
27
27
 
28
28
  interface ColumnState {
29
- columns: ColumnType[];
30
- start: ColumnType[];
31
- end: ColumnType[];
32
- cols: ColumnType[];
29
+ columns: TableColumn[];
30
+ start: TableColumn[];
31
+ end: TableColumn[];
32
+ cols: TableColumn[];
33
33
  }
34
34
 
35
35
  type ColumnActions =
36
- | { type: "columns"; columns: ColumnType[] }
36
+ | { type: "columns"; columns: TableColumn[] }
37
37
  | { type: "hide"; id: string };
38
38
 
39
- export const useTableColumns = (columns: ColumnType[]) => {
39
+ export const useTableColumns = (columns: TableColumn[]) => {
40
40
  const [state, dispatch] = useReducer(
41
41
  (state: ColumnState, action: ColumnActions) => {
42
42
  if (action.type === "columns") {