@rebasepro/admin 0.6.0 → 0.7.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 (149) hide show
  1. package/README.md +7 -3
  2. package/dist/{CollectionEditorDialog-jA-PLh6N.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
  3. package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
  4. package/dist/{PropertyEditView-DEUddmg_.js → PropertyEditView-C9yhLyja.js} +129 -15
  5. package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
  6. package/dist/{CollectionsStudioView-UKouBPOm.js → RouterCollectionsStudioView-Dqx27GD8.js} +61 -39
  7. package/dist/RouterCollectionsStudioView-Dqx27GD8.js.map +1 -0
  8. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  9. package/dist/collection_editor/index.d.ts +6 -0
  10. package/dist/collection_editor/serializable_types.d.ts +374 -0
  11. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  12. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -1
  13. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  14. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  15. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  16. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  17. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  18. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  19. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  20. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  21. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  22. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  23. package/dist/collection_editor_ui.d.ts +4 -0
  24. package/dist/collection_editor_ui.js +33 -4
  25. package/dist/collection_editor_ui.js.map +1 -0
  26. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  27. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  28. package/dist/components/EntityEditView.d.ts +3 -2
  29. package/dist/components/RebaseCMS.d.ts +1 -1
  30. package/dist/editor/editor.d.ts +10 -4
  31. package/dist/editor.js +6 -4
  32. package/dist/editor.js.map +1 -1
  33. package/dist/form/EntityForm.d.ts +10 -9
  34. package/dist/form/EntityFormBinding.d.ts +27 -0
  35. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +2 -2
  36. package/dist/form/form_utils.d.ts +8 -0
  37. package/dist/form/index.d.ts +5 -1
  38. package/dist/{history-BL49TIDb.js → history-ChHgyyvM.js} +2 -2
  39. package/dist/{history-BL49TIDb.js.map → history-ChHgyyvM.js.map} +1 -1
  40. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  41. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  42. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  43. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  44. package/dist/hooks/navigation/utils.d.ts +1 -1
  45. package/dist/index.d.ts +3 -1
  46. package/dist/index.js +2 -2
  47. package/dist/routes/CustomViewRoute.d.ts +2 -1
  48. package/dist/{src-BJ8fkM8y.js → src-DOX2nOyh.js} +1634 -1330
  49. package/dist/src-DOX2nOyh.js.map +1 -0
  50. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  51. package/dist/types/components/EntityFormProps.d.ts +59 -0
  52. package/dist/types/fields.d.ts +6 -1
  53. package/dist/util/dataControllerAdapter.d.ts +30 -0
  54. package/dist/util/navigation_utils.d.ts +1 -1
  55. package/dist/util/propertyConfigMapper.d.ts +17 -0
  56. package/package.json +18 -18
  57. package/src/collection_editor/extensibility_types.ts +168 -0
  58. package/src/collection_editor/index.ts +46 -0
  59. package/src/collection_editor/serializable_types.ts +487 -0
  60. package/src/collection_editor/serializable_utils.ts +588 -0
  61. package/src/collection_editor/ui/AddKanbanColumnAction.tsx +2 -0
  62. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +4 -4
  63. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +134 -70
  64. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  65. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  66. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  67. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  68. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  69. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  70. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +155 -10
  71. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  72. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  73. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  74. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  75. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  76. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  77. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  78. package/src/collection_editor_ui.ts +6 -0
  79. package/src/components/ArrayContainer.tsx +5 -5
  80. package/src/components/DefaultDrawer.tsx +13 -3
  81. package/src/components/DrawerNavigationGroup.tsx +2 -2
  82. package/src/components/DrawerNavigationItem.tsx +4 -4
  83. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  84. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +14 -11
  85. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  86. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +54 -183
  87. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  88. package/src/components/EntityEditView.tsx +5 -4
  89. package/src/components/EntityEditViewFormActions.tsx +32 -26
  90. package/src/components/HomePage/ContentHomePage.tsx +13 -4
  91. package/src/components/HomePage/NavigationCard.tsx +8 -14
  92. package/src/components/HomePage/NavigationGroup.tsx +1 -1
  93. package/src/components/HomePage/SmallNavigationCard.tsx +1 -5
  94. package/src/components/RebaseCMS.tsx +3 -2
  95. package/src/components/RebaseNavigation.tsx +71 -7
  96. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  97. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +28 -21
  98. package/src/components/SideDialogs.tsx +0 -7
  99. package/src/components/common/default_entity_actions.tsx +18 -10
  100. package/src/contexts/BreacrumbsContext.tsx +15 -6
  101. package/src/editor/editor.tsx +14 -8
  102. package/src/editor/selectors/ai-selector.tsx +0 -1
  103. package/src/form/EntityForm.tsx +191 -428
  104. package/src/form/EntityFormActions.tsx +33 -47
  105. package/src/form/EntityFormBinding.tsx +323 -0
  106. package/src/form/components/FormLayout.tsx +1 -1
  107. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -4
  108. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  109. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
  110. package/src/form/form_utils.ts +163 -0
  111. package/src/form/index.tsx +14 -5
  112. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  113. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  114. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  115. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  116. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  117. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  118. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  119. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  120. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  121. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  122. package/src/hooks/navigation/utils.ts +7 -4
  123. package/src/index.ts +7 -2
  124. package/src/routes/CustomViewRoute.tsx +14 -6
  125. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  126. package/src/types/components/EntityFormProps.tsx +68 -0
  127. package/src/types/fields.tsx +7 -1
  128. package/src/util/dataControllerAdapter.ts +93 -0
  129. package/src/util/navigation_utils.ts +1 -1
  130. package/src/util/propertyConfigMapper.ts +134 -0
  131. package/LICENSE +0 -21
  132. package/dist/CollectionEditorDialog-jA-PLh6N.js.map +0 -1
  133. package/dist/CollectionsStudioView-UKouBPOm.js.map +0 -1
  134. package/dist/PropertyEditView-DEUddmg_.js.map +0 -1
  135. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  136. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  137. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  138. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  139. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  140. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  141. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  142. package/dist/src-BJ8fkM8y.js.map +0 -1
  143. package/src/components/EntityCollectionView/Board.tsx +0 -475
  144. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  145. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  146. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  147. package/src/components/EntityCollectionView/board_types.ts +0 -113
  148. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  149. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -3,9 +3,9 @@ import React, { useEffect, useMemo, useState } from "react";
3
3
 
4
4
  import { Field, getIn, useFormex } from "@rebasepro/formex";
5
5
  import {
6
- useLargeLayout,
7
- useSnackbarController
6
+ useLargeLayout
8
7
  } from "@rebasepro/core";
8
+ import { useSafeSnackbarController } from "../../useSafeSnackbarController";
9
9
  import { ErrorBoundary } from "@rebasepro/ui";
10
10
  import {
11
11
  Button,
@@ -16,6 +16,7 @@ import {
16
16
  defaultBorderMixin,
17
17
  FileSearchIcon,
18
18
  IconButton,
19
+ iconSize,
19
20
  PlusIcon,
20
21
  Tooltip,
21
22
  Typography
@@ -29,6 +30,8 @@ import { PropertyTree } from "./PropertyTree";
29
30
  import { GetCodeDialog } from "./GetCodeDialog";
30
31
  import { useAIModifiedPaths } from "./AIModifiedPathsContext";
31
32
  import { useCollectionsConfigController } from "../../useCollectionsConfigController";
33
+ import type { PropertyTypePreset, PropertyType } from "../../extensibility_types";
34
+ import type { SerializableProperty, SerializableCollection } from "../../serializable_types";
32
35
 
33
36
  type PropertyOrBuilder = Property | Record<string, unknown>;
34
37
 
@@ -43,7 +46,15 @@ type CollectionEditorFormProps = {
43
46
  getData?: () => Promise<object[]>;
44
47
  doCollectionInference?: (collection: EntityCollection) => Promise<Partial<EntityCollection> | null> | undefined;
45
48
  propertyConfigs: Record<string, PropertyConfig>;
46
-
49
+ propertyTypePresets?: PropertyTypePreset[];
50
+ hiddenPropertyTypes?: PropertyType[];
51
+ renderExtraPropertyFields?: (params: {
52
+ metadata: Record<string, unknown>;
53
+ onMetadataChange: (key: string, value: unknown) => void;
54
+ property: SerializableProperty;
55
+ collection: SerializableCollection;
56
+ }) => React.ReactNode;
57
+ standalone?: boolean;
47
58
  };
48
59
 
49
60
  export function CollectionPropertiesEditorForm({
@@ -56,7 +67,11 @@ export function CollectionPropertiesEditorForm({
56
67
  getUser,
57
68
  getData,
58
69
  doCollectionInference,
59
- propertyConfigs
70
+ propertyConfigs,
71
+ propertyTypePresets,
72
+ hiddenPropertyTypes,
73
+ renderExtraPropertyFields,
74
+ standalone,
60
75
 
61
76
  }: CollectionEditorFormProps) {
62
77
 
@@ -69,11 +84,12 @@ export function CollectionPropertiesEditorForm({
69
84
  dirty
70
85
  } = useFormex<EntityCollection>();
71
86
 
72
- const snackbarController = useSnackbarController();
73
- const configController = useCollectionsConfigController();
87
+ const snackbarController = useSafeSnackbarController();
88
+ const configControllerFromContext = useCollectionsConfigController();
89
+ const configController = standalone ? { readOnly: false } : configControllerFromContext;
74
90
 
75
91
  const largeLayout = useLargeLayout();
76
- const asDialog = !largeLayout
92
+ const asDialog = standalone ? false : !largeLayout
77
93
 
78
94
  // index of the selected property within the namespace
79
95
  const [selectedPropertyIndex, setSelectedPropertyIndex] = useState<number | undefined>();
@@ -115,7 +131,7 @@ export function CollectionPropertiesEditorForm({
115
131
  promise.then((newCollection) => {
116
132
 
117
133
  if (!newCollection) {
118
- snackbarController.open({
134
+ snackbarController?.open?.({
119
135
  type: "error",
120
136
  message: "Could not infer properties from data"
121
137
  });
@@ -222,7 +238,7 @@ export function CollectionPropertiesEditorForm({
222
238
 
223
239
  // Check if there are any changes (new properties or modified nested properties)
224
240
  if (allNewPropertyKeys.length === 0) {
225
- snackbarController.open({
241
+ snackbarController?.open?.({
226
242
  type: "info",
227
243
  message: "No new properties found in existing data"
228
244
  });
@@ -241,7 +257,7 @@ export function CollectionPropertiesEditorForm({
241
257
  updatePropertiesOrder(updatedPropertiesOrder);
242
258
  setInferredPropertyKeys(allNewPropertyKeys);
243
259
 
244
- snackbarController.open({
260
+ snackbarController?.open?.({
245
261
  type: "success",
246
262
  message: `Added ${allNewPropertyKeys.length} new ${allNewPropertyKeys.length === 1 ? "property" : "properties"}`
247
263
  });
@@ -404,98 +420,87 @@ export function CollectionPropertiesEditorForm({
404
420
  };
405
421
 
406
422
  const body = (
407
- <div className={"grid grid-cols-12 gap-2 h-full bg-white dark:bg-surface-950"}>
423
+ <div className={"grid grid-cols-12 h-full bg-surface-50 dark:bg-surface-800"}>
408
424
  <div className={cls(
409
- "bg-surface-50 dark:bg-surface-900",
410
- "p-4 md:p-8",
411
- "col-span-12 lg:col-span-5 h-full overflow-auto",
425
+ "col-span-12 lg:col-span-5 h-full flex flex-col bg-surface-50 dark:bg-surface-800",
412
426
  !asDialog && "border-r " + defaultBorderMixin
413
427
  )}>
414
-
415
- <div className="flex my-2">
416
-
417
- <div className="grow mb-4">
418
-
428
+ {/* Sidebar Header */}
429
+ <div className={cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px] shrink-0", defaultBorderMixin)}>
430
+ <div className="flex-grow min-w-0 pr-2">
419
431
  <Field
420
432
  name={"name"}
421
433
  as={DebouncedTextField}
422
434
  invisible={true}
423
435
  className="-ml-1"
424
- inputClassName="text-2xl font-headers"
436
+ inputClassName="text-sm font-semibold truncate bg-transparent border-0 outline-none focus:ring-0"
425
437
  placeholder={"Collection name"}
426
438
  size={"small"}
427
439
  required
428
440
  error={Boolean(errors?.name)}/>
429
-
430
- {owner &&
431
- <Typography variant={"body2"}
432
- className={"ml-2"}
433
- color={"secondary"}>
434
- Created by {owner.displayName}
435
- </Typography>}
436
441
  </div>
437
442
 
438
- {extraIcon && <div className="ml-4">
439
- {extraIcon}
440
- </div>}
443
+ {extraIcon && <div className="flex-shrink-0">{extraIcon}</div>}
441
444
 
442
- <div className="ml-4 flex flex-row gap-2 items-center flex-shrink-0">
443
- {/* <Tooltip title={"Get the code for this collection"}
444
- asChild={true}>
445
- <IconButton
446
- variant={"filled"}
447
- disabled={inferringProperties}
448
- onClick={() => setCodeDialogOpen(true)}>
449
- <CodeIcon />
450
- </IconButton>
451
- </Tooltip> */}
452
- {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
453
- asChild={true}>
445
+ <div className="flex items-center gap-1.5 flex-shrink-0 ml-2">
446
+ {inferPropertiesFromData && (
447
+ <Tooltip title={"Add new properties based on data"} asChild={true}>
448
+ <IconButton
449
+ size="small"
450
+ disabled={inferringProperties}
451
+ onClick={inferPropertiesFromData}
452
+ >
453
+ {inferringProperties ? <CircularProgress size={"smallest"}/> : <FileSearchIcon size={iconSize.smallest}/>}
454
+ </IconButton>
455
+ </Tooltip>
456
+ )}
457
+ <Tooltip title={"Add new property"} asChild={true}>
454
458
  <IconButton
455
- variant={"filled"}
456
- disabled={inferringProperties}
457
- onClick={inferPropertiesFromData}>
458
- {inferringProperties ? <CircularProgress size={"small"}/> : <FileSearchIcon/>}
459
- </IconButton>
460
- </Tooltip>}
461
- <Tooltip title={"Add new property"}
462
- asChild={true}>
463
- <Button
459
+ size="small"
464
460
  disabled={configController?.readOnly}
465
- onClick={() => setNewPropertyDialogOpen(true)}>
466
- <PlusIcon/>
467
- </Button>
461
+ onClick={() => setNewPropertyDialogOpen(true)}
462
+ >
463
+ <PlusIcon size={iconSize.smallest}/>
464
+ </IconButton>
468
465
  </Tooltip>
469
466
  </div>
470
467
  </div>
471
468
 
472
- <ErrorBoundary>
473
- <PropertyTree
474
- className={"mt-2"}
475
- inferredPropertyKeys={inferredPropertyKeys}
476
- selectedPropertyKey={selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined}
477
- properties={values.properties}
478
- additionalFields={values.additionalFields}
479
- propertiesOrder={usedPropertiesOrder}
480
- onPropertyClick={onPropertyClick}
481
- onPropertyMove={onPropertyMove}
482
- onPropertyRemove={(isNewCollection || (inferredPropertyKeys && inferredPropertyKeys.length > 0)) && !configController?.readOnly ? deleteProperty : undefined}
483
-
484
- errors={errors}/>
485
- </ErrorBoundary>
486
-
487
- <Button className={"mt-4 xl:mt-8 w-full"}
488
- variant="filled"
489
- color="neutral"
490
- disabled={configController?.readOnly}
491
- onClick={() => setNewPropertyDialogOpen(true)}
492
- startIcon={<PlusIcon/>}>
493
- Add new property
494
- </Button>
469
+ {/* Sidebar Content */}
470
+ <div className="flex-grow overflow-y-auto p-3 no-scrollbar space-y-3 bg-surface-50 dark:bg-surface-800">
471
+ {owner && (
472
+ <div className="px-1 py-0.5">
473
+ <Typography variant="body2" color="secondary">
474
+ Created by {owner.displayName}
475
+ </Typography>
476
+ </div>
477
+ )}
478
+ <ErrorBoundary>
479
+ <PropertyTree
480
+ inferredPropertyKeys={inferredPropertyKeys}
481
+ selectedPropertyKey={selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined}
482
+ properties={values.properties}
483
+ additionalFields={values.additionalFields}
484
+ propertiesOrder={usedPropertiesOrder}
485
+ onPropertyClick={onPropertyClick}
486
+ onPropertyMove={onPropertyMove}
487
+ onPropertyRemove={(isNewCollection || (inferredPropertyKeys && inferredPropertyKeys.length > 0)) && !configController?.readOnly ? deleteProperty : undefined}
488
+ errors={errors}/>
489
+ </ErrorBoundary>
490
+
491
+ <Button className={"w-full"}
492
+ variant="outlined"
493
+ color="neutral"
494
+ disabled={configController?.readOnly}
495
+ onClick={() => setNewPropertyDialogOpen(true)}
496
+ startIcon={<PlusIcon/>}>
497
+ Add new property
498
+ </Button>
499
+ </div>
495
500
  </div>
496
501
 
497
502
  {!asDialog &&
498
- <div className={"col-span-12 lg:col-span-7 p-4 md:py-8 md:px-4 h-full overflow-auto"}>
503
+ <div className={"col-span-12 lg:col-span-7 p-4 md:py-8 md:px-4 h-full overflow-auto bg-surface-50 dark:bg-surface-800"}>
499
504
  <div
500
505
  className="sticky top-8 min-h-full w-full flex flex-col justify-center">
501
506
 
@@ -519,6 +524,10 @@ export function CollectionPropertiesEditorForm({
519
524
  initialErrors={initialErrors}
520
525
  getData={getData}
521
526
  propertyConfigs={propertyConfigs}
527
+ propertyTypePresets={propertyTypePresets}
528
+ hiddenPropertyTypes={hiddenPropertyTypes}
529
+ renderExtraPropertyFields={renderExtraPropertyFields}
530
+ collectionValues={values}
522
531
 
523
532
  />}
524
533
 
@@ -563,6 +572,10 @@ export function CollectionPropertiesEditorForm({
563
572
  initialErrors={initialErrors}
564
573
  getData={getData}
565
574
  propertyConfigs={propertyConfigs}
575
+ propertyTypePresets={propertyTypePresets}
576
+ hiddenPropertyTypes={hiddenPropertyTypes}
577
+ renderExtraPropertyFields={renderExtraPropertyFields}
578
+ collectionValues={values}
566
579
 
567
580
  onCancel={closePropertyDialog}
568
581
  onOkClicked={asDialog
@@ -589,6 +602,10 @@ export function CollectionPropertiesEditorForm({
589
602
  getData={getData}
590
603
  allowDataInference={!isNewCollection}
591
604
  propertyConfigs={propertyConfigs}
605
+ propertyTypePresets={propertyTypePresets}
606
+ hiddenPropertyTypes={hiddenPropertyTypes}
607
+ renderExtraPropertyFields={renderExtraPropertyFields}
608
+ collectionValues={values}
592
609
  existingPropertyKeys={values.propertiesOrder as string[]}/>
593
610
 
594
611
  <ErrorBoundary>
@@ -1,23 +1,31 @@
1
1
 
2
2
  import { useUnsavedChangesDialog, UnsavedChangesDialog } from "@rebasepro/core";
3
- import { useUrlController } from "../../_cms_internals";
4
3
  import * as React from "react";
5
- import { useState, useMemo, useEffect } from "react";
4
+ import { useState } from "react";
6
5
  import { Button, PlusIcon, Typography } from "@rebasepro/ui";
7
- import { useSnackbarController } from "@rebasepro/core";
8
6
  import { CollectionEditorDialogProps } from "./CollectionEditorDialog";
9
7
  import { AIModifiedPathsProvider } from "./AIModifiedPathsContext";
10
8
  import { CollectionEditor } from "./CollectionEditorDialog";
11
- import { useNavigate } from "react-router-dom";
9
+ import type { EntityCollection } from "@rebasepro/types";
12
10
 
13
11
  export type CollectionStudioViewProps = Omit<CollectionEditorDialogProps, "open" | "isNewCollection" | "editedCollectionId" | "handleClose" | "handleCancel"> & {
14
12
  collectionId?: string | "new";
13
+
14
+ /**
15
+ * Called after a successful save.
16
+ * Receives the saved collection (or undefined if dismissed without saving).
17
+ * When not provided, save completes silently.
18
+ */
19
+ onSave?: (collection?: EntityCollection) => void;
20
+
21
+ /**
22
+ * Called when the user cancels editing.
23
+ * When not provided, cancel is a no-op.
24
+ */
25
+ onCancel?: () => void;
15
26
  };
16
27
 
17
- export function CollectionStudioView({ collectionId, ...props }: CollectionStudioViewProps) {
18
- const snackbarController = useSnackbarController();
19
- const navigate = useNavigate();
20
- const urlController = useUrlController();
28
+ export function CollectionStudioView({ collectionId, onSave, onCancel, ...props }: CollectionStudioViewProps) {
21
29
 
22
30
  // Form state from the editor
23
31
  const [formDirty, setFormDirty] = useState<boolean>(false);
@@ -28,12 +36,11 @@ export function CollectionStudioView({ collectionId, ...props }: CollectionStudi
28
36
  () => setFormDirty(false)
29
37
  );
30
38
 
31
- // Map collectionId from URL params if missing? We can pass it directly.
32
39
  const activeCollectionId = collectionId;
33
40
 
34
41
  const handleCancelClick = () => {
35
42
  if (!formDirty) {
36
- navigate(urlController.buildAppUrlPath("/"));
43
+ onCancel?.();
37
44
  } else {
38
45
  setCancelRequested(true);
39
46
  triggerDialog();
@@ -41,7 +48,7 @@ export function CollectionStudioView({ collectionId, ...props }: CollectionStudi
41
48
  };
42
49
 
43
50
  return (
44
- <div className="flex-grow flex flex-col h-full w-full bg-white dark:bg-surface-950">
51
+ <div className="flex-grow flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800">
45
52
  <AIModifiedPathsProvider>
46
53
  {activeCollectionId ? (
47
54
  <CollectionEditor
@@ -55,13 +62,9 @@ export function CollectionStudioView({ collectionId, ...props }: CollectionStudi
55
62
  handleClose={(savedCollection) => {
56
63
  setFormDirty(false);
57
64
  if (savedCollection) {
58
- snackbarController.open({
59
- type: "success",
60
- message: `Collection ${savedCollection.name || savedCollection.slug} saved`
61
- });
62
- if (activeCollectionId === "new") {
63
- navigate(urlController.buildAppUrlPath(`s/schema/${savedCollection.slug}`));
64
- }
65
+ setTimeout(() => {
66
+ onSave?.(savedCollection);
67
+ }, 0);
65
68
  }
66
69
  }}
67
70
  setFormDirty={setFormDirty}
@@ -73,7 +76,7 @@ export function CollectionStudioView({ collectionId, ...props }: CollectionStudi
73
76
  </Typography>
74
77
  <Button
75
78
  disabled={props.configController?.readOnly}
76
- onClick={() => navigate(urlController.buildAppUrlPath("schema/new"))}
79
+ onClick={() => onSave?.()}
77
80
  >
78
81
  <PlusIcon/>
79
82
  Add new collection
@@ -86,7 +89,7 @@ export function CollectionStudioView({ collectionId, ...props }: CollectionStudi
86
89
  handleOk={() => {
87
90
  dialogProps.handleOk();
88
91
  if (cancelRequested) {
89
- navigate(urlController.buildAppUrlPath("/"));
92
+ onCancel?.();
90
93
  setCancelRequested(false);
91
94
  }
92
95
  }}
@@ -1,9 +1,6 @@
1
1
 
2
2
  import { IconForView } from "@rebasepro/core";
3
- ;
4
- import { useUrlController } from "../../_cms_internals";
5
3
  import React, { useState, useEffect } from "react";
6
- import { useLocation, useNavigate } from "react-router-dom";
7
4
  import {
8
5
  Button,
9
6
  cls,
@@ -15,26 +12,70 @@ import {
15
12
  Tooltip,
16
13
  Typography
17
14
  } from "@rebasepro/ui";
18
- ;
15
+ import type { EntityCollection } from "@rebasepro/types";
19
16
  import { CollectionsConfigController } from "../../types/config_controller";
20
17
  import { CollectionStudioView } from "./CollectionStudioView";
18
+ import type { CollectionEditorExtensionProps } from "../../extensibility_types";
21
19
 
22
- export interface CollectionsStudioViewProps {
20
+ export interface CollectionsStudioViewProps extends CollectionEditorExtensionProps {
23
21
  configController: CollectionsConfigController;
22
+
23
+ /**
24
+ * Collections to show in the sidebar.
25
+ * When provided, overrides the collections from `configController`.
26
+ * Use this to control exactly which collections the editor displays.
27
+ */
28
+ collections?: EntityCollection[];
29
+
30
+ /**
31
+ * Controlled active collection ID.
32
+ * When provided together with `onActiveCollectionChange`,
33
+ * navigation is fully external — no internal state is used.
34
+ */
35
+ activeCollectionId?: string;
36
+
37
+ /**
38
+ * Called when the user clicks a collection, "new", or navigates.
39
+ * When provided, the component is fully controlled (no internal state).
40
+ * When not provided, the component manages selection via internal state.
41
+ *
42
+ * Pass `"new"` to create a new collection. Pass `undefined` to deselect.
43
+ */
44
+ onActiveCollectionChange?: (collectionId: string | undefined) => void;
24
45
  }
25
46
 
26
- export function CollectionsStudioView({ configController }: CollectionsStudioViewProps) {
27
- const navigate = useNavigate();
28
- const urlController = useUrlController();
29
- const location = useLocation();
47
+ export function CollectionsStudioView({
48
+ configController,
49
+ collections: collectionsProp,
50
+ activeCollectionId: controlledActiveId,
51
+ onActiveCollectionChange,
52
+ propertyTypePresets,
53
+ hiddenPropertyTypes,
54
+ renderExtraPropertyFields,
55
+ renderExtraCollectionFields,
56
+ visibleTabs,
57
+ standalone,
58
+ }: CollectionsStudioViewProps) {
30
59
 
31
- // Determine the active collection from the URL segment after "schema/"
32
- const basePath = urlController.buildAppUrlPath("schema");
33
- const relativePath = location.pathname.replace(basePath, "").replace(/^\//, "");
60
+ // ── Navigation state ────────────────────────────────────────────────
61
+ // If onActiveCollectionChange is provided, the component is controlled.
62
+ // Otherwise, use internal state.
63
+ const [internalActiveId, setInternalActiveId] = useState<string | undefined>(undefined);
64
+ const isControlled = onActiveCollectionChange !== undefined;
65
+ const activeCollectionId = isControlled ? controlledActiveId : internalActiveId;
34
66
 
35
- // The collectionId could be "new", empty (no selection), or a collection slug
36
- const activeCollectionId = relativePath.split("/")[0] || undefined;
67
+ const setActiveCollectionId = (id: string | undefined) => {
68
+ if (isControlled) {
69
+ onActiveCollectionChange?.(id);
70
+ } else {
71
+ setInternalActiveId(id);
72
+ }
73
+ };
74
+
75
+ // ── Collections list ────────────────────────────────────────────────
76
+ const collections = collectionsProp ?? configController.collections ?? [];
37
77
 
78
+ // ── Sidebar sizing ──────────────────────────────────────────────────
38
79
  const [sidebarSize, setSidebarSize] = useState(() => {
39
80
  try {
40
81
  const saved = localStorage.getItem("rebase_collections_editor_sidebar_size");
@@ -52,19 +93,17 @@ export function CollectionsStudioView({ configController }: CollectionsStudioVie
52
93
  }
53
94
  }, [sidebarSize]);
54
95
 
55
- const collections = configController.collections || [];
56
-
57
96
  return (
58
- <div className="flex h-full w-full bg-white dark:bg-surface-950 overflow-hidden text-text-primary dark:text-text-primary-dark">
97
+ <div className="flex h-full w-full bg-surface-50 dark:bg-surface-800 overflow-hidden text-text-primary dark:text-text-primary-dark">
59
98
  <ResizablePanels
60
99
  orientation="horizontal"
61
100
  panelSizePercent={sidebarSize}
62
101
  onPanelSizeChange={setSidebarSize}
63
102
  minPanelSizePx={220}
64
103
  firstPanel={
65
- <div className={cls("flex flex-col h-full w-full bg-white dark:bg-surface-950 border-r", defaultBorderMixin)}>
104
+ <div className={cls("flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800 border-r", defaultBorderMixin)}>
66
105
  <div className={cls("flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]", defaultBorderMixin)}>
67
- <Typography variant="caption" className="font-bold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark">
106
+ <Typography variant="caption" className="font-semibold text-[11px] uppercase tracking-wider text-surface-400 dark:text-surface-400">
68
107
  Collections
69
108
  </Typography>
70
109
  <Tooltip title={configController.readOnly ? configController.readOnlyReason || "Read only" : "Add collection"}>
@@ -72,7 +111,7 @@ export function CollectionsStudioView({ configController }: CollectionsStudioVie
72
111
  <IconButton
73
112
  size="small"
74
113
  disabled={configController.readOnly}
75
- onClick={() => navigate(urlController.buildAppUrlPath("schema/new"))}
114
+ onClick={() => setActiveCollectionId("new")}
76
115
  className={activeCollectionId === "new" ? "text-primary dark:text-primary-dark" : "text-text-secondary dark:text-text-secondary-dark"}
77
116
  >
78
117
  <PlusIcon size={iconSize.smallest}/>
@@ -95,18 +134,18 @@ export function CollectionsStudioView({ configController }: CollectionsStudioVie
95
134
  return (
96
135
  <div
97
136
  key={collectionKey}
98
- onClick={() => navigate(urlController.buildAppUrlPath(`schema/${collectionKey}`))}
137
+ onClick={() => setActiveCollectionId(collectionKey)}
99
138
  className={cls(
100
- "flex items-center gap-3 px-3 py-2 cursor-pointer rounded-md text-sm transition-colors",
139
+ "flex items-center gap-2.5 px-3 h-[30px] cursor-pointer rounded-lg text-[13px] font-medium transition-colors",
101
140
  isSelected
102
- ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light"
103
- : "hover:bg-surface-100 dark:hover:bg-surface-900 text-text-secondary dark:text-text-secondary-dark"
141
+ ? "bg-primary/8 text-primary dark:bg-primary/10 dark:text-primary-light font-semibold"
142
+ : "hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white"
104
143
  )}
105
144
  >
106
- <IconForView collectionOrView={collection} size={"small"} className={cls(
145
+ <IconForView collectionOrView={collection} size={"smallest"} className={cls(
107
146
  isSelected
108
147
  ? "text-primary dark:text-primary-light"
109
- : "text-text-secondary dark:text-text-secondary-dark"
148
+ : "text-surface-500 dark:text-text-secondary-dark"
110
149
  )}/>
111
150
  <span className="truncate flex-1">
112
151
  {collection.name || collection.slug}
@@ -125,6 +164,19 @@ export function CollectionsStudioView({ configController }: CollectionsStudioVie
125
164
  key={activeCollectionId}
126
165
  configController={configController}
127
166
  collectionId={activeCollectionId}
167
+ onSave={(savedCollection) => {
168
+ // After creating a new collection, switch to it
169
+ if (activeCollectionId === "new" && savedCollection?.slug) {
170
+ setActiveCollectionId(savedCollection.slug);
171
+ }
172
+ }}
173
+ onCancel={() => setActiveCollectionId(undefined)}
174
+ propertyTypePresets={propertyTypePresets}
175
+ hiddenPropertyTypes={hiddenPropertyTypes}
176
+ renderExtraPropertyFields={renderExtraPropertyFields}
177
+ renderExtraCollectionFields={renderExtraCollectionFields}
178
+ visibleTabs={visibleTabs}
179
+ standalone={standalone}
128
180
  />
129
181
  ) : (
130
182
  <div className="flex flex-col items-center justify-center h-full gap-4">
@@ -133,7 +185,7 @@ export function CollectionsStudioView({ configController }: CollectionsStudioVie
133
185
  </Typography>
134
186
  <Button
135
187
  disabled={configController.readOnly}
136
- onClick={() => navigate(urlController.buildAppUrlPath("schema/new"))}
188
+ onClick={() => setActiveCollectionId("new")}
137
189
  >
138
190
  <PlusIcon/>
139
191
  Add new collection
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { FieldCaption } from "../../_cms_internals";
3
3
  import React, { useMemo, useState } from "react";
4
- import { useAuthController, useCustomizationController } from "@rebasepro/core";
4
+ import { useCustomizationController } from "@rebasepro/core";
5
5
  import { getFieldConfig } from "../../../components/field_configs";
6
6
  import { PropertyConfigBadge } from "../../../components/PropertyConfigBadge";
7
7
  import { EntityCollection, Property } from "@rebasepro/types";
@@ -27,9 +27,11 @@ import { useCollectionsConfigController } from "../../useCollectionsConfigContro
27
27
  import { unslugify } from "@rebasepro/utils";
28
28
 
29
29
  export function DisplaySettingsForm({
30
- expandKanban
30
+ expandKanban,
31
+ standalone,
31
32
  }: {
32
33
  expandKanban?: boolean;
34
+ standalone?: boolean;
33
35
  }) {
34
36
 
35
37
  const {
@@ -41,9 +43,9 @@ export function DisplaySettingsForm({
41
43
 
42
44
  const [orderPropertyDialogOpen, setOrderPropertyDialogOpen] = useState(false);
43
45
 
44
- const authController = useAuthController();
45
46
  const customizationController = useCustomizationController();
46
- const configController = useCollectionsConfigController();
47
+ const configControllerFromContext = useCollectionsConfigController();
48
+ const configController = standalone ? { readOnly: false } : configControllerFromContext;
47
49
 
48
50
  // Get text properties (for orderProperty - uses string fractional indexing keys)
49
51
  const textProperties = useMemo(() => {
@@ -118,7 +120,7 @@ export function DisplaySettingsForm({
118
120
  }
119
121
  const prop = textProperties.find(p => p.key === value);
120
122
  if (!prop) return "Select a property";
121
- const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
123
+ const fieldConfig = getFieldConfig(prop.property, customizationController?.propertyConfigs ?? {});
122
124
  return (
123
125
  <div className="flex items-center gap-2">
124
126
  <PropertyConfigBadge propertyConfig={fieldConfig}/>
@@ -139,7 +141,7 @@ export function DisplaySettingsForm({
139
141
  ) : undefined}
140
142
  >
141
143
  {textProperties.map((prop) => {
142
- const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
144
+ const fieldConfig = getFieldConfig(prop.property, customizationController?.propertyConfigs ?? {});
143
145
  return (
144
146
  <SelectItem key={prop.key} value={prop.key}>
145
147
  <div className="flex items-center gap-3">
@@ -205,7 +207,7 @@ hideFromCollection: true }
205
207
  autoUpdateId={false}
206
208
  inArray={false}
207
209
  allowDataInference={false}
208
- propertyConfigs={customizationController.propertyConfigs}
210
+ propertyConfigs={customizationController?.propertyConfigs ?? {}}
209
211
 
210
212
  existingPropertyKeys={Object.keys(values.properties ?? {})}
211
213
  onPropertyChanged={({ id, property }) => {