@rebasepro/admin 0.0.1-canary.eae7889 → 0.1.0

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 (197) hide show
  1. package/dist/{CollectionEditorDialog-B2M9lCyL.js → CollectionEditorDialog-MbvXGzEq.js} +42 -31
  2. package/dist/CollectionEditorDialog-MbvXGzEq.js.map +1 -0
  3. package/dist/{CollectionsStudioView-WG6soyfs.js → CollectionsStudioView-D9X6aiAr.js} +12 -12
  4. package/dist/CollectionsStudioView-D9X6aiAr.js.map +1 -0
  5. package/dist/{ContentHomePage-CDF_a6Lp.js → ContentHomePage-CfVB1eUo.js} +26 -26
  6. package/dist/ContentHomePage-CfVB1eUo.js.map +1 -0
  7. package/dist/{ExportCollectionAction-Dc0VOWMN.js → ExportCollectionAction-CUwJg4F9.js} +2 -2
  8. package/dist/{ExportCollectionAction-Dc0VOWMN.js.map → ExportCollectionAction-CUwJg4F9.js.map} +1 -1
  9. package/dist/{ImportCollectionAction-DpCagAOy.js → ImportCollectionAction-DGa_SF_8.js} +2 -2
  10. package/dist/{ImportCollectionAction-DpCagAOy.js.map → ImportCollectionAction-DGa_SF_8.js.map} +1 -1
  11. package/dist/{PropertyEditView-DS67DxoT.js → PropertyEditView-C4nlYmAc.js} +82 -104
  12. package/dist/PropertyEditView-C4nlYmAc.js.map +1 -0
  13. package/dist/{RolesView-CIuYBimF.js → RolesView-CNWxnR8e.js} +7 -5
  14. package/dist/RolesView-CNWxnR8e.js.map +1 -0
  15. package/dist/{UsersView-B5zelXnH.js → UsersView-YiTIcXkA.js} +14 -35
  16. package/dist/UsersView-YiTIcXkA.js.map +1 -0
  17. package/dist/collection_editor/ConfigControllerProvider.d.ts +0 -4
  18. package/dist/collection_editor/types/collection_editor_controller.d.ts +6 -3
  19. package/dist/collection_editor/types/config_controller.d.ts +14 -7
  20. package/dist/collection_editor/ui/AddKanbanColumnAction.d.ts +3 -2
  21. package/dist/collection_editor/ui/CollectionViewHeaderAction.d.ts +3 -2
  22. package/dist/collection_editor/ui/EditorCollectionAction.d.ts +1 -1
  23. package/dist/collection_editor/ui/EditorCollectionActionStart.d.ts +1 -1
  24. package/dist/collection_editor/ui/EditorEntityAction.d.ts +1 -1
  25. package/dist/collection_editor/ui/KanbanSetupAction.d.ts +3 -2
  26. package/dist/collection_editor/ui/PropertyAddColumnComponent.d.ts +3 -2
  27. package/dist/collection_editor/ui/collection_editor/CollectionDetailsForm.d.ts +3 -4
  28. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -3
  29. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  30. package/dist/collection_editor/ui/collection_editor/SubcollectionsEditTab.d.ts +3 -2
  31. package/dist/collection_editor_ui.js +3 -3
  32. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  33. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +1 -1
  34. package/dist/components/EntityCollectionTable/column_utils.d.ts +2 -2
  35. package/dist/components/EntityCollectionTable/fields/TableMultipleRelationField.d.ts +1 -1
  36. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +1 -1
  37. package/dist/components/EntityCollectionTable/fields/TableRelationField.d.ts +1 -1
  38. package/dist/components/EntityCollectionTable/fields/TableRelationSelectorField.d.ts +2 -2
  39. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -1
  40. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -2
  41. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +2 -1
  42. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +4 -2
  43. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +4 -2
  44. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -2
  45. package/dist/components/EntityCollectionView/SplitListView.d.ts +3 -2
  46. package/dist/components/EntityEditView.d.ts +9 -2
  47. package/dist/components/RebaseCMS.d.ts +1 -1
  48. package/dist/components/ReferenceTable/EntitySelectionTable.d.ts +2 -2
  49. package/dist/components/ReferenceWidget.d.ts +2 -2
  50. package/dist/components/RelationSelector.d.ts +1 -1
  51. package/dist/components/SelectableTable/SelectableTable.d.ts +2 -2
  52. package/dist/editor.js +2 -2
  53. package/dist/editor.js.map +1 -1
  54. package/dist/hooks/navigation/useNavigationRegistry.d.ts +2 -1
  55. package/dist/hooks/useEntityHistory.d.ts +1 -1
  56. package/dist/{index-CHxgwt6E.js → index-CtzpHzMQ.js} +11 -4
  57. package/dist/index-CtzpHzMQ.js.map +1 -0
  58. package/dist/{index-Dey5WJpO.js → index-DKlrVD1m.js} +3 -3
  59. package/dist/index-DKlrVD1m.js.map +1 -0
  60. package/dist/{index-CBhrgpR7.js → index-kHJXfLNI.js} +3 -3
  61. package/dist/index-kHJXfLNI.js.map +1 -0
  62. package/dist/index.js +79 -63
  63. package/dist/index.js.map +1 -1
  64. package/dist/{useEntityHistory-Dcj4zhGj.js → useEntityHistory-UVsSclfZ.js} +3 -1
  65. package/dist/useEntityHistory-UVsSclfZ.js.map +1 -0
  66. package/dist/util/navigation_utils.d.ts +10 -1
  67. package/dist/{util-BQ82ySL3.js → util-CwLmSpGp.js} +1653 -1257
  68. package/dist/util-CwLmSpGp.js.map +1 -0
  69. package/package.json +9 -17
  70. package/src/collection_editor/ConfigControllerProvider.tsx +19 -28
  71. package/src/collection_editor/types/collection_editor_controller.tsx +3 -3
  72. package/src/collection_editor/types/config_controller.tsx +7 -7
  73. package/src/collection_editor/ui/AddKanbanColumnAction.tsx +4 -4
  74. package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +3 -3
  75. package/src/collection_editor/ui/EditorCollectionAction.tsx +3 -3
  76. package/src/collection_editor/ui/EditorCollectionActionStart.tsx +7 -7
  77. package/src/collection_editor/ui/EditorEntityAction.tsx +3 -3
  78. package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +4 -2
  79. package/src/collection_editor/ui/KanbanSetupAction.tsx +4 -3
  80. package/src/collection_editor/ui/MissingReferenceWidget.tsx +3 -2
  81. package/src/collection_editor/ui/NewCollectionButton.tsx +2 -1
  82. package/src/collection_editor/ui/NewCollectionCard.tsx +2 -1
  83. package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +3 -3
  84. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -50
  85. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +12 -20
  86. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +1 -3
  87. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +17 -2
  88. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -3
  89. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +2 -1
  90. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +18 -12
  91. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +1 -2
  92. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +1 -2
  93. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  94. package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +4 -4
  95. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  96. package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +15 -49
  97. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +4 -5
  98. package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +1 -1
  99. package/src/collection_editor/ui/collection_editor/templates/products_template.ts +2 -2
  100. package/src/components/DefaultAppBar.tsx +2 -2
  101. package/src/components/DefaultDrawer.tsx +25 -17
  102. package/src/components/DrawerNavigationGroup.tsx +4 -4
  103. package/src/components/DrawerNavigationItem.tsx +6 -6
  104. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +4 -4
  105. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  106. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +4 -4
  107. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  108. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +3 -3
  109. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +3 -3
  110. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +4 -4
  111. package/src/components/EntityCollectionTable/fields/TableRelationSelectorField.tsx +3 -3
  112. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +8 -2
  113. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +1 -1
  114. package/src/components/EntityCollectionTable/internal/common.tsx +5 -5
  115. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +1 -1
  116. package/src/components/EntityCollectionTable/table_bindings.tsx +45 -35
  117. package/src/components/EntityCollectionView/EntityBoardCard.tsx +18 -19
  118. package/src/components/EntityCollectionView/EntityCard.tsx +2 -2
  119. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +42 -14
  120. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +4 -3
  121. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +157 -54
  122. package/src/components/EntityCollectionView/EntityCollectionView.tsx +169 -75
  123. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +23 -13
  124. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +21 -12
  125. package/src/components/EntityCollectionView/FiltersDialog.tsx +7 -7
  126. package/src/components/EntityCollectionView/SplitListView.tsx +24 -8
  127. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +33 -5
  128. package/src/components/EntityEditView.tsx +85 -85
  129. package/src/components/EntitySidePanel.tsx +18 -10
  130. package/src/components/HomePage/ContentHomePage.tsx +24 -15
  131. package/src/components/HomePage/NavigationCard.tsx +4 -4
  132. package/src/components/HomePage/NavigationGroup.tsx +2 -2
  133. package/src/components/RebaseAuthGate.tsx +2 -0
  134. package/src/components/RebaseCMS.tsx +4 -3
  135. package/src/components/RebaseNavigation.tsx +8 -5
  136. package/src/components/ReferenceTable/EntitySelectionTable.tsx +4 -4
  137. package/src/components/ReferenceWidget.tsx +3 -3
  138. package/src/components/RelationSelector.tsx +33 -5
  139. package/src/components/SelectableTable/SelectableTable.tsx +6 -6
  140. package/src/components/UserSelector.tsx +1 -1
  141. package/src/components/admin/RolesView.tsx +10 -3
  142. package/src/components/admin/UsersView.tsx +13 -25
  143. package/src/components/app/Scaffold.tsx +4 -4
  144. package/src/components/field_configs.tsx +29 -32
  145. package/src/components/history/EntityHistoryView.tsx +12 -1
  146. package/src/editor/editor.tsx +2 -2
  147. package/src/form/EntityForm.tsx +5 -4
  148. package/src/form/PropertyFieldBinding.tsx +14 -10
  149. package/src/form/components/FieldHelperText.tsx +1 -1
  150. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +3 -3
  151. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +5 -5
  152. package/src/form/field_bindings/BlockFieldBinding.tsx +4 -4
  153. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  154. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  155. package/src/form/field_bindings/MapFieldBinding.tsx +7 -7
  156. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +1 -1
  157. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +3 -3
  158. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +7 -7
  159. package/src/form/field_bindings/ReferenceFieldBinding.tsx +2 -2
  160. package/src/form/field_bindings/RelationFieldBinding.tsx +4 -4
  161. package/src/form/field_bindings/RepeatFieldBinding.tsx +5 -5
  162. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  163. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
  164. package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -1
  165. package/src/form/field_bindings/TextFieldBinding.tsx +7 -7
  166. package/src/form/field_bindings/UserSelectFieldBinding.tsx +1 -1
  167. package/src/form/useClearRestoreValue.tsx +1 -1
  168. package/src/form/validation.ts +1 -1
  169. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -1
  170. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -3
  171. package/src/hooks/navigation/useNavigationRegistry.ts +14 -3
  172. package/src/hooks/navigation/useResolvedViews.tsx +1 -3
  173. package/src/hooks/navigation/useTopLevelNavigation.ts +1 -1
  174. package/src/hooks/navigation/utils.ts +1 -1
  175. package/src/hooks/useEntityHistory.ts +7 -2
  176. package/src/preview/PropertyPreview.tsx +27 -23
  177. package/src/preview/components/StorageThumbnail.tsx +4 -1
  178. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
  179. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +1 -1
  180. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +1 -1
  181. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +3 -3
  182. package/src/preview/property_previews/StringPropertyPreview.tsx +3 -3
  183. package/src/routes/RebaseRoute.tsx +57 -11
  184. package/src/util/navigation_utils.ts +21 -2
  185. package/src/util/previews.ts +15 -6
  186. package/src/util/property_utils.tsx +3 -3
  187. package/dist/CollectionEditorDialog-B2M9lCyL.js.map +0 -1
  188. package/dist/CollectionsStudioView-WG6soyfs.js.map +0 -1
  189. package/dist/ContentHomePage-CDF_a6Lp.js.map +0 -1
  190. package/dist/PropertyEditView-DS67DxoT.js.map +0 -1
  191. package/dist/RolesView-CIuYBimF.js.map +0 -1
  192. package/dist/UsersView-B5zelXnH.js.map +0 -1
  193. package/dist/index-CBhrgpR7.js.map +0 -1
  194. package/dist/index-CHxgwt6E.js.map +0 -1
  195. package/dist/index-Dey5WJpO.js.map +0 -1
  196. package/dist/useEntityHistory-Dcj4zhGj.js.map +0 -1
  197. package/dist/util-BQ82ySL3.js.map +0 -1
@@ -147,29 +147,28 @@ function EntityBoardCardInner<M extends Record<string, unknown> = Record<string,
147
147
  </div>
148
148
  )}
149
149
 
150
- {/* Selection checkbox overlay — visible on hover or when selected */}
151
- {selectionEnabled && (
150
+ {/* Selection checkbox overlay — always in the DOM, visibility controlled by CSS */}
151
+ <div className={cls(
152
+ "absolute inset-0 flex items-center justify-center rounded-md transition-[opacity,background-color] duration-200",
153
+ !selectionEnabled && "invisible pointer-events-none",
154
+ selectionEnabled && (selected
155
+ ? "opacity-100 bg-primary/10 dark:bg-primary/20"
156
+ : "opacity-0 group-hover/card:opacity-100")
157
+ )}>
152
158
  <div className={cls(
153
- "absolute inset-0 flex items-center justify-center rounded-md transition-all duration-200",
159
+ "transition-transform duration-200",
154
160
  selected
155
- ? "opacity-100 bg-primary/10 dark:bg-primary/20"
156
- : "opacity-0 group-hover/card:opacity-100"
161
+ ? "scale-100"
162
+ : "scale-75 group-hover/card:scale-100"
157
163
  )}>
158
- <div className={cls(
159
- "transition-transform duration-200",
160
- selected
161
- ? "scale-100"
162
- : "scale-75 group-hover/card:scale-100"
163
- )}>
164
- <Checkbox
165
- checked={selected ?? false}
166
- onCheckedChange={handleSelectionChange}
167
- size="small"
168
- padding={false}
169
- />
170
- </div>
164
+ <Checkbox
165
+ checked={selected ?? false}
166
+ onCheckedChange={handleSelectionChange}
167
+ size="small"
168
+ padding={false}
169
+ />
171
170
  </div>
172
- )}
171
+ </div>
173
172
  </div>
174
173
 
175
174
  {/* Content */}
@@ -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-950",
98
+ highlighted && !selected && "ring-2 ring-primary ring-opacity-50 bg-surface-accent-50/50 dark:bg-surface-accent-950/50"
99
99
  )}
100
100
  onClick={handleClick}
101
101
  >
@@ -30,7 +30,7 @@ export type EntityCollectionBoardViewProps<M extends Record<string, unknown> = R
30
30
  collection: EntityCollection<M>;
31
31
  tableController: EntityTableController<M>;
32
32
  fullPath: string;
33
- parentCollectionIds?: string[];
33
+ parentCollectionSlugs?: string[], parentEntityIds?: string[];
34
34
  columnProperty: string;
35
35
  onEntityClick?: (entity: Entity<M>) => void;
36
36
  selectionController?: SelectionController<M>;
@@ -48,7 +48,8 @@ export function EntityCollectionBoardView<M extends Record<string, unknown> = Re
48
48
  collection,
49
49
  tableController,
50
50
  fullPath,
51
- parentCollectionIds = [],
51
+ parentCollectionSlugs = [],
52
+ parentEntityIds = [],
52
53
  columnProperty,
53
54
  onEntityClick,
54
55
  selectionController,
@@ -225,13 +226,13 @@ export function EntityCollectionBoardView<M extends Record<string, unknown> = Re
225
226
  .forEach(plugin => {
226
227
  plugin.hooks!.onKanbanColumnsReorder!({
227
228
  fullPath,
228
- parentCollectionIds,
229
+ parentCollectionSlugs, parentEntityIds,
229
230
  collection,
230
231
  kanbanColumnProperty: columnProperty,
231
232
  newColumnsOrder: newColumns
232
233
  });
233
234
  });
234
- }, [plugins, fullPath, parentCollectionIds, collection, columnProperty, analyticsController]);
235
+ }, [plugins, fullPath, parentCollectionSlugs, parentEntityIds, collection, columnProperty, analyticsController]);
235
236
 
236
237
  // Collection-level count queries to detect missing order property
237
238
  // Just TWO counts: total and ordered (for the entire collection, not per column)
@@ -432,24 +433,51 @@ export function EntityCollectionBoardView<M extends Record<string, unknown> = Re
432
433
  return selectionController?.isEntitySelected(entity) ?? false;
433
434
  }, [selectionController]);
434
435
 
435
- const ItemComponent = useCallback((props: BoardItemViewProps<M>) => {
436
- return (
436
+ // Store latest callbacks in refs so the stable ItemComponent always
437
+ // reads fresh values without changing its own identity.
438
+ const handleEntityClickRef = useRef(handleEntityClick);
439
+ handleEntityClickRef.current = handleEntityClick;
440
+ const isEntitySelectedRef = useRef(isEntitySelected);
441
+ isEntitySelectedRef.current = isEntitySelected;
442
+ const handleSelectionChangeRef = useRef(handleSelectionChange);
443
+ handleSelectionChangeRef.current = handleSelectionChange;
444
+ const selectionEnabledRef = useRef(selectionEnabled);
445
+ selectionEnabledRef.current = selectionEnabled;
446
+
447
+ // Stable callback wrappers — identity never changes, delegates to refs.
448
+ const stableOnClick = useCallback((entity: Entity<M>) => {
449
+ handleEntityClickRef.current(entity);
450
+ }, []);
451
+ const stableOnSelectionChange = useCallback((entity: Entity<M>, sel: boolean) => {
452
+ handleSelectionChangeRef.current(entity, sel);
453
+ }, []);
454
+
455
+ // Build a single, truly stable component reference.
456
+ // Uses refs for ALL dynamic values so the component type never changes.
457
+ // When ItemComponent identity changes, React.memo'd SortableItem remounts
458
+ // the card → DOM is destroyed/recreated → CSS :hover state is lost → flicker.
459
+ // eslint-disable-next-line react-hooks/exhaustive-deps
460
+ const ItemComponent = useMemo(() => {
461
+ const Comp = (props: BoardItemViewProps<M>) => (
437
462
  <EntityBoardCard
438
463
  {...props}
439
- collection={collection}
440
- onClick={handleEntityClick}
441
- selected={isEntitySelected(props.item.entity)}
442
- onSelectionChange={handleSelectionChange}
443
- selectionEnabled={selectionEnabled}
464
+ collection={collectionRef.current as EntityCollection<M>}
465
+ onClick={stableOnClick}
466
+ selected={isEntitySelectedRef.current(props.item.entity)}
467
+ onSelectionChange={stableOnSelectionChange}
468
+ selectionEnabled={selectionEnabledRef.current}
444
469
  />
445
470
  );
446
- }, [collection, handleEntityClick, isEntitySelected, handleSelectionChange, selectionEnabled]);
471
+ Comp.displayName = "KanbanItemComponent";
472
+ return Comp;
473
+ }, []);
447
474
 
448
475
  // Get KanbanSetupComponent from plugin slots
449
476
  const kanbanSetupSlots = useSlot("kanban.setup", {
450
477
  collection,
451
478
  fullPath,
452
- parentCollectionIds
479
+ parentCollectionSlugs,
480
+ parentEntityIds
453
481
  });
454
482
  const KanbanSetupComponent = kanbanSetupSlots.length > 0 ? () => <>{kanbanSetupSlots[0]}</> : null;
455
483
 
@@ -457,7 +485,7 @@ export function EntityCollectionBoardView<M extends Record<string, unknown> = Re
457
485
  const addKanbanColumnSlots = useSlot("kanban.add-column", {
458
486
  collection,
459
487
  fullPath,
460
- parentCollectionIds,
488
+ parentCollectionSlugs, parentEntityIds,
461
489
  columnProperty
462
490
  });
463
491
  const AddKanbanColumnComponent = addKanbanColumnSlots.length > 0 ? () => <>{addKanbanColumnSlots[0]}</> : null;
@@ -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
@@ -88,6 +87,44 @@ function getRowClasses(size: CollectionSize): string {
88
87
  }
89
88
  }
90
89
 
90
+ /**
91
+ * Estimated row height in pixels for virtualization, based on size.
92
+ */
93
+ function getEstimatedRowHeight(size: CollectionSize): number {
94
+ switch (size) {
95
+ case "xs": return 44;
96
+ case "s": return 52;
97
+ case "m": return 64;
98
+ case "l": return 76;
99
+ case "xl": return 88;
100
+ default: return 64;
101
+ }
102
+ }
103
+
104
+ /** Number of extra rows rendered above/below the viewport. */
105
+ const OVERSCAN_COUNT = 8;
106
+
107
+ /** Threshold in pixels from the bottom of the scroll area to trigger loading more. */
108
+ const LOAD_MORE_THRESHOLD = 400;
109
+
110
+ /**
111
+ * Walk up the DOM from `element` to find the nearest scrollable ancestor.
112
+ */
113
+ function getScrollParent(element: HTMLElement | null): HTMLElement | null {
114
+ let parent = element?.parentElement ?? null;
115
+ while (parent) {
116
+ const style = getComputedStyle(parent);
117
+ if (
118
+ style.overflowY === "auto" || style.overflowY === "scroll" ||
119
+ style.overflow === "auto" || style.overflow === "scroll"
120
+ ) {
121
+ return parent;
122
+ }
123
+ parent = parent.parentElement;
124
+ }
125
+ return document.documentElement;
126
+ }
127
+
91
128
  /**
92
129
  * Returns true if a property type should NOT be rendered via
93
130
  * PropertyPreview in list row columns (because it would blow up height).
@@ -446,22 +483,77 @@ export function EntityCollectionListView<M extends Record<string, unknown> = Rec
446
483
  // Empty state
447
484
  const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
448
485
 
486
+ // ── Virtualization: scroll-parent windowing ──
487
+ const estimatedRowHeight = getEstimatedRowHeight(size);
488
+ const [effectiveScrollTop, setEffectiveScrollTop] = useState(0);
489
+ const [viewportHeight, setViewportHeight] = useState(800);
490
+
491
+ // Keep mutable refs for values used in the scroll handler to avoid
492
+ // re-attaching the listener every time pagination state changes.
493
+ const paginationStateRef = useRef({ paginationEnabled, noMoreToLoad, itemCount, pageSize });
494
+ useEffect(() => {
495
+ paginationStateRef.current = { paginationEnabled, noMoreToLoad, itemCount, pageSize };
496
+ }, [paginationEnabled, noMoreToLoad, itemCount, pageSize]);
449
497
 
450
- // Sentinel ref for IntersectionObserver-based infinite scroll
451
- const sentinelRef = useRef<HTMLDivElement | null>(null);
452
498
  useEffect(() => {
453
- if (!paginationEnabled || noMoreToLoad || dataLoading) return;
454
- const sentinel = sentinelRef.current;
455
- if (!sentinel) return;
456
- const observer = new IntersectionObserver((entries) => {
457
- if (entries[0]?.isIntersecting && !isLoadingMore.current) {
499
+ const el = containerRef.current;
500
+ if (!el) return;
501
+ const scrollEl = getScrollParent(el);
502
+ if (!scrollEl) return;
503
+
504
+ let rafId: number | null = null;
505
+
506
+ const update = () => {
507
+ rafId = null;
508
+ const scrollRect = scrollEl.getBoundingClientRect();
509
+ const listRect = el.getBoundingClientRect();
510
+
511
+ // How much of the list has scrolled past the viewport top
512
+ const listTopRelative = listRect.top - scrollRect.top;
513
+ setEffectiveScrollTop(Math.max(0, -listTopRelative));
514
+ setViewportHeight(scrollRect.height);
515
+
516
+ // Infinite scroll: trigger load-more when near the bottom
517
+ const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
518
+ if (
519
+ pe &&
520
+ !nm &&
521
+ !isLoadingMore.current &&
522
+ scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < LOAD_MORE_THRESHOLD
523
+ ) {
458
524
  isLoadingMore.current = true;
459
- setItemCount?.((itemCount ?? pageSize) + pageSize);
525
+ setItemCount?.((ic ?? ps) + ps);
460
526
  }
461
- }, { rootMargin: "200px" });
462
- observer.observe(sentinel);
463
- return () => observer.disconnect();
464
- }, [paginationEnabled, noMoreToLoad, dataLoading, data.length, itemCount, pageSize, setItemCount]);
527
+ };
528
+
529
+ const onScroll = () => {
530
+ if (rafId === null) rafId = requestAnimationFrame(update);
531
+ };
532
+
533
+ scrollEl.addEventListener("scroll", onScroll, { passive: true });
534
+ const ro = new ResizeObserver(() => update());
535
+ ro.observe(scrollEl);
536
+ update(); // initial measurement
537
+
538
+ return () => {
539
+ scrollEl.removeEventListener("scroll", onScroll);
540
+ ro.disconnect();
541
+ if (rafId !== null) cancelAnimationFrame(rafId);
542
+ };
543
+ }, [setItemCount]); // stable deps only — mutable state via refs
544
+
545
+ // Compute the visible window of rows
546
+ const totalHeight = data.length * estimatedRowHeight;
547
+ const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
548
+ const endIndex = Math.min(
549
+ data.length,
550
+ Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT
551
+ );
552
+ const visibleData = data.slice(startIndex, endIndex);
553
+ const offsetY = startIndex * estimatedRowHeight;
554
+
555
+ // Footer height for loading/end indicators
556
+ const footerHeight = dataLoading ? 48 : (!dataLoading && noMoreToLoad && data.length > 0) ? 32 : 0;
465
557
 
466
558
  return (
467
559
  <div
@@ -479,9 +571,11 @@ export function EntityCollectionListView<M extends Record<string, unknown> = Rec
479
571
  </Typography>
480
572
  </div>
481
573
  ) : isInitialLoading ? (
482
- <CircularProgressCenter/>
574
+ <div className="flex items-center justify-center py-12 px-8">
575
+ <CircularProgress size="small"/>
576
+ </div>
483
577
  ) : isEmpty ? (
484
- <div className="flex items-center justify-center p-8">
578
+ <div className="flex items-center justify-center py-12 px-8">
485
579
  {emptyComponent ?? (
486
580
  <Typography variant="label" color="secondary">
487
581
  No entries found
@@ -489,55 +583,64 @@ export function EntityCollectionListView<M extends Record<string, unknown> = Rec
489
583
  )}
490
584
  </div>
491
585
  ) : (
492
- <>
493
- {data.map((entity, index) => {
494
- const isLast = index === data.length - 1;
495
- return (
496
- <div
497
- key={entity.id}
498
- className={cls(
499
- !isLast && "border-b",
500
- !isLast && defaultBorderMixin
501
- )}
502
- >
503
- <ListRow
504
- entity={entity}
505
- collection={resolvedCollection}
506
- onClick={handleEntityClick}
507
- selected={isEntitySelected(entity)}
508
- highlighted={isEntityHighlighted(entity)}
509
- onSelectionChange={handleSelectionChange}
510
- selectionEnabled={selectionEnabled}
511
- columns={visibleColumns}
512
- slotKeys={slotKeys}
513
- rowClasses={rowClasses}
514
- showImage={showImage}
515
- size={size}
516
- isLast={isLast}
517
- isActive={selectedEntityId !== undefined && entity.id === selectedEntityId}
518
- />
519
- </div>
520
- );
521
- })}
522
-
523
- {/* Sentinel for infinite scroll pagination */}
524
- {paginationEnabled && !noMoreToLoad && (
525
- <div ref={sentinelRef} className="h-1"/>
526
- )}
586
+ /* Spacer with total height — no internal scroll.
587
+ The nearest scrollable ancestor provides the scrollbar. */
588
+ <div style={{ height: totalHeight + footerHeight, position: "relative" }}>
589
+ {/* Windowed rows */}
590
+ <div style={{ position: "absolute", top: offsetY, left: 0, right: 0 }}>
591
+ {visibleData.map((entity, i) => {
592
+ const actualIndex = startIndex + i;
593
+ const isLast = actualIndex === data.length - 1;
594
+ return (
595
+ <div
596
+ key={entity.id}
597
+ style={{ height: estimatedRowHeight }}
598
+ className={cls(
599
+ !isLast && "border-b",
600
+ !isLast && defaultBorderMixin
601
+ )}
602
+ >
603
+ <ListRow
604
+ entity={entity}
605
+ collection={resolvedCollection}
606
+ onClick={handleEntityClick}
607
+ selected={isEntitySelected(entity)}
608
+ highlighted={isEntityHighlighted(entity)}
609
+ onSelectionChange={handleSelectionChange}
610
+ selectionEnabled={selectionEnabled}
611
+ columns={visibleColumns}
612
+ slotKeys={slotKeys}
613
+ rowClasses={rowClasses}
614
+ showImage={showImage}
615
+ size={size}
616
+ isLast={isLast}
617
+ isActive={selectedEntityId !== undefined && entity.id === selectedEntityId}
618
+ />
619
+ </div>
620
+ );
621
+ })}
622
+ </div>
527
623
 
624
+ {/* Loading / end indicators pinned at the bottom */}
528
625
  {dataLoading && (
529
- <div className="flex items-center justify-center py-4">
626
+ <div
627
+ className="flex items-center justify-center py-3"
628
+ style={{ position: "absolute", top: totalHeight, left: 0, right: 0 }}
629
+ >
530
630
  <CircularProgress size="small"/>
531
631
  </div>
532
632
  )}
533
633
  {!dataLoading && noMoreToLoad && data.length > 0 && (
534
- <div className="flex items-center justify-center py-2">
634
+ <div
635
+ className="flex items-center justify-center py-2 dark:bg-surface-900"
636
+ style={{ position: "absolute", top: totalHeight, left: 0, right: 0 }}
637
+ >
535
638
  <Typography variant="caption" color="secondary">
536
639
  All {data.length} entries loaded
537
640
  </Typography>
538
641
  </div>
539
642
  )}
540
- </>
643
+ </div>
541
644
  )}
542
645
  </div>
543
646
  );
@@ -619,7 +722,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
619
722
  isActive
620
723
  ? "bg-surface-accent-100 dark:bg-surface-accent-950 hover:bg-surface-accent-200 dark:hover:bg-surface-accent-900"
621
724
  : selected
622
- ? "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-950 hover:bg-surface-accent-100 dark:hover:bg-surface-accent-900"
623
726
  : highlighted
624
727
  ? "bg-surface-accent-50 dark:bg-surface-accent-950 hover:bg-surface-50 dark:hover:bg-surface-800/40"
625
728
  : "bg-white dark:bg-surface-900 hover:bg-surface-50 dark:hover:bg-surface-800/40"