@rebasepro/admin 0.12.1-canary.gf5f1d39 → 0.13.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 (178) hide show
  1. package/README.md +1 -1
  2. package/dist/{CollectionEditorDialog-Dpc75wIZ.js → CollectionEditorDialog-CjXkWZ64.js} +75 -62
  3. package/dist/{CollectionEditorDialog-Dpc75wIZ.js.map → CollectionEditorDialog-CjXkWZ64.js.map} +1 -1
  4. package/dist/{PropertyEditView-DH3XZC2x.js → PropertyEditView-W8I3YV89.js} +36 -16
  5. package/dist/{PropertyEditView-DH3XZC2x.js.map → PropertyEditView-W8I3YV89.js.map} +1 -1
  6. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js → RouterCollectionsStudioView-BsLTNoqX.js} +5 -5
  7. package/dist/{RouterCollectionsStudioView-BZLq4MGp.js.map → RouterCollectionsStudioView-BsLTNoqX.js.map} +1 -1
  8. package/dist/collection_editor/serializable_types.d.ts +4 -1
  9. package/dist/collection_editor/ui/collection_editor/CollectionRelationsTab.d.ts +1 -0
  10. package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +8 -0
  11. package/dist/collection_editor_ui.js +5 -5
  12. package/dist/collection_editor_ui.js.map +1 -1
  13. package/dist/components/CollectionTableBinding/column_utils.d.ts +17 -0
  14. package/dist/components/CollectionViewBinding/CollectionViewStartActions.d.ts +7 -2
  15. package/dist/components/CollectionViewBinding/SortButton.d.ts +24 -0
  16. package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +10 -0
  17. package/dist/components/CollectionViewBinding/SplitViewContext.d.ts +23 -0
  18. package/dist/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.d.ts +26 -0
  19. package/dist/components/DetailViewBinding.d.ts +5 -0
  20. package/dist/components/EditFormActions.d.ts +12 -1
  21. package/dist/components/EditViewBinding.d.ts +6 -1
  22. package/dist/components/EntityIdentityBar.d.ts +53 -0
  23. package/dist/components/EntityInspector.d.ts +35 -0
  24. package/dist/components/EntityPreviewNesting.d.ts +25 -0
  25. package/dist/components/EntityViewBinding.d.ts +9 -0
  26. package/dist/components/InlineEntityPreview.d.ts +47 -0
  27. package/dist/components/history/EntityHistoryView.d.ts +4 -1
  28. package/dist/components/index.d.ts +2 -0
  29. package/dist/editor.js +18 -10
  30. package/dist/editor.js.map +1 -1
  31. package/dist/{export-BzIpUdSK.js → export-J79rMoNj.js} +2 -2
  32. package/dist/{export-BzIpUdSK.js.map → export-J79rMoNj.js.map} +1 -1
  33. package/dist/form/EntityForm.d.ts +1 -1
  34. package/dist/form/EntityFormActions.d.ts +11 -1
  35. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  36. package/dist/form/components/FieldBlock.d.ts +30 -0
  37. package/dist/form/components/FormRail.d.ts +18 -0
  38. package/dist/form/components/FormSections.d.ts +16 -0
  39. package/dist/form/components/index.d.ts +3 -2
  40. package/dist/form/components/useRailVisible.d.ts +24 -0
  41. package/dist/form/field_bindings/BinaryFieldBinding.d.ts +1 -1
  42. package/dist/form/field_bindings/DateTimeFieldBinding.d.ts +1 -1
  43. package/dist/form/field_bindings/GeopointFieldBinding.d.ts +1 -1
  44. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  45. package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +1 -1
  46. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  47. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  48. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
  49. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -1
  50. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  51. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +1 -1
  52. package/dist/form/field_bindings/VectorFieldBinding.d.ts +1 -1
  53. package/dist/{history-g9688IaK.js → history-D7L_JlLO.js} +35 -33
  54. package/dist/history-D7L_JlLO.js.map +1 -0
  55. package/dist/hooks/navigation/utils.d.ts +16 -0
  56. package/dist/hooks/useBuildSidePanel.d.ts +14 -0
  57. package/dist/hooks/useEntityDisplayTitle.d.ts +18 -0
  58. package/dist/hooks/useHistory.d.ts +7 -0
  59. package/dist/hooks/useRecordActions.d.ts +17 -0
  60. package/dist/{import-BjUG-cIY.js → import-BvYl--U4.js} +2 -2
  61. package/dist/{import-BjUG-cIY.js.map → import-BvYl--U4.js.map} +1 -1
  62. package/dist/index.js +106 -35
  63. package/dist/index.js.map +1 -1
  64. package/dist/markdown-CklalUUk.js.map +1 -1
  65. package/dist/preview/components/InlineEntityListPreview.d.ts +15 -0
  66. package/dist/preview/index.d.ts +1 -0
  67. package/dist/preview/property_previews/ArrayOfReferencesPreview.d.ts +1 -1
  68. package/dist/preview/property_previews/ArrayOfRelationsPreview.d.ts +1 -1
  69. package/dist/types/components/EntityFormActionsProps.d.ts +0 -1
  70. package/dist/types/components/EntityFormProps.d.ts +0 -5
  71. package/dist/types/fields.d.ts +24 -0
  72. package/dist/{util-B5fJm1FA.js → util-GiwPgxnL.js} +2106 -1117
  73. package/dist/util-GiwPgxnL.js.map +1 -0
  74. package/package.json +32 -34
  75. package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
  76. package/src/collection_editor/serializable_types.ts +4 -3
  77. package/src/collection_editor/serializable_utils.ts +51 -17
  78. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +65 -22
  79. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +38 -30
  80. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -1
  81. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
  82. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +1 -1
  83. package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +16 -0
  84. package/src/collection_editor/useLocalCollectionsConfigController.tsx +127 -15
  85. package/src/collection_editor/validateCollectionJson.ts +3 -3
  86. package/src/components/AdminModeSyncer.tsx +1 -1
  87. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +10 -2
  88. package/src/components/CollectionTableBinding/column_utils.tsx +59 -2
  89. package/src/components/CollectionTableBinding/table_bindings.tsx +2 -1
  90. package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +23 -16
  91. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +7 -1
  92. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +9 -2
  93. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +47 -12
  94. package/src/components/CollectionViewBinding/SortButton.tsx +189 -0
  95. package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +33 -0
  96. package/src/components/CollectionViewBinding/SplitListView.tsx +18 -3
  97. package/src/components/CollectionViewBinding/SplitViewContext.tsx +34 -0
  98. package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +48 -9
  99. package/src/components/CollectionViewBinding/useBoardDataController.tsx +80 -25
  100. package/src/components/CollectionViewBinding/usePreviewSlots.ts +18 -6
  101. package/src/components/DefaultAppBar.tsx +1 -1
  102. package/src/components/DefaultDrawer.tsx +1 -1
  103. package/src/components/DetailViewBinding.tsx +112 -148
  104. package/src/components/DrawerNavigationItem.tsx +1 -1
  105. package/src/components/EditFormActions.tsx +54 -129
  106. package/src/components/EditViewBinding.tsx +202 -99
  107. package/src/components/EntityIdentityBar.tsx +288 -0
  108. package/src/components/EntityInspector.tsx +137 -0
  109. package/src/components/EntityPreviewBinding.tsx +6 -2
  110. package/src/components/EntityPreviewNesting.tsx +50 -0
  111. package/src/components/EntityViewBinding.tsx +35 -32
  112. package/src/components/HomePage/FavouritesView.tsx +1 -1
  113. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  114. package/src/components/HomePage/SmallNavigationCard.tsx +1 -1
  115. package/src/components/InlineEntityPreview.tsx +149 -0
  116. package/src/components/RebaseLayout.tsx +1 -1
  117. package/src/components/RebaseNavigation.tsx +45 -5
  118. package/src/components/RebaseRouteDefs.tsx +1 -1
  119. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  120. package/src/components/SidePanelBinding.tsx +19 -13
  121. package/src/components/UserSelector.tsx +12 -6
  122. package/src/components/common/default_entity_actions.tsx +4 -3
  123. package/src/components/history/EntityHistoryEntry.tsx +28 -7
  124. package/src/components/history/EntityHistoryView.tsx +29 -24
  125. package/src/components/history/LastEditedByIndicator.tsx +4 -3
  126. package/src/components/index.ts +2 -0
  127. package/src/data_import/utils/get_import_inference_type.ts +6 -0
  128. package/src/form/EntityForm.tsx +267 -179
  129. package/src/form/EntityFormActions.tsx +50 -99
  130. package/src/form/EntityFormBinding.tsx +4 -2
  131. package/src/form/PropertyFieldBinding.tsx +4 -0
  132. package/src/form/components/FieldBlock.tsx +119 -0
  133. package/src/form/components/FormRail.tsx +125 -0
  134. package/src/form/components/FormSections.tsx +118 -0
  135. package/src/form/components/index.tsx +3 -2
  136. package/src/form/components/useRailVisible.tsx +46 -0
  137. package/src/form/field_bindings/BinaryFieldBinding.tsx +3 -2
  138. package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -2
  139. package/src/form/field_bindings/GeopointFieldBinding.tsx +3 -2
  140. package/src/form/field_bindings/MapFieldBinding.tsx +1 -2
  141. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +3 -2
  142. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
  143. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
  144. package/src/form/field_bindings/ReferenceFieldBinding.tsx +2 -1
  145. package/src/form/field_bindings/RelationFieldBinding.tsx +6 -4
  146. package/src/form/field_bindings/RepeatFieldBinding.tsx +4 -1
  147. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
  148. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +31 -8
  149. package/src/form/field_bindings/SwitchFieldBinding.tsx +7 -3
  150. package/src/form/field_bindings/TextFieldBinding.tsx +18 -16
  151. package/src/form/field_bindings/UserSelectFieldBinding.tsx +3 -2
  152. package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
  153. package/src/hooks/navigation/useBuildUrlController.tsx +1 -1
  154. package/src/hooks/navigation/useTopLevelNavigation.ts +18 -7
  155. package/src/hooks/navigation/utils.ts +20 -0
  156. package/src/hooks/useBuildSideDialogsController.tsx +2 -2
  157. package/src/hooks/useBuildSidePanel.tsx +22 -61
  158. package/src/hooks/useEntityDisplayTitle.tsx +60 -0
  159. package/src/hooks/useHistory.ts +20 -10
  160. package/src/hooks/useRecordActions.tsx +69 -0
  161. package/src/hooks/useResolvedUsers.ts +10 -5
  162. package/src/preview/PropertyPreview.tsx +48 -22
  163. package/src/preview/components/InlineEntityListPreview.tsx +42 -0
  164. package/src/preview/components/ReferencePreview.tsx +34 -14
  165. package/src/preview/components/RelationPreview.tsx +34 -14
  166. package/src/preview/index.ts +1 -0
  167. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +24 -1
  168. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +25 -1
  169. package/src/routes/RebaseRoute.tsx +2 -2
  170. package/src/types/components/EntityFormActionsProps.tsx +0 -1
  171. package/src/types/components/EntityFormProps.tsx +0 -6
  172. package/src/types/fields.tsx +27 -0
  173. package/dist/form/components/FormEntry.d.ts +0 -6
  174. package/dist/form/components/FormLayout.d.ts +0 -5
  175. package/dist/history-g9688IaK.js.map +0 -1
  176. package/dist/util-B5fJm1FA.js.map +0 -1
  177. package/src/form/components/FormEntry.tsx +0 -22
  178. package/src/form/components/FormLayout.tsx +0 -16
@@ -52,6 +52,7 @@ export function BinaryFieldBinding({
52
52
  autoFocus,
53
53
  property,
54
54
  includeDescription,
55
+ hideLabel,
55
56
  size = "large"
56
57
  }: FieldProps<BinaryProperty>) {
57
58
 
@@ -87,11 +88,11 @@ export function BinaryFieldBinding({
87
88
  return (
88
89
  <div className="flex flex-col gap-2 w-full mt-2">
89
90
  <div className="flex items-center justify-between mb-1">
90
- <LabelWithIcon
91
+ {!hideLabel && <LabelWithIcon
91
92
  icon={getIconForProperty(property, "small")}
92
93
  required={property.validation?.required}
93
94
  title={property.name ?? propertyKey}
94
- />
95
+ />}
95
96
  </div>
96
97
 
97
98
  <PropertyIdCopyTooltip propertyKey={propertyKey}>
@@ -31,7 +31,8 @@ export function DateTimeFieldBinding({
31
31
  disabled,
32
32
  touched,
33
33
  property,
34
- includeDescription
34
+ includeDescription,
35
+ hideLabel
35
36
  }: DateTimeFieldProps) {
36
37
 
37
38
  const { locale } = useCustomizationController();
@@ -54,7 +55,7 @@ export function DateTimeFieldBinding({
54
55
  locale={locale}
55
56
  error={showError}
56
57
  disabled={disabled}
57
- label={<LabelWithIcon
58
+ label={hideLabel ? undefined : <LabelWithIcon
58
59
  icon={getIconForProperty(property, "small")}
59
60
  required={property.validation?.required}
60
61
  className={showError ? "text-red-500 dark:text-red-500" : "text-text-secondary dark:text-text-secondary-dark"}
@@ -62,6 +62,7 @@ export function GeopointFieldBinding({
62
62
  autoFocus,
63
63
  property,
64
64
  includeDescription,
65
+ hideLabel,
65
66
  size = "large"
66
67
  }: FieldProps<GeopointProperty>) {
67
68
 
@@ -129,11 +130,11 @@ export function GeopointFieldBinding({
129
130
  return (
130
131
  <div className="flex flex-col gap-2 w-full mt-2">
131
132
  <div className="flex items-center justify-between mb-1">
132
- <LabelWithIcon
133
+ {!hideLabel && <LabelWithIcon
133
134
  icon={getIconForProperty(property, "small")}
134
135
  required={property.validation?.required}
135
136
  title={property.name ?? propertyKey}
136
- />
137
+ />}
137
138
  {!disabled && (latText || lngText) && (
138
139
  <IconButton
139
140
  size="small"
@@ -46,7 +46,7 @@ export function MapFieldBinding({
46
46
 
47
47
  const mapFormView = <>
48
48
  <div
49
- className={cls("py-1 flex flex-col space-y-2", minimalistView && property.admin?.widthPercentage !== undefined ? "mt-8" : undefined)}>
49
+ className={"py-1 flex flex-col space-y-2"}>
50
50
  {Object.entries(mapProperties)
51
51
  .filter(([_, property]) => !isHidden(property))
52
52
  .map(([entryKey, childProperty], index) => {
@@ -96,7 +96,6 @@ export function MapFieldBinding({
96
96
  expanded }
97
97
  });
98
98
  }}
99
- className={property.admin?.widthPercentage !== undefined ? "mt-8" : undefined}
100
99
  innerClassName={"px-2 md:px-4 pb-2 md:pb-4 pt-1 md:pt-2 bg-white dark:bg-surface-900"}
101
100
  title={<LabelWithIconAndTooltip
102
101
  propertyKey={propertyKey}
@@ -74,6 +74,7 @@ export function MarkdownEditorFieldBinding({
74
74
  value,
75
75
  setValue,
76
76
  includeDescription,
77
+ hideLabel,
77
78
  showError,
78
79
  error,
79
80
  minimalistView,
@@ -216,12 +217,12 @@ export function MarkdownEditorFieldBinding({
216
217
  return (
217
218
  <>
218
219
  <div className="flex items-center w-full">
219
- <LabelWithIconAndTooltip
220
+ {!hideLabel && <LabelWithIconAndTooltip
220
221
  propertyKey={propertyKey}
221
222
  icon={getIconForProperty(property, "small")}
222
223
  required={property.validation?.required}
223
224
  title={property.name ?? propertyKey}
224
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
225
+ className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
225
226
  <div className="flex-grow"/>
226
227
  {property.admin?.clearable && !disabled && (
227
228
  <IconButton
@@ -27,6 +27,7 @@ export function MultiSelectFieldBinding({
27
27
  disabled,
28
28
  property,
29
29
  includeDescription,
30
+ hideLabel,
30
31
  size = "large",
31
32
  autoFocus
32
33
  }: FieldProps<ArrayProperty, unknown, Record<string, unknown>>) {
@@ -91,7 +92,7 @@ export function MultiSelectFieldBinding({
91
92
  value={validValue ? value.map((v) => v?.toString()) : []}
92
93
  disabled={disabled}
93
94
  modalPopover={true}
94
- label={<LabelWithIconAndTooltip
95
+ label={hideLabel ? undefined : <LabelWithIconAndTooltip
95
96
  propertyKey={propertyKey}
96
97
  icon={getIconForProperty(property, "small")}
97
98
  required={property.validation?.required}
@@ -27,6 +27,7 @@ export function ReadOnlyFieldBinding({
27
27
  minimalistView,
28
28
  property,
29
29
  includeDescription,
30
+ hideLabel,
30
31
  context
31
32
  }: FieldProps<Property>) {
32
33
 
@@ -39,7 +40,7 @@ export function ReadOnlyFieldBinding({
39
40
 
40
41
  <>
41
42
 
42
- {!minimalistView && <LabelWithIconAndTooltip
43
+ {!minimalistView && !hideLabel && <LabelWithIconAndTooltip
43
44
  propertyKey={propertyKey}
44
45
  icon={getIconForProperty(property, "small")}
45
46
  required={property.validation?.required}
@@ -49,6 +49,7 @@ function ReferenceFieldBindingInternal({
49
49
  autoFocus,
50
50
  property,
51
51
  includeDescription,
52
+ hideLabel,
52
53
  size = "medium"
53
54
  }: FieldProps<ReferenceProperty>) {
54
55
  if (!property.path) {
@@ -96,7 +97,7 @@ function ReferenceFieldBindingInternal({
96
97
 
97
98
  return (
98
99
  <>
99
- {!minimalistView && <LabelWithIconAndTooltip
100
+ {!minimalistView && !hideLabel && <LabelWithIconAndTooltip
100
101
  propertyKey={propertyKey}
101
102
  icon={getIconForProperty(property, "small")}
102
103
  required={property.validation?.required}
@@ -63,6 +63,7 @@ function RelationSelectorBinding({
63
63
  isSubmitting,
64
64
  property,
65
65
  includeDescription,
66
+ hideLabel,
66
67
  setValue,
67
68
  relation,
68
69
  manyRelation
@@ -75,12 +76,12 @@ function RelationSelectorBinding({
75
76
 
76
77
  return (
77
78
  <div className="">
78
- <LabelWithIconAndTooltip
79
+ {!hideLabel && <LabelWithIconAndTooltip
79
80
  propertyKey={propertyKey}
80
81
  icon={getIconForProperty(property, "small")}
81
82
  required={property.validation?.required}
82
83
  title={property.name ?? propertyKey}
83
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
84
+ className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
84
85
 
85
86
  <RelationSelector
86
87
  relation={relation}
@@ -116,6 +117,7 @@ function SingleRelationFieldBinding({
116
117
  isSubmitting,
117
118
  property,
118
119
  includeDescription,
120
+ hideLabel,
119
121
  setValue,
120
122
  relation
121
123
  }: FieldProps<RelationProperty> & { relation: Relation | undefined }) {
@@ -150,12 +152,12 @@ function SingleRelationFieldBinding({
150
152
 
151
153
  return (
152
154
  <>
153
- <LabelWithIconAndTooltip
155
+ {!hideLabel && <LabelWithIconAndTooltip
154
156
  propertyKey={propertyKey}
155
157
  icon={getIconForProperty(property, "small")}
156
158
  required={property.validation?.required}
157
159
  title={property.name ?? propertyKey}
158
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
160
+ className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
159
161
 
160
162
  {!collection && <ErrorView
161
163
  error={"The specified collection does not exist. Check console"}/>}
@@ -85,6 +85,10 @@ export function RepeatFieldBinding({
85
85
  context,
86
86
  partOfArray: true,
87
87
  minimalistView: false,
88
+ // The panel header already names the field; repeating "Locale" on
89
+ // every row of a three-item array is noise, and the label was what
90
+ // made each row a 76px card around a two-character value.
91
+ hideLabel: true,
88
92
  autoFocus: internalId === lastAddedId
89
93
  };
90
94
  return <ErrorBoundary>
@@ -104,7 +108,6 @@ export function RepeatFieldBinding({
104
108
  sortable={sortable}
105
109
  newDefaultEntry={getDefaultValueFor(property.of)}
106
110
  onValueChange={(value) => setFieldValue(propertyKey, value)}
107
- className={property.admin?.widthPercentage !== undefined ? "mt-8" : undefined}
108
111
  />;
109
112
 
110
113
  const title = (<>
@@ -31,6 +31,7 @@ export function SelectFieldBinding({
31
31
  touched,
32
32
  property,
33
33
  includeDescription,
34
+ hideLabel,
34
35
  size = "large"
35
36
  }: FieldProps<StringProperty | NumberProperty>) {
36
37
 
@@ -58,7 +59,7 @@ export function SelectFieldBinding({
58
59
  fullWidth={true}
59
60
  position="item-aligned"
60
61
  inputClassName={cls("w-full")}
61
- label={
62
+ label={hideLabel ? undefined : (
62
63
  <PropertyIdCopyTooltip propertyKey={propertyKey}>
63
64
  <LabelWithIcon
64
65
  icon={getIconForProperty(property, "small")}
@@ -66,7 +67,7 @@ export function SelectFieldBinding({
66
67
  title={property.name ?? propertyKey}
67
68
  className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5 my-0"}
68
69
  />
69
- </PropertyIdCopyTooltip>}
70
+ </PropertyIdCopyTooltip>)}
70
71
  endAdornment={
71
72
  property.admin?.clearable && !disabled && <IconButton
72
73
  size="small"
@@ -41,7 +41,15 @@ import {
41
41
  } from "@rebasepro/ui";
42
42
  import { useClearRestoreValue } from "../useClearRestoreValue";
43
43
 
44
- const dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent min-h-[254px] outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
44
+ // Height comes from the thumbnails now. It used to be a flat `min-h-[254px]`
45
+ // on top of another 250px inside, so a field holding one small image
46
+ // reserved close to 300px of the form for mostly empty space.
47
+ // One fixed height for the whole zone — 126px of preview row, 34px of hint,
48
+ // and the padding between. Pinned on the root rather than on the children
49
+ // because the hint reflows once files exist ("Drag to reorder"), and any
50
+ // content-driven height makes a filled field taller than the same field
51
+ // empty. Extra thumbnails scroll horizontally instead of growing it.
52
+ const dropZoneClasses = "box-border relative pt-[2px] h-[172px] items-center border border-transparent outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
45
53
  const disabledClasses = fieldBackgroundDisabledMixin;
46
54
  const nonActiveDropClasses = fieldBackgroundHoverMixin
47
55
  const activeDropClasses = "pt-0 border-2 border-solid"
@@ -58,6 +66,7 @@ export function StorageUploadFieldBinding({
58
66
  minimalistView,
59
67
  property,
60
68
  includeDescription,
69
+ hideLabel,
61
70
  context,
62
71
  isSubmitting
63
72
  }: FieldProps<StringProperty | ArrayProperty>) {
@@ -98,7 +107,7 @@ export function StorageUploadFieldBinding({
98
107
 
99
108
  <>
100
109
 
101
- {!minimalistView &&
110
+ {!minimalistView && !hideLabel &&
102
111
  <LabelWithIconAndTooltip
103
112
  propertyKey={propertyKey}
104
113
  icon={getIconForProperty(property, "small")}
@@ -214,7 +223,12 @@ function SortableStorageItem({
214
223
  style={style}
215
224
  {...attributes}
216
225
  {...listeners}
217
- className={cls("rounded-md m-1")}
226
+ // No vertical margin: the strip already pads itself, and `m-1` made
227
+ // each item 8px taller than the row's content box — enough to make a
228
+ // field holding one image scroll vertically behind a hidden
229
+ // scrollbar. `shrink-0` keeps thumbnails square once there are more
230
+ // of them than fit across.
231
+ className={cls("rounded-md shrink-0")}
218
232
  tabIndex={-1}
219
233
  >
220
234
  {child}
@@ -310,9 +324,15 @@ function FileDropComponent({
310
324
  })}
311
325
  >
312
326
  <div
313
- className={cls("flex items-center p-1 px-4 no-scrollbar",
314
- multipleFilesSupported && internalValue.length ? "overflow-auto" : "",
315
- multipleFilesSupported && internalValue.length ? "min-h-[180px]" : "min-h-[250px]"
327
+ className={cls("flex items-center gap-2 p-1 px-3 no-scrollbar",
328
+ // Horizontal only. `overflow-auto` scrolls both axes, so any
329
+ // pixel of vertical overflow turned the field into a
330
+ // scrollable box with nothing visible to scroll to.
331
+ multipleFilesSupported && internalValue.length ? "overflow-x-auto overflow-y-hidden" : "",
332
+ // A fixed height, not a minimum: the 118px preview overflowed
333
+ // a `min-h`, so a field with a file stood taller than the same
334
+ // field empty. 126px is the preview plus its padding.
335
+ "h-[126px]"
316
336
  )}
317
337
  >
318
338
  <input
@@ -339,9 +359,12 @@ function FileDropComponent({
339
359
  </div>
340
360
 
341
361
  <div
342
- className="flex-grow min-h-[38px] box-border m-2 text-center">
362
+ // Two lines' worth, fixed: the hint gains "Drag to reorder" once
363
+ // the field holds something, and a reflow there put the filled
364
+ // state 25px taller than the empty one.
365
+ className="box-border mx-3 mb-2.5 h-[34px] flex items-center justify-center text-center">
343
366
  <Typography align={"center"}
344
- variant={"label"}
367
+ variant={"caption"}
345
368
  className={disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : ""}>
346
369
  {helpText}
347
370
  </Typography>
@@ -27,7 +27,8 @@ export const SwitchFieldBinding = function SwitchFieldBinding({
27
27
  disabled,
28
28
  size = "large",
29
29
  property,
30
- includeDescription
30
+ includeDescription,
31
+ hideLabel
31
32
  }: FieldProps<BooleanProperty>) {
32
33
 
33
34
  useClearRestoreValue({
@@ -44,8 +45,11 @@ export const SwitchFieldBinding = function SwitchFieldBinding({
44
45
  value={value as boolean | null}
45
46
  onValueChange={(v) => setValue(v)}
46
47
  error={showError}
47
- className={property.admin?.widthPercentage !== undefined ? "mt-8" : undefined}
48
- label={<LabelWithIcon
48
+ // The control is `justify-between`, so with the label lifted
49
+ // out the switch would drift to the far edge of an otherwise
50
+ // empty box. Anchor it to the start instead.
51
+ position={hideLabel ? "start" : undefined}
52
+ label={hideLabel ? undefined : <LabelWithIcon
49
53
  icon={getIconForProperty(property, "small")}
50
54
  required={property.validation?.required}
51
55
  title={property.name ?? propertyKey}/>}
@@ -36,6 +36,7 @@ export function TextFieldBinding<T extends string | number>({
36
36
  autoFocus,
37
37
  property,
38
38
  includeDescription,
39
+ hideLabel,
39
40
  size = "large"
40
41
  }: FieldProps<StringProperty | NumberProperty>) {
41
42
 
@@ -85,12 +86,17 @@ export function TextFieldBinding<T extends string | number>({
85
86
  else if (property.admin?.urlPreview) inputType = "url";
86
87
  }
87
88
 
88
- const label = (
89
- <LabelWithIcon
90
- icon={getIconForProperty(property, "small")}
91
- required={property.validation?.required || property.isId === true}
92
- title={property.name ?? propertyKey}/>
93
- );
89
+ // `undefined` rather than an empty element: TextField and the multiline box
90
+ // both reserve a label row whenever `label` is truthy, so a null-rendering
91
+ // element would leave the gap behind without the text.
92
+ const label = hideLabel
93
+ ? undefined
94
+ : (
95
+ <LabelWithIcon
96
+ icon={getIconForProperty(property, "small")}
97
+ required={property.validation?.required || property.isId === true}
98
+ title={property.name ?? propertyKey}/>
99
+ );
94
100
  return (<>
95
101
  <PropertyIdCopyTooltip propertyKey={propertyKey}>
96
102
  {isMultiline ? (
@@ -98,20 +104,20 @@ export function TextFieldBinding<T extends string | number>({
98
104
  "rounded-md relative max-w-full min-h-[64px]",
99
105
  fieldBackgroundMixin,
100
106
  fieldBackgroundHoverMixin,
101
- showError && error ? "border border-red-500 dark:border-red-600" : "",
102
- property.admin?.widthPercentage !== undefined ? "mt-8" : undefined
107
+ showError && error ? "border border-red-500 dark:border-red-600" : ""
103
108
  )}>
104
- <div
109
+ {label && <div
105
110
  className="pointer-events-none absolute top-1 text-xs font-medium px-3 text-text-secondary dark:text-text-secondary-dark">
106
111
  {label}
107
- </div>
112
+ </div>}
108
113
  <TextareaAutosize
109
114
  value={displayValue}
110
115
  onChange={onChange}
111
116
  autoFocus={autoFocus}
112
117
  disabled={disabled}
113
118
  className={cls(
114
- "rounded-md resize-none w-full outline-none p-[32px] text-base bg-transparent min-h-[64px] px-3 pt-8",
119
+ "rounded-md resize-none w-full outline-none text-base bg-transparent min-h-[64px] px-3",
120
+ label ? "pt-8 pb-2" : "py-2",
115
121
  disabled && "outline-none opacity-50 text-surface-accent-600 dark:text-surface-accent-500",
116
122
  showError && error ? "text-red-500 dark:text-red-600" : ""
117
123
  )}
@@ -131,11 +137,7 @@ export function TextFieldBinding<T extends string | number>({
131
137
  value={displayValue}
132
138
  onChange={onChange}
133
139
  autoFocus={autoFocus}
134
- className={property.admin?.widthPercentage !== undefined ? "mt-8" : undefined}
135
- label={<LabelWithIcon
136
- icon={getIconForProperty(property, "small")}
137
- required={property.validation?.required || property.isId === true}
138
- title={property.name ?? propertyKey}/>}
140
+ label={label}
139
141
  type={inputType}
140
142
  disabled={disabled}
141
143
  endAdornment={
@@ -30,6 +30,7 @@ export function UserSelectFieldBinding({
30
30
  touched,
31
31
  property,
32
32
  includeDescription,
33
+ hideLabel,
33
34
  size = "large"
34
35
  }: UserSelectProps) {
35
36
 
@@ -37,14 +38,14 @@ export function UserSelectFieldBinding({
37
38
 
38
39
  return (
39
40
  <>
40
- <PropertyIdCopyTooltip propertyKey={propertyKey}>
41
+ {!hideLabel && <PropertyIdCopyTooltip propertyKey={propertyKey}>
41
42
  <LabelWithIcon
42
43
  icon={getIconForProperty(property, "small")}
43
44
  required={property.validation?.required}
44
45
  title={property.name}
45
46
  className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5 my-0"}
46
47
  />
47
- </PropertyIdCopyTooltip>
48
+ </PropertyIdCopyTooltip>}
48
49
 
49
50
  <UserSelector
50
51
  value={value as string | null | undefined}
@@ -37,6 +37,7 @@ export function VectorFieldBinding({
37
37
  autoFocus,
38
38
  property,
39
39
  includeDescription,
40
+ hideLabel,
40
41
  size = "large"
41
42
  }: FieldProps<VectorProperty>) {
42
43
  const isVectorObject = (val: unknown): val is { value: number[] } => {
@@ -86,7 +87,7 @@ export function VectorFieldBinding({
86
87
  });
87
88
  };
88
89
 
89
- const label = (
90
+ const label = hideLabel ? null : (
90
91
  <LabelWithIcon
91
92
  icon={getIconForProperty(property, "small")}
92
93
  required={property.validation?.required}
@@ -2,7 +2,7 @@ import type { UrlController, NavigateOptions } from "@rebasepro/admin-types";
2
2
  import { useCallback, useMemo } from "react";
3
3
  import { removeInitialAndTrailingSlashes, resolveCollectionPathIds } from "@rebasepro/app";
4
4
  import { CollectionRegistryController } from "@rebasepro/types";
5
- import { useNavigate } from "react-router-dom";
5
+ import { useNavigate } from "react-router";
6
6
 
7
7
  export function useBuildUrlController(props: {
8
8
  basePath: string,
@@ -6,7 +6,7 @@ import { deepEqual as equal } from "fast-equals";
6
6
  import { CollectionRegistryController } from "@rebasepro/types";
7
7
  import { CollectionRegistry } from "@rebasepro/common";
8
8
 
9
- import { computeNavigationGroups, getGroup, NAVIGATION_ADMIN_GROUP_NAME, NAVIGATION_DEFAULT_GROUP_NAME } from "./utils";
9
+ import { computeNavigationGroups, getGroup, isBottomPinnedGroupName, NAVIGATION_ADMIN_GROUP_NAME, NAVIGATION_DEFAULT_GROUP_NAME } from "./utils";
10
10
 
11
11
  export type UseTopLevelNavigationProps = {
12
12
  collections: AdminCollection[];
@@ -178,10 +178,18 @@ export function useTopLevelNavigation(
178
178
  }, [] as NavigationEntry[])
179
179
  ];
180
180
 
181
- const groupOrderValue = (groupName?: string): number => {
182
- if (groupName === NAVIGATION_ADMIN_GROUP_NAME || groupName === "Settings" || groupName === "Admin") return 1;
183
- return 0;
184
- };
181
+ // Groups that sink to the bottom. Two sources, because the string
182
+ // comparison predates the flag and apps depend on it: the names in
183
+ // `NAVIGATION_BOTTOM_GROUP_NAMES`, and any group holding a view that
184
+ // asked for it — which is the only one of the two that survives the
185
+ // group being renamed or translated.
186
+ const pinnedGroups = new Set<string>();
187
+ for (const view of [...(views ?? []), ...(adminViews ?? [])]) {
188
+ if (view.pinToBottom) pinnedGroups.add(getGroup(view));
189
+ }
190
+
191
+ const groupOrderValue = (groupName?: string): number =>
192
+ (isBottomPinnedGroupName(groupName) || (groupName !== undefined && pinnedGroups.has(groupName))) ? 1 : 0;
185
193
 
186
194
  const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
187
195
  navigationEntries = navigationEntries.sort((a, b) => {
@@ -218,8 +226,11 @@ export function useTopLevelNavigation(
218
226
  ];
219
227
 
220
228
  const uniqueGroupsArray = [...new Set(allDefinedGroups)];
221
- const lastGroups = uniqueGroupsArray.filter(g => g === NAVIGATION_ADMIN_GROUP_NAME || g === "Settings" || g === "Admin");
222
- const otherGroups = uniqueGroupsArray.filter(g => g !== NAVIGATION_ADMIN_GROUP_NAME && g !== "Settings" && g !== "Admin");
229
+ // Same rule the entry sort uses read through `groupOrderValue` rather
230
+ // than repeated inline, so the drawer's group order and the entries
231
+ // inside it cannot disagree about which groups are last.
232
+ const lastGroups = uniqueGroupsArray.filter(g => groupOrderValue(g) === 1);
233
+ const otherGroups = uniqueGroupsArray.filter(g => groupOrderValue(g) === 0);
223
234
  const uniqueGroups = [...otherGroups, ...lastGroups] as string[];
224
235
 
225
236
  const computedTopLevelNav = {
@@ -7,6 +7,26 @@ import { deepEqual as equal } from "fast-equals";
7
7
  export const NAVIGATION_DEFAULT_GROUP_NAME = "Views";
8
8
  export const NAVIGATION_ADMIN_GROUP_NAME = "Admin";
9
9
 
10
+ /**
11
+ * Group names that sink to the bottom of the drawer on their name alone.
12
+ *
13
+ * A magic string, and it stays one for compatibility: apps have been relying on
14
+ * `group: "Settings"` landing last since before there was a flag, and taking it
15
+ * away would silently reshuffle their navigation.
16
+ *
17
+ * It is a bad way to ask for the behaviour, though — it is invisible from the
18
+ * `group?: string` type, and it is defeated by the most ordinary edit there is:
19
+ * translating the label. `group: "Ajustes"` is the same group to a reader and a
20
+ * different one to this comparison, so the ordering just quietly stops
21
+ * happening. Set {@link AppView.pinToBottom} instead, which says so.
22
+ */
23
+ export const NAVIGATION_BOTTOM_GROUP_NAMES = ["Admin", "Settings"] as const;
24
+
25
+ /** Whether a group sinks to the bottom by virtue of its name. @see NAVIGATION_BOTTOM_GROUP_NAMES */
26
+ export function isBottomPinnedGroupName(groupName?: string): boolean {
27
+ return groupName !== undefined && (NAVIGATION_BOTTOM_GROUP_NAMES as readonly string[]).includes(groupName);
28
+ }
29
+
10
30
  export function getGroup(collectionOrView: AdminCollection | AppView) {
11
31
  const trimmed = collectionOrView.group?.trim();
12
32
  if (!trimmed || trimmed === "") {
@@ -1,9 +1,9 @@
1
1
  import type { SideDialogPanelProps, SideDialogsController } from "../hooks/useSideDialogsController";
2
2
  import { useCallback, useEffect, useRef, useState, useMemo } from "react";
3
- import { useLocation, useNavigate } from "react-router-dom";
3
+ import { useLocation, useNavigate } from "react-router";
4
4
  ;
5
5
  import { deepEqual as equal } from "fast-equals"
6
- import { Location } from "react-router-dom";
6
+ import { Location } from "react-router";
7
7
 
8
8
  interface SideDialogLocationState {
9
9
  base_location?: Location;