@rebasepro/admin 0.0.1-canary.f81da60 → 0.1.2

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 (95) hide show
  1. package/dist/{CollectionEditorDialog-D509-IMx.js → CollectionEditorDialog-ywdxhs1L.js} +18 -18
  2. package/dist/CollectionEditorDialog-ywdxhs1L.js.map +1 -0
  3. package/dist/{CollectionsStudioView-B549BDpU.js → CollectionsStudioView-BDzMFzqH.js} +4 -4
  4. package/dist/{CollectionsStudioView-B549BDpU.js.map → CollectionsStudioView-BDzMFzqH.js.map} +1 -1
  5. package/dist/{ContentHomePage--Bl1FXk7.js → ContentHomePage-0tHuEIm_.js} +26 -26
  6. package/dist/ContentHomePage-0tHuEIm_.js.map +1 -0
  7. package/dist/{ExportCollectionAction-CttNAdM1.js → ExportCollectionAction-BIrq92To.js} +2 -2
  8. package/dist/{ExportCollectionAction-CttNAdM1.js.map → ExportCollectionAction-BIrq92To.js.map} +1 -1
  9. package/dist/{ImportCollectionAction-BB33kxAN.js → ImportCollectionAction-h8yg_To8.js} +2 -2
  10. package/dist/{ImportCollectionAction-BB33kxAN.js.map → ImportCollectionAction-h8yg_To8.js.map} +1 -1
  11. package/dist/{PropertyEditView-UtDO8g0A.js → PropertyEditView-BuZrNnBN.js} +79 -101
  12. package/dist/PropertyEditView-BuZrNnBN.js.map +1 -0
  13. package/dist/{RolesView-B0E7L0hE.js → RolesView-CMPsaIXo.js} +2 -2
  14. package/dist/{RolesView-B0E7L0hE.js.map → RolesView-CMPsaIXo.js.map} +1 -1
  15. package/dist/{UsersView-BM2_7VPV.js → UsersView-BkeblMVT.js} +6 -28
  16. package/dist/UsersView-BkeblMVT.js.map +1 -0
  17. package/dist/collection_editor/ConfigControllerProvider.d.ts +0 -4
  18. package/dist/collection_editor/ui/collection_editor/CollectionDetailsForm.d.ts +1 -3
  19. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +0 -2
  20. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  21. package/dist/collection_editor_ui.js +3 -3
  22. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -1
  23. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -1
  24. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +2 -1
  25. package/dist/components/EntityEditView.d.ts +6 -0
  26. package/dist/components/RebaseCMS.d.ts +1 -1
  27. package/dist/{index-C9YDsMC9.js → index-BuZaHcyc.js} +3 -3
  28. package/dist/index-BuZaHcyc.js.map +1 -0
  29. package/dist/{index-CNDetux9.js → index-CS6uJ7oW.js} +2 -2
  30. package/dist/{index-CNDetux9.js.map → index-CS6uJ7oW.js.map} +1 -1
  31. package/dist/{index-DO7lMeNB.js → index-eRJbMvHi.js} +3 -3
  32. package/dist/index-eRJbMvHi.js.map +1 -0
  33. package/dist/index.js +18 -14
  34. package/dist/index.js.map +1 -1
  35. package/dist/util/navigation_utils.d.ts +10 -1
  36. package/dist/{util-DK1O3uM0.js → util-zfU1zOCX.js} +713 -603
  37. package/dist/util-zfU1zOCX.js.map +1 -0
  38. package/package.json +8 -8
  39. package/src/collection_editor/ConfigControllerProvider.tsx +1 -10
  40. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +3 -47
  41. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +2 -10
  42. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +1 -3
  43. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -3
  44. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +0 -1
  45. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  46. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
  47. package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +15 -49
  48. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +2 -3
  49. package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +1 -1
  50. package/src/collection_editor/ui/collection_editor/templates/products_template.ts +2 -2
  51. package/src/components/DefaultAppBar.tsx +2 -2
  52. package/src/components/DefaultDrawer.tsx +25 -17
  53. package/src/components/DrawerNavigationGroup.tsx +4 -4
  54. package/src/components/DrawerNavigationItem.tsx +6 -6
  55. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +5 -3
  56. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +1 -1
  57. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +8 -2
  58. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +2 -2
  59. package/src/components/EntityCollectionTable/table_bindings.tsx +37 -27
  60. package/src/components/EntityCollectionView/EntityCard.tsx +2 -2
  61. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +4 -3
  62. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +7 -6
  63. package/src/components/EntityCollectionView/EntityCollectionView.tsx +50 -7
  64. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +17 -8
  65. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +8 -4
  66. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +33 -5
  67. package/src/components/EntityEditView.tsx +80 -81
  68. package/src/components/EntitySidePanel.tsx +11 -7
  69. package/src/components/HomePage/ContentHomePage.tsx +24 -15
  70. package/src/components/HomePage/NavigationCard.tsx +4 -4
  71. package/src/components/HomePage/NavigationGroup.tsx +2 -2
  72. package/src/components/RebaseAuthGate.tsx +2 -0
  73. package/src/components/RebaseCMS.tsx +4 -3
  74. package/src/components/RebaseNavigation.tsx +7 -4
  75. package/src/components/RelationSelector.tsx +30 -2
  76. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  77. package/src/components/UserSelector.tsx +1 -1
  78. package/src/components/admin/UsersView.tsx +2 -17
  79. package/src/components/app/Scaffold.tsx +3 -3
  80. package/src/components/field_configs.tsx +3 -3
  81. package/src/form/PropertyFieldBinding.tsx +10 -6
  82. package/src/hooks/navigation/useResolvedViews.tsx +1 -3
  83. package/src/hooks/navigation/useTopLevelNavigation.ts +1 -1
  84. package/src/hooks/navigation/utils.ts +1 -1
  85. package/src/preview/PropertyPreview.tsx +17 -13
  86. package/src/routes/RebaseRoute.tsx +27 -2
  87. package/src/util/navigation_utils.ts +16 -2
  88. package/src/util/previews.ts +14 -5
  89. package/dist/CollectionEditorDialog-D509-IMx.js.map +0 -1
  90. package/dist/ContentHomePage--Bl1FXk7.js.map +0 -1
  91. package/dist/PropertyEditView-UtDO8g0A.js.map +0 -1
  92. package/dist/UsersView-BM2_7VPV.js.map +0 -1
  93. package/dist/index-C9YDsMC9.js.map +0 -1
  94. package/dist/index-DO7lMeNB.js.map +0 -1
  95. package/dist/util-DK1O3uM0.js.map +0 -1
@@ -22,7 +22,7 @@ export function DrawerNavigationItem({
22
22
  }) {
23
23
 
24
24
  const iconWrap = <div
25
- className={"shrink-0 flex items-center justify-center w-[56px] h-[40px] text-text-secondary dark:text-text-secondary-dark"}>
25
+ className={"shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4"}>
26
26
  {icon}
27
27
  </div>;
28
28
 
@@ -33,12 +33,12 @@ export function DrawerNavigationItem({
33
33
  width: "100%",
34
34
  transition: drawerOpen ? "width 150ms ease-in" : undefined
35
35
  }}
36
- className={({ isActive }: any) => cls("rounded-lg truncate",
37
- "hover:bg-surface-100 dark:hover:bg-surface-800/60 text-text-primary dark:text-surface-300 hover:text-surface-900 dark:hover:text-white",
36
+ className={({ isActive }: any) => cls("rounded-md truncate",
37
+ "hover:bg-surface-100 dark:hover:bg-surface-800/60 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white",
38
38
  "flex flex-row items-center",
39
- drawerOpen ? "pr-4 h-10" : "h-10",
40
- "font-semibold text-xs",
41
- isActive ? "bg-surface-100 dark:bg-surface-800/50 text-surface-900 dark:text-white" : ""
39
+ drawerOpen ? "pr-4 h-[30px]" : "h-[30px]",
40
+ "font-medium text-[13px]",
41
+ isActive ? "bg-surface-900/[0.06] dark:bg-surface-800/50 text-surface-900 dark:text-white" : ""
42
42
  )}
43
43
  to={url}
44
44
  >
@@ -91,8 +91,10 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
91
91
  const content = (
92
92
  <div
93
93
  className={cls(
94
- "h-full flex items-center justify-center flex-col bg-surface-50 dark:bg-surface-800 bg-opacity-90 bg-surface-50/90 dark:bg-opacity-90 dark:bg-surface-800/90 z-10",
95
- "h-full flex items-center justify-center flex-col bg-surface-50/90 dark:bg-surface-800/90 z-10",
94
+ "h-full flex items-center justify-center flex-col z-10",
95
+ isSelected
96
+ ? "bg-surface-accent-50 dark:bg-surface-accent-900"
97
+ : "bg-surface-50/90 dark:bg-surface-900/90",
96
98
  frozen ? "sticky left-0" : ""
97
99
  )}
98
100
  onClick={useCallback((event: React.MouseEvent) => {
@@ -219,7 +221,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
219
221
  style={sortableStyle}
220
222
  className={cls(
221
223
  "flex-shrink-0",
222
- frozen && "sticky left-0 z-10 bg-white dark:bg-surface-800"
224
+ frozen && "sticky left-0 z-10 bg-white dark:bg-surface-900"
223
225
  )}
224
226
  {...sortableAttrsWithoutTabIndex}
225
227
  >
@@ -350,7 +350,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
350
350
 
351
351
  <div ref={ref}
352
352
  style={style}
353
- className={cls("h-full w-full flex flex-col bg-white dark:bg-surface-800", className)}>
353
+ className={cls("h-full w-full flex flex-col bg-white dark:bg-surface-900", className)}>
354
354
 
355
355
  {!hideToolbar && <CollectionTableToolbar
356
356
  onTextSearch={onTextSearch}
@@ -13,6 +13,10 @@ interface CollectionTableToolbarProps {
13
13
  viewModeToggle?: React.ReactNode;
14
14
  title?: React.ReactNode,
15
15
  onTextSearch?: (searchString?: string) => void;
16
+ /**
17
+ * Initial search string to pre-populate the search bar (e.g. from URL params).
18
+ */
19
+ initialSearchText?: string;
16
20
  /**
17
21
  * When true the toolbar is in "compact" mode for the split-view left panel.
18
22
  * - Search bar, loading spinner, and view-mode toggle are hidden.
@@ -29,6 +33,7 @@ export function CollectionTableToolbar({
29
33
  onTextSearch,
30
34
  title,
31
35
  viewModeToggle,
36
+ initialSearchText,
32
37
  compact = false
33
38
  }: CollectionTableToolbarProps) {
34
39
 
@@ -54,7 +59,7 @@ export function CollectionTableToolbar({
54
59
  {viewModeToggle}
55
60
  </div>
56
61
 
57
- {title && <div className={"hidden lg:block"}>
62
+ {title && <div className={"flex items-center"}>
58
63
  {title}
59
64
  </div>}
60
65
 
@@ -84,7 +89,8 @@ export function CollectionTableToolbar({
84
89
  size={"small"}
85
90
  placeholder={t("search")}
86
91
  onTextSearch={onTextSearch}
87
- expandable={true}/>}
92
+ expandable={true}
93
+ initialValue={initialSearchText}/>}
88
94
  </div>
89
95
 
90
96
  {/* Secondary actions — always inline */}
@@ -204,7 +204,7 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
204
204
  `flex relative h-full rounded-md p-${p} border-4`,
205
205
  showSaved ? "bg-primary/20 dark:bg-primary/20" : (onHover && !disabled ? "bg-surface-50 dark:bg-surface-900" : ""),
206
206
  hideOverflow ? "overflow-hidden" : "",
207
- isSelected && !showSaved ? "bg-surface-50 dark:bg-surface-900" : "",
207
+ isSelected && !showSaved ? "bg-surface-accent-50 dark:bg-surface-accent-900" : "",
208
208
  borderClass
209
209
  )}
210
210
  ref={ref}
@@ -265,7 +265,7 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
265
265
  style={sortableStyle}
266
266
  className={cls(
267
267
  "flex-shrink-0",
268
- frozen && "sticky left-0 z-10 bg-white dark:bg-surface-800"
268
+ frozen && "sticky left-0 z-10 bg-white dark:bg-surface-900"
269
269
  )}
270
270
  {...sortableAttrsWithoutTabIndex}
271
271
  >
@@ -252,37 +252,12 @@ path: referenceProperty.path as string }) : undefined;
252
252
  if (property.relation) {
253
253
  if (property.ui?.widget === "dialog") {
254
254
  return {
255
- Component: ({ propertyKey, internalValue, updateValue, disabled, size, property }: any) => (
256
- <TableRelationField
257
- name={propertyKey}
258
- internalValue={internalValue as EntityRelation}
259
- updateValue={updateValue}
260
- disabled={disabled}
261
- size={size}
262
- multiselect={false}
263
- relation={property.relation}
264
- previewProperties={property.ui?.previewProperties}
265
- includeId={property.includeId}
266
- includeEntityLink={property.includeEntityLink}
267
- title={property.name ?? propertyKey}
268
- fixedFilter={property.fixedFilter}
269
- />
270
- ),
255
+ Component: RelationDialogBindingComponent,
271
256
  allowScroll: false
272
257
  };
273
258
  } else {
274
259
  return {
275
- Component: ({ propertyKey, internalValue, updateValue, disabled, property }: any) => (
276
- <TableRelationSelectorField
277
- name={propertyKey}
278
- internalValue={internalValue as EntityRelation}
279
- updateValue={updateValue}
280
- disabled={disabled}
281
- size={"small"}
282
- relation={property.relation!}
283
- fixedFilter={property.fixedFilter}
284
- />
285
- ),
260
+ Component: RelationSelectorBindingComponent,
286
261
  allowScroll: false
287
262
  };
288
263
  }
@@ -344,3 +319,38 @@ path: referenceProperty.path as string }) : undefined;
344
319
 
345
320
  return undefined;
346
321
  }
322
+
323
+ /** Stable component for relation fields rendered with the dialog widget */
324
+ function RelationDialogBindingComponent({ propertyKey, internalValue, updateValue, disabled, size, property }: any) {
325
+ return (
326
+ <TableRelationField
327
+ name={propertyKey}
328
+ internalValue={internalValue as EntityRelation}
329
+ updateValue={updateValue}
330
+ disabled={disabled}
331
+ size={size}
332
+ multiselect={false}
333
+ relation={property.relation}
334
+ previewProperties={property.ui?.previewProperties}
335
+ includeId={property.includeId}
336
+ includeEntityLink={property.includeEntityLink}
337
+ title={property.name ?? propertyKey}
338
+ fixedFilter={property.fixedFilter}
339
+ />
340
+ );
341
+ }
342
+
343
+ /** Stable component for relation fields rendered with the inline selector */
344
+ function RelationSelectorBindingComponent({ propertyKey, internalValue, updateValue, disabled, property }: any) {
345
+ return (
346
+ <TableRelationSelectorField
347
+ name={propertyKey}
348
+ internalValue={internalValue as EntityRelation}
349
+ updateValue={updateValue}
350
+ disabled={disabled}
351
+ size={"small"}
352
+ relation={property.relation!}
353
+ fixedFilter={property.fixedFilter}
354
+ />
355
+ );
356
+ }
@@ -94,8 +94,8 @@ export function EntityCard<M extends Record<string, unknown> = Record<string, un
94
94
  "cursor-pointer overflow-hidden group relative",
95
95
  "transition-all duration-200",
96
96
  "hover:shadow-lg hover:-translate-y-0.5",
97
- selected && "ring-2 ring-primary",
98
- highlighted && !selected && "ring-2 ring-primary ring-opacity-50"
97
+ selected && "ring-2 ring-primary bg-surface-accent-50 dark:bg-surface-accent-900",
98
+ highlighted && !selected && "ring-2 ring-primary ring-opacity-50 bg-surface-accent-50/50 dark:bg-surface-accent-900"
99
99
  )}
100
100
  onClick={handleClick}
101
101
  >
@@ -5,7 +5,6 @@ import { EntityCard } from "./EntityCard";
5
5
  import {
6
6
  cls,
7
7
  CircularProgress,
8
- CircularProgressCenter,
9
8
  Typography
10
9
  } from "@rebasepro/ui";
11
10
  import { useAuthController, useCustomizationController } from "@rebasepro/core";
@@ -213,9 +212,11 @@ export function EntityCollectionCardView<M extends Record<string, unknown> = Rec
213
212
  </Typography>
214
213
  </div>
215
214
  ) : isInitialLoading ? (
216
- <CircularProgressCenter/>
215
+ <div className="flex items-center justify-center py-12 px-8">
216
+ <CircularProgress size="small"/>
217
+ </div>
217
218
  ) : isEmpty ? (
218
- <div className="h-full flex items-center justify-center p-8">
219
+ <div className="flex items-center justify-center py-12 px-8">
219
220
  {emptyComponent ?? (
220
221
  <Typography variant="label" color="secondary">
221
222
  No entries found
@@ -7,7 +7,6 @@ import {
7
7
  Checkbox,
8
8
  Chip,
9
9
  CircularProgress,
10
- CircularProgressCenter,
11
10
  cls,
12
11
  defaultBorderMixin,
13
12
  Typography
@@ -572,9 +571,11 @@ export function EntityCollectionListView<M extends Record<string, unknown> = Rec
572
571
  </Typography>
573
572
  </div>
574
573
  ) : isInitialLoading ? (
575
- <CircularProgressCenter/>
574
+ <div className="flex items-center justify-center py-12 px-8">
575
+ <CircularProgress size="small"/>
576
+ </div>
576
577
  ) : isEmpty ? (
577
- <div className="flex items-center justify-center p-8">
578
+ <div className="flex items-center justify-center py-12 px-8">
578
579
  {emptyComponent ?? (
579
580
  <Typography variant="label" color="secondary">
580
581
  No entries found
@@ -719,11 +720,11 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
719
720
  "flex items-center gap-4 cursor-pointer group transition-colors duration-200 relative h-full",
720
721
  rowClasses,
721
722
  isActive
722
- ? "bg-surface-accent-100 dark:bg-surface-accent-950 hover:bg-surface-accent-200 dark:hover:bg-surface-accent-900"
723
+ ? "bg-surface-accent-100 dark:bg-surface-accent-950 hover:bg-surface-accent-200 dark:hover:bg-surface-accent-950"
723
724
  : selected
724
- ? "bg-primary-50 dark:bg-primary-900/20 hover:bg-surface-50 dark:hover:bg-surface-800/40"
725
+ ? "bg-surface-accent-50 dark:bg-surface-accent-900 hover:bg-surface-accent-100 dark:hover:bg-surface-accent-950"
725
726
  : highlighted
726
- ? "bg-surface-accent-50 dark:bg-surface-accent-950 hover:bg-surface-50 dark:hover:bg-surface-800/40"
727
+ ? "bg-surface-accent-50 dark:bg-surface-accent-900 hover:bg-surface-50 dark:hover:bg-surface-800/40"
727
728
  : "bg-white dark:bg-surface-900 hover:bg-surface-50 dark:hover:bg-surface-800/40"
728
729
  )}
729
730
  onClick={handleClick}
@@ -30,7 +30,8 @@ import {
30
30
  useLargeLayout,
31
31
  usePermissions,
32
32
  useTranslation,
33
- useSlot
33
+ useSlot,
34
+ IconForView
34
35
  } from "@rebasepro/core";
35
36
  import { useUserConfigurationPersistence } from "@rebasepro/core";
36
37
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
@@ -373,6 +374,23 @@ export const EntityCollectionView = React.memo(
373
374
  })
374
375
  }, [path, sideEntityController]);
375
376
 
377
+ const openNewDocument = useCallback((defaultValues?: Record<string, unknown>) => {
378
+ const collection = collectionRef.current;
379
+ analyticsController.onAnalyticsEvent?.("new_entity_click", {
380
+ path: path
381
+ });
382
+ navigateToEntity({
383
+ openEntityMode,
384
+ collection,
385
+ entityId: undefined,
386
+ defaultValues,
387
+ path: path,
388
+ sideEntityController,
389
+ navigation: urlController,
390
+ onClose: unselectNavigatedEntity
391
+ });
392
+ }, [path, sideEntityController, openEntityMode, urlController, unselectNavigatedEntity]);
393
+
376
394
  const onMultipleDeleteClick = () => {
377
395
  analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
378
396
  path: path
@@ -837,7 +855,8 @@ export const EntityCollectionView = React.memo(
837
855
  : null;
838
856
 
839
857
  // Shared empty state — plugin slot takes priority, then default
840
- const isFilteredOrSorted = tableController.filterValues !== undefined || tableController.sortBy !== undefined;
858
+ const isSearching = !!tableController.searchString;
859
+ const isFilteredOrSorted = tableController.filterValues !== undefined || tableController.sortBy !== undefined || isSearching;
841
860
  const emptyComponent = pluginEmptyStates.length > 0
842
861
  ? <>{pluginEmptyStates}</>
843
862
  : canCreateEntities && !isFilteredOrSorted
@@ -851,13 +870,18 @@ export const EntityCollectionView = React.memo(
851
870
  {t("create_your_first_entry")}
852
871
  </Button>
853
872
  </div>
854
- : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>;
873
+ : <Typography variant={"label"}>
874
+ {isSearching
875
+ ? t("no_results_search", { search: tableController.searchString ?? "" })
876
+ : t("no_results_filter_sort")}
877
+ </Typography>;
855
878
 
856
879
  const toolbarNode = (
857
880
  <CollectionTableToolbar
858
881
  compact={isCompact}
859
882
  loading={tableController.dataLoading}
860
883
  onTextSearch={tableController.setSearchString}
884
+ initialSearchText={tableController.searchString}
861
885
  viewModeToggle={viewModeToggleElement}
862
886
  actionsStart={<EntityCollectionViewStartActions
863
887
  parentCollectionSlugs={parentCollectionSlugs ?? EMPTY_ARRAY} parentEntityIds={parentEntityIds ?? EMPTY_ARRAY}
@@ -868,6 +892,7 @@ export const EntityCollectionView = React.memo(
868
892
  selectionController={usedSelectionController}
869
893
  collectionEntitiesCount={docsCount ?? undefined}
870
894
  resolvedProperties={resolvedCollection.properties}
895
+ openNewDocument={openNewDocument}
871
896
  compact={isCompact}/>}
872
897
  actions={
873
898
  <EntityCollectionViewActions
@@ -876,6 +901,7 @@ export const EntityCollectionView = React.memo(
876
901
  tableController={tableController}
877
902
  onMultipleDeleteClick={onMultipleDeleteClick}
878
903
  onNewClick={onNewClick}
904
+ openNewDocument={openNewDocument}
879
905
  path={path}
880
906
  relativePath={collection.slug}
881
907
  selectionController={usedSelectionController}
@@ -1146,6 +1172,13 @@ export const EntityCollectionView = React.memo(
1146
1172
  equal(a.selectedTab, b.selectedTab);
1147
1173
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
1148
1174
 
1175
+ /**
1176
+ * Inflight count request deduplication map.
1177
+ * Keyed by `path|filterKey|sortByProperty|sortDir` so that concurrent
1178
+ * callers (e.g. React StrictMode double-mount) share the same promise.
1179
+ */
1180
+ const inflightCountRequests = new Map<string, Promise<number>>();
1181
+
1149
1182
  function EntitiesCount({
1150
1183
  path,
1151
1184
  collection,
@@ -1204,10 +1237,20 @@ function EntitiesCount({
1204
1237
  const whereParams = Object.keys(whereMap).length > 0 ? whereMap : undefined;
1205
1238
  const orderByParams = sortByProperty ? `${String(sortByProperty)}:${currentSort}` : undefined;
1206
1239
 
1207
- accessor.count({
1208
- where: whereParams,
1209
- orderBy: orderByParams
1210
- }).then((c) => {
1240
+ // Deduplicate inflight count requests (e.g. React StrictMode double-mount)
1241
+ const cacheKey = `${path}|${filterKey}|${sortByProperty ?? ""}|${currentSort ?? ""}`;
1242
+ let countPromise = inflightCountRequests.get(cacheKey);
1243
+ if (!countPromise) {
1244
+ countPromise = accessor.count({
1245
+ where: whereParams,
1246
+ orderBy: orderByParams
1247
+ });
1248
+ inflightCountRequests.set(cacheKey, countPromise);
1249
+ // Clean up the inflight entry once resolved/rejected
1250
+ countPromise.finally(() => inflightCountRequests.delete(cacheKey));
1251
+ }
1252
+
1253
+ countPromise.then((c) => {
1211
1254
  if (!cancelled) onCountChangeRef.current?.(c);
1212
1255
  }).catch((e) => {
1213
1256
  console.warn("Error fetching count", e);
@@ -2,7 +2,7 @@
2
2
  import type { EntityCollection } from "@rebasepro/types";
3
3
  import React, { lazy, Suspense } from "react";
4
4
 
5
- import { useLargeLayout, useTranslation, useSlot } from "@rebasepro/core";
5
+ import { useLargeLayout, useTranslation, useSlot, resolveComponentRef } from "@rebasepro/core";
6
6
  import { CollectionActionsProps, EntityTableController, SelectionController } from "@rebasepro/types";
7
7
  import { Button, IconButton, Tooltip, Popover, iconSize } from "@rebasepro/ui";
8
8
  import { PlusIcon, Trash2Icon, MoreVerticalIcon } from "lucide-react";
@@ -29,6 +29,7 @@ export type EntityCollectionViewActionsProps<M extends Record<string, unknown>>
29
29
  collectionEntitiesCount?: number;
30
30
  compact?: boolean;
31
31
  children?: React.ReactNode;
32
+ openNewDocument: (defaultValues?: Record<string, unknown>) => void;
32
33
  }
33
34
 
34
35
  export function EntityCollectionViewActions<M extends Record<string, unknown>>({
@@ -43,7 +44,8 @@ export function EntityCollectionViewActions<M extends Record<string, unknown>>({
43
44
  tableController,
44
45
  collectionEntitiesCount,
45
46
  compact,
46
- children
47
+ children,
48
+ openNewDocument
47
49
  }: EntityCollectionViewActionsProps<M>) {
48
50
  const context = useCMSContext();
49
51
 
@@ -116,15 +118,22 @@ export function EntityCollectionViewActions<M extends Record<string, unknown>>({
116
118
  selectionController,
117
119
  context,
118
120
  tableController,
119
- collectionEntitiesCount
121
+ collectionEntitiesCount,
122
+ openNewDocument
120
123
  };
121
124
 
122
125
  const actions = toArray(collection.Actions)
123
- .map((Action, i) => (
124
- <ErrorBoundary key={`actions_${i}`}>
125
- <Action {...actionProps}/>
126
- </ErrorBoundary>
127
- ));
126
+ .map((actionRef, i) => {
127
+ const Action = resolveComponentRef(actionRef);
128
+ if (!Action) return null;
129
+ return (
130
+ <ErrorBoundary key={`actions_${i}`}>
131
+ <Suspense fallback={null}>
132
+ <Action {...actionProps}/>
133
+ </Suspense>
134
+ </ErrorBoundary>
135
+ );
136
+ });
128
137
 
129
138
  const pluginActions = useSlot("collection.actions", actionProps as any);
130
139
 
@@ -25,6 +25,7 @@ export type EntityCollectionViewStartActionsProps<M extends Record<string, unkno
25
25
  */
26
26
  resolvedProperties?: Properties;
27
27
  compact?: boolean;
28
+ openNewDocument: (defaultValues?: Record<string, unknown>) => void;
28
29
  }
29
30
 
30
31
  export function EntityCollectionViewStartActions<M extends Record<string, unknown>>({
@@ -36,7 +37,8 @@ export function EntityCollectionViewStartActions<M extends Record<string, unknow
36
37
  tableController,
37
38
  collectionEntitiesCount,
38
39
  resolvedProperties,
39
- compact
40
+ compact,
41
+ openNewDocument
40
42
  }: EntityCollectionViewStartActionsProps<M>) {
41
43
 
42
44
  const context = useCMSContext();
@@ -64,7 +66,8 @@ export function EntityCollectionViewStartActions<M extends Record<string, unknow
64
66
  selectionController,
65
67
  context,
66
68
  tableController,
67
- collectionEntitiesCount
69
+ collectionEntitiesCount,
70
+ openNewDocument
68
71
  };
69
72
 
70
73
  const handleBackClick = useCallback(() => {
@@ -108,13 +111,14 @@ export function EntityCollectionViewStartActions<M extends Record<string, unknow
108
111
  {t("filters")}{activeFilterCount > 0 ? ` (${activeFilterCount})` : ""}
109
112
  </Button>
110
113
  ) : (
111
- <IconButton
114
+ <Button
115
+ variant="text"
112
116
  size="small"
113
117
  onClick={() => setFiltersDialogOpen(true)}
114
118
  className={cls(activeFilterCount > 0 && "text-primary")}
115
119
  >
116
120
  <FilterIcon size={iconSize.small}/>
117
- </IconButton>
121
+ </Button>
118
122
  )}
119
123
  </Badge>
120
124
  </Tooltip>
@@ -110,11 +110,39 @@ export function resolveCollectionSlotKeys(
110
110
 
111
111
  // Status: first string-enum that isn't the title
112
112
  let statusKey: string | undefined;
113
- for (const [key, prop] of Object.entries(collection.properties)) {
114
- const p = prop as Property;
115
- if (p.type === "string" && "enum" in p && p.enum && key !== titleKey) {
116
- statusKey = key;
117
- break;
113
+
114
+ // 1. Explicitly defined in previewProperties
115
+ if (!statusKey && collection.previewProperties) {
116
+ for (const key of collection.previewProperties) {
117
+ const p = collection.properties[key] as Property | undefined;
118
+ if (p?.type === "string" && "enum" in p && p.enum && key !== titleKey) {
119
+ statusKey = key;
120
+ break;
121
+ }
122
+ }
123
+ }
124
+
125
+ // 2. Explicitly defined in propertiesOrder
126
+ if (!statusKey && collection.propertiesOrder) {
127
+ for (const key of collection.propertiesOrder) {
128
+ if (typeof key === "string" && !key.startsWith("subcollection:")) {
129
+ const p = collection.properties[key] as Property | undefined;
130
+ if (p?.type === "string" && "enum" in p && p.enum && key !== titleKey) {
131
+ statusKey = key;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ // 3. Default automatic inference
139
+ if (!statusKey) {
140
+ for (const [key, prop] of Object.entries(collection.properties)) {
141
+ const p = prop as Property;
142
+ if (p.type === "string" && "enum" in p && p.enum && key !== titleKey) {
143
+ statusKey = key;
144
+ break;
145
+ }
118
146
  }
119
147
  }
120
148