@firecms/core 3.0.1 → 3.1.0-canary.24c8270

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 (186) 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/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
  10. package/dist/components/EntityCollectionView/Board.d.ts +2 -0
  11. package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
  12. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
  13. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
  14. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
  15. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  16. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  18. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  19. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  20. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  21. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +44 -0
  22. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  23. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  24. package/dist/components/ErrorBoundary.d.ts +1 -1
  25. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  26. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  27. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  28. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +3 -1
  29. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  30. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
  31. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  32. package/dist/components/VirtualTable/types.d.ts +2 -0
  33. package/dist/components/index.d.ts +3 -0
  34. package/dist/contexts/index.d.ts +10 -0
  35. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  36. package/dist/core/index.d.ts +1 -0
  37. package/dist/form/components/ErrorFocus.d.ts +1 -1
  38. package/dist/form/validation.d.ts +3 -2
  39. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  40. package/dist/hooks/useCollapsedGroups.d.ts +4 -1
  41. package/dist/index.es.js +5316 -1592
  42. package/dist/index.es.js.map +1 -1
  43. package/dist/index.umd.js +5309 -1586
  44. package/dist/index.umd.js.map +1 -1
  45. package/dist/internal/useRestoreScroll.d.ts +1 -1
  46. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  47. package/dist/preview/components/DatePreview.d.ts +13 -3
  48. package/dist/preview/components/ImagePreview.d.ts +5 -1
  49. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  50. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  51. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  52. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  53. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  54. package/dist/types/analytics.d.ts +1 -1
  55. package/dist/types/collections.d.ts +50 -2
  56. package/dist/types/datasource.d.ts +0 -1
  57. package/dist/types/plugins.d.ts +62 -1
  58. package/dist/types/properties.d.ts +259 -4
  59. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  60. package/dist/util/__tests__/objects.test.d.ts +1 -0
  61. package/dist/util/conditions.d.ts +26 -0
  62. package/dist/util/entities.d.ts +2 -3
  63. package/dist/util/index.d.ts +2 -1
  64. package/dist/util/property_utils.d.ts +2 -1
  65. package/dist/util/resolutions.d.ts +3 -3
  66. package/package.json +14 -11
  67. package/src/app/Scaffold.tsx +14 -15
  68. package/src/components/AIIcon.tsx +39 -0
  69. package/src/components/ArrayContainer.tsx +1 -4
  70. package/src/components/ClearFilterSortButton.tsx +19 -16
  71. package/src/components/ConfirmationDialog.tsx +0 -2
  72. package/src/components/DeleteEntityDialog.tsx +2 -4
  73. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
  74. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  75. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  76. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  77. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
  78. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  79. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  80. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  81. package/src/components/EntityCollectionView/Board.tsx +324 -0
  82. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  83. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  84. package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
  85. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  86. package/src/components/EntityCollectionView/EntityCard.tsx +235 -0
  87. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +733 -0
  88. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
  89. package/src/components/EntityCollectionView/EntityCollectionView.tsx +519 -203
  90. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
  91. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
  92. package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
  93. package/src/components/EntityCollectionView/ViewModeToggle.tsx +199 -0
  94. package/src/components/EntityCollectionView/board_types.ts +113 -0
  95. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  96. package/src/components/ErrorTooltip.tsx +2 -1
  97. package/src/components/HomePage/DefaultHomePage.tsx +47 -10
  98. package/src/components/HomePage/HomePageDnD.tsx +56 -41
  99. package/src/components/HomePage/NavigationCard.tsx +20 -18
  100. package/src/components/HomePage/NavigationGroup.tsx +17 -16
  101. package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
  102. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  103. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
  104. package/src/components/ReferenceWidget.tsx +2 -4
  105. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  106. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  107. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
  108. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
  109. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
  110. package/src/components/UnsavedChangesDialog.tsx +0 -2
  111. package/src/components/UserDisplay.tsx +4 -4
  112. package/src/components/VirtualTable/VirtualTable.tsx +272 -118
  113. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  114. package/src/components/VirtualTable/VirtualTableHeader.tsx +59 -50
  115. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
  116. package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
  117. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  118. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  119. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +19 -6
  120. package/src/components/VirtualTable/types.tsx +2 -0
  121. package/src/components/common/useColumnsIds.tsx +95 -3
  122. package/src/components/common/useDataSourceTableController.tsx +21 -4
  123. package/src/components/index.tsx +4 -0
  124. package/src/contexts/BreacrumbsContext.tsx +15 -8
  125. package/src/contexts/index.ts +10 -0
  126. package/src/core/DefaultAppBar.tsx +40 -27
  127. package/src/core/DefaultDrawer.tsx +42 -56
  128. package/src/core/DrawerNavigationGroup.tsx +118 -0
  129. package/src/core/DrawerNavigationItem.tsx +4 -3
  130. package/src/core/EntityEditView.tsx +41 -43
  131. package/src/core/EntitySidePanel.tsx +28 -26
  132. package/src/core/SideDialogs.tsx +4 -2
  133. package/src/core/field_configs.tsx +14 -9
  134. package/src/core/index.tsx +1 -0
  135. package/src/form/EntityForm.tsx +69 -60
  136. package/src/form/PropertyFieldBinding.tsx +61 -46
  137. package/src/form/components/ErrorFocus.tsx +3 -3
  138. package/src/form/components/StorageItemPreview.tsx +2 -1
  139. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  140. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  141. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  142. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  143. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +22 -18
  144. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +83 -83
  145. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  146. package/src/form/validation.ts +245 -160
  147. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  148. package/src/hooks/useBuildNavigationController.tsx +71 -28
  149. package/src/hooks/useCollapsedGroups.ts +12 -4
  150. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  151. package/src/internal/useBuildDataSource.ts +68 -34
  152. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  153. package/src/internal/useBuildSideEntityController.tsx +24 -24
  154. package/src/internal/useRestoreScroll.tsx +26 -14
  155. package/src/preview/PropertyPreview.tsx +41 -32
  156. package/src/preview/PropertyPreviewProps.tsx +6 -0
  157. package/src/preview/components/DatePreview.tsx +72 -4
  158. package/src/preview/components/EmptyValue.tsx +1 -1
  159. package/src/preview/components/ImagePreview.tsx +37 -21
  160. package/src/preview/components/StorageThumbnail.tsx +16 -12
  161. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  162. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  163. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  164. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  165. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  166. package/src/routes/CustomCMSRoute.tsx +1 -0
  167. package/src/routes/FireCMSRoute.tsx +26 -13
  168. package/src/types/analytics.ts +10 -0
  169. package/src/types/collections.ts +57 -3
  170. package/src/types/datasource.ts +54 -56
  171. package/src/types/plugins.tsx +69 -1
  172. package/src/types/properties.ts +347 -27
  173. package/src/util/__tests__/conditions.test.ts +506 -0
  174. package/src/util/__tests__/objects.test.ts +196 -0
  175. package/src/util/callbacks.ts +6 -3
  176. package/src/util/collections.ts +51 -6
  177. package/src/util/conditions.ts +339 -0
  178. package/src/util/entities.ts +29 -30
  179. package/src/util/entity_cache.ts +2 -1
  180. package/src/util/index.ts +2 -1
  181. package/src/util/join_collections.ts +10 -8
  182. package/src/util/objects.ts +31 -13
  183. package/src/util/{references.ts → previews.ts} +16 -2
  184. package/src/util/property_utils.tsx +37 -11
  185. package/src/util/resolutions.ts +62 -58
  186. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -28,7 +28,7 @@ export function EntityTableCellActions({
28
28
  }
29
29
  }, []);
30
30
 
31
- const iconRef = useRef<HTMLButtonElement>();
31
+ const iconRef = useRef<HTMLButtonElement>(undefined);
32
32
  useEffect(() => {
33
33
  if (iconRef.current && selected) {
34
34
  iconRef.current.focus({ preventScroll: true });
@@ -1,20 +1,20 @@
1
1
  import React, { useCallback, useEffect } from "react";
2
2
 
3
3
  interface DraggableProps {
4
- containerRef: React.RefObject<HTMLDivElement>,
5
- innerRef: React.RefObject<HTMLDivElement>,
4
+ containerRef: React.RefObject<HTMLDivElement | null>,
5
+ innerRef: React.RefObject<HTMLDivElement | null>,
6
6
  x?: number;
7
7
  y?: number;
8
8
  onMove: (params: { x: number, y: number }) => void,
9
9
  }
10
10
 
11
11
  export function useDraggable({
12
- containerRef,
13
- innerRef,
14
- x,
15
- y,
16
- onMove
17
- }: DraggableProps) {
12
+ containerRef,
13
+ innerRef,
14
+ x,
15
+ y,
16
+ onMove
17
+ }: DraggableProps) {
18
18
 
19
19
  let relX = 0;
20
20
  let relY = 0;
@@ -62,9 +62,9 @@ export function useDraggable({
62
62
  if (event.target.localName === "input" || !listeningRef.current)
63
63
  return;
64
64
  onMove({
65
- x: event.screenX - relX,
66
- y: event.screenY - relY
67
- }
65
+ x: event.screenX - relX,
66
+ y: event.screenY - relY
67
+ }
68
68
  );
69
69
  event.stopPropagation();
70
70
  };
@@ -0,0 +1,324 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import {
3
+ DndContext,
4
+ DragEndEvent,
5
+ DragOverEvent,
6
+ DragOverlay,
7
+ DragStartEvent,
8
+ PointerSensor,
9
+ pointerWithin,
10
+ useSensor,
11
+ useSensors
12
+ } from "@dnd-kit/core";
13
+ import { arrayMove, horizontalListSortingStrategy, SortableContext } from "@dnd-kit/sortable";
14
+ import { BoardColumn } from "./BoardColumn";
15
+ import { BoardItem, BoardItemMap, BoardItemViewProps, BoardProps } from "./board_types";
16
+ import { cls } from "@firecms/ui";
17
+
18
+ export function Board<M extends Record<string, any>, COLUMN extends string>({
19
+ data,
20
+ columns: columnsProp,
21
+ columnLabels,
22
+ columnColors,
23
+ className,
24
+ assignColumn,
25
+ allowColumnReorder = false,
26
+ onColumnReorder,
27
+ onItemsReorder,
28
+ ItemComponent,
29
+ columnLoadingState,
30
+ onLoadMoreColumn,
31
+ onAddItemToColumn,
32
+ AddColumnComponent,
33
+ }: BoardProps<M, COLUMN>) {
34
+
35
+ const [activeItem, setActiveItem] = useState<BoardItem<M> | null>(null);
36
+ const [activeColumn, setActiveColumn] = useState<COLUMN | null>(null);
37
+ const [isDragging, setIsDragging] = useState(false);
38
+ const [dragOverColumnId, setDragOverColumnId] = useState<string | null>(null);
39
+ const [itemMapState, setItemMapState] = useState<BoardItemMap<M>>(() => {
40
+ const dataColumnMap: Record<string, COLUMN> = data.reduce((prev, item: BoardItem<M>) => ({
41
+ ...prev,
42
+ [item.id]: assignColumn(item)
43
+ }), {});
44
+ return columnsProp.reduce(
45
+ (previous: BoardItemMap<M>, column: COLUMN) => ({
46
+ ...previous,
47
+ [column]: data.filter((item: BoardItem<M>) => dataColumnMap[item.id] === column)
48
+ }),
49
+ {}
50
+ );
51
+ });
52
+
53
+ const sensors = useSensors(
54
+ useSensor(PointerSensor, {
55
+ activationConstraint: {
56
+ distance: 5,
57
+ },
58
+ })
59
+ );
60
+
61
+ useEffect(() => {
62
+ const dataColumnMap: Record<string, COLUMN> = data.reduce((prev, item) => ({
63
+ ...prev,
64
+ [item.id]: assignColumn(item)
65
+ }), {});
66
+
67
+ setItemMapState(() => columnsProp.reduce(
68
+ (previous: BoardItemMap<M>, column: COLUMN) => ({
69
+ ...previous,
70
+ [column]: data.filter((item: BoardItem<M>) => dataColumnMap[item.id] === column)
71
+ }),
72
+ {}
73
+ ));
74
+ }, [data, columnsProp, assignColumn]);
75
+
76
+ const findColumnByItemId = (id: string): string | undefined => {
77
+ return Object.keys(itemMapState).find(col => itemMapState[col]?.some(i => i.id === id));
78
+ };
79
+
80
+ const handleDragStart = (event: DragStartEvent) => {
81
+ setIsDragging(true);
82
+ setDragOverColumnId(null);
83
+ const { active } = event;
84
+
85
+ if (active.data.current?.type === "COLUMN") {
86
+ const columnId = active.id as string;
87
+ const column = columnsProp.find(col => String(col) === columnId);
88
+ if (column) {
89
+ setActiveColumn(column);
90
+ }
91
+ } else if (active.data.current?.type === "ITEM") {
92
+ const columnId = findColumnByItemId(active.id as string);
93
+ if (columnId) {
94
+ const item = itemMapState[columnId]?.find(i => i.id === active.id);
95
+ setActiveItem(item || null);
96
+ }
97
+ }
98
+ };
99
+
100
+ const handleDragOver = (event: DragOverEvent) => {
101
+ const {
102
+ active,
103
+ over
104
+ } = event;
105
+
106
+ if (!over) {
107
+ setDragOverColumnId(null);
108
+ return;
109
+ }
110
+
111
+ let currentHoveredColumnId: string | null = null;
112
+ const overId = over.id as string;
113
+ const overDataType = over.data.current?.type as string | undefined;
114
+
115
+ if (overDataType === "ITEM-LIST" || overDataType === "COLUMN") {
116
+ currentHoveredColumnId = overId;
117
+ } else if (overDataType === "ITEM") {
118
+ currentHoveredColumnId = findColumnByItemId(overId) || null;
119
+ } else if (columnsProp.includes(overId as COLUMN)) {
120
+ currentHoveredColumnId = overId;
121
+ }
122
+
123
+ setDragOverColumnId(currentHoveredColumnId);
124
+
125
+ // Skip item reordering if dragging a column
126
+ if (active.data.current?.type !== "ITEM") {
127
+ return;
128
+ }
129
+
130
+ const activeId = active.id as string;
131
+ const activeColumn = findColumnByItemId(activeId);
132
+ let overColumnForMove = findColumnByItemId(overId);
133
+
134
+ if (!overColumnForMove && overDataType === "ITEM-LIST") {
135
+ overColumnForMove = overId;
136
+ }
137
+ if (!overColumnForMove && columnsProp.includes(overId as COLUMN)) {
138
+ overColumnForMove = overId;
139
+ }
140
+
141
+ if (!activeColumn || !overColumnForMove) return;
142
+ if (activeColumn === overColumnForMove && activeId === overId && overDataType !== "ITEM-LIST") return;
143
+
144
+ // Prevent moving to a column if item with same ID already exists there
145
+ if (overColumnForMove !== activeColumn &&
146
+ itemMapState[overColumnForMove]?.some(i => i.id === activeId)) {
147
+ return;
148
+ }
149
+
150
+ setItemMapState(currentMap => {
151
+ const activeItems = [...(currentMap[activeColumn] || [])];
152
+ const overItems = [...(currentMap[overColumnForMove!] || [])];
153
+ const activeIndex = activeItems.findIndex(i => i.id === activeId);
154
+
155
+ if (activeIndex === -1) return currentMap;
156
+
157
+ let overIndex;
158
+ if (overDataType === "ITEM-LIST" || (columnsProp.includes(overId as COLUMN) && !findColumnByItemId(overId))) {
159
+ overIndex = overItems.length;
160
+ } else {
161
+ overIndex = overItems.findIndex(i => i.id === overId);
162
+ if (overIndex !== -1) {
163
+ const { y } = event.delta;
164
+ const overRect = over.rect;
165
+ if (overRect) {
166
+ const threshold = overRect.height / 2;
167
+ if (y > threshold) {
168
+ overIndex += 1;
169
+ }
170
+ }
171
+ } else {
172
+ overIndex = overItems.length;
173
+ }
174
+ }
175
+
176
+ if (activeColumn === overColumnForMove && activeIndex === overIndex) return currentMap;
177
+
178
+ const newItemMap = { ...currentMap };
179
+ if (activeColumn === overColumnForMove) {
180
+ newItemMap[activeColumn] = arrayMove(activeItems, activeIndex, overIndex);
181
+ } else {
182
+ const [moved] = activeItems.splice(activeIndex, 1);
183
+ overItems.splice(overIndex, 0, moved);
184
+ newItemMap[activeColumn] = activeItems;
185
+ newItemMap[overColumnForMove] = overItems;
186
+ }
187
+ return newItemMap;
188
+ });
189
+ };
190
+
191
+ const handleDragEnd = (event: DragEndEvent) => {
192
+ const {
193
+ active,
194
+ over
195
+ } = event;
196
+
197
+ setIsDragging(false);
198
+ setActiveItem(null);
199
+ setActiveColumn(null);
200
+ setDragOverColumnId(null);
201
+
202
+ if (!over) return;
203
+
204
+ const activeId = active.id as string;
205
+ const overId = over.id as string;
206
+
207
+ if (active.data.current?.type === "COLUMN" &&
208
+ over.data.current?.type === "COLUMN" &&
209
+ activeId !== overId) {
210
+
211
+ const oldIndex = columnsProp.findIndex(col => String(col) === activeId);
212
+ const newIndex = columnsProp.findIndex(col => String(col) === overId);
213
+
214
+ if (oldIndex !== -1 && newIndex !== -1 && onColumnReorder && allowColumnReorder) {
215
+ const newOrder = arrayMove([...columnsProp], oldIndex, newIndex);
216
+ onColumnReorder(newOrder);
217
+ }
218
+ } else if (active.data.current?.type === "ITEM" && onItemsReorder) {
219
+ // Find the original column assignment from the input data
220
+ const originalColumn = data.find(item => item.id === activeId)
221
+ ? assignColumn(data.find(item => item.id === activeId)!)
222
+ : undefined;
223
+
224
+ // Find the current column assignment from our internal state
225
+ const currentColumn = findColumnByItemId(activeId) as COLUMN | undefined;
226
+
227
+ // When items have been reordered, convert itemMapState to a flat list
228
+ const allItems: BoardItem<M>[] = [];
229
+
230
+ // Collect all items from all columns in their current order
231
+ Object.entries(itemMapState).forEach(([, columnItems]) => {
232
+ if (columnItems && (columnItems as BoardItem<M>[]).length > 0) {
233
+ allItems.push(...(columnItems as BoardItem<M>[]));
234
+ }
235
+ });
236
+
237
+ // Notify parent component of the change, including column movement information
238
+ if (originalColumn !== currentColumn && originalColumn && currentColumn) {
239
+ // Item has moved between columns - provide this context to parent
240
+ onItemsReorder(allItems, {
241
+ itemId: activeId,
242
+ sourceColumn: originalColumn,
243
+ targetColumn: currentColumn
244
+ });
245
+ } else if (currentColumn) {
246
+ // Reordering within the same column - still need to provide moveInfo
247
+ onItemsReorder(allItems, {
248
+ itemId: activeId,
249
+ sourceColumn: currentColumn,
250
+ targetColumn: currentColumn
251
+ });
252
+ }
253
+ }
254
+ };
255
+
256
+ return (
257
+ <DndContext
258
+ sensors={sensors}
259
+ collisionDetection={pointerWithin}
260
+ onDragStart={handleDragStart}
261
+ onDragOver={handleDragOver}
262
+ onDragEnd={handleDragEnd}
263
+ >
264
+ <DragOverlay dropAnimation={{
265
+ duration: 300,
266
+ easing: "cubic-bezier(0.18, 0.67, 0.6, 1.22)",
267
+ }}>
268
+ {activeItem ? (
269
+ <ItemComponent
270
+ item={activeItem}
271
+ isDragging={true}
272
+ index={-1}
273
+ style={{
274
+ boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
275
+ opacity: 0.9,
276
+ }}
277
+ />
278
+ ) : activeColumn ? (
279
+ <BoardColumn
280
+ key={String(activeColumn)}
281
+ index={-1}
282
+ id={String(activeColumn)}
283
+ title={columnLabels?.[activeColumn] ?? String(activeColumn)}
284
+ items={itemMapState[String(activeColumn)] || []}
285
+ ItemComponent={ItemComponent}
286
+ isDragging={true}
287
+ isDragOverColumn={false}
288
+ />
289
+ ) : null}
290
+ </DragOverlay>
291
+
292
+ <SortableContext
293
+ items={columnsProp.map(String)}
294
+ strategy={horizontalListSortingStrategy}
295
+ >
296
+ <div className={cls("p-2 md:p-3 lg:p-4 h-full min-w-full inline-flex", className)}>
297
+ {columnsProp.map((key: COLUMN, index: number) => (
298
+ <BoardColumn
299
+ key={String(key)}
300
+ index={index}
301
+ id={String(key)}
302
+ title={columnLabels?.[key] ?? String(key)}
303
+ color={columnColors?.[key]}
304
+ items={itemMapState[String(key)] || []}
305
+ ItemComponent={ItemComponent}
306
+ isDragging={isDragging}
307
+ isDragOverColumn={String(key) === dragOverColumnId}
308
+ allowReorder={allowColumnReorder}
309
+ loading={columnLoadingState?.[String(key)]?.loading}
310
+ hasMore={columnLoadingState?.[String(key)]?.hasMore}
311
+ totalCount={columnLoadingState?.[String(key)]?.totalCount}
312
+ onLoadMore={onLoadMoreColumn ? () => onLoadMoreColumn(key) : undefined}
313
+ onAddItem={onAddItemToColumn ? () => onAddItemToColumn(key) : undefined}
314
+ style={{
315
+ opacity: activeColumn === key ? 0 : 1
316
+ }}
317
+ />
318
+ ))}
319
+ {AddColumnComponent}
320
+ </div>
321
+ </SortableContext>
322
+ </DndContext>
323
+ );
324
+ }
@@ -0,0 +1,158 @@
1
+ import React, { memo, useMemo } from "react";
2
+ import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
3
+ import { CSS } from "@dnd-kit/utilities";
4
+ import { BoardSortableList } from "./BoardSortableList";
5
+ import { BoardColumnTitle } from "./BoardColumnTitle";
6
+ import { BoardItem, BoardItemViewProps } from "./board_types";
7
+ import { AddIcon, ChipColorKey, ChipColorScheme, cls, defaultBorderMixin, IconButton } from "@firecms/ui";
8
+
9
+ export interface BoardColumnProps<M extends Record<string, any>> {
10
+ id: string;
11
+ title: string;
12
+ items: BoardItem<M>[];
13
+ index: number;
14
+ ItemComponent: React.ComponentType<BoardItemViewProps<M>>;
15
+ isDragging: boolean;
16
+ isDragOverColumn: boolean;
17
+ /**
18
+ * Whether column reordering is allowed (shows drag handle)
19
+ */
20
+ allowReorder?: boolean;
21
+ /**
22
+ * Whether items are loading for this column
23
+ */
24
+ loading?: boolean;
25
+ /**
26
+ * Whether there are more items to load
27
+ */
28
+ hasMore?: boolean;
29
+ /**
30
+ * Callback to load more items
31
+ */
32
+ onLoadMore?: () => void;
33
+ /**
34
+ * Callback to add a new item to this column
35
+ */
36
+ onAddItem?: () => void;
37
+ /**
38
+ * Total count of entities in this column
39
+ */
40
+ totalCount?: number;
41
+ /**
42
+ * Color of the column header indicator
43
+ */
44
+ color?: ChipColorKey | ChipColorScheme;
45
+ style?: React.CSSProperties;
46
+ }
47
+
48
+ // Memoized to prevent unnecessary re-renders when other columns change
49
+ export const BoardColumn = memo(function BoardColumn<M extends Record<string, any>>({
50
+ id,
51
+ title,
52
+ items,
53
+ ItemComponent,
54
+ isDragging,
55
+ isDragOverColumn,
56
+ allowReorder = false,
57
+ loading = false,
58
+ hasMore = false,
59
+ onLoadMore,
60
+ onAddItem,
61
+ totalCount,
62
+ color,
63
+ style
64
+ }: BoardColumnProps<M>) {
65
+ const {
66
+ setNodeRef,
67
+ attributes,
68
+ listeners,
69
+ isDragging: isColumnBeingDragged,
70
+ transform,
71
+ transition,
72
+ } = useSortable({
73
+ id,
74
+ data: { type: "COLUMN" },
75
+ disabled: !allowReorder
76
+ });
77
+
78
+ // Memoize combined style to avoid object recreation
79
+ const combinedStyle = useMemo(() => ({
80
+ ...style,
81
+ transform: CSS.Transform.toString(transform),
82
+ transition,
83
+ zIndex: isColumnBeingDragged ? 2 : 1,
84
+ }), [style, transform, transition, isColumnBeingDragged]);
85
+
86
+ // Only apply drag listeners if reordering is allowed
87
+ const dragListeners = allowReorder ? listeners : {};
88
+
89
+ // Memoize className to avoid recomputation
90
+ const columnClassName = useMemo(() => cls(
91
+ "border h-full w-80 min-w-80 mx-2 flex flex-col rounded-md",
92
+ defaultBorderMixin,
93
+ isColumnBeingDragged ? "ring-2 ring-primary" : ""
94
+ ), [isColumnBeingDragged]);
95
+
96
+ const headerClassName = useMemo(() => cls(
97
+ "flex items-center justify-between px-2 rounded-t-md transition-colors duration-200 ease-in-out",
98
+ isColumnBeingDragged
99
+ ? "bg-surface-100 dark:bg-surface-900"
100
+ : "bg-surface-50 hover:bg-surface-100 dark:bg-surface-950 dark:hover:bg-surface-900",
101
+ allowReorder ? "cursor-grab" : ""
102
+ ), [isColumnBeingDragged, allowReorder]);
103
+
104
+ // Memoize items IDs array to avoid recreating on each render
105
+ const itemIds = useMemo(() => items.map(i => i.id), [items]);
106
+
107
+ return (
108
+ <div
109
+ ref={setNodeRef}
110
+ style={combinedStyle}
111
+ {...attributes}
112
+ className={columnClassName}
113
+ >
114
+ <div
115
+ {...dragListeners}
116
+ className={headerClassName}
117
+ >
118
+ <div className="flex items-center gap-2">
119
+ <BoardColumnTitle aria-label={`${title} item list`} color={color}>
120
+ {title}
121
+ </BoardColumnTitle>
122
+ {totalCount !== undefined && (
123
+ <span className="text-xs text-surface-500 dark:text-surface-400">
124
+ {totalCount}
125
+ </span>
126
+ )}
127
+ </div>
128
+ {onAddItem && (
129
+ <IconButton
130
+ size="small"
131
+ onClick={(e: React.MouseEvent) => {
132
+ e.stopPropagation();
133
+ onAddItem();
134
+ }}
135
+ className="opacity-60 hover:opacity-100"
136
+ >
137
+ <AddIcon size="small"/>
138
+ </IconButton>
139
+ )}
140
+ </div>
141
+ <SortableContext
142
+ items={itemIds}
143
+ strategy={verticalListSortingStrategy}
144
+ >
145
+ <BoardSortableList
146
+ columnId={id}
147
+ items={items}
148
+ ItemComponent={ItemComponent}
149
+ isDragging={isDragging}
150
+ isDragOverColumn={isDragOverColumn}
151
+ loading={loading}
152
+ hasMore={hasMore}
153
+ onLoadMore={onLoadMore}
154
+ />
155
+ </SortableContext>
156
+ </div>
157
+ );
158
+ }) as <M extends Record<string, any>>(props: BoardColumnProps<M>) => React.ReactElement;
@@ -0,0 +1,45 @@
1
+ import { ChipColorKey, ChipColorScheme, getColorSchemeForKey, cls } from "@firecms/ui";
2
+ import React, { useMemo } from "react";
3
+
4
+ export interface BoardColumnTitleProps {
5
+ children: React.ReactNode;
6
+ className?: string;
7
+ "aria-label"?: string;
8
+ color?: ChipColorKey | ChipColorScheme;
9
+ }
10
+
11
+ export function BoardColumnTitle({
12
+ children,
13
+ className,
14
+ color,
15
+ ...props
16
+ }: BoardColumnTitleProps) {
17
+ const colorScheme = useMemo(() => {
18
+ if (!color) return undefined;
19
+ if (typeof color === "string") {
20
+ return getColorSchemeForKey(color);
21
+ }
22
+ return color;
23
+ }, [color]);
24
+
25
+ return (
26
+ <h4
27
+ className={
28
+ cls("py-3 px-3 transition-colors duration-200 flex-grow select-none relative outline-none focus:outline focus:outline-2 focus:outline-offset-2 flex items-center gap-3",
29
+ "text-sm font-semibold text-surface-800 dark:text-surface-200",
30
+ className)
31
+ }
32
+ {...props}
33
+ >
34
+ {colorScheme && (
35
+ <div
36
+ className="w-3 h-3 rounded-full flex-shrink-0"
37
+ style={{
38
+ backgroundColor: colorScheme.color
39
+ }}
40
+ />
41
+ )}
42
+ {children}
43
+ </h4>
44
+ );
45
+ }