@firecms/core 3.0.1 → 3.1.0-canary.768c91f

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 (185) 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 +5266 -1578
  42. package/dist/index.es.js.map +1 -1
  43. package/dist/index.umd.js +5260 -1573
  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/index.tsx +4 -0
  123. package/src/contexts/BreacrumbsContext.tsx +15 -8
  124. package/src/contexts/index.ts +10 -0
  125. package/src/core/DefaultAppBar.tsx +40 -27
  126. package/src/core/DefaultDrawer.tsx +42 -56
  127. package/src/core/DrawerNavigationGroup.tsx +118 -0
  128. package/src/core/DrawerNavigationItem.tsx +4 -3
  129. package/src/core/EntityEditView.tsx +41 -43
  130. package/src/core/EntitySidePanel.tsx +28 -26
  131. package/src/core/SideDialogs.tsx +4 -2
  132. package/src/core/field_configs.tsx +14 -9
  133. package/src/core/index.tsx +1 -0
  134. package/src/form/EntityForm.tsx +69 -60
  135. package/src/form/PropertyFieldBinding.tsx +61 -46
  136. package/src/form/components/ErrorFocus.tsx +3 -3
  137. package/src/form/components/StorageItemPreview.tsx +2 -1
  138. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  139. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  140. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  141. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  142. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +22 -18
  143. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +83 -83
  144. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  145. package/src/form/validation.ts +245 -160
  146. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  147. package/src/hooks/useBuildNavigationController.tsx +46 -23
  148. package/src/hooks/useCollapsedGroups.ts +12 -4
  149. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  150. package/src/internal/useBuildDataSource.ts +68 -34
  151. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  152. package/src/internal/useBuildSideEntityController.tsx +2 -4
  153. package/src/internal/useRestoreScroll.tsx +26 -14
  154. package/src/preview/PropertyPreview.tsx +41 -32
  155. package/src/preview/PropertyPreviewProps.tsx +6 -0
  156. package/src/preview/components/DatePreview.tsx +72 -4
  157. package/src/preview/components/EmptyValue.tsx +1 -1
  158. package/src/preview/components/ImagePreview.tsx +37 -21
  159. package/src/preview/components/StorageThumbnail.tsx +16 -12
  160. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  161. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  162. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  163. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  164. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  165. package/src/routes/CustomCMSRoute.tsx +1 -0
  166. package/src/routes/FireCMSRoute.tsx +26 -13
  167. package/src/types/analytics.ts +10 -0
  168. package/src/types/collections.ts +57 -3
  169. package/src/types/datasource.ts +54 -56
  170. package/src/types/plugins.tsx +69 -1
  171. package/src/types/properties.ts +347 -27
  172. package/src/util/__tests__/conditions.test.ts +506 -0
  173. package/src/util/__tests__/objects.test.ts +196 -0
  174. package/src/util/callbacks.ts +6 -3
  175. package/src/util/collections.ts +51 -6
  176. package/src/util/conditions.ts +339 -0
  177. package/src/util/entities.ts +29 -30
  178. package/src/util/entity_cache.ts +2 -1
  179. package/src/util/index.ts +2 -1
  180. package/src/util/join_collections.ts +10 -8
  181. package/src/util/objects.ts +31 -13
  182. package/src/util/{references.ts → previews.ts} +16 -2
  183. package/src/util/property_utils.tsx +37 -11
  184. package/src/util/resolutions.ts +62 -58
  185. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -12,12 +12,12 @@ import { ErrorBoundary } from "../../components";
12
12
  * @group Preview components
13
13
  */
14
14
  export function ArrayPropertyPreview({
15
- propertyKey,
16
- value,
17
- property: inputProperty,
18
- // entity,
19
- size
20
- }: PropertyPreviewProps<any[]>) {
15
+ propertyKey,
16
+ value,
17
+ property: inputProperty,
18
+ // entity,
19
+ size
20
+ }: PropertyPreviewProps<any[]>) {
21
21
 
22
22
  const authController = useAuthController();
23
23
  const customizationController = useCustomizationController();
@@ -28,6 +28,8 @@ export function ArrayPropertyPreview({
28
28
  authController
29
29
  });
30
30
 
31
+ if (!property) return null;
32
+
31
33
  if (!property.of) {
32
34
  throw Error(`You need to specify an 'of' prop (or specify a custom field) in your array property ${propertyKey}`);
33
35
  }
@@ -45,24 +47,24 @@ export function ArrayPropertyPreview({
45
47
  <div className="w-full flex flex-col gap-2">
46
48
  {values &&
47
49
  values.map((value, index) => {
48
- const of: ResolvedProperty = property.resolvedProperties[index] ??
49
- (property.resolvedProperties[index] ?? (Array.isArray(property.of) ? property.of[index] : property.of));
50
- return of
51
- ? <React.Fragment
52
- key={"preview_array_" + index}>
53
- <div className={cls(defaultBorderMixin, "m-1 border-b last:border-b-0")}>
54
- <ErrorBoundary>
55
- <PropertyPreview
56
- propertyKey={propertyKey}
57
- // entity={entity}
58
- value={value}
59
- property={of}
60
- size={childSize}/>
61
- </ErrorBoundary>
62
- </div>
63
- </React.Fragment>
64
- : null;
65
- }
50
+ const of: ResolvedProperty = property.resolvedProperties[index] ??
51
+ (property.resolvedProperties[index] ?? (Array.isArray(property.of) ? property.of[index] : property.of));
52
+ return of
53
+ ? <React.Fragment
54
+ key={"preview_array_" + index}>
55
+ <div className={cls(defaultBorderMixin, "m-1 border-b last:border-b-0")}>
56
+ <ErrorBoundary>
57
+ <PropertyPreview
58
+ propertyKey={propertyKey}
59
+ // entity={entity}
60
+ value={value}
61
+ property={of}
62
+ size={childSize} />
63
+ </ErrorBoundary>
64
+ </div>
65
+ </React.Fragment>
66
+ : null;
67
+ }
66
68
  )}
67
69
  </div>
68
70
  );
@@ -19,9 +19,9 @@ export interface SkeletonPropertyComponentProps {
19
19
  * @group Preview components
20
20
  */
21
21
  export function SkeletonPropertyComponent({
22
- property,
23
- size
24
- }: SkeletonPropertyComponentProps
22
+ property,
23
+ size
24
+ }: SkeletonPropertyComponentProps
25
25
  ) {
26
26
 
27
27
  if (!property) {
@@ -101,7 +101,7 @@ function renderMap<T extends Record<string, any>>(property: ResolvedMapProperty<
101
101
  {property.properties && property.properties[key] &&
102
102
  <SkeletonPropertyComponent
103
103
  property={property.properties[key]}
104
- size={"medium"}/>}
104
+ size={"medium"} />}
105
105
  </div>
106
106
  ))}
107
107
  </div>
@@ -110,29 +110,29 @@ function renderMap<T extends Record<string, any>>(property: ResolvedMapProperty<
110
110
  return (
111
111
  <table className="table-auto">
112
112
  <tbody>
113
- {mapPropertyKeys &&
114
- mapPropertyKeys.map((key, index) => {
115
- return (
116
- <tr
117
- key={`map_preview_table__${index}`}
118
- className="border-b last:border-b-0">
119
- <th key={`table-cell-title--${key}`}
120
- className="align-top"
121
- style={{ width: "30%" }}>
122
- <p className="text-xs text-secondary">
123
- {property.properties![key].name}
124
- </p>
125
- </th>
126
- <th key={`table-cell-${key}`}
127
- style={{ width: "70%" }}>
128
- {property.properties && property.properties[key] &&
129
- <SkeletonPropertyComponent
130
- property={property.properties[key]}
131
- size={"medium"}/>}
132
- </th>
133
- </tr>
134
- );
135
- })}
113
+ {mapPropertyKeys &&
114
+ mapPropertyKeys.map((key, index) => {
115
+ return (
116
+ <tr
117
+ key={`map_preview_table__${index}`}
118
+ className="border-b last:border-b-0">
119
+ <th key={`table-cell-title--${key}`}
120
+ className="align-top"
121
+ style={{ width: "30%" }}>
122
+ <p className="text-xs text-secondary">
123
+ {property.properties![key].name}
124
+ </p>
125
+ </th>
126
+ <th key={`table-cell-${key}`}
127
+ style={{ width: "70%" }}>
128
+ {property.properties && property.properties[key] &&
129
+ <SkeletonPropertyComponent
130
+ property={property.properties[key]}
131
+ size={"medium"} />}
132
+ </th>
133
+ </tr>
134
+ );
135
+ })}
136
136
  </tbody>
137
137
  </table>
138
138
  );
@@ -149,24 +149,24 @@ function renderArrayOfMaps<M extends Record<string, any>>(properties: ResolvedPr
149
149
  return (
150
150
  <table className="table-auto">
151
151
  <tbody>
152
- {
153
- [0, 1, 2].map((value, index) => {
154
- return (
155
- <tr key={`table_${value}_${index}`}>
156
- {tableProperties && tableProperties.map(
157
- (key) => (
158
- <th
159
- key={`table-cell-${key}`}
160
- >
161
- <SkeletonPropertyComponent
162
- property={(properties)[key]}
163
- size={"medium"}/>
164
- </th>
165
- )
166
- )}
167
- </tr>
168
- );
169
- })}
152
+ {
153
+ [0, 1, 2].map((value, index) => {
154
+ return (
155
+ <tr key={`table_${value}_${index}`}>
156
+ {tableProperties && tableProperties.map(
157
+ (key) => (
158
+ <th
159
+ key={`table-cell-${key}`}
160
+ >
161
+ <SkeletonPropertyComponent
162
+ property={(properties)[key]}
163
+ size={"medium"} />
164
+ </th>
165
+ )
166
+ )}
167
+ </tr>
168
+ );
169
+ })}
170
170
  </tbody>
171
171
  </table>
172
172
  );
@@ -203,14 +203,14 @@ function renderGenericArrayCell(
203
203
  return (
204
204
 
205
205
  <div key={"array_index_" + index}
206
- className={"flex flex-col gap-2"}>
206
+ className={"flex flex-col gap-2"}>
207
207
 
208
208
  {
209
209
  [0, 1].map((value, index) =>
210
210
  <>
211
211
  <SkeletonPropertyComponent key={`i_${index}`}
212
- property={property}
213
- size={"medium"}/>
212
+ property={property}
213
+ size={"medium"} />
214
214
  </>
215
215
  )}
216
216
  </div>
@@ -220,16 +220,21 @@ function renderGenericArrayCell(
220
220
  function renderUrlAudioComponent() {
221
221
  return (
222
222
  <Skeleton width={300}
223
- height={100}/>
223
+ height={100} />
224
224
  );
225
225
  }
226
226
 
227
- export function renderSkeletonImageThumbnail(size: PreviewSize) {
227
+ export function renderSkeletonImageThumbnail(size: PreviewSize, fill?: boolean) {
228
+ if (fill) {
229
+ return (
230
+ <Skeleton className="w-full h-full" />
231
+ );
232
+ }
228
233
  // eslint-disable-next-line react-hooks/rules-of-hooks
229
234
  const imageSize = size === "small" ? 40 : size === "medium" ? 100 : 200;
230
235
  return (
231
236
  <Skeleton width={imageSize}
232
- height={imageSize}/>
237
+ height={imageSize} />
233
238
  );
234
239
  }
235
240
 
@@ -237,12 +242,12 @@ function renderUrlVideo(size: PreviewSize) {
237
242
 
238
243
  return (
239
244
  <Skeleton width={size !== "large" ? 300 : 500}
240
- height={size !== "large" ? 200 : 250}/>
245
+ height={size !== "large" ? 200 : 250} />
241
246
  );
242
247
  }
243
248
 
244
249
  function renderReference() {
245
- return <Skeleton width={200} height={100}/>;
250
+ return <Skeleton width={200} height={100} />;
246
251
  }
247
252
 
248
253
  function renderUrlComponent(property: ResolvedStringProperty, size: PreviewSize = "large") {
@@ -270,14 +275,14 @@ function renderUrlFile(size: PreviewSize) {
270
275
  }
271
276
 
272
277
  export function renderSkeletonText(index?: number, width = 120) {
273
- return <Skeleton width={width} key={`skeleton_${index}`}/>;
278
+ return <Skeleton width={width} key={`skeleton_${index}`} />;
274
279
  }
275
280
 
276
281
  export function renderSkeletonCaptionText(index?: number) {
277
282
  return <Skeleton
278
- height={20}/>;
283
+ height={20} />;
279
284
  }
280
285
 
281
286
  export function renderSkeletonIcon() {
282
- return <Skeleton width={24} height={24}/>;
287
+ return <Skeleton width={24} height={24} />;
283
288
  }
@@ -13,6 +13,7 @@ export function CustomCMSRoute({ cmsView }: {
13
13
  breadcrumbs: [{
14
14
  title: cmsView.name,
15
15
  url: cmsView.path
16
+ // count: undefined (not applicable for custom views)
16
17
  }]
17
18
  });
18
19
  }, [cmsView.path]);
@@ -35,22 +35,34 @@ export function FireCMSRoute() {
35
35
  });
36
36
 
37
37
  useEffect(() => {
38
+ const lastEntry = navigationEntries[navigationEntries.length - 1];
39
+ const isViewingCollection = lastEntry?.type === "collection";
40
+
38
41
  breadcrumbs.set({
39
- breadcrumbs: navigationEntries.map(entry => {
42
+ breadcrumbs: navigationEntries.map((entry, index) => {
43
+ const isLastEntry = index === navigationEntries.length - 1;
44
+
40
45
  if (entry.type === "entity") {
41
46
  return ({
42
47
  title: entry.entityId,
43
48
  url: navigation.buildUrlCollectionPath(entry.fullPath)
49
+ // count: undefined (not applicable for entities)
44
50
  });
45
51
  } else if (entry.type === "custom_view") {
46
52
  return ({
47
53
  title: entry.view.name,
48
54
  url: navigation.buildUrlCollectionPath(entry.fullPath)
55
+ // count: undefined (not applicable for custom views)
49
56
  });
50
57
  } else if (entry.type === "collection") {
58
+ // Only show count badge (loading state) when viewing this collection directly
59
+ // Don't show count for parent collections when viewing an entity
60
+ const showCount = isLastEntry && isViewingCollection;
51
61
  return ({
52
62
  title: entry.collection.name,
53
- url: navigation.buildUrlCollectionPath(entry.fullPath)
63
+ url: navigation.buildUrlCollectionPath(entry.fullPath),
64
+ id: entry.fullPath,
65
+ ...(showCount ? { count: null } : {}) // null = loading, undefined = no badge
54
66
  });
55
67
  } else {
56
68
  throw new Error("Unexpected navigation entry type");
@@ -59,6 +71,7 @@ export function FireCMSRoute() {
59
71
  });
60
72
  }, [navigationEntries.map(entry => entry.path).join(",")]);
61
73
 
74
+
62
75
  if (isNew) {
63
76
  return <EntityFullScreenRoute
64
77
  pathname={pathname}
@@ -83,7 +96,7 @@ export function FireCMSRoute() {
83
96
  fullIdPath={collection.id}
84
97
  updateUrl={true}
85
98
  {...collection}
86
- Actions={toArray(collection.Actions)}/>
99
+ Actions={toArray(collection.Actions)} />
87
100
  }
88
101
 
89
102
  if (isSidePanel) {
@@ -104,7 +117,7 @@ export function FireCMSRoute() {
104
117
  fullPath={collection.path}
105
118
  updateUrl={true}
106
119
  {...collection}
107
- Actions={toArray(collection.Actions)}/>;
120
+ Actions={toArray(collection.Actions)} />;
108
121
  }
109
122
  }
110
123
 
@@ -131,11 +144,11 @@ function getSelectedTabFromUrl(isNew: boolean, lastCustomView: NavigationViewCol
131
144
  }
132
145
 
133
146
  function EntityFullScreenRoute({
134
- pathname,
135
- navigationEntries,
136
- isNew,
137
- isCopy
138
- }: {
147
+ pathname,
148
+ navigationEntries,
149
+ isNew,
150
+ isCopy
151
+ }: {
139
152
  pathname: string;
140
153
  navigationEntries: NavigationViewInternal[],
141
154
  isNew: boolean,
@@ -178,8 +191,8 @@ function EntityFullScreenRoute({
178
191
  let blocker: Blocker | undefined = undefined;
179
192
  try {
180
193
  blocker = useBlocker(({
181
- nextLocation
182
- }) => {
194
+ nextLocation
195
+ }) => {
183
196
  if (nextLocation.pathname.startsWith(entityPath))
184
197
  return false;
185
198
  return blocked.current;
@@ -195,7 +208,7 @@ function EntityFullScreenRoute({
195
208
  }
196
209
 
197
210
  if (!isNew && !lastEntityEntry) {
198
- return <NotFoundPage/>;
211
+ return <NotFoundPage />;
199
212
  }
200
213
 
201
214
  const collection = isNew ? lastCollectionEntry!.collection : lastEntityEntry!.parentCollection;
@@ -240,7 +253,7 @@ function EntityFullScreenRoute({
240
253
  open={blocker?.state === "blocked"}
241
254
  handleOk={() => blocker?.proceed?.()}
242
255
  handleCancel={() => blocker?.reset?.()}
243
- body={"You have unsaved changes in this entity."}/>
256
+ body={"You have unsaved changes in this entity."} />
244
257
 
245
258
  </>;
246
259
  }
@@ -34,5 +34,15 @@ export type CMSAnalyticsEvent =
34
34
 
35
35
  | "collection_inline_editing"
36
36
 
37
+ | "view_mode_changed"
38
+
39
+ | "kanban_card_moved"
40
+ | "kanban_column_reorder"
41
+ | "kanban_property_changed"
42
+ | "kanban_new_entity_in_column"
43
+ | "kanban_backfill_order"
44
+
45
+ | "card_view_entity_click"
46
+
37
47
  | "unmapped_event"
38
48
  ;
@@ -364,8 +364,61 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
364
364
  * Defaults to `manual_apply`.
365
365
  */
366
366
  localChangesBackup?: "manual_apply" | "auto_apply" | false;
367
+
368
+ /**
369
+ * Default view mode for displaying this collection.
370
+ * - "table": Display entities in a spreadsheet-like table (default)
371
+ * - "cards": Display entities as a grid of cards with thumbnails
372
+ * - "kanban": Display entities in a Kanban board grouped by a property
373
+ * Defaults to "table".
374
+ */
375
+ defaultViewMode?: ViewMode;
376
+
377
+ /**
378
+ * Which view modes are available for this collection.
379
+ * Possible values: "table", "cards", "kanban".
380
+ * Defaults to all three: ["table", "cards", "kanban"].
381
+ * Note: "kanban" will only be available if the collection has at least
382
+ * one string property with enumValues defined, regardless of this setting.
383
+ */
384
+ enabledViews?: ViewMode[];
385
+
386
+ /**
387
+ * Configuration for Kanban board view mode.
388
+ * When set, the Kanban view mode becomes available.
389
+ */
390
+ kanban?: KanbanConfig<M>;
391
+
392
+ /**
393
+ * Property key to use for ordering items.
394
+ * Must reference a number property. When items are reordered,
395
+ * this property will be updated to reflect the new order using
396
+ * fractional indexing. Used by Kanban view for ordering within columns
397
+ * and can be used for general ordering purposes.
398
+ */
399
+ orderProperty?: Extract<keyof M, string>;
400
+ }
401
+
402
+ /**
403
+ * Configuration for Kanban board view mode.
404
+ * @group Collections
405
+ */
406
+ export interface KanbanConfig<M extends Record<string, any> = any> {
407
+ /**
408
+ * Property key to use for Kanban board columns.
409
+ * Must reference a string property with enumValues defined.
410
+ * Entities will be grouped into columns based on this property's value.
411
+ * The column order is determined by the order of enumValues in the property.
412
+ */
413
+ columnProperty: Extract<keyof M, string>;
367
414
  }
368
415
 
416
+ /**
417
+ * View mode for displaying a collection.
418
+ * @group Collections
419
+ */
420
+ export type ViewMode = "table" | "cards" | "kanban";
421
+
369
422
  /**
370
423
  * Parameter passed to the `Actions` prop in the collection configuration.
371
424
  * The component will receive this prop when it is rendered in the collection
@@ -413,9 +466,10 @@ export interface CollectionActionsProps<M extends Record<string, any> = any, USE
413
466
  context: FireCMSContext<USER>;
414
467
 
415
468
  /**
416
- * Count of the entities in this collection
469
+ * Count of the entities in this collection.
470
+ * undefined means the count is still loading.
417
471
  */
418
- collectionEntitiesCount: number;
472
+ collectionEntitiesCount?: number;
419
473
 
420
474
  }
421
475
 
@@ -647,7 +701,7 @@ export type EntityTableController<M extends Record<string, any> = any> = {
647
701
  paginationEnabled?: boolean;
648
702
  pageSize?: number;
649
703
  checkFilterCombination?: (filterValues: FilterValues<any>,
650
- sortBy?: [string, "asc" | "desc"]) => boolean;
704
+ sortBy?: [string, "asc" | "desc"]) => boolean;
651
705
  popupCell?: SelectedCellProps<M>;
652
706
  setPopupCell?: (popupCell?: SelectedCellProps<M>) => void;
653
707
 
@@ -19,9 +19,9 @@ export interface FetchEntityProps<M extends Record<string, any> = any> {
19
19
  export type ListenEntityProps<M extends Record<string, any> = any> =
20
20
  FetchEntityProps<M>
21
21
  & {
22
- onUpdate: (entity: Entity<M>) => void,
23
- onError?: (error: Error) => void,
24
- }
22
+ onUpdate: (entity: Entity<M>) => void,
23
+ onError?: (error: Error) => void,
24
+ }
25
25
 
26
26
  /**
27
27
  * @group Datasource
@@ -88,15 +88,15 @@ export interface DataSource {
88
88
  * @see useCollectionFetch if you need this functionality implemented as a hook
89
89
  */
90
90
  fetchCollection<M extends Record<string, any> = any>({
91
- path,
92
- collection,
93
- filter,
94
- limit,
95
- startAfter,
96
- orderBy,
97
- order,
98
- searchString
99
- }: FetchCollectionProps<M>
91
+ path,
92
+ collection,
93
+ filter,
94
+ limit,
95
+ startAfter,
96
+ orderBy,
97
+ order,
98
+ searchString
99
+ }: FetchCollectionProps<M>
100
100
  ): Promise<Entity<M>[]>;
101
101
 
102
102
  /**
@@ -137,11 +137,11 @@ export interface DataSource {
137
137
  * @param collection
138
138
  */
139
139
  fetchEntity<M extends Record<string, any> = any>({
140
- path,
141
- entityId,
142
- databaseId,
143
- collection
144
- }: FetchEntityProps<M>
140
+ path,
141
+ entityId,
142
+ databaseId,
143
+ collection
144
+ }: FetchEntityProps<M>
145
145
  ): Promise<Entity<M> | undefined>;
146
146
 
147
147
  /**
@@ -154,12 +154,12 @@ export interface DataSource {
154
154
  * @return Function to cancel subscription
155
155
  */
156
156
  listenEntity?<M extends Record<string, any> = any>({
157
- path,
158
- entityId,
159
- collection,
160
- onUpdate,
161
- onError
162
- }: ListenEntityProps<M>): () => void;
157
+ path,
158
+ entityId,
159
+ collection,
160
+ onUpdate,
161
+ onError
162
+ }: ListenEntityProps<M>): () => void;
163
163
 
164
164
  /**
165
165
  * Save entity to the specified path
@@ -274,14 +274,14 @@ export interface DataSourceDelegate {
274
274
  * @see useCollectionFetch if you need this functionality implemented as a hook
275
275
  */
276
276
  fetchCollection<M extends Record<string, any> = any>({
277
- path,
278
- filter,
279
- limit,
280
- startAfter,
281
- orderBy,
282
- order,
283
- searchString
284
- }: FetchCollectionDelegateProps<M>): Promise<Entity<M>[]>;
277
+ path,
278
+ filter,
279
+ limit,
280
+ startAfter,
281
+ orderBy,
282
+ order,
283
+ searchString
284
+ }: FetchCollectionDelegateProps<M>): Promise<Entity<M>[]>;
285
285
 
286
286
  /**
287
287
  * Listen to a collection in a given path. If you don't implement this method
@@ -299,16 +299,16 @@ export interface DataSourceDelegate {
299
299
  * @see useCollectionFetch if you need this functionality implemented as a hook
300
300
  */
301
301
  listenCollection?<M extends Record<string, any> = any>({
302
- path,
303
- filter,
304
- limit,
305
- startAfter,
306
- searchString,
307
- orderBy,
308
- order,
309
- onUpdate,
310
- onError
311
- }: ListenCollectionDelegateProps<M>): () => void;
302
+ path,
303
+ filter,
304
+ limit,
305
+ startAfter,
306
+ searchString,
307
+ orderBy,
308
+ order,
309
+ onUpdate,
310
+ onError
311
+ }: ListenCollectionDelegateProps<M>): () => void;
312
312
 
313
313
  /**
314
314
  * Retrieve an entity given a path and a collection
@@ -316,9 +316,9 @@ export interface DataSourceDelegate {
316
316
  * @param entityId
317
317
  */
318
318
  fetchEntity<M extends Record<string, any> = any>({
319
- path,
320
- entityId,
321
- }: FetchEntityProps<M>): Promise<Entity<M> | undefined>;
319
+ path,
320
+ entityId,
321
+ }: FetchEntityProps<M>): Promise<Entity<M> | undefined>;
322
322
 
323
323
  /**
324
324
  * Get realtime updates on one entity.
@@ -330,11 +330,11 @@ export interface DataSourceDelegate {
330
330
  * @return Function to cancel subscription
331
331
  */
332
332
  listenEntity?<M extends Record<string, any> = any>({
333
- path,
334
- entityId,
335
- onUpdate,
336
- onError
337
- }: ListenEntityProps<M>): () => void;
333
+ path,
334
+ entityId,
335
+ onUpdate,
336
+ onError
337
+ }: ListenEntityProps<M>): () => void;
338
338
 
339
339
  /**
340
340
  * Save entity to the specified path
@@ -344,11 +344,11 @@ export interface DataSourceDelegate {
344
344
  * @param status
345
345
  */
346
346
  saveEntity<M extends Record<string, any> = any>({
347
- path,
348
- entityId,
349
- values,
350
- status
351
- }: SaveEntityDelegateProps<M>): Promise<Entity<M>>;
347
+ path,
348
+ entityId,
349
+ values,
350
+ status
351
+ }: SaveEntityDelegateProps<M>): Promise<Entity<M>>;
352
352
 
353
353
  /**
354
354
  * Delete an entity
@@ -395,8 +395,6 @@ export interface DataSourceDelegate {
395
395
 
396
396
  cmsToDelegateModel: (data: any) => any;
397
397
 
398
- setDateToMidnight: (input?: any) => any;
399
-
400
398
  initTextSearch?: (props: {
401
399
  context: FireCMSContext,
402
400
  path: string,