@firecms/core 3.0.1 → 3.1.0-canary.1df3b2c

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 (170) hide show
  1. package/README.md +1 -1
  2. package/dist/components/AIIcon.d.ts +16 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  5. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
  6. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
  7. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
  8. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
  9. package/dist/components/EntityCollectionView/Board.d.ts +2 -0
  10. package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
  11. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
  12. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
  13. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
  14. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  15. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  16. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  18. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  19. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  20. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +49 -0
  21. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  22. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  23. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  24. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  25. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  26. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
  27. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  28. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
  29. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  30. package/dist/components/VirtualTable/types.d.ts +2 -0
  31. package/dist/components/index.d.ts +3 -0
  32. package/dist/contexts/index.d.ts +10 -0
  33. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  34. package/dist/core/index.d.ts +1 -0
  35. package/dist/form/validation.d.ts +3 -2
  36. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  37. package/dist/hooks/useCollapsedGroups.d.ts +4 -1
  38. package/dist/index.es.js +5239 -1590
  39. package/dist/index.es.js.map +1 -1
  40. package/dist/index.umd.js +5233 -1585
  41. package/dist/index.umd.js.map +1 -1
  42. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  43. package/dist/preview/components/DatePreview.d.ts +13 -3
  44. package/dist/preview/components/ImagePreview.d.ts +5 -1
  45. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  46. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  47. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  48. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  49. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  50. package/dist/types/collections.d.ts +42 -2
  51. package/dist/types/datasource.d.ts +0 -1
  52. package/dist/types/plugins.d.ts +46 -1
  53. package/dist/types/properties.d.ts +259 -4
  54. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  55. package/dist/util/__tests__/objects.test.d.ts +1 -0
  56. package/dist/util/conditions.d.ts +26 -0
  57. package/dist/util/entities.d.ts +1 -2
  58. package/dist/util/index.d.ts +2 -1
  59. package/dist/util/property_utils.d.ts +2 -1
  60. package/dist/util/resolutions.d.ts +1 -1
  61. package/package.json +10 -7
  62. package/src/app/Scaffold.tsx +14 -15
  63. package/src/components/AIIcon.tsx +39 -0
  64. package/src/components/ArrayContainer.tsx +1 -4
  65. package/src/components/ClearFilterSortButton.tsx +19 -16
  66. package/src/components/ConfirmationDialog.tsx +0 -2
  67. package/src/components/DeleteEntityDialog.tsx +2 -4
  68. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
  69. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  70. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  71. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  72. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
  73. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  74. package/src/components/EntityCollectionView/Board.tsx +324 -0
  75. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  76. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  77. package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
  78. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  79. package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
  80. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
  81. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
  82. package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
  83. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
  84. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
  85. package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
  86. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  87. package/src/components/EntityCollectionView/board_types.ts +113 -0
  88. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  89. package/src/components/ErrorTooltip.tsx +2 -1
  90. package/src/components/HomePage/DefaultHomePage.tsx +47 -10
  91. package/src/components/HomePage/HomePageDnD.tsx +56 -41
  92. package/src/components/HomePage/NavigationCard.tsx +20 -18
  93. package/src/components/HomePage/NavigationGroup.tsx +17 -16
  94. package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
  95. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  96. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
  97. package/src/components/ReferenceWidget.tsx +2 -4
  98. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  99. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  100. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
  101. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
  102. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
  103. package/src/components/UnsavedChangesDialog.tsx +0 -2
  104. package/src/components/UserDisplay.tsx +4 -4
  105. package/src/components/VirtualTable/VirtualTable.tsx +170 -19
  106. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  107. package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
  108. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
  109. package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
  110. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  111. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  112. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
  113. package/src/components/VirtualTable/types.tsx +2 -0
  114. package/src/components/common/useColumnsIds.tsx +95 -3
  115. package/src/components/index.tsx +4 -0
  116. package/src/contexts/BreacrumbsContext.tsx +15 -8
  117. package/src/contexts/index.ts +10 -0
  118. package/src/core/DefaultAppBar.tsx +39 -26
  119. package/src/core/DefaultDrawer.tsx +42 -56
  120. package/src/core/DrawerNavigationGroup.tsx +118 -0
  121. package/src/core/DrawerNavigationItem.tsx +4 -3
  122. package/src/core/EntityEditView.tsx +41 -43
  123. package/src/core/SideDialogs.tsx +4 -2
  124. package/src/core/index.tsx +1 -0
  125. package/src/form/PropertyFieldBinding.tsx +58 -43
  126. package/src/form/components/StorageItemPreview.tsx +2 -1
  127. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  128. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  129. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  130. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  131. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
  132. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  133. package/src/form/validation.ts +245 -160
  134. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  135. package/src/hooks/useBuildNavigationController.tsx +42 -19
  136. package/src/hooks/useCollapsedGroups.ts +12 -4
  137. package/src/internal/useBuildDataSource.ts +69 -34
  138. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  139. package/src/internal/useBuildSideEntityController.tsx +2 -4
  140. package/src/internal/useRestoreScroll.tsx +26 -14
  141. package/src/preview/PropertyPreview.tsx +40 -32
  142. package/src/preview/PropertyPreviewProps.tsx +6 -0
  143. package/src/preview/components/DatePreview.tsx +72 -4
  144. package/src/preview/components/EmptyValue.tsx +1 -1
  145. package/src/preview/components/ImagePreview.tsx +37 -21
  146. package/src/preview/components/StorageThumbnail.tsx +16 -12
  147. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  148. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  149. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  150. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  151. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  152. package/src/routes/CustomCMSRoute.tsx +1 -0
  153. package/src/routes/FireCMSRoute.tsx +26 -13
  154. package/src/types/collections.ts +48 -3
  155. package/src/types/datasource.ts +54 -56
  156. package/src/types/plugins.tsx +51 -1
  157. package/src/types/properties.ts +347 -27
  158. package/src/util/__tests__/conditions.test.ts +506 -0
  159. package/src/util/__tests__/objects.test.ts +196 -0
  160. package/src/util/callbacks.ts +6 -3
  161. package/src/util/collections.ts +51 -6
  162. package/src/util/conditions.ts +339 -0
  163. package/src/util/entities.ts +28 -29
  164. package/src/util/entity_cache.ts +2 -1
  165. package/src/util/index.ts +2 -1
  166. package/src/util/objects.ts +31 -13
  167. package/src/util/{references.ts → previews.ts} +14 -0
  168. package/src/util/property_utils.tsx +36 -10
  169. package/src/util/resolutions.ts +57 -55
  170. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -21,6 +21,16 @@ import { VirtualTableRow } from "./VirtualTableRow";
21
21
  import { VirtualTableCell } from "./VirtualTableCell";
22
22
  import { AssignmentIcon, CenteredView, cls, Typography } from "@firecms/ui";
23
23
  import { useDebounceCallback } from "../common/useDebouncedCallback";
24
+ import {
25
+ closestCenter,
26
+ DndContext,
27
+ DragEndEvent,
28
+ DragStartEvent,
29
+ PointerSensor,
30
+ useSensor,
31
+ useSensors
32
+ } from "@dnd-kit/core";
33
+ import { arrayMove, horizontalListSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable";
24
34
 
25
35
  const VirtualListContext = createContext<VirtualTableContextProps<any>>({} as any);
26
36
  VirtualListContext.displayName = "VirtualListContext";
@@ -56,7 +66,7 @@ const innerElementType = forwardRef<HTMLDivElement, InnerElementProps>(({
56
66
  minHeight: "100%",
57
67
  position: "relative"
58
68
  }}>
59
- <VirtualTableHeaderRow {...virtualTableProps}/>
69
+ <VirtualTableHeaderRow {...virtualTableProps} />
60
70
  {!customView && children}
61
71
  </div>
62
72
 
@@ -115,6 +125,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
115
125
  endAdornment,
116
126
  AddColumnComponent,
117
127
  initialScroll = 0,
128
+ onColumnsOrderChange,
118
129
  }: VirtualTableProps<T>) {
119
130
 
120
131
  const sortByProperty: string | undefined = sortBy ? sortBy[0] : undefined;
@@ -127,6 +138,44 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
127
138
 
128
139
  const debouncedScroll = useDebounceCallback(onScrollProp, 200);
129
140
 
141
+ // Drag and drop state
142
+ const [draggingColumnId, setDraggingColumnId] = useState<string | null>(null);
143
+
144
+ const sensors = useSensors(
145
+ useSensor(PointerSensor, {
146
+ activationConstraint: {
147
+ distance: 5,
148
+ },
149
+ })
150
+ );
151
+
152
+ const handleDragStart = useCallback((event: DragStartEvent) => {
153
+ setDraggingColumnId(event.active.id as string);
154
+ }, []);
155
+
156
+ const handleDragEnd = useCallback((event: DragEndEvent) => {
157
+ const {
158
+ active,
159
+ over
160
+ } = event;
161
+ setDraggingColumnId(null);
162
+
163
+ if (over && active.id !== over.id && onColumnsOrderChange) {
164
+ const oldIndex = columns.findIndex((col) => col.key === active.id);
165
+ const newIndex = columns.findIndex((col) => col.key === over.id);
166
+
167
+ if (oldIndex !== -1 && newIndex !== -1) {
168
+ const newColumns = arrayMove(columns, oldIndex, newIndex);
169
+ setColumns(newColumns);
170
+ onColumnsOrderChange(newColumns);
171
+ }
172
+ }
173
+ }, [columns, onColumnsOrderChange]);
174
+
175
+ const handleDragCancel = useCallback(() => {
176
+ setDraggingColumnId(null);
177
+ }, []);
178
+
130
179
  // Set initial scroll position
131
180
  useEffect(() => {
132
181
  if (tableRef.current && initialScroll) {
@@ -315,14 +364,28 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
315
364
  createFilterField,
316
365
  rowClassName,
317
366
  endAdornment,
318
- AddColumnComponent
319
- }), [data, rowHeight, cellRenderer, columns, currentSort, onRowClick, customView, onColumnResizeInternal, onColumnResizeEndInternal, filterInput, onColumnSort, onFilterUpdateInternal, sortByProperty, hoverRow, createFilterField, rowClassName, endAdornment, AddColumnComponent]);
320
-
321
- return (
367
+ AddColumnComponent,
368
+ onColumnsOrderChange: onColumnsOrderChange ? (newColumns: VirtualTableColumn[]) => {
369
+ setColumns(newColumns);
370
+ onColumnsOrderChange(newColumns);
371
+ } : undefined,
372
+ draggingColumnId
373
+ }), [data, rowHeight, cellRenderer, columns, currentSort, onRowClick, customView, onColumnResizeInternal, onColumnResizeEndInternal, filterInput, onColumnSort, onFilterUpdateInternal, sortByProperty, hoverRow, createFilterField, rowClassName, endAdornment, AddColumnComponent, onColumnsOrderChange, draggingColumnId]);
374
+
375
+ // Get sortable column keys (excluding frozen columns)
376
+ const sortableColumnKeys = columns
377
+ .filter(col => !col.frozen)
378
+ .map(col => col.key);
379
+
380
+ const tableContent = (
322
381
  <div
323
382
  ref={measureRef}
324
383
  style={style}
325
- className={cls("h-full w-full", className)}>
384
+ className={cls(
385
+ "h-full w-full",
386
+ className,
387
+ draggingColumnId && "overflow-hidden"
388
+ )}>
326
389
  <VirtualListContext.Provider
327
390
  value={virtualListController}>
328
391
 
@@ -332,16 +395,89 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
332
395
  width={bounds.width}
333
396
  height={bounds.height}
334
397
  itemCount={(data?.length ?? 0) + (endAdornment ? 1 : 0)}
335
- onScroll={onScroll}
398
+ onScroll={draggingColumnId ? undefined : onScroll}
336
399
  includeAddColumn={Boolean(AddColumnComponent)}
337
400
  itemSize={rowHeight}/>
338
401
 
339
402
  </VirtualListContext.Provider>
340
403
  </div>
341
404
  );
405
+
406
+ // Wrap with DndContext if column reorder is enabled
407
+ if (onColumnsOrderChange) {
408
+ return (
409
+ <DndContext
410
+ sensors={sensors}
411
+ collisionDetection={closestCenter}
412
+ onDragStart={handleDragStart}
413
+ onDragEnd={handleDragEnd}
414
+ onDragCancel={handleDragCancel}
415
+ >
416
+ <SortableContext
417
+ items={sortableColumnKeys}
418
+ strategy={horizontalListSortingStrategy}
419
+ >
420
+ {tableContent}
421
+ </SortableContext>
422
+ </DndContext>
423
+ );
424
+ }
425
+
426
+ return tableContent;
342
427
  },
343
428
  equal
344
429
  );
430
+ // Wrapper that applies sortable transforms to cells
431
+ const SortableCellWrapper = ({
432
+ columnKey,
433
+ width,
434
+ isDragging,
435
+ isDraggable,
436
+ frozen,
437
+ children
438
+ }: {
439
+ columnKey: string;
440
+ width: number;
441
+ isDragging: boolean;
442
+ isDraggable: boolean;
443
+ frozen?: boolean;
444
+ children: React.ReactNode;
445
+ }) => {
446
+ const {
447
+ attributes,
448
+ listeners,
449
+ setNodeRef,
450
+ transform,
451
+ transition,
452
+ } = useSortable({
453
+ id: columnKey,
454
+ disabled: !isDraggable || frozen
455
+ });
456
+
457
+ const style = {
458
+ // Only use translate, ignore any scale transforms
459
+ transform: transform ? `translateX(${transform.x}px)` : undefined,
460
+ // Don't transition the dragged item - only other items should animate
461
+ transition: isDragging ? undefined : transition,
462
+ minWidth: width,
463
+ maxWidth: width,
464
+ width: width,
465
+ };
466
+
467
+ return (
468
+ <div
469
+ ref={setNodeRef}
470
+ style={style}
471
+ className={cls(
472
+ "flex-shrink-0",
473
+ frozen && "sticky left-0 z-10 bg-white dark:bg-surface-950"
474
+ )}
475
+ {...attributes}
476
+ >
477
+ {children}
478
+ </div>
479
+ );
480
+ };
345
481
 
346
482
  function MemoizedList({
347
483
  outerRef,
@@ -356,7 +492,7 @@ function MemoizedList({
356
492
  width: number;
357
493
  height: number;
358
494
  itemCount: number;
359
- onScroll: (params: {
495
+ onScroll?: (params: {
360
496
  scrollDirection: "forward" | "backward",
361
497
  scrollOffset: number,
362
498
  scrollUpdateWasRequested: boolean;
@@ -378,7 +514,9 @@ function MemoizedList({
378
514
  cellRenderer,
379
515
  hoverRow,
380
516
  rowClassName,
381
- endAdornment
517
+ endAdornment,
518
+ draggingColumnId,
519
+ onColumnsOrderChange
382
520
  }) => {
383
521
 
384
522
  if (endAdornment && index === (data ?? []).length) {
@@ -411,16 +549,29 @@ function MemoizedList({
411
549
 
412
550
  {columns.map((column: VirtualTableColumn, columnIndex: number) => {
413
551
  const cellData = rowData && rowData[column.key];
414
- return <VirtualTableCell
415
- key={`cell_${column.key}`}
416
- dataKey={column.key}
417
- cellRenderer={cellRenderer}
418
- column={column}
419
- columns={columns}
420
- rowData={rowData}
421
- cellData={cellData}
422
- rowIndex={index}
423
- columnIndex={columnIndex}/>;
552
+ const isDragging = draggingColumnId === column.key;
553
+ const isDraggable = !column.frozen && !!onColumnsOrderChange;
554
+
555
+ return (
556
+ <SortableCellWrapper
557
+ key={`cell_wrapper_${column.key}`}
558
+ columnKey={column.key}
559
+ width={column.width}
560
+ isDragging={isDragging}
561
+ isDraggable={isDraggable}
562
+ frozen={column.frozen}
563
+ >
564
+ <VirtualTableCell
565
+ dataKey={column.key}
566
+ cellRenderer={cellRenderer}
567
+ column={column}
568
+ columns={columns}
569
+ rowData={rowData}
570
+ cellData={cellData}
571
+ rowIndex={index}
572
+ columnIndex={columnIndex}/>
573
+ </SortableCellWrapper>
574
+ );
424
575
  })}
425
576
 
426
577
  {includeAddColumn && <div className={"w-20"}/>}
@@ -13,6 +13,13 @@ type VirtualTableCellProps<T extends any> = {
13
13
  rowIndex: any;
14
14
  columnIndex: number;
15
15
  cellRenderer: React.ComponentType<CellRendererParams<T>>;
16
+ // Sortable props
17
+ sortableNodeRef?: (node: HTMLElement | null) => void;
18
+ sortableStyle?: React.CSSProperties;
19
+ sortableAttributes?: Record<string, any>;
20
+ isDragging?: boolean;
21
+ isDraggable?: boolean;
22
+ frozen?: boolean;
16
23
  };
17
24
 
18
25
  export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
@@ -27,7 +34,13 @@ export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
27
34
  column: props.column,
28
35
  columns: props.columns,
29
36
  columnIndex: props.columnIndex,
30
- width: props.column.width
37
+ width: props.column.width,
38
+ sortableNodeRef: props.sortableNodeRef,
39
+ sortableStyle: props.sortableStyle,
40
+ sortableAttributes: props.sortableAttributes,
41
+ isDragging: props.isDragging,
42
+ isDraggable: props.isDraggable,
43
+ frozen: props.frozen
31
44
  } as CellRendererParams<T>
32
45
  );
33
46
  },
@@ -37,6 +50,9 @@ export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
37
50
  equal(a.cellData, b.cellData) &&
38
51
  equal(a.rowIndex, b.rowIndex) &&
39
52
  equal(a.cellRenderer, b.cellRenderer) &&
40
- equal(a.columnIndex, b.columnIndex)
53
+ equal(a.columnIndex, b.columnIndex) &&
54
+ a.isDragging === b.isDragging &&
55
+ a.isDraggable === b.isDraggable &&
56
+ a.frozen === b.frozen
41
57
  }
42
58
  );
@@ -45,6 +45,8 @@ type VirtualTableHeaderProps<M extends Record<string, any>> = {
45
45
  onClickResizeColumn?: (columnIndex: number, column: VirtualTableColumn) => void;
46
46
  createFilterField?: (props: FilterFormFieldProps<any>) => React.ReactNode;
47
47
  AdditionalHeaderWidget?: (props: { onHover: boolean }) => React.ReactNode;
48
+ isDragging?: boolean;
49
+ isDraggable?: boolean;
48
50
  };
49
51
 
50
52
  export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
@@ -59,7 +61,9 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
59
61
  column,
60
62
  onClickResizeColumn,
61
63
  createFilterField,
62
- AdditionalHeaderWidget
64
+ AdditionalHeaderWidget,
65
+ isDragging,
66
+ isDraggable
63
67
  }: VirtualTableHeaderProps<M>) {
64
68
 
65
69
  const [onHover, setOnHover] = useState(false);
@@ -85,14 +89,16 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
85
89
  return (
86
90
  <ErrorBoundary>
87
91
  <div
88
- className={cls("flex py-0 px-3 h-full text-xs uppercase font-semibold relative select-none items-center bg-surface-50 dark:bg-surface-900",
92
+ className={cls("flex py-0 px-3 h-full text-xs uppercase font-semibold relative select-none items-center",
93
+ isDragging
94
+ ? "bg-primary-bg dark:bg-primary-bg-dark"
95
+ : "bg-surface-50 dark:bg-surface-900",
89
96
  "text-text-secondary hover:text-text-primary dark:text-text-secondary-dark dark:hover:text-text-primary-dark",
90
- "hover:bg-surface-100 dark:hover:bg-surface-800 hover:bg-opacity-50 dark:hover:bg-opacity-50",
91
- column.frozen ? "sticky left-0 z-10" : "relative z-0"
97
+ !isDragging && "hover:bg-surface-100 dark:hover:bg-surface-800 hover:bg-opacity-50 hover:bg-surface-100/50 dark:hover:bg-opacity-50 dark:hover:bg-surface-800/50",
98
+ column.frozen ? "sticky left-0 z-10" : "relative z-0",
99
+ isDraggable && "cursor-grab"
92
100
  )}
93
101
  style={{
94
- // transition: "color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms",
95
- // fontSize: "0.750rem",
96
102
  left: column.frozen ? 0 : undefined,
97
103
  minWidth: column.width,
98
104
  maxWidth: column.width
@@ -179,11 +185,17 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
179
185
 
180
186
  {column.resizable && <div
181
187
  ref={resizeHandleRef}
188
+ data-no-dnd="true"
182
189
  className={cls(
183
190
  "absolute h-full w-[6px] top-0 right-0 cursor-col-resize",
184
191
  hovered && "bg-surface-300 dark:bg-surface-700"
185
192
  )}
186
- onMouseDown={onClickResizeColumn ? () => onClickResizeColumn(columnIndex, column) : undefined}
193
+ onPointerDown={(e) => {
194
+ e.stopPropagation();
195
+ if (onClickResizeColumn) {
196
+ onClickResizeColumn(columnIndex, column);
197
+ }
198
+ }}
187
199
  />}
188
200
  </div>
189
201
 
@@ -243,7 +255,7 @@ function FilterForm<M>({
243
255
  className={cls(defaultBorderMixin, "py-4 px-6 typography-label border-b")}>
244
256
  {column.title ?? id}
245
257
  </div>
246
- {filterField && <div className="m-4">
258
+ {filterField && <div className="m-4 w-[400px]">
247
259
  {filterField}
248
260
  </div>}
249
261
  <div className="flex justify-end m-4">
@@ -251,13 +263,10 @@ function FilterForm<M>({
251
263
  className="mr-4"
252
264
  disabled={!filterIsSet}
253
265
  variant={"text"}
254
- color="primary"
255
266
  type="reset"
256
267
  aria-label="filter clear"
257
268
  onClick={reset}>Clear</Button>
258
269
  <Button
259
- variant="outlined"
260
- color="primary"
261
270
  type="submit">Filter</Button>
262
271
  </div>
263
272
  </form>
@@ -1,25 +1,132 @@
1
- import React, { createRef, useCallback, useEffect, useState } from "react";
1
+ import React, { createRef, useCallback, useEffect, useMemo, useState } from "react";
2
2
 
3
3
  import { VirtualTableColumn, VirtualTableWhereFilterOp } from "./VirtualTableProps";
4
4
  import { ErrorBoundary } from "../ErrorBoundary";
5
5
  import { VirtualTableHeader } from "./VirtualTableHeader";
6
6
  import { VirtualTableContextProps } from "./types";
7
7
  import { cls, defaultBorderMixin } from "@firecms/ui";
8
+ import { useSortable } from "@dnd-kit/sortable";
9
+ import { CSS } from "@dnd-kit/utilities";
10
+
11
+ // Sortable column header wrapper
12
+ const SortableColumnHeader = ({
13
+ column,
14
+ columnIndex,
15
+ columnRefs,
16
+ isResizing,
17
+ onFilterUpdate,
18
+ filter,
19
+ sortByProperty,
20
+ currentSort,
21
+ onColumnSort,
22
+ onClickResizeColumn,
23
+ createFilterField,
24
+ isDragging,
25
+ isDraggable
26
+ }: {
27
+ column: VirtualTableColumn;
28
+ columnIndex: number;
29
+ columnRefs: React.RefObject<HTMLDivElement>[];
30
+ isResizing: number;
31
+ onFilterUpdate: any;
32
+ filter: [VirtualTableWhereFilterOp, any] | undefined;
33
+ sortByProperty: string | undefined;
34
+ currentSort: "asc" | "desc" | undefined;
35
+ onColumnSort: any;
36
+ onClickResizeColumn: (index: number) => void;
37
+ createFilterField: any;
38
+ isDragging: boolean;
39
+ isDraggable: boolean;
40
+ }) => {
41
+ const [isPressing, setIsPressing] = useState(false);
42
+
43
+ const {
44
+ attributes,
45
+ listeners,
46
+ setNodeRef,
47
+ transform,
48
+ transition,
49
+ } = useSortable({
50
+ id: column.key,
51
+ disabled: !isDraggable || column.frozen
52
+ });
53
+
54
+ const style = {
55
+ // Only use translate, ignore any scale transforms
56
+ transform: transform ? `translateX(${transform.x}px)` : undefined,
57
+ // Don't transition the dragged item - only other items should animate
58
+ transition: isDragging ? undefined : transition,
59
+ minWidth: column.width,
60
+ maxWidth: column.width,
61
+ width: column.width,
62
+ };
63
+
64
+ // Combine our press handlers with dnd-kit listeners
65
+ const combinedListeners = isDraggable ? {
66
+ ...listeners,
67
+ onPointerDown: (e: React.PointerEvent) => {
68
+ setIsPressing(true);
69
+ listeners?.onPointerDown?.(e);
70
+ },
71
+ onPointerUp: () => setIsPressing(false),
72
+ onPointerCancel: () => setIsPressing(false),
73
+ } : {};
74
+
75
+ // Reset pressing state when drag ends
76
+ React.useEffect(() => {
77
+ if (!isDragging) {
78
+ setIsPressing(false);
79
+ }
80
+ }, [isDragging]);
81
+
82
+ return (
83
+ <div
84
+ ref={setNodeRef}
85
+ style={style}
86
+ className={cls(
87
+ "flex-shrink-0 h-full",
88
+ column.frozen && "sticky left-0 z-10"
89
+ )}
90
+ {...attributes}
91
+ {...combinedListeners}
92
+ >
93
+ <VirtualTableHeader
94
+ resizeHandleRef={columnRefs[columnIndex]}
95
+ columnIndex={columnIndex}
96
+ isResizingIndex={isResizing}
97
+ onFilterUpdate={onFilterUpdate}
98
+ filter={filter}
99
+ sort={sortByProperty === column.key ? currentSort : undefined}
100
+ onColumnSort={onColumnSort}
101
+ onClickResizeColumn={onClickResizeColumn}
102
+ column={column}
103
+ createFilterField={createFilterField}
104
+ AdditionalHeaderWidget={column.AdditionalHeaderWidget}
105
+ isDragging={isDragging || isPressing}
106
+ isDraggable={isDraggable} />
107
+ </div>
108
+ );
109
+ };
8
110
 
9
111
  export const VirtualTableHeaderRow = ({
10
- columns,
11
- currentSort,
12
- onColumnSort,
13
- onFilterUpdate,
14
- sortByProperty,
15
- filter,
16
- onColumnResize,
17
- onColumnResizeEnd,
18
- createFilterField,
19
- AddColumnComponent
20
- }: VirtualTableContextProps<any>) => {
21
-
22
- const columnRefs = columns.map(() => createRef<HTMLDivElement>());
112
+ columns,
113
+ currentSort,
114
+ onColumnSort,
115
+ onFilterUpdate,
116
+ sortByProperty,
117
+ filter,
118
+ onColumnResize,
119
+ onColumnResizeEnd,
120
+ createFilterField,
121
+ AddColumnComponent,
122
+ onColumnsOrderChange,
123
+ data,
124
+ cellRenderer: CellRenderer,
125
+ rowHeight = 54,
126
+ draggingColumnId
127
+ }: VirtualTableContextProps<any>) => {
128
+
129
+ const columnRefs = useMemo(() => columns.map(() => createRef<HTMLDivElement>()), [columns.length]);
23
130
  const [isResizing, setIsResizing] = useState(-1);
24
131
 
25
132
  const adjustWidthColumn = useCallback((index: number, width: number, end: boolean) => {
@@ -99,33 +206,42 @@ export const VirtualTableHeaderRow = ({
99
206
  }, [setCursorDocument]);
100
207
 
101
208
  return (
102
- <div
103
- className={cls(defaultBorderMixin, "z-20 sticky min-w-full flex w-fit flex-row top-0 left-0 h-12 border-b bg-surface-50 dark:bg-surface-900")}>
104
- {columns.map((c, columnIndex) => {
105
- const column = columns[columnIndex];
106
-
107
- const filterForThisProperty: [VirtualTableWhereFilterOp, any] | undefined =
108
- column && filter && filter[column.key]
109
- ? filter[column.key]
110
- : undefined;
111
- return <ErrorBoundary key={"header_" + column.key}>
112
- <VirtualTableHeader
113
- resizeHandleRef={columnRefs[columnIndex]}
114
- columnIndex={columnIndex}
115
- isResizingIndex={isResizing}
116
- onFilterUpdate={onFilterUpdate}
117
- filter={filterForThisProperty}
118
- sort={sortByProperty === column.key ? currentSort : undefined}
119
- onColumnSort={onColumnSort}
120
- onClickResizeColumn={onClickResizeColumn}
121
- column={column}
122
- createFilterField={createFilterField}
123
- AdditionalHeaderWidget={column.AdditionalHeaderWidget}/>
124
- </ErrorBoundary>;
125
- })}
126
-
127
- {AddColumnComponent && <AddColumnComponent/>}
128
-
129
- </div>
209
+ <>
210
+ <div
211
+ className={cls(defaultBorderMixin, "z-20 sticky min-w-full flex w-fit flex-row top-0 left-0 h-12 border-b bg-surface-50 dark:bg-surface-900")}>
212
+ {columns.map((column, columnIndex) => {
213
+ const filterForThisProperty: [VirtualTableWhereFilterOp, any] | undefined =
214
+ column && filter && filter[column.key]
215
+ ? filter[column.key]
216
+ : undefined;
217
+
218
+ const isDraggable = !column.frozen && !!onColumnsOrderChange;
219
+ const isDragging = draggingColumnId === column.key;
220
+
221
+ return (
222
+ <ErrorBoundary key={"header_" + column.key}>
223
+ <SortableColumnHeader
224
+ column={column}
225
+ columnIndex={columnIndex}
226
+ columnRefs={columnRefs}
227
+ isResizing={isResizing}
228
+ onFilterUpdate={onFilterUpdate}
229
+ filter={filterForThisProperty}
230
+ sortByProperty={sortByProperty}
231
+ currentSort={currentSort}
232
+ onColumnSort={onColumnSort}
233
+ onClickResizeColumn={onClickResizeColumn}
234
+ createFilterField={createFilterField}
235
+ isDragging={isDragging}
236
+ isDraggable={isDraggable}
237
+ />
238
+ </ErrorBoundary>
239
+ );
240
+ })}
241
+
242
+ {AddColumnComponent && <AddColumnComponent />}
243
+
244
+ </div>
245
+ </>
130
246
  );
131
247
  };
@@ -38,7 +38,7 @@ export interface VirtualTableProps<T extends Record<string, any>> {
38
38
  * @param sortBy
39
39
  */
40
40
  checkFilterCombination?: (filterValues: VirtualTableFilterValues<Extract<keyof T, string>>,
41
- sortBy?: [string, "asc" | "desc"]) => boolean;
41
+ sortBy?: [string, "asc" | "desc"]) => boolean;
42
42
 
43
43
  /**
44
44
  * A callback function when scrolling the table to near the end
@@ -162,6 +162,12 @@ export interface VirtualTableProps<T extends Record<string, any>> {
162
162
  */
163
163
  initialScroll?: number;
164
164
 
165
+ /**
166
+ * Callback when columns are reordered via drag-and-drop.
167
+ * @param columns The new column order
168
+ */
169
+ onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
170
+
165
171
  }
166
172
 
167
173
  export type CellRendererParams<T = any> = {
@@ -172,6 +178,13 @@ export type CellRendererParams<T = any> = {
172
178
  rowIndex: number;
173
179
  width: number;
174
180
  isScrolling?: boolean;
181
+ // Sortable props for dnd-kit integration
182
+ sortableNodeRef?: (node: HTMLElement | null) => void;
183
+ sortableStyle?: React.CSSProperties;
184
+ sortableAttributes?: Record<string, any>;
185
+ isDragging?: boolean;
186
+ isDraggable?: boolean;
187
+ frozen?: boolean;
175
188
  };
176
189
 
177
190
  /**
@@ -29,7 +29,7 @@ export const VirtualTableRow = React.memo<VirtualTableRowProps<any>>(
29
29
  return (
30
30
  <div
31
31
  className={cls(
32
- "flex min-w-full text-sm border-b border-surface-200 dark:border-surface-800 border-opacity-40 dark:border-opacity-40",
32
+ "flex min-w-full text-sm border-b border-surface-200 dark:border-surface-800 border-opacity-40 border-surface-200/40 dark:border-opacity-40 dark:border-surface-800/40",
33
33
  rowClassName ? rowClassName(rowData) : "",
34
34
  {
35
35
  "hover:bg-opacity-95": hoverRow,
@@ -6,6 +6,7 @@ export function VirtualTableDateField(props: {
6
6
  name: string;
7
7
  error: Error | undefined;
8
8
  mode?: "date" | "date_time";
9
+ timezone?: string;
9
10
  internalValue: Date | undefined | null;
10
11
  updateValue: (newValue: (Date | null)) => void;
11
12
  focused: boolean;
@@ -18,6 +19,7 @@ export function VirtualTableDateField(props: {
18
19
  disabled,
19
20
  error,
20
21
  mode,
22
+ timezone,
21
23
  internalValue,
22
24
  updateValue
23
25
  } = props;
@@ -31,6 +33,7 @@ export function VirtualTableDateField(props: {
31
33
  inputClassName={cls("w-full h-full", focusedDisabled)}
32
34
  className={cls("w-full h-full", focusedDisabled)}
33
35
  mode={mode}
36
+ timezone={timezone}
34
37
  locale={locale}
35
38
  />
36
39
  );