@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
@@ -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" ||
@@ -520,7 +571,10 @@ function PropertyEditFormFields({
520
571
  showError={Boolean(selectedWidgetError)}
521
572
  existing={existing}
522
573
  propertyConfigs={propertyConfigs}
523
- inArray={inArray}/>
574
+ inArray={inArray}
575
+ propertyTypePresets={propertyTypePresets}
576
+ hiddenPropertyTypes={hiddenPropertyTypes}
577
+ onPresetSelected={onPresetSelected}/>
524
578
 
525
579
  {!!selectedWidgetError &&
526
580
  <Typography variant="caption"
@@ -553,6 +607,23 @@ function PropertyEditFormFields({
553
607
 
554
608
  {childComponent}
555
609
 
610
+ {renderExtraPropertyFields && collectionValues && (
611
+ <div className="col-span-12 mt-4 px-2">
612
+ {renderExtraPropertyFields({
613
+ metadata: ((values as unknown as Record<string, unknown>).metadata as Record<string, unknown>) ?? {},
614
+ onMetadataChange: (key: string, value: unknown) => {
615
+ const currentMetadata = ((values as unknown as Record<string, unknown>).metadata as Record<string, unknown>) ?? {};
616
+ setValues({
617
+ ...values,
618
+ metadata: { ...currentMetadata, [key]: value },
619
+ } as PropertyWithId);
620
+ },
621
+ property: toSerializableProperty(values as Property),
622
+ collection: toSerializableCollection(collectionValues),
623
+ })}
624
+ </div>
625
+ )}
626
+
556
627
  <div className={"col-span-12"}>
557
628
  <AdvancedPropertyValidation disabled={disabled}/>
558
629
  </div>
@@ -626,6 +697,34 @@ const WIDGET_TYPE_MAP: Record<PropertyConfigId, string> = {
626
697
  vector_input: "Number"
627
698
  };
628
699
 
700
+ const WIDGET_BASE_TYPE_MAP: Record<string, PropertyType> = {
701
+ text_field: "string",
702
+ multiline: "string",
703
+ markdown: "string",
704
+ url: "string",
705
+ email: "string",
706
+ select: "string",
707
+ multi_select: "string",
708
+ file_upload: "string",
709
+ multi_file_upload: "string",
710
+ reference_as_string: "string",
711
+ user_select: "string",
712
+ number_input: "number",
713
+ number_select: "number",
714
+ multi_number_select: "number",
715
+ switch: "boolean",
716
+ date_time: "date",
717
+ reference: "reference",
718
+ multi_references: "reference",
719
+ relation: "relation",
720
+ group: "map",
721
+ key_value: "map",
722
+ block: "map",
723
+ repeat: "array",
724
+ custom_array: "array",
725
+ vector_input: "vector",
726
+ };
727
+
629
728
  function WidgetSelectView({
630
729
  initialProperty,
631
730
  value,
@@ -636,7 +735,10 @@ function WidgetSelectView({
636
735
  showError,
637
736
  existing,
638
737
  propertyConfigs,
639
- inArray
738
+ inArray,
739
+ propertyTypePresets,
740
+ hiddenPropertyTypes,
741
+ onPresetSelected
640
742
  }: {
641
743
  initialProperty?: PropertyWithId,
642
744
  value?: PropertyConfigId,
@@ -647,14 +749,24 @@ function WidgetSelectView({
647
749
  disabled: boolean,
648
750
  existing: boolean,
649
751
  propertyConfigs: Record<string, PropertyConfig>,
650
- inArray?: boolean
752
+ inArray?: boolean,
753
+ propertyTypePresets?: PropertyTypePreset[],
754
+ hiddenPropertyTypes?: PropertyType[],
755
+ onPresetSelected?: (preset: PropertyTypePreset) => void,
651
756
  }) {
652
757
 
653
758
  const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
654
759
 
760
+ const filteredFields = hiddenPropertyTypes && !propertyTypePresets
761
+ ? allSupportedFields.filter(([key]) => {
762
+ const baseType = WIDGET_BASE_TYPE_MAP[key];
763
+ return !baseType || !hiddenPropertyTypes.includes(baseType);
764
+ })
765
+ : allSupportedFields;
766
+
655
767
  const displayedWidgets = (inArray
656
- ? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property as Property) && (propertyConfig.property as Property)?.type !== "array")
657
- : allSupportedFields)
768
+ ? filteredFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property as Property) && (propertyConfig.property as Property)?.type !== "array")
769
+ : filteredFields)
658
770
  .map(([key, propertyConfig]) => ({
659
771
  [key]: propertyConfig
660
772
  }))
@@ -665,6 +777,11 @@ function WidgetSelectView({
665
777
  }
666
778
  }, {});
667
779
 
780
+ const activePreset = useMemo(() => {
781
+ if (!propertyTypePresets || !initialProperty) return undefined;
782
+ return propertyTypePresets.find(p => p.detect?.(toSerializableProperty(initialProperty as Property)));
783
+ }, [propertyTypePresets, initialProperty]);
784
+
668
785
  const key = value;
669
786
  const propertyConfig = key ? (DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key]) : undefined;
670
787
  const baseProperty = propertyConfig?.property;
@@ -711,9 +828,36 @@ function WidgetSelectView({
711
828
  onOpenChange={(open: boolean) => onOpenChange(open, Boolean(value))}
712
829
  maxWidth={"4xl"}>
713
830
  <DialogTitle>
714
- Select a property widget
831
+ {propertyTypePresets ? "Select a property type" : "Select a property widget"}
715
832
  </DialogTitle>
716
833
  <DialogContent>
834
+ {propertyTypePresets ? (
835
+ <div className={"grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 mt-4"}>
836
+ {propertyTypePresets.map((preset) => (
837
+ <Card
838
+ key={preset.id}
839
+ onClick={() => {
840
+ onPresetSelected?.(preset);
841
+ onOpenChange(false, true);
842
+ }}
843
+ className={cls(
844
+ "flex flex-row items-center px-4 py-2 m-1",
845
+ activePreset?.id === preset.id && "ring-2 ring-primary"
846
+ )}>
847
+ <div className={cls("flex flex-row items-center text-base min-h-[48px]")}>
848
+ {preset.icon && (
849
+ <div className={"mr-4 text-2xl"}>
850
+ {typeof preset.icon === "string" ? preset.icon : preset.icon}
851
+ </div>
852
+ )}
853
+ <div>
854
+ <Typography variant={"label"}>{preset.label}</Typography>
855
+ </div>
856
+ </div>
857
+ </Card>
858
+ ))}
859
+ </div>
860
+ ) : (
717
861
  <div>
718
862
  {groups.map(group => {
719
863
  return <div key={group} className={"mt-4"}>
@@ -749,6 +893,7 @@ function WidgetSelectView({
749
893
  {/* existing={existing}/>;*/}
750
894
  {/*})}*/}
751
895
  </div>
896
+ )}
752
897
  </DialogContent>
753
898
  </Dialog>
754
899
  </>;
@@ -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
  })