@react-fabric/data 1.0.0-beta.6 → 1.0.0-beta.61

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 (104) hide show
  1. package/{src/histogram/Histogram.module.css → css/_histogram.css} +16 -13
  2. package/{dist/esm/tree/types.js → css/styles.css} +2 -7
  3. package/dist/cjs/checklist/CheckList.js +27 -4
  4. package/dist/cjs/histogram/Histogram.js +39 -9
  5. package/dist/cjs/hooks/useFilteredList.js +88 -10
  6. package/dist/cjs/hooks/usePagination.js +27 -4
  7. package/dist/cjs/hooks/useSelectableList.js +29 -0
  8. package/dist/cjs/i18n/ar.json.js +8 -1
  9. package/dist/cjs/i18n/en.json.js +8 -1
  10. package/dist/cjs/json/JsonViewer.js +68 -28
  11. package/dist/cjs/pagination/Pagination.js +14 -4
  12. package/dist/cjs/table/AddColumn.js +3 -3
  13. package/dist/cjs/table/BodyCell.js +14 -6
  14. package/dist/cjs/table/BodyRow.js +4 -4
  15. package/dist/cjs/table/CheckboxCell.js +2 -2
  16. package/dist/cjs/table/ColumnFilters.js +97 -0
  17. package/dist/cjs/table/HeaderCell.js +71 -43
  18. package/dist/cjs/table/Table.js +62 -13
  19. package/dist/cjs/table/types.js +29 -0
  20. package/dist/cjs/table/useResizer.js +4 -4
  21. package/dist/cjs/table/useVirtualTable.js +127 -19
  22. package/dist/cjs/tree/TreeNode.js +50 -32
  23. package/dist/cjs/tree/TreePanel.js +52 -19
  24. package/dist/cjs/tree/useTree.js +199 -38
  25. package/dist/cjs/tree/utils.js +39 -12
  26. package/dist/cjs/virtual/Gallery.js +97 -45
  27. package/dist/cjs/virtual/List.js +93 -39
  28. package/dist/cjs/virtual/Scroller.js +16 -8
  29. package/dist/esm/checklist/CheckList.js +28 -5
  30. package/dist/esm/histogram/Histogram.js +41 -11
  31. package/dist/esm/hooks/useFilteredList.js +89 -11
  32. package/dist/esm/hooks/usePagination.js +27 -4
  33. package/dist/esm/hooks/useSelectableList.js +29 -0
  34. package/dist/esm/i18n/ar.json.js +8 -2
  35. package/dist/esm/i18n/en.json.js +8 -2
  36. package/dist/esm/json/JsonViewer.js +69 -29
  37. package/dist/esm/pagination/Pagination.js +15 -5
  38. package/dist/esm/table/AddColumn.js +4 -4
  39. package/dist/esm/table/BodyCell.js +15 -7
  40. package/dist/esm/table/BodyRow.js +4 -4
  41. package/dist/esm/table/CheckboxCell.js +3 -3
  42. package/dist/esm/table/ColumnFilters.js +97 -0
  43. package/dist/esm/table/HeaderCell.js +72 -44
  44. package/dist/esm/table/Table.js +63 -14
  45. package/dist/esm/table/types.js +29 -0
  46. package/dist/esm/table/useResizer.js +4 -4
  47. package/dist/esm/table/useVirtualTable.js +129 -21
  48. package/dist/esm/tree/TreeNode.js +53 -35
  49. package/dist/esm/tree/TreePanel.js +54 -21
  50. package/dist/esm/tree/useTree.js +202 -41
  51. package/dist/esm/tree/utils.js +40 -13
  52. package/dist/esm/virtual/Gallery.js +97 -45
  53. package/dist/esm/virtual/List.js +93 -39
  54. package/dist/esm/virtual/Scroller.js +17 -9
  55. package/dist/types/checklist/CheckList.d.ts +22 -0
  56. package/dist/types/histogram/Histogram.d.ts +28 -1
  57. package/dist/types/hooks/useFilteredList.d.ts +66 -5
  58. package/dist/types/hooks/usePagination.d.ts +19 -0
  59. package/dist/types/hooks/useSelectableList.d.ts +29 -0
  60. package/dist/types/json/JsonViewer.d.ts +27 -2
  61. package/dist/types/pagination/Pagination.d.ts +10 -1
  62. package/dist/types/table/ColumnFilters.d.ts +9 -0
  63. package/dist/types/table/HeaderCell.d.ts +1 -1
  64. package/dist/types/table/Table.d.ts +14 -1
  65. package/dist/types/table/types.d.ts +46 -7
  66. package/dist/types/table/useVirtualTable.d.ts +23 -9
  67. package/dist/types/tree/TreeNode.d.ts +1 -1
  68. package/dist/types/tree/TreePanel.d.ts +42 -1
  69. package/dist/types/tree/types.d.ts +65 -21
  70. package/dist/types/tree/useTree.d.ts +9 -10
  71. package/dist/types/tree/utils.d.ts +10 -3
  72. package/dist/types/virtual/Gallery.d.ts +42 -3
  73. package/dist/types/virtual/List.d.ts +42 -3
  74. package/package.json +16 -14
  75. package/src/checklist/CheckList.tsx +39 -7
  76. package/src/histogram/Histogram.tsx +43 -19
  77. package/src/hooks/useFilteredList.tsx +109 -9
  78. package/src/hooks/usePagination.tsx +32 -7
  79. package/src/hooks/useSelectableList.ts +29 -0
  80. package/src/i18n/ar.json +5 -0
  81. package/src/i18n/en.json +5 -0
  82. package/src/json/JsonViewer.tsx +76 -37
  83. package/src/pagination/Pagination.tsx +15 -5
  84. package/src/table/AddColumn.tsx +4 -4
  85. package/src/table/BodyCell.tsx +24 -7
  86. package/src/table/BodyRow.tsx +7 -4
  87. package/src/table/CheckboxCell.tsx +5 -5
  88. package/src/table/ColumnFilters.tsx +105 -0
  89. package/src/table/HeaderCell.tsx +74 -21
  90. package/src/table/Table.tsx +107 -12
  91. package/src/table/types.ts +49 -10
  92. package/src/table/useResizer.ts +5 -3
  93. package/src/table/useVirtualTable.ts +158 -27
  94. package/src/tree/TreeNode.tsx +96 -62
  95. package/src/tree/TreePanel.tsx +88 -22
  96. package/src/tree/types.ts +73 -23
  97. package/src/tree/useTree.ts +220 -44
  98. package/src/tree/utils.ts +58 -20
  99. package/src/virtual/Gallery.tsx +118 -54
  100. package/src/virtual/List.tsx +99 -39
  101. package/src/virtual/Scroller.tsx +25 -9
  102. package/dist/cjs/histogram/Histogram.module.css +0 -1
  103. package/dist/cjs/tree/types.js +0 -30
  104. package/dist/esm/histogram/Histogram.module.css +0 -1
@@ -50,7 +50,7 @@ export interface VirtualGalleryRef {
50
50
 
51
51
  export interface VirtualGalleryProps<T> extends TestProps {
52
52
  children: (props: {
53
- data: T;
53
+ item: T;
54
54
  index: number;
55
55
  isLast: boolean;
56
56
  }) => ReactElement;
@@ -67,6 +67,12 @@ export interface VirtualGalleryProps<T> extends TestProps {
67
67
  * default item width
68
68
  */
69
69
  defaultWidth?: number;
70
+ /**
71
+ * full width list
72
+ */
73
+ fullWidth?: boolean;
74
+ maxWidth?: string | number;
75
+ minWidth?: string | number;
70
76
  /**
71
77
  * padding
72
78
  */
@@ -75,6 +81,7 @@ export interface VirtualGalleryProps<T> extends TestProps {
75
81
  * data list
76
82
  */
77
83
  items: T[];
84
+ total?: number;
78
85
  /**
79
86
  * loading state
80
87
  */
@@ -87,7 +94,7 @@ export interface VirtualGalleryProps<T> extends TestProps {
87
94
  /**
88
95
  * load more callback
89
96
  */
90
- onLoadMore?: () => void;
97
+ onLoadMore?: (lastIndex?: number) => void;
91
98
  /**
92
99
  * scroll handler
93
100
  */
@@ -96,10 +103,14 @@ export interface VirtualGalleryProps<T> extends TestProps {
96
103
 
97
104
  const createItemList = memoize((items) => items);
98
105
 
99
- const _VirtualGallery = <T extends KeyValue>({
106
+ const _VirtualGallery = <T extends AnyObject>({
100
107
  items,
101
108
  children,
102
109
  columns,
110
+ fullWidth,
111
+ maxWidth,
112
+ minWidth,
113
+ total = 0,
103
114
  padding = "md",
104
115
  defaultHeight: height = 48,
105
116
  defaultWidth: width = 48,
@@ -112,18 +123,18 @@ const _VirtualGallery = <T extends KeyValue>({
112
123
  ...aria
113
124
  }: VirtualGalleryProps<T>) => {
114
125
  const count = useDeferredValue(items.length);
115
- const extraSize = useMemo(() => (onLoadMore ? 64 : 0), [onLoadMore]);
126
+ const extraSize = useMemo(() => (onLoadMore ? 8 : 0), [onLoadMore]);
116
127
  const itemList: Array<{
117
128
  index: number;
118
- data: T;
129
+ item: T;
119
130
  isLast: boolean;
120
131
  isSticky: boolean;
121
132
  }> = createItemList(
122
- items.map((item: AnyObject, idx) => {
133
+ items.map((item: AnyObject, index) => {
123
134
  return {
124
- index: idx,
125
- data: item,
126
- isLast: idx + 1 === items.length,
135
+ index,
136
+ item,
137
+ isLast: index + 1 === items.length,
127
138
  };
128
139
  }),
129
140
  );
@@ -139,9 +150,19 @@ const _VirtualGallery = <T extends KeyValue>({
139
150
  if (scrollerRef.current) {
140
151
  const el = scrollerRef.current;
141
152
  const handler = () => {
153
+ const pad = parseInt(
154
+ getComputedStyle(el.firstElementChild?.firstElementChild as any)
155
+ .padding ?? 8,
156
+ );
142
157
  setColumnCount(
143
158
  columns ??
144
- Math.max(1, Math.floor(((el?.offsetWidth ?? 800) - 32) / width)),
159
+ Math.max(
160
+ 1,
161
+ Math.floor(
162
+ ((el?.offsetWidth ?? 800) - 32 - (isNaN(pad) ? 8 : pad) * 2) /
163
+ width,
164
+ ),
165
+ ),
145
166
  );
146
167
  };
147
168
  const ob = new ResizeObserver(handler);
@@ -161,13 +182,14 @@ const _VirtualGallery = <T extends KeyValue>({
161
182
  count: Math.ceil(count / columnCount),
162
183
  getScrollElement: () => scrollerRef.current,
163
184
  estimateSize: () => height,
185
+ overscan: 0,
164
186
  });
165
187
 
166
188
  const virtualItems = virtualizer.getVirtualItems();
167
189
 
168
190
  const currentIndex = useRef(0);
169
191
  useEffect(() => {
170
- currentIndex.current = virtualItems[1]?.index ?? 0;
192
+ currentIndex.current = virtualItems[0]?.index ?? 0;
171
193
  if (
172
194
  scrollerRef.current &&
173
195
  virtualItems[0] &&
@@ -205,47 +227,44 @@ const _VirtualGallery = <T extends KeyValue>({
205
227
  }, [count, calculateActiveScroll]);
206
228
 
207
229
  useEffect(() => {
208
- const db = debounce(() => {
209
- const lastItem = virtualItems?.slice(-1).pop();
210
- if (
211
- lastItem &&
212
- !loading &&
213
- lastItem.index * columnCount + columnCount >= count - 1
214
- )
215
- onLoadMore?.();
216
- });
217
- db();
218
- return () => db.cancel();
219
- }, [onLoadMore, loading, itemList, virtualItems, count, columnCount]);
220
-
221
- useEffect(() => {
222
- const firstItem = virtualItems[0]?.index;
223
- if (
224
- onLoadMore &&
225
- firstItem !== undefined &&
226
- items[firstItem * columnCount] === null
227
- ) {
230
+ if (onLoadMore) {
231
+ const firstItem = virtualItems[0]?.index;
232
+ const lastItem = virtualItems?.slice(-1).pop()?.index;
233
+ const firstIndex = firstItem * columnCount;
234
+ const lastIndex = (lastItem ?? 0) * columnCount + columnCount;
235
+ const canLoadPrevPage =
236
+ firstItem !== undefined && items[firstIndex] === null;
237
+ const canLoadNextPage =
238
+ lastItem !== undefined && lastIndex >= count - 1 && count < total;
228
239
  const db = debounce(onLoadMore);
229
- db(firstItem * columnCount);
240
+ if (canLoadPrevPage) {
241
+ db(firstIndex);
242
+ } else if (!loading && canLoadNextPage) {
243
+ db(lastIndex + 1);
244
+ }
230
245
  return () => db.cancel();
231
246
  }
232
- }, [onLoadMore, items, columnCount, virtualItems, count]);
247
+ }, [onLoadMore, virtualItems]);
233
248
 
249
+ const isRenderedRef = useRef(false);
234
250
  useLayoutEffect(() => {
235
- setTimeout(() => {
251
+ if (!isRenderedRef.current && itemList.length > 0) {
252
+ isRenderedRef.current = true;
236
253
  initialScroll &&
237
- virtualizer.scrollToIndex(Math.floor(initialScroll / columnCount), {
238
- align: "start",
239
- });
240
- }, 100);
241
- }, [columnCount]);
254
+ setTimeout(() => {
255
+ virtualizer.scrollToIndex(Math.floor(initialScroll / columnCount), {
256
+ align: "start",
257
+ });
258
+ }, 200);
259
+ }
260
+ }, [columnCount, initialScroll, itemList]);
242
261
 
243
262
  useImperativeHandle(
244
263
  galleryRef,
245
264
  () => ({
246
265
  scrollToItem(index) {
247
266
  virtualizer.scrollToIndex(Math.floor(index / columnCount), {
248
- align: "start",
267
+ align: "auto",
249
268
  });
250
269
  },
251
270
  hilight(index) {
@@ -253,7 +272,7 @@ const _VirtualGallery = <T extends KeyValue>({
253
272
  ?.querySelector(".hilight")
254
273
  ?.classList.remove("hilight", "ring-1");
255
274
  virtualizer.scrollToIndex(Math.floor(index / columnCount), {
256
- align: "start",
275
+ align: "auto",
257
276
  });
258
277
  setTimeout(() => {
259
278
  scrollerRef.current
@@ -274,53 +293,65 @@ const _VirtualGallery = <T extends KeyValue>({
274
293
  <div
275
294
  ref={scrollerRef}
276
295
  onScroll={handleScroll}
277
- className={classNames(
278
- "area-content bg-base overflow-auto contain-strict",
279
- )}
296
+ className={classNames("area-content bg-base overflow-auto")}
280
297
  {...aria}
281
298
  >
282
299
  <div
283
300
  className={classNames(
284
- "inline-flex flex-nowrap gap-2 justify-center min-h-full min-w-full",
301
+ "flex flex-nowrap justify-center gap-2 min-h-full",
285
302
  )}
286
303
  >
287
304
  <div
288
305
  className={classNames(
289
306
  "box-content",
307
+ fullWidth && "flex-1",
290
308
  padding === "sm" && "p-1",
291
309
  padding === "md" && "p-2",
292
310
  padding === "lg" && "p-4",
293
311
  )}
312
+ style={{
313
+ minWidth,
314
+ maxWidth,
315
+ }}
294
316
  >
295
317
  <div
296
318
  style={{
297
- height: virtualizer.getTotalSize(),
319
+ height: virtualizer.getTotalSize() + extraSize,
298
320
  }}
299
321
  >
300
322
  <div style={{ height: virtualItems[0]?.start }} />
301
- {virtualItems.map(({ key, index, measureElement }) => (
323
+ {virtualItems.map(({ key, index }) => (
302
324
  <div
303
325
  className="flex"
304
326
  key={key}
305
327
  data-index={index}
306
- ref={measureElement}
328
+ ref={virtualizer.measureElement}
307
329
  >
308
330
  {columnMap.map((col) => (
309
331
  <div
310
332
  key={`${key}:${col}`}
311
333
  data-index={index * columnCount + col}
312
334
  className={classNames("grid rounded-capped")}
335
+ style={{
336
+ minWidth: width,
337
+ minHeight: height,
338
+ }}
313
339
  >
314
- {itemList[index * columnCount + col] &&
315
- children(
316
- itemList[index * columnCount + col] as AnyObject,
317
- )}
340
+ {itemList[index * columnCount + col]?.item
341
+ ? children(
342
+ itemList[index * columnCount + col] as AnyObject,
343
+ )
344
+ : itemList[index * columnCount + col]?.item === null && (
345
+ <Skeleton className="p-2" />
346
+ )}
318
347
  </div>
319
348
  ))}
320
349
  </div>
321
350
  ))}
322
- {onLoadMore && (
323
- <div className="h-16">{loading && <Skeleton />}</div>
351
+ {loading && (
352
+ <div>
353
+ <Skeleton />
354
+ </div>
324
355
  )}
325
356
  </div>
326
357
  </div>
@@ -349,4 +380,37 @@ const _VirtualGallery = <T extends KeyValue>({
349
380
  _VirtualGallery.displayName = "VirtualGallery";
350
381
 
351
382
  const GenericMemo: <T>(c: T) => T = memo;
383
+
384
+ /**
385
+ * VirtualGallery component provides a virtualized gallery view for displaying a large list of items efficiently.
386
+ * It supports features like fixed columns, loading states, and customizable item rendering.
387
+ * The component uses virtualization to render only the visible items, improving performance for large datasets.
388
+ * It also provides a scroller for easy navigation through the items.
389
+ *
390
+ * @param {VirtualGalleryProps<T>} props - The properties for the VirtualGallery component.
391
+ * @returns {JSX.Element} The rendered VirtualGallery component.
392
+ *
393
+ * @example
394
+ * ```jsx
395
+ * <VirtualGallery
396
+ * items={items}
397
+ * columns={3}
398
+ * defaultHeight={100}
399
+ * defaultWidth={100}
400
+ * fullWidth={true}
401
+ * onLoadMore={(lastIndex) => console.log("Load more items from index:", lastIndex)}
402
+ * onScroll={(top) => console.log("Scrolled to top:", top)}
403
+ * >
404
+ * {({ item, index, isLast }) => (
405
+ * <div className="gallery-item" key={item.id}>
406
+ * <img src={item.image} alt={item.label} />
407
+ * <p>{item.label}</p>
408
+ * </div>
409
+ * )}
410
+ * </VirtualGallery>
411
+ * ```
412
+ *
413
+ * @see {@link https://adarshpastakia.github.io/react-fabric/?path=/docs/data-virtualgallery--docs}
414
+ * @see {@link https://tanstack.com/docs/react-virtual/latest/overview}
415
+ */
352
416
  export const VirtualGallery = GenericMemo(_VirtualGallery);
@@ -57,7 +57,7 @@ export interface VirtualListRef {
57
57
 
58
58
  export interface VirtualListProps<T> extends TestProps {
59
59
  children: (props: {
60
- data: T;
60
+ item: T;
61
61
  index: number;
62
62
  isLast: boolean;
63
63
  }) => ReactElement;
@@ -74,6 +74,8 @@ export interface VirtualListProps<T> extends TestProps {
74
74
  * full width list
75
75
  */
76
76
  fullWidth?: boolean;
77
+ maxWidth?: string | number;
78
+ minWidth?: string | number;
77
79
  /**
78
80
  * padding
79
81
  */
@@ -86,6 +88,7 @@ export interface VirtualListProps<T> extends TestProps {
86
88
  * data list
87
89
  */
88
90
  items: T[];
91
+ total?: number;
89
92
  initialScroll?: number;
90
93
  /**
91
94
  * loading state
@@ -98,7 +101,7 @@ export interface VirtualListProps<T> extends TestProps {
98
101
  /**
99
102
  * load more callback
100
103
  */
101
- onLoadMore?: (page?: number) => void;
104
+ onLoadMore?: (lastIndex?: number) => void;
102
105
  /**
103
106
  * scroll handler
104
107
  */
@@ -107,10 +110,13 @@ export interface VirtualListProps<T> extends TestProps {
107
110
 
108
111
  const createItemList = memoizeOne((items) => items);
109
112
 
110
- const _VirtualList = <T extends KeyValue>({
113
+ const _VirtualList = <T extends AnyObject>({
111
114
  items,
115
+ total = 0,
112
116
  children,
113
117
  fullWidth,
118
+ maxWidth,
119
+ minWidth,
114
120
  padding = "md",
115
121
  orientation = "vertical",
116
122
  defaultHeight: height = 48,
@@ -124,18 +130,18 @@ const _VirtualList = <T extends KeyValue>({
124
130
  ...aria
125
131
  }: VirtualListProps<T>) => {
126
132
  const count = useDeferredValue(items.length);
127
- const extraSize = useMemo(() => (onLoadMore ? 64 : 0), [onLoadMore]);
133
+ const extraSize = useMemo(() => (onLoadMore ? 8 : 0), [onLoadMore]);
128
134
  const itemList: Array<{
129
135
  index: number;
130
- data: T;
136
+ item: T;
131
137
  isLast: boolean;
132
138
  isSticky: boolean;
133
139
  }> = createItemList(
134
- items.map((item: AnyObject, idx) => {
140
+ items.map((item: AnyObject, index) => {
135
141
  return {
136
- index: idx,
137
- data: item,
138
- isLast: idx + 1 === items.length,
142
+ index,
143
+ item,
144
+ isLast: index + 1 === items.length,
139
145
  };
140
146
  }),
141
147
  );
@@ -143,6 +149,7 @@ const _VirtualList = <T extends KeyValue>({
143
149
  const scrollerRef = useRef<HTMLDivElement>(null);
144
150
  const virtualizer = useVirtualizer({
145
151
  count,
152
+ overscan: 0,
146
153
  horizontal: orientation === "horizontal",
147
154
  getScrollElement: () => scrollerRef.current,
148
155
  estimateSize: () => (orientation === "horizontal" ? width : height),
@@ -152,7 +159,7 @@ const _VirtualList = <T extends KeyValue>({
152
159
 
153
160
  const currentIndex = useRef(0);
154
161
  useEffect(() => {
155
- currentIndex.current = virtualItems[1]?.index ?? 0;
162
+ currentIndex.current = virtualItems[0]?.index ?? 0;
156
163
  if (
157
164
  scrollerRef.current &&
158
165
  virtualItems[0] &&
@@ -201,42 +208,48 @@ const _VirtualList = <T extends KeyValue>({
201
208
  }, [itemList, calculateActiveScroll]);
202
209
 
203
210
  useEffect(() => {
204
- const db = debounce(() => {
205
- const lastItem = virtualItems?.slice(-1).pop();
206
- if (lastItem && !loading && lastItem.index === count - 1) onLoadMore?.();
207
- });
208
- db();
209
- return () => db.cancel();
210
- }, [onLoadMore, loading, itemList, virtualItems, count]);
211
-
212
- useEffect(() => {
213
- const firstItem = virtualItems[0]?.index;
214
- if (onLoadMore && firstItem !== undefined && items[firstItem] === null) {
211
+ if (onLoadMore) {
212
+ const firstItem = virtualItems[0]?.index;
213
+ const lastItem = virtualItems?.slice(-1).pop()?.index;
214
+ const canLoadPrevPage =
215
+ firstItem !== undefined && items[firstItem] === null;
216
+ const canLoadNextPage =
217
+ lastItem !== undefined && lastItem === count - 1 && count < total;
215
218
  const db = debounce(onLoadMore);
216
- db(firstItem);
219
+ if (canLoadPrevPage) {
220
+ db(firstItem);
221
+ } else if (!loading && canLoadNextPage) {
222
+ db(lastItem + 1);
223
+ }
217
224
  return () => db.cancel();
218
225
  }
219
- }, [onLoadMore, items, virtualItems, count]);
226
+ }, [onLoadMore, virtualItems]);
220
227
 
228
+ const isRenderedRef = useRef(false);
221
229
  useLayoutEffect(() => {
222
- setTimeout(() => {
230
+ if (!isRenderedRef.current && itemList.length > 0) {
231
+ isRenderedRef.current = true;
223
232
  initialScroll &&
224
- virtualizer.scrollToIndex(initialScroll, { align: "start" });
225
- }, 100);
226
- }, []);
233
+ setTimeout(() => {
234
+ virtualizer.scrollToIndex(Math.floor(initialScroll), {
235
+ align: "start",
236
+ });
237
+ }, 200);
238
+ }
239
+ }, [itemList, initialScroll]);
227
240
 
228
241
  useImperativeHandle(
229
242
  listRef,
230
243
  () => ({
231
244
  scrollToItem(index) {
232
- virtualizer.scrollToIndex(index, { align: "start" });
245
+ virtualizer.scrollToIndex(index, { align: "auto" });
233
246
  },
234
247
  hilight(index) {
235
248
  scrollerRef.current
236
249
  ?.querySelector(".hilight")
237
250
  ?.classList.remove("hilight", "ring-1");
238
251
  virtualizer.scrollToIndex(Math.floor(index), {
239
- align: "start",
252
+ align: "auto",
240
253
  });
241
254
  setTimeout(() => {
242
255
  scrollerRef.current
@@ -257,14 +270,12 @@ const _VirtualList = <T extends KeyValue>({
257
270
  <div
258
271
  ref={scrollerRef}
259
272
  onScroll={handleScroll}
260
- className={classNames(
261
- "area-content bg-base overflow-auto contain-strict",
262
- )}
273
+ className={classNames("area-content bg-base overflow-auto")}
263
274
  {...aria}
264
275
  >
265
276
  <div
266
277
  className={classNames(
267
- "inline-flex flex-nowrap gap-2 justify-center min-h-full min-w-full",
278
+ "flex flex-nowrap gap-2 justify-center min-h-full min-w-full",
268
279
  orientation === "horizontal" && "flex-col",
269
280
  )}
270
281
  >
@@ -278,12 +289,16 @@ const _VirtualList = <T extends KeyValue>({
278
289
  padding === "md" && "p-2",
279
290
  padding === "lg" && "p-4",
280
291
  )}
292
+ style={{
293
+ minWidth,
294
+ maxWidth,
295
+ }}
281
296
  >
282
297
  <div
283
298
  style={
284
299
  orientation === "vertical"
285
- ? { height: virtualizer.getTotalSize() }
286
- : { width: virtualizer.getTotalSize() }
300
+ ? { height: virtualizer.getTotalSize() + extraSize }
301
+ : { width: virtualizer.getTotalSize() + extraSize }
287
302
  }
288
303
  >
289
304
  <div
@@ -302,14 +317,22 @@ const _VirtualList = <T extends KeyValue>({
302
317
  "rounded-capped",
303
318
  orientation === "horizontal" ? "inline-grid" : "grid",
304
319
  )}
320
+ style={{
321
+ minWidth: width,
322
+ minHeight: height,
323
+ }}
305
324
  >
306
- {children(itemList[index] as AnyObject)}
325
+ {itemList[index]?.item
326
+ ? children(itemList[index] as AnyObject)
327
+ : itemList[index]?.item === null && (
328
+ <Skeleton className="p-2" />
329
+ )}
307
330
  </div>
308
331
  ))}
309
332
  </div>
310
- {onLoadMore && (
311
- <div className={orientation === "horizontal" ? "min-w-16" : "h-16"}>
312
- {loading && <Skeleton />}
333
+ {loading && (
334
+ <div className={orientation === "horizontal" ? "min-w-16" : ""}>
335
+ <Skeleton />
313
336
  </div>
314
337
  )}
315
338
  </div>
@@ -351,4 +374,41 @@ const _VirtualList = <T extends KeyValue>({
351
374
  _VirtualList.displayName = "VirtualList";
352
375
 
353
376
  const GenericMemo: <T>(c: T) => T = memo;
377
+
378
+ /**
379
+ * VirtualList component for rendering large lists efficiently using virtualization.
380
+ * It supports horizontal and vertical orientations, custom item heights and widths,
381
+ * and provides a scroller for navigating through the list.
382
+ * It also supports loading more items on scroll and provides a way to hilight items.
383
+ * It can be used with a ref to control scrolling and hilighting of items.
384
+ *
385
+ * @param {VirtualListProps<T>} props - The properties for the VirtualList component.
386
+ * @returns {JSX.Element} The rendered VirtualList component.
387
+ * @template T - The type of items in the list, extending AnyObject.
388
+ *
389
+ * @example
390
+ * ```jsx
391
+ * <VirtualList
392
+ * items={items}
393
+ * children={({ item, index, isLast }) => (
394
+ * <div key={index} className="item">
395
+ * {item.label}
396
+ * </div>
397
+ * )}
398
+ * fullWidth={true}
399
+ * orientation="vertical"
400
+ * defaultHeight={50}
401
+ * onLoadMore={(lastIndex) => console.log("Load more items from index:", lastIndex)}
402
+ * onScroll={(top) => console.log("Scrolled to top:", top)}
403
+ * listRef={virtualListRef}
404
+ * >
405
+ * {({ item, index, isLast }) => (
406
+ * <div className="gallery-item" key={item.id}>
407
+ * <img src={item.image} alt={item.label} />
408
+ * <p>{item.label}</p>
409
+ * </div>
410
+ * )}
411
+ * </VirtualList>
412
+ * ```
413
+ */
354
414
  export const VirtualList = GenericMemo(_VirtualList);
@@ -21,7 +21,7 @@
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 { Button, ButtonGroup, CoreIcons } from "@react-fabric/core";
24
+ import { Button, ButtonGroup } from "@react-fabric/core";
25
25
 
26
26
  export const Scroller = ({
27
27
  vertical = true,
@@ -41,38 +41,54 @@ export const Scroller = ({
41
41
  return (
42
42
  <ButtonGroup vertical={vertical} variant="link" className="outline">
43
43
  <Button
44
- rtlFlip
45
44
  size="sm"
46
45
  variant="link"
47
46
  aria-label="scroll to top"
48
- icon={vertical ? CoreIcons.chevronsUp : CoreIcons.chevronsLeft}
47
+ icon={{
48
+ icon: vertical
49
+ ? "icon-[mdi--chevron-double-up]"
50
+ : "icon-[mdi--chevron-double-left]",
51
+ rtlFlip: true,
52
+ }}
49
53
  disabled={(scrollActive & 2) === 0}
50
54
  onClick={onStart}
51
55
  />
52
56
  <Button
53
- rtlFlip
54
57
  size="sm"
55
58
  variant="link"
56
59
  aria-label="scroll up"
57
- icon={vertical ? CoreIcons.chevronUp : CoreIcons.chevronLeft}
60
+ icon={{
61
+ icon: vertical
62
+ ? "icon-[mdi--chevron-up]"
63
+ : "icon-[mdi--chevron-left]",
64
+ rtlFlip: true,
65
+ }}
58
66
  disabled={(scrollActive & 2) === 0}
59
67
  onClick={onPrevious}
60
68
  />
61
69
  <Button
62
- rtlFlip
63
70
  size="sm"
64
71
  variant="link"
65
72
  aria-label="scroll down"
66
- icon={vertical ? CoreIcons.chevronDown : CoreIcons.chevronRight}
73
+ icon={{
74
+ icon: vertical
75
+ ? "icon-[mdi--chevron-down]"
76
+ : "icon-[mdi--chevron-right]",
77
+ rtlFlip: true,
78
+ }}
67
79
  disabled={scrollActive % 2 === 0}
68
80
  onClick={onNext}
69
81
  />
70
82
  <Button
71
- rtlFlip
72
83
  size="sm"
73
84
  variant="link"
74
85
  aria-label="scroll to bottom"
75
- icon={vertical ? CoreIcons.chevronsDown : CoreIcons.chevronsRight}
86
+ icon={{
87
+ icon: vertical
88
+ ? "icon-[mdi--chevron-double-down]"
89
+ : "icon-[mdi--chevron-double-right]",
90
+ rtlFlip: true,
91
+ }}
76
92
  disabled={scrollActive % 2 === 0}
77
93
  onClick={onEnd}
78
94
  />
@@ -1 +0,0 @@
1
- @layer comp{.histogram{.histoLabel{--tw-gradient-from:var(--color) var(--tw-gradient-from-position);--tw-gradient-to:color-mix(in lab,var(--color) 20%,var(--fabric-bg)) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);background-color:color(from var(--color) srgb r g b/5%);&:hover{background-color:color(from var(--color) srgb r g b/15%)}&[data-disabled=true]{--color:theme(colors.tint.300);color:theme(colors.tint.600)}}.histoBar{box-shadow:-2px 0 0 var(--color) inset;:global([dir=rtl]) &{box-shadow:2px 0 0 var(--color) inset}}.histoBadge{background-color:color(from var(--color) srgb r g b/15%)}}}
@@ -1,30 +0,0 @@
1
- 'use strict';
2
-
3
- /*
4
- * React Fabric
5
- * @version: 1.0.0
6
- *
7
- *
8
- * The MIT License (MIT)
9
- * Copyright (c) 2024 Adarsh Pastakia
10
- *
11
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
12
- * and associated documentation files (the "Software"), to deal in the Software without restriction,
13
- * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
- * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
15
- * subject to the following conditions:
16
- *
17
- * The above copyright notice and this permission notice shall be included in all copies or substantial
18
- * portions of the Software.
19
- *
20
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21
- * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23
- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
- */
25
-
26
- var iconExpandAll = "M12.984 9v6h3l-3.984 3.984-3.984-3.984h3v-6h-3l3.984-3.984 3.984 3.984h-3zM3.984 2.016h16.031v1.969h-16.031v-1.969zM3.984 20.016h16.031v1.969h-16.031v-1.969z";
27
- var iconCollapseAll = "M3.984 11.016h16.031v1.969h-16.031v-1.969zM15.984 5.016l-3.984 3.984-3.984-3.984h3v-4.031h1.969v4.031h3zM8.016 18.984l3.984-3.984 3.984 3.984h-3v4.031h-1.969v-4.031h-3z";
28
-
29
- exports.iconCollapseAll = iconCollapseAll;
30
- exports.iconExpandAll = iconExpandAll;
@@ -1 +0,0 @@
1
- @layer comp{.histogram{.histoLabel{--tw-gradient-from:var(--color) var(--tw-gradient-from-position);--tw-gradient-to:color-mix(in lab,var(--color) 20%,var(--fabric-bg)) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to);background-color:color(from var(--color) srgb r g b/5%);&:hover{background-color:color(from var(--color) srgb r g b/15%)}&[data-disabled=true]{--color:theme(colors.tint.300);color:theme(colors.tint.600)}}.histoBar{box-shadow:-2px 0 0 var(--color) inset;:global([dir=rtl]) &{box-shadow:2px 0 0 var(--color) inset}}.histoBadge{background-color:color(from var(--color) srgb r g b/15%)}}}