@rebasepro/admin 0.6.1 → 0.8.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 (187) hide show
  1. package/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-BO_aQ-Z4.js} +236 -155
  2. package/dist/CollectionEditorDialog-BO_aQ-Z4.js.map +1 -0
  3. package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-DKzOpMjR.js} +190 -50
  4. package/dist/PropertyEditView-DKzOpMjR.js.map +1 -0
  5. package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-BQTIExjA.js} +61 -39
  6. package/dist/RouterCollectionsStudioView-BQTIExjA.js.map +1 -0
  7. package/dist/collection_editor/_cms_internals.d.ts +1 -1
  8. package/dist/collection_editor/api/generateCollectionApi.d.ts +3 -2
  9. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  10. package/dist/collection_editor/index.d.ts +6 -0
  11. package/dist/collection_editor/serializable_types.d.ts +370 -0
  12. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  13. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +3 -3
  14. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  15. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  16. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  17. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  18. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  19. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  20. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  21. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  22. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  23. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  24. package/dist/collection_editor_ui.d.ts +4 -0
  25. package/dist/collection_editor_ui.js +33 -4
  26. package/dist/collection_editor_ui.js.map +1 -0
  27. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  28. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  29. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  30. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  31. package/dist/components/EntityEditView.d.ts +3 -2
  32. package/dist/components/RebaseCMS.d.ts +1 -1
  33. package/dist/components/SelectableTable/SelectionStore.d.ts +12 -21
  34. package/dist/components/field_configs.d.ts +1 -1
  35. package/dist/editor/editor.d.ts +0 -6
  36. package/dist/editor.js +1 -3
  37. package/dist/editor.js.map +1 -1
  38. package/dist/form/EntityForm.d.ts +10 -9
  39. package/dist/form/EntityFormBinding.d.ts +27 -0
  40. package/dist/form/components/StorageUploadProgress.d.ts +6 -1
  41. package/dist/form/form_utils.d.ts +8 -0
  42. package/dist/form/index.d.ts +5 -2
  43. package/dist/{history-BO6VT85a.js → history-B3cqBzEV.js} +2 -2
  44. package/dist/{history-BO6VT85a.js.map → history-B3cqBzEV.js.map} +1 -1
  45. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  46. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  47. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  48. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  49. package/dist/hooks/navigation/utils.d.ts +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +2 -2
  52. package/dist/preview/components/StorageThumbnail.d.ts +3 -1
  53. package/dist/routes/CustomViewRoute.d.ts +2 -1
  54. package/dist/{src-BYniefVu.js → src-cxV1ODRG.js} +1731 -1710
  55. package/dist/src-cxV1ODRG.js.map +1 -0
  56. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  57. package/dist/types/components/EntityFormProps.d.ts +59 -0
  58. package/dist/types/components/PropertyPreviewProps.d.ts +2 -1
  59. package/dist/types/fields.d.ts +10 -7
  60. package/dist/util/dataControllerAdapter.d.ts +30 -0
  61. package/dist/util/navigation_utils.d.ts +1 -1
  62. package/dist/util/propertyConfigMapper.d.ts +17 -0
  63. package/package.json +8 -8
  64. package/src/collection_editor/_cms_internals.ts +1 -1
  65. package/src/collection_editor/api/generateCollectionApi.ts +3 -2
  66. package/src/collection_editor/extensibility_types.ts +168 -0
  67. package/src/collection_editor/index.ts +46 -0
  68. package/src/collection_editor/pgColumnToProperty.ts +2 -2
  69. package/src/collection_editor/serializable_types.ts +474 -0
  70. package/src/collection_editor/serializable_utils.ts +576 -0
  71. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +6 -6
  72. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +170 -89
  73. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  74. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  75. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  76. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  77. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  78. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  79. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +154 -18
  80. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  81. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  82. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  83. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  84. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  85. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +35 -1
  86. package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +1 -1
  87. package/src/collection_editor/ui/collection_editor/utils/supported_fields.tsx +0 -1
  88. package/src/collection_editor/ui/collection_editor/utils/update_property_for_widget.ts +7 -29
  89. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  90. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  91. package/src/collection_editor_ui.ts +6 -0
  92. package/src/components/DefaultDrawer.tsx +14 -4
  93. package/src/components/DrawerNavigationGroup.tsx +2 -2
  94. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +11 -9
  95. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +2 -1
  96. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +2 -1
  97. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +23 -20
  98. package/src/components/EntityCollectionTable/index.tsx +1 -1
  99. package/src/components/EntityCollectionTable/table_bindings.tsx +24 -44
  100. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  101. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +13 -12
  102. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  103. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +66 -184
  104. package/src/components/EntityCollectionView/EntityCollectionView.tsx +8 -25
  105. package/src/components/EntityCollectionView/hooks/useCollectionInlineEditor.ts +1 -1
  106. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  107. package/src/components/EntityCollectionView/useBoardDataController.tsx +12 -40
  108. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +3 -3
  109. package/src/components/EntityDetailView.tsx +2 -2
  110. package/src/components/EntityEditView.tsx +7 -6
  111. package/src/components/EntityEditViewFormActions.tsx +35 -29
  112. package/src/components/HomePage/ContentHomePage.tsx +14 -5
  113. package/src/components/RebaseCMS.tsx +3 -3
  114. package/src/components/RebaseNavigation.tsx +74 -9
  115. package/src/components/RelationSelector.tsx +2 -2
  116. package/src/components/SelectableTable/SelectableTable.tsx +13 -5
  117. package/src/components/SelectableTable/SelectionStore.ts +18 -48
  118. package/src/components/SideDialogs.tsx +0 -7
  119. package/src/components/common/default_entity_actions.tsx +22 -11
  120. package/src/components/field_configs.tsx +0 -14
  121. package/src/contexts/BreacrumbsContext.tsx +15 -6
  122. package/src/editor/editor.tsx +4 -7
  123. package/src/editor/selectors/ai-selector.tsx +0 -1
  124. package/src/form/EntityForm.tsx +191 -428
  125. package/src/form/EntityFormActions.tsx +35 -49
  126. package/src/form/EntityFormBinding.tsx +323 -0
  127. package/src/form/components/FormLayout.tsx +1 -1
  128. package/src/form/components/StorageUploadProgress.tsx +9 -2
  129. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +12 -1
  130. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +4 -4
  131. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  132. package/src/form/field_bindings/RelationFieldBinding.tsx +5 -7
  133. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +19 -15
  134. package/src/form/form_utils.ts +163 -0
  135. package/src/form/index.tsx +14 -6
  136. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  137. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  138. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  139. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  140. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  141. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  142. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  143. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  144. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  145. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  146. package/src/hooks/navigation/utils.ts +7 -4
  147. package/src/index.ts +6 -1
  148. package/src/preview/PropertyPreview.tsx +1 -16
  149. package/src/preview/components/StorageThumbnail.tsx +24 -7
  150. package/src/routes/CustomViewRoute.tsx +14 -6
  151. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  152. package/src/types/components/EntityFormProps.tsx +68 -0
  153. package/src/types/components/PropertyPreviewProps.tsx +1 -1
  154. package/src/types/fields.tsx +9 -8
  155. package/src/util/dataControllerAdapter.ts +93 -0
  156. package/src/util/navigation_utils.ts +1 -1
  157. package/src/util/previews.ts +20 -0
  158. package/src/util/propertyConfigMapper.ts +134 -0
  159. package/src/util/property_utils.tsx +1 -2
  160. package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
  161. package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
  162. package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
  163. package/dist/components/EntityCollectionTable/fields/VirtualTableDateField.d.ts +0 -12
  164. package/dist/components/EntityCollectionTable/fields/VirtualTableInput.d.ts +0 -10
  165. package/dist/components/EntityCollectionTable/fields/VirtualTableNumberInput.d.ts +0 -9
  166. package/dist/components/EntityCollectionTable/fields/VirtualTableSwitch.d.ts +0 -8
  167. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  168. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  169. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  170. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  171. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  172. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +0 -11
  173. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  174. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  175. package/dist/src-BYniefVu.js.map +0 -1
  176. package/src/components/EntityCollectionTable/fields/VirtualTableDateField.tsx +0 -39
  177. package/src/components/EntityCollectionTable/fields/VirtualTableInput.tsx +0 -90
  178. package/src/components/EntityCollectionTable/fields/VirtualTableNumberInput.tsx +0 -83
  179. package/src/components/EntityCollectionTable/fields/VirtualTableSwitch.tsx +0 -32
  180. package/src/components/EntityCollectionView/Board.tsx +0 -475
  181. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  182. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  183. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  184. package/src/components/EntityCollectionView/board_types.ts +0 -113
  185. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +0 -141
  186. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  187. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -2,7 +2,7 @@
2
2
  import { IconForView } from "@rebasepro/core";
3
3
  import { FieldCaption } from "../../_cms_internals";
4
4
  import React, { useState } from "react";
5
- import { useAuthController, useCustomizationController } from "@rebasepro/core";
5
+
6
6
  import { SearchIconsView } from "../../_cms_internals";
7
7
  import type { EntityCollection, PostgresCollection } from "@rebasepro/types";
8
8
  import {
@@ -26,17 +26,23 @@ import {
26
26
  import { Field, getIn, useFormex } from "@rebasepro/formex";
27
27
  import { useCollectionsConfigController } from "../../useCollectionsConfigController";
28
28
  import { singular, toSnakeCase } from "@rebasepro/utils";
29
+ import type { ExtraCollectionFieldsParams } from "../../extensibility_types";
30
+ import { toSerializableCollection } from "../../serializable_utils";
29
31
 
30
32
  export function GeneralSettingsForm({
31
33
  isNewCollection,
32
34
  existingPaths,
33
35
  existingIds,
34
- parentCollection
36
+ parentCollection,
37
+ renderExtraCollectionFields,
38
+ standalone,
35
39
  }: {
36
40
  isNewCollection: boolean;
37
41
  existingPaths?: string[];
38
42
  existingIds?: string[];
39
43
  parentCollection?: EntityCollection;
44
+ renderExtraCollectionFields?: (params: ExtraCollectionFieldsParams) => React.ReactNode;
45
+ standalone?: boolean;
40
46
  }) {
41
47
 
42
48
  const {
@@ -51,9 +57,8 @@ export function GeneralSettingsForm({
51
57
 
52
58
  const [iconDialogOpen, setIconDialogOpen] = useState(false);
53
59
 
54
- const authController = useAuthController();
55
- const customizationController = useCustomizationController();
56
- const configController = useCollectionsConfigController();
60
+ const configControllerFromContext = useCollectionsConfigController();
61
+ const configController = standalone ? { readOnly: false } : configControllerFromContext;
57
62
 
58
63
  const updateDatabaseId = (databaseId: string) => {
59
64
  setFieldValue("databaseId", databaseId ?? undefined);
@@ -243,6 +248,18 @@ export function GeneralSettingsForm({
243
248
  </div>
244
249
  </Dialog>
245
250
 
251
+ {renderExtraCollectionFields && (
252
+ <div className="mt-4">
253
+ {renderExtraCollectionFields({
254
+ metadata: (values.metadata ?? {}) as Record<string, unknown>,
255
+ onMetadataChange: (key: string, value: unknown) => {
256
+ setFieldValue(`metadata.${key}`, value);
257
+ },
258
+ collection: toSerializableCollection(values),
259
+ })}
260
+ </div>
261
+ )}
262
+
246
263
  </Container>
247
264
  </div>
248
265
  );
@@ -1,4 +1,4 @@
1
- import { useSnackbarController } from "@rebasepro/core";
1
+ import { useSafeSnackbarController } from "../../useSafeSnackbarController";
2
2
  import { EntityCollection } from "@rebasepro/types";
3
3
  import { Button, CopyIcon, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@rebasepro/ui";
4
4
  import React from "react";
@@ -14,7 +14,7 @@ export function GetCodeDialog({
14
14
  open
15
15
  }: { onOpenChange: (open: boolean) => void, collection: EntityCollection, open: boolean }) {
16
16
 
17
- const snackbarController = useSnackbarController();
17
+ const snackbarController = useSafeSnackbarController();
18
18
 
19
19
  const code = collection
20
20
  ? "import { EntityCollection } from \"@rebasepro/core\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode({ ...collection }), null, "\t")
@@ -64,7 +64,7 @@ export function GetCodeDialog({
64
64
  onClick={(e) => {
65
65
  e.stopPropagation();
66
66
  e.preventDefault();
67
- snackbarController.open({
67
+ snackbarController?.open({
68
68
  type: "success",
69
69
  message: "Copied"
70
70
  })
@@ -1,12 +1,15 @@
1
1
 
2
- import React, { useDeferredValue, useEffect, useRef, useState } from "react";
2
+ import React, { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { deepEqual as equal } from "fast-equals"
4
4
 
5
5
  import { Formex, FormexController, getIn, useCreateFormex } from "@rebasepro/formex";
6
6
  import { ConfirmationDialog } from "@rebasepro/core";
7
7
  import { DEFAULT_FIELD_CONFIGS, getFieldConfig, getFieldId } from "../../../components/field_configs";
8
8
  import { PropertyConfigBadge } from "../../../components/PropertyConfigBadge";
9
- import { Property, PropertyConfig, PropertyConfigId } from "@rebasepro/types";
9
+ import { EntityCollection, Property, PropertyConfig, PropertyConfigId } from "@rebasepro/types";
10
+ import type { PropertyTypePreset, PropertyType } from "../../extensibility_types";
11
+ import { toSerializableProperty, toSerializableCollection } from "../../serializable_utils";
12
+ import type { SerializableProperty, SerializableCollection } from "../../serializable_types";
10
13
  import { isPropertyBuilder } from "@rebasepro/common";
11
14
  import {
12
15
  AlertTriangleIcon,
@@ -82,6 +85,15 @@ export type PropertyFormProps = {
82
85
  getData?: () => Promise<object[]>;
83
86
  getController?: (formex: FormexController<PropertyWithId>) => void;
84
87
  propertyConfigs: Record<string, PropertyConfig>;
88
+ propertyTypePresets?: PropertyTypePreset[];
89
+ hiddenPropertyTypes?: PropertyType[];
90
+ renderExtraPropertyFields?: (params: {
91
+ metadata: Record<string, unknown>;
92
+ onMetadataChange: (key: string, value: unknown) => void;
93
+ property: SerializableProperty;
94
+ collection: SerializableCollection;
95
+ }) => React.ReactNode;
96
+ collectionValues?: EntityCollection;
85
97
 
86
98
  };
87
99
 
@@ -108,7 +120,11 @@ export const PropertyForm = React.memo(
108
120
  allowDataInference,
109
121
  getController,
110
122
  getData,
111
- propertyConfigs
123
+ propertyConfigs,
124
+ propertyTypePresets,
125
+ hiddenPropertyTypes,
126
+ renderExtraPropertyFields,
127
+ collectionValues
112
128
  } = props;
113
129
 
114
130
  const initialValue: PropertyWithId = {
@@ -228,6 +244,10 @@ export const PropertyForm = React.memo(
228
244
  getData={getData}
229
245
  allowDataInference={allowDataInference}
230
246
  propertyConfigs={propertyConfigs}
247
+ propertyTypePresets={propertyTypePresets}
248
+ hiddenPropertyTypes={hiddenPropertyTypes}
249
+ renderExtraPropertyFields={renderExtraPropertyFields}
250
+ collectionValues={collectionValues}
231
251
  {...formexController}/>
232
252
  </Formex>;
233
253
  }, (a, b) =>
@@ -324,7 +344,11 @@ function PropertyEditFormFields({
324
344
  inArray,
325
345
  getData,
326
346
  allowDataInference,
327
- propertyConfigs
347
+ propertyConfigs,
348
+ propertyTypePresets,
349
+ hiddenPropertyTypes,
350
+ renderExtraPropertyFields,
351
+ collectionValues
328
352
  }: {
329
353
  includeIdAndTitle?: boolean;
330
354
  existing: boolean;
@@ -341,6 +365,10 @@ function PropertyEditFormFields({
341
365
  getData?: () => Promise<object[]>;
342
366
  allowDataInference: boolean;
343
367
  propertyConfigs: Record<string, PropertyConfig>;
368
+ propertyTypePresets?: PropertyTypePreset[];
369
+ hiddenPropertyTypes?: PropertyType[];
370
+ renderExtraPropertyFields?: PropertyFormProps["renderExtraPropertyFields"];
371
+ collectionValues?: EntityCollection;
344
372
 
345
373
  } & FormexController<PropertyWithId>) {
346
374
 
@@ -389,6 +417,29 @@ function PropertyEditFormFields({
389
417
  }, 0);
390
418
  };
391
419
 
420
+ const onPresetSelected = useCallback((preset: PropertyTypePreset) => {
421
+ // Map baseType to the default widgetId
422
+ const BASE_TYPE_TO_WIDGET: Record<string, string> = {
423
+ "string": "text_field",
424
+ "number": "number_input",
425
+ "boolean": "switch",
426
+ "date": "date_time",
427
+ "reference": "reference",
428
+ "relation": "relation",
429
+ "map": "group",
430
+ "array": "repeat",
431
+ "vector": "vector_input",
432
+ "geopoint": "text_field",
433
+ "binary": "text_field",
434
+ };
435
+ const widgetId = BASE_TYPE_TO_WIDGET[preset.baseType] || "text_field";
436
+ setSelectedFieldConfigId(widgetId);
437
+ // Merge preset defaults into the property
438
+ const updatedValues = updatePropertyFromWidget(values, widgetId, propertyConfigs);
439
+ const mergedValues = { ...updatedValues, ...preset.defaults, propertyConfig: widgetId };
440
+ setValues(mergedValues as PropertyWithId);
441
+ }, [values, propertyConfigs, setValues]);
442
+
392
443
  let childComponent;
393
444
  if (selectedFieldConfigId === "text_field" ||
394
445
  selectedFieldConfigId === "multiline" ||
@@ -454,13 +505,6 @@ function PropertyEditFormFields({
454
505
  existing={existing}
455
506
  multiple={false}
456
507
  disabled={disabled}/>;
457
- } else if (selectedFieldConfigId === "reference_as_string") {
458
- childComponent =
459
- <ReferencePropertyField showErrors={showErrors}
460
- existing={existing}
461
- asString={true}
462
- multiple={false}
463
- disabled={disabled}/>;
464
508
  } else if (selectedFieldConfigId === "date_time") {
465
509
  childComponent = <DateTimePropertyField disabled={disabled}/>;
466
510
  } else if (selectedFieldConfigId === "multi_references") {
@@ -520,7 +564,10 @@ function PropertyEditFormFields({
520
564
  showError={Boolean(selectedWidgetError)}
521
565
  existing={existing}
522
566
  propertyConfigs={propertyConfigs}
523
- inArray={inArray}/>
567
+ inArray={inArray}
568
+ propertyTypePresets={propertyTypePresets}
569
+ hiddenPropertyTypes={hiddenPropertyTypes}
570
+ onPresetSelected={onPresetSelected}/>
524
571
 
525
572
  {!!selectedWidgetError &&
526
573
  <Typography variant="caption"
@@ -553,6 +600,23 @@ function PropertyEditFormFields({
553
600
 
554
601
  {childComponent}
555
602
 
603
+ {renderExtraPropertyFields && collectionValues && (
604
+ <div className="col-span-12 mt-4 px-2">
605
+ {renderExtraPropertyFields({
606
+ metadata: ((values as unknown as Record<string, unknown>).metadata as Record<string, unknown>) ?? {},
607
+ onMetadataChange: (key: string, value: unknown) => {
608
+ const currentMetadata = ((values as unknown as Record<string, unknown>).metadata as Record<string, unknown>) ?? {};
609
+ setValues({
610
+ ...values,
611
+ metadata: { ...currentMetadata, [key]: value },
612
+ } as PropertyWithId);
613
+ },
614
+ property: toSerializableProperty(values as Property),
615
+ collection: toSerializableCollection(collectionValues),
616
+ })}
617
+ </div>
618
+ )}
619
+
556
620
  <div className={"col-span-12"}>
557
621
  <AdvancedPropertyValidation disabled={disabled}/>
558
622
  </div>
@@ -615,7 +679,6 @@ const WIDGET_TYPE_MAP: Record<PropertyConfigId, string> = {
615
679
  multi_file_upload: "File",
616
680
  relation: "Relation",
617
681
  reference: "Reference",
618
- reference_as_string: "Text",
619
682
  multi_references: "Reference",
620
683
  date_time: "Date",
621
684
  group: "Group",
@@ -626,6 +689,33 @@ const WIDGET_TYPE_MAP: Record<PropertyConfigId, string> = {
626
689
  vector_input: "Number"
627
690
  };
628
691
 
692
+ const WIDGET_BASE_TYPE_MAP: Record<string, PropertyType> = {
693
+ text_field: "string",
694
+ multiline: "string",
695
+ markdown: "string",
696
+ url: "string",
697
+ email: "string",
698
+ select: "string",
699
+ multi_select: "string",
700
+ file_upload: "string",
701
+ multi_file_upload: "string",
702
+ user_select: "string",
703
+ number_input: "number",
704
+ number_select: "number",
705
+ multi_number_select: "number",
706
+ switch: "boolean",
707
+ date_time: "date",
708
+ reference: "reference",
709
+ multi_references: "reference",
710
+ relation: "relation",
711
+ group: "map",
712
+ key_value: "map",
713
+ block: "map",
714
+ repeat: "array",
715
+ custom_array: "array",
716
+ vector_input: "vector",
717
+ };
718
+
629
719
  function WidgetSelectView({
630
720
  initialProperty,
631
721
  value,
@@ -636,7 +726,10 @@ function WidgetSelectView({
636
726
  showError,
637
727
  existing,
638
728
  propertyConfigs,
639
- inArray
729
+ inArray,
730
+ propertyTypePresets,
731
+ hiddenPropertyTypes,
732
+ onPresetSelected
640
733
  }: {
641
734
  initialProperty?: PropertyWithId,
642
735
  value?: PropertyConfigId,
@@ -647,14 +740,24 @@ function WidgetSelectView({
647
740
  disabled: boolean,
648
741
  existing: boolean,
649
742
  propertyConfigs: Record<string, PropertyConfig>,
650
- inArray?: boolean
743
+ inArray?: boolean,
744
+ propertyTypePresets?: PropertyTypePreset[],
745
+ hiddenPropertyTypes?: PropertyType[],
746
+ onPresetSelected?: (preset: PropertyTypePreset) => void,
651
747
  }) {
652
748
 
653
749
  const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
654
750
 
751
+ const filteredFields = hiddenPropertyTypes && !propertyTypePresets
752
+ ? allSupportedFields.filter(([key]) => {
753
+ const baseType = WIDGET_BASE_TYPE_MAP[key];
754
+ return !baseType || !hiddenPropertyTypes.includes(baseType);
755
+ })
756
+ : allSupportedFields;
757
+
655
758
  const displayedWidgets = (inArray
656
- ? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property as Property) && (propertyConfig.property as Property)?.type !== "array")
657
- : allSupportedFields)
759
+ ? filteredFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property as Property) && (propertyConfig.property as Property)?.type !== "array")
760
+ : filteredFields)
658
761
  .map(([key, propertyConfig]) => ({
659
762
  [key]: propertyConfig
660
763
  }))
@@ -665,6 +768,11 @@ function WidgetSelectView({
665
768
  }
666
769
  }, {});
667
770
 
771
+ const activePreset = useMemo(() => {
772
+ if (!propertyTypePresets || !initialProperty) return undefined;
773
+ return propertyTypePresets.find(p => p.detect?.(toSerializableProperty(initialProperty as Property)));
774
+ }, [propertyTypePresets, initialProperty]);
775
+
668
776
  const key = value;
669
777
  const propertyConfig = key ? (DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key]) : undefined;
670
778
  const baseProperty = propertyConfig?.property;
@@ -711,9 +819,36 @@ function WidgetSelectView({
711
819
  onOpenChange={(open: boolean) => onOpenChange(open, Boolean(value))}
712
820
  maxWidth={"4xl"}>
713
821
  <DialogTitle>
714
- Select a property widget
822
+ {propertyTypePresets ? "Select a property type" : "Select a property widget"}
715
823
  </DialogTitle>
716
824
  <DialogContent>
825
+ {propertyTypePresets ? (
826
+ <div className={"grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 mt-4"}>
827
+ {propertyTypePresets.map((preset) => (
828
+ <Card
829
+ key={preset.id}
830
+ onClick={() => {
831
+ onPresetSelected?.(preset);
832
+ onOpenChange(false, true);
833
+ }}
834
+ className={cls(
835
+ "flex flex-row items-center px-4 py-2 m-1",
836
+ activePreset?.id === preset.id && "ring-2 ring-primary"
837
+ )}>
838
+ <div className={cls("flex flex-row items-center text-base min-h-[48px]")}>
839
+ {preset.icon && (
840
+ <div className={"mr-4 text-2xl"}>
841
+ {typeof preset.icon === "string" ? preset.icon : preset.icon}
842
+ </div>
843
+ )}
844
+ <div>
845
+ <Typography variant={"label"}>{preset.label}</Typography>
846
+ </div>
847
+ </div>
848
+ </Card>
849
+ ))}
850
+ </div>
851
+ ) : (
717
852
  <div>
718
853
  {groups.map(group => {
719
854
  return <div key={group} className={"mt-4"}>
@@ -749,6 +884,7 @@ function WidgetSelectView({
749
884
  {/* existing={existing}/>;*/}
750
885
  {/*})}*/}
751
886
  </div>
887
+ )}
752
888
  </DialogContent>
753
889
  </Dialog>
754
890
  </>;
@@ -36,9 +36,9 @@ export function PropertyFieldPreview({
36
36
 
37
37
  return <ErrorBoundary>
38
38
  <div onClick={onClick} className={onClick ? "cursor-pointer" : ""}>
39
- <Paper
39
+ <div
40
40
  className={cls(
41
- "w-full flex flex-row gap-3 items-center px-3 py-2 rounded-lg transition-all duration-200 border bg-white dark:bg-surface-800 shadow-xs",
41
+ "w-full flex flex-row gap-3 items-center px-3 py-2 rounded-lg transition-all duration-200 border bg-white dark:bg-surface-900 shadow-xs",
42
42
  borderColorClass || "border-surface-200 dark:border-surface-700",
43
43
  selected
44
44
  ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light ring-1 ring-inset ring-primary border-primary/30"
@@ -81,7 +81,7 @@ export function PropertyFieldPreview({
81
81
  </ErrorBoundary>
82
82
 
83
83
  {includeEditButton && <Typography variant={"button"}>EDIT</Typography>}
84
- </Paper>
84
+ </div>
85
85
  </div>
86
86
  </ErrorBoundary>
87
87
  }
@@ -103,9 +103,9 @@ export function NonEditablePropertyPreview({
103
103
 
104
104
  return (
105
105
  <div onClick={onClick} className={onClick ? "cursor-pointer" : ""}>
106
- <Paper
106
+ <div
107
107
  className={cls(
108
- "w-full flex flex-row gap-3 items-center px-3 py-2 rounded-lg transition-all duration-200 border bg-white dark:bg-surface-800 border-surface-200 dark:border-surface-700 shadow-xs",
108
+ "w-full flex flex-row gap-3 items-center px-3 py-2 rounded-lg transition-all duration-200 border bg-white dark:bg-surface-900 border-surface-200 dark:border-surface-700 shadow-xs",
109
109
  selected
110
110
  ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light ring-1 ring-inset ring-primary border-primary/30"
111
111
  : "hover:bg-surface-50 dark:hover:bg-surface-700"
@@ -149,7 +149,7 @@ export function NonEditablePropertyPreview({
149
149
  {"columnType" in property ? (property as { columnType?: string }).columnType ?? property.type : property.type}
150
150
  </Typography>
151
151
  </ErrorBoundary>}
152
- </Paper>
152
+ </div>
153
153
  </div>
154
154
  )
155
155
  }
@@ -276,6 +276,7 @@ export function PropertyTreeEntry({
276
276
  {!isPropertyBuilder(property) && !additionalField && editable
277
277
  ? <PropertyFieldPreview
278
278
  property={property}
279
+ propertyKey={propertyKey}
279
280
  onClick={onPropertyClick ? () => onPropertyClick(propertyKey, namespace) : undefined}
280
281
  includeName={true}
281
282
  selected={selected}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Router-aware wrapper around CollectionStudioView.
3
+ *
4
+ * This component provides the react-router-based navigation behavior
5
+ * that Rebase Studio expects: cancel navigates to the root path,
6
+ * save navigates to the saved collection's slug, and a snackbar
7
+ * toast is shown on success.
8
+ *
9
+ * External consumers should use `CollectionStudioView` directly
10
+ * (which has no react-router dependency).
11
+ */
12
+ import React from "react";
13
+ import { useNavigate } from "react-router-dom";
14
+ import { useUrlController } from "../../_cms_internals";
15
+ import { useSnackbarController } from "@rebasepro/core";
16
+ import { CollectionStudioView, CollectionStudioViewProps } from "./CollectionStudioView";
17
+
18
+ export type RouterCollectionStudioViewProps = Omit<CollectionStudioViewProps, "onSave" | "onCancel">;
19
+
20
+ export function RouterCollectionStudioView(props: RouterCollectionStudioViewProps) {
21
+ const navigate = useNavigate();
22
+ const urlController = useUrlController();
23
+ const snackbarController = useSnackbarController();
24
+
25
+ return (
26
+ <CollectionStudioView
27
+ {...props}
28
+ onCancel={() => {
29
+ navigate(urlController.buildAppUrlPath("/"));
30
+ }}
31
+ onSave={(savedCollection) => {
32
+ if (savedCollection) {
33
+ snackbarController.open({
34
+ type: "success",
35
+ message: `Collection ${savedCollection.name || savedCollection.slug} saved`
36
+ });
37
+ if (props.collectionId === "new") {
38
+ navigate(urlController.buildAppUrlPath(`s/schema/${savedCollection.slug}`));
39
+ }
40
+ }
41
+ }}
42
+ />
43
+ );
44
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Router-aware wrapper around CollectionsStudioView.
3
+ *
4
+ * This component provides the react-router-based navigation behavior
5
+ * that Rebase Studio expects: the active collection is derived from the
6
+ * URL, and clicking a collection navigates via react-router.
7
+ *
8
+ * External consumers should use `CollectionsStudioView` directly
9
+ * (which has no react-router dependency).
10
+ */
11
+ import React from "react";
12
+ import { useLocation, useNavigate } from "react-router-dom";
13
+ import { useUrlController } from "../../_cms_internals";
14
+ import { CollectionsStudioView, CollectionsStudioViewProps } from "./CollectionsStudioView";
15
+
16
+ export type RouterCollectionsStudioViewProps = Omit<CollectionsStudioViewProps, "activeCollectionId" | "onActiveCollectionChange">;
17
+
18
+ export function RouterCollectionsStudioView(props: RouterCollectionsStudioViewProps) {
19
+ const navigate = useNavigate();
20
+ const urlController = useUrlController();
21
+ const location = useLocation();
22
+
23
+ // Determine the active collection from the URL segment after "schema/"
24
+ const basePath = urlController.buildAppUrlPath("schema");
25
+ const relativePath = location.pathname.replace(basePath, "").replace(/^\//, "");
26
+ const activeCollectionId = relativePath.split("/")[0] || undefined;
27
+
28
+ return (
29
+ <CollectionsStudioView
30
+ {...props}
31
+ activeCollectionId={activeCollectionId}
32
+ onActiveCollectionChange={(id) => {
33
+ if (id) {
34
+ navigate(urlController.buildAppUrlPath(`schema/${id}`));
35
+ } else {
36
+ navigate(urlController.buildAppUrlPath("schema"));
37
+ }
38
+ }}
39
+ />
40
+ );
41
+ }
@@ -1,6 +1,7 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { getIn, useFormex } from "@rebasepro/formex";
3
- import { useSnackbarController, useTranslation } from "@rebasepro/core";
3
+ import { useTranslation } from "@rebasepro/core";
4
+ import { useSafeSnackbarController } from "../../../useSafeSnackbarController";
4
5
  import { EnumValueConfig, EnumValues } from "@rebasepro/types";
5
6
  import { resolveEnumValues } from "@rebasepro/common";
6
7
  import { Select, SelectItem } from "@rebasepro/ui";
@@ -34,7 +35,7 @@ export function EnumPropertyField({
34
35
  setFieldValue
35
36
  } = useFormex<PropertyWithId>();
36
37
 
37
- const snackbarContext = useSnackbarController();
38
+ const snackbarContext = useSafeSnackbarController();
38
39
  const { t } = useTranslation();
39
40
 
40
41
  const enumValuesPath = multiselect ? "of.enum" : "enum";
@@ -56,7 +57,7 @@ export function EnumPropertyField({
56
57
  const enumIds = value.filter(v => Boolean(v?.id)).map((v: EnumValueConfig) => v.id);
57
58
  if (defaultValue && !enumIds.includes(defaultValue)) {
58
59
  setFieldValue("defaultValue", undefined);
59
- snackbarContext.open({
60
+ snackbarContext?.open({
60
61
  type: "warning",
61
62
  message: "Default value was cleared"
62
63
  })
@@ -13,7 +13,8 @@ import {
13
13
  } from "@rebasepro/ui";
14
14
 
15
15
  import { Field, FormexFieldProps, getIn, useFormex } from "@rebasepro/formex";
16
- import { useTranslation } from "@rebasepro/core";
16
+ import { useStorageSources, useTranslation } from "@rebasepro/core";
17
+ import { DEFAULT_STORAGE_SOURCE_KEY } from "@rebasepro/types";
17
18
  import { GeneralPropertyValidation } from "./validation/GeneralPropertyValidation";
18
19
  import { ArrayPropertyValidation } from "./validation/ArrayPropertyValidation";
19
20
  import { ValidationPanel } from "./validation/ValidationPanel";
@@ -42,9 +43,11 @@ export function StoragePropertyField({
42
43
  setFieldValue
43
44
  } = useFormex();
44
45
  const { t } = useTranslation();
46
+ const storageSources = useStorageSources();
45
47
 
46
48
  const baseStoragePath = multiple ? "of.storage" : "storage";
47
49
  const acceptedFiles = `${baseStoragePath}.acceptedFiles`;
50
+ const storageSource = `${baseStoragePath}.storageSource`;
48
51
 
49
52
  const metadata = `${baseStoragePath}.metadata`;
50
53
  const fileName = `${baseStoragePath}.fileName`;
@@ -76,6 +79,11 @@ export function StoragePropertyField({
76
79
  const fileTypesValue: string[] | undefined = Array.isArray(storedValue) ? storedValue : undefined;
77
80
  const allFileTypesSelected = !fileTypesValue || fileTypesValue.length === 0;
78
81
 
82
+ // Available storage backends (from `<Rebase storageSources={...}>`).
83
+ const sourceKeys = Object.keys(storageSources.sources)
84
+ .filter((key) => key !== DEFAULT_STORAGE_SOURCE_KEY);
85
+ const storageSourceValue = (getIn(values, storageSource) as string | undefined) ?? DEFAULT_STORAGE_SOURCE_KEY;
86
+
79
87
  const handleTypesChange = (value: string[]) => {
80
88
  if (!value) setFieldValue(acceptedFiles, undefined);
81
89
  else setFieldValue(acceptedFiles, value);
@@ -102,6 +110,32 @@ export function StoragePropertyField({
102
110
 
103
111
  <div className={"grid grid-cols-12 gap-2 p-4"}>
104
112
 
113
+ {sourceKeys.length > 0 && (
114
+ <div className={"col-span-12"}>
115
+ <Select
116
+ className={"w-full"}
117
+ disabled={disabled}
118
+ name={storageSource}
119
+ value={storageSourceValue}
120
+ onValueChange={(value) =>
121
+ setFieldValue(storageSource, !value || value === DEFAULT_STORAGE_SOURCE_KEY ? undefined : value)}
122
+ label={"Storage backend"}
123
+ renderValue={(key) => {
124
+ const label = storageSources.registry[key]?.label;
125
+ return label ?? (key === DEFAULT_STORAGE_SOURCE_KEY ? "Default" : key);
126
+ }}>
127
+ <SelectItem value={DEFAULT_STORAGE_SOURCE_KEY}>
128
+ Default
129
+ </SelectItem>
130
+ {sourceKeys.map((key) => (
131
+ <SelectItem key={key} value={key}>
132
+ {storageSources.registry[key]?.label ?? key}
133
+ </SelectItem>
134
+ ))}
135
+ </Select>
136
+ </div>
137
+ )}
138
+
105
139
  <div className={"col-span-12"}>
106
140
 
107
141
  <MultiSelect
@@ -93,7 +93,7 @@ export const blogCollectionTemplate = {
93
93
  publish_date: {
94
94
  name: "Publish date",
95
95
  type: "date",
96
- clearable: true
96
+ ui: { clearable: true }
97
97
  },
98
98
  reviewed: {
99
99
  name: "Reviewed",
@@ -16,7 +16,6 @@ export const supportedFieldsIds: PropertyConfigId[] = [
16
16
  "file_upload",
17
17
  "multi_file_upload",
18
18
  "reference",
19
- "reference_as_string",
20
19
  "multi_references",
21
20
  "relation",
22
21
  "switch",