@firecms/core 3.1.0-canary.1df3b2c → 3.1.0-canary.501d471

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 (246) hide show
  1. package/dist/components/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
  2. package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
  3. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +5 -10
  4. package/dist/components/ErrorBoundary.d.ts +4 -2
  5. package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
  6. package/dist/components/LanguageToggle.d.ts +1 -0
  7. package/dist/components/UnsavedChangesDialog.d.ts +1 -0
  8. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -1
  9. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  10. package/dist/components/VirtualTable/VirtualTableProps.d.ts +6 -1
  11. package/dist/components/VirtualTable/types.d.ts +1 -0
  12. package/dist/components/index.d.ts +1 -0
  13. package/dist/core/DrawerNavigationGroup.d.ts +2 -2
  14. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  15. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  16. package/dist/editor/components/editor-bubble.d.ts +8 -0
  17. package/dist/editor/components/image-bubble.d.ts +5 -0
  18. package/dist/editor/components/index.d.ts +16 -0
  19. package/dist/editor/components/table-bubble.d.ts +5 -0
  20. package/dist/editor/editor.d.ts +30 -0
  21. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  22. package/dist/editor/extensions/Image/index.d.ts +6 -0
  23. package/dist/editor/extensions/Image.d.ts +6 -0
  24. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  25. package/dist/editor/extensions/clipboard.d.ts +7 -0
  26. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  27. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  28. package/dist/editor/hooks/useProseMirror.d.ts +13 -0
  29. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  30. package/dist/editor/index.d.ts +2 -0
  31. package/dist/editor/markdown.d.ts +5 -0
  32. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  33. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  34. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  35. package/dist/editor/nodeViews/index.d.ts +6 -0
  36. package/dist/editor/plugins/index.d.ts +2 -0
  37. package/dist/editor/plugins/inputrules.d.ts +6 -0
  38. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  39. package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
  40. package/dist/editor/schema.d.ts +2 -0
  41. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  42. package/dist/editor/selectors/color-selector.d.ts +10 -0
  43. package/dist/editor/selectors/link-selector.d.ts +8 -0
  44. package/dist/editor/selectors/node-selector.d.ts +15 -0
  45. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  46. package/dist/editor/types.d.ts +5 -0
  47. package/dist/editor/useProseMirror.d.ts +16 -0
  48. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  49. package/dist/editor/utils/remove_classes.d.ts +1 -0
  50. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  51. package/dist/form/components/ErrorFocus.d.ts +1 -1
  52. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  53. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  54. package/dist/hooks/index.d.ts +1 -0
  55. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  56. package/dist/hooks/useCollapsedGroups.d.ts +3 -3
  57. package/dist/hooks/useTranslation.d.ts +17 -0
  58. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  59. package/dist/index.d.ts +5 -0
  60. package/dist/index.es.js +29682 -18363
  61. package/dist/index.es.js.map +1 -1
  62. package/dist/index.umd.js +29681 -18382
  63. package/dist/index.umd.js.map +1 -1
  64. package/dist/internal/useRestoreScroll.d.ts +1 -1
  65. package/dist/locales/de.d.ts +2 -0
  66. package/dist/locales/en.d.ts +10 -0
  67. package/dist/locales/es.d.ts +10 -0
  68. package/dist/locales/fr.d.ts +2 -0
  69. package/dist/locales/hi.d.ts +2 -0
  70. package/dist/locales/it.d.ts +2 -0
  71. package/dist/locales/pt.d.ts +7 -0
  72. package/dist/types/analytics.d.ts +1 -1
  73. package/dist/types/collections.d.ts +46 -0
  74. package/dist/types/customization_controller.d.ts +2 -1
  75. package/dist/types/firecms.d.ts +2 -1
  76. package/dist/types/index.d.ts +1 -0
  77. package/dist/types/navigation.d.ts +2 -2
  78. package/dist/types/plugins.d.ts +23 -0
  79. package/dist/types/properties.d.ts +9 -8
  80. package/dist/types/storage.d.ts +1 -0
  81. package/dist/types/translations.d.ts +669 -0
  82. package/dist/util/entities.d.ts +1 -1
  83. package/dist/util/index.d.ts +1 -0
  84. package/dist/util/lazy_eager.d.ts +7 -0
  85. package/dist/util/objects.d.ts +1 -0
  86. package/dist/util/resolutions.d.ts +2 -2
  87. package/dist/util/useStorageUploadController.d.ts +10 -1
  88. package/package.json +49 -13
  89. package/src/app/Scaffold.tsx +7 -5
  90. package/src/components/AIIcon.tsx +3 -1
  91. package/src/components/ArrayContainer.tsx +6 -4
  92. package/src/components/ClearFilterSortButton.tsx +6 -3
  93. package/src/components/ConfirmationDialog.tsx +4 -2
  94. package/src/components/DeleteEntityDialog.tsx +10 -7
  95. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -3
  96. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  97. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -1
  98. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  99. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  100. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  101. package/src/components/EntityCollectionView/BoardSortableList.tsx +3 -1
  102. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  103. package/src/components/EntityCollectionView/EntityBoardCard.tsx +1 -1
  104. package/src/components/EntityCollectionView/EntityCard.tsx +4 -0
  105. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +39 -46
  106. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +17 -25
  107. package/src/components/EntityCollectionView/EntityCollectionView.tsx +71 -31
  108. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -3
  109. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -2
  110. package/src/components/EntityCollectionView/FiltersDialog.tsx +8 -5
  111. package/src/components/EntityCollectionView/ViewModeToggle.tsx +37 -37
  112. package/src/components/EntityJsonPreview.tsx +2 -1
  113. package/src/components/EntityView.tsx +3 -2
  114. package/src/components/ErrorBoundary.tsx +27 -15
  115. package/src/components/HomePage/DefaultHomePage.tsx +19 -13
  116. package/src/components/HomePage/HomePageDnD.tsx +3 -1
  117. package/src/components/HomePage/NavigationGroup.tsx +3 -1
  118. package/src/components/HomePage/RenameGroupDialog.tsx +15 -13
  119. package/src/components/LanguageToggle.tsx +66 -0
  120. package/src/components/NotFoundPage.tsx +5 -3
  121. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -7
  122. package/src/components/ReferenceWidget.tsx +3 -2
  123. package/src/components/SearchIconsView.tsx +3 -1
  124. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +11 -0
  125. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +15 -2
  126. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +11 -0
  127. package/src/components/UnsavedChangesDialog.tsx +6 -4
  128. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
  129. package/src/components/VirtualTable/VirtualTable.tsx +121 -116
  130. package/src/components/VirtualTable/VirtualTableHeader.tsx +59 -56
  131. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +9 -4
  132. package/src/components/VirtualTable/VirtualTableProps.tsx +7 -1
  133. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +3 -3
  134. package/src/components/VirtualTable/types.tsx +1 -0
  135. package/src/components/common/default_entity_actions.tsx +4 -0
  136. package/src/components/common/useDataSourceTableController.tsx +12 -4
  137. package/src/components/index.tsx +1 -0
  138. package/src/core/DefaultAppBar.tsx +15 -11
  139. package/src/core/DefaultDrawer.tsx +8 -2
  140. package/src/core/DrawerNavigationGroup.tsx +5 -3
  141. package/src/core/EntityEditView.tsx +54 -8
  142. package/src/core/EntityEditViewFormActions.tsx +24 -17
  143. package/src/core/EntitySidePanel.tsx +34 -30
  144. package/src/core/FireCMS.tsx +33 -6
  145. package/src/core/field_configs.tsx +18 -11
  146. package/src/editor/components/SlashCommandMenu.tsx +516 -0
  147. package/src/editor/components/editor-bubble-item.tsx +32 -0
  148. package/src/editor/components/editor-bubble.tsx +118 -0
  149. package/src/editor/components/image-bubble.tsx +156 -0
  150. package/src/editor/components/index.ts +14 -0
  151. package/src/editor/components/table-bubble.tsx +165 -0
  152. package/src/editor/editor.tsx +455 -0
  153. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  154. package/src/editor/extensions/Image/index.ts +133 -0
  155. package/src/editor/extensions/Image.ts +159 -0
  156. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  157. package/src/editor/extensions/clipboard.ts +72 -0
  158. package/src/editor/extensions/custom-keymap.ts +24 -0
  159. package/src/editor/extensions/drag-and-drop.tsx +480 -0
  160. package/src/editor/hooks/useProseMirror.ts +124 -0
  161. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  162. package/src/editor/index.ts +2 -0
  163. package/src/editor/markdown.ts +172 -0
  164. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  165. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  166. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  167. package/src/editor/nodeViews/index.ts +35 -0
  168. package/src/editor/plugins/index.ts +58 -0
  169. package/src/editor/plugins/inputrules.ts +82 -0
  170. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  171. package/src/editor/plugins/slashCommandPlugin.ts +61 -0
  172. package/src/editor/schema.ts +240 -0
  173. package/src/editor/selectors/ai-selector.tsx +111 -0
  174. package/src/editor/selectors/color-selector.tsx +200 -0
  175. package/src/editor/selectors/link-selector.tsx +118 -0
  176. package/src/editor/selectors/node-selector.tsx +157 -0
  177. package/src/editor/selectors/text-buttons.tsx +86 -0
  178. package/src/editor/types.ts +6 -0
  179. package/src/editor/useProseMirror.ts +126 -0
  180. package/src/editor/utils/prosemirror-utils.ts +108 -0
  181. package/src/editor/utils/remove_classes.ts +17 -0
  182. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  183. package/src/form/EntityForm.tsx +149 -67
  184. package/src/form/EntityFormActions.tsx +19 -12
  185. package/src/form/PropertyFieldBinding.tsx +10 -8
  186. package/src/form/components/ErrorFocus.tsx +3 -3
  187. package/src/form/components/LocalChangesMenu.tsx +13 -13
  188. package/src/form/components/StorageItemPreview.tsx +3 -2
  189. package/src/form/components/StorageUploadProgress.tsx +18 -3
  190. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
  191. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +22 -9
  192. package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
  193. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  194. package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -24
  195. package/src/form/field_bindings/MapFieldBinding.tsx +27 -11
  196. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +74 -37
  197. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
  198. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
  199. package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
  200. package/src/form/field_bindings/RepeatFieldBinding.tsx +21 -6
  201. package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
  202. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +110 -92
  203. package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
  204. package/src/form/field_bindings/TextFieldBinding.tsx +10 -7
  205. package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
  206. package/src/hooks/index.tsx +1 -0
  207. package/src/hooks/useBuildNavigationController.tsx +49 -22
  208. package/src/hooks/useCollapsedGroups.ts +7 -6
  209. package/src/hooks/useTranslation.ts +31 -0
  210. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  211. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  212. package/src/index.ts +5 -0
  213. package/src/internal/useBuildDataSource.ts +1 -2
  214. package/src/internal/useBuildSideEntityController.tsx +22 -20
  215. package/src/locales/de.ts +718 -0
  216. package/src/locales/en.ts +730 -0
  217. package/src/locales/es.ts +730 -0
  218. package/src/locales/fr.ts +718 -0
  219. package/src/locales/hi.ts +718 -0
  220. package/src/locales/it.ts +718 -0
  221. package/src/locales/pt.ts +727 -0
  222. package/src/preview/PropertyPreview.tsx +4 -2
  223. package/src/preview/components/ReferencePreview.tsx +2 -1
  224. package/src/preview/components/UrlComponentPreview.tsx +4 -2
  225. package/src/preview/components/UserPreview.tsx +3 -1
  226. package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
  227. package/src/routes/FireCMSRoute.tsx +63 -54
  228. package/src/types/analytics.ts +10 -0
  229. package/src/types/collections.ts +49 -0
  230. package/src/types/customization_controller.tsx +2 -1
  231. package/src/types/firecms.tsx +2 -1
  232. package/src/types/index.ts +1 -0
  233. package/src/types/navigation.ts +2 -2
  234. package/src/types/plugins.tsx +26 -0
  235. package/src/types/properties.ts +12 -10
  236. package/src/types/storage.ts +2 -1
  237. package/src/types/translations.ts +752 -0
  238. package/src/util/entities.ts +1 -1
  239. package/src/util/index.ts +1 -0
  240. package/src/util/join_collections.ts +10 -8
  241. package/src/util/lazy_eager.tsx +33 -0
  242. package/src/util/objects.ts +15 -0
  243. package/src/util/previews.ts +2 -2
  244. package/src/util/property_utils.tsx +1 -1
  245. package/src/util/resolutions.ts +5 -3
  246. package/src/util/useStorageUploadController.tsx +23 -29
@@ -38,9 +38,12 @@ import {
38
38
  fieldBackgroundDisabledMixin,
39
39
  fieldBackgroundHoverMixin,
40
40
  fieldBackgroundMixin,
41
- Typography
41
+ Typography,
42
+ IconButton,
43
+ CloseIcon
42
44
  } from "@firecms/ui";
43
45
  import { useClearRestoreValue } from "../useClearRestoreValue";
46
+ import { useTranslation } from "../../hooks/useTranslation";
44
47
 
45
48
  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";
46
49
  const disabledClasses = fieldBackgroundDisabledMixin;
@@ -52,18 +55,18 @@ const rejectDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0
52
55
  type StorageUploadFieldProps = FieldProps<string | string[]>;
53
56
 
54
57
  export function StorageUploadFieldBinding({
55
- propertyKey,
56
- value,
57
- setValue,
58
- error,
59
- showError,
60
- autoFocus,
61
- minimalistView,
62
- property,
63
- includeDescription,
64
- context,
65
- isSubmitting,
66
- }: StorageUploadFieldProps) {
58
+ propertyKey,
59
+ value,
60
+ setValue,
61
+ error,
62
+ showError,
63
+ autoFocus,
64
+ minimalistView,
65
+ property,
66
+ includeDescription,
67
+ context,
68
+ isSubmitting,
69
+ }: StorageUploadFieldProps) {
67
70
 
68
71
  const authController = useAuthController();
69
72
 
@@ -100,7 +103,7 @@ export function StorageUploadFieldBinding({
100
103
  });
101
104
 
102
105
  const resolvedProperty = resolveProperty({
103
- propertyOrBuilder: property as PropertyOrBuilder,
106
+ propertyOrBuilder: property as PropertyOrBuilder<string>,
104
107
  authController
105
108
  }) as ResolvedStringProperty | ResolvedArrayProperty<string[]>;
106
109
 
@@ -109,12 +112,27 @@ export function StorageUploadFieldBinding({
109
112
  <>
110
113
 
111
114
  {!minimalistView &&
112
- <LabelWithIconAndTooltip
113
- propertyKey={propertyKey}
114
- icon={getIconForProperty(property, "small")}
115
- required={property.validation?.required}
116
- title={property.name}
117
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
115
+ <div className="flex items-center w-full">
116
+ <LabelWithIconAndTooltip
117
+ propertyKey={propertyKey}
118
+ icon={getIconForProperty(property, "small")}
119
+ required={property.validation?.required}
120
+ title={property.name}
121
+ className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"} />
122
+ <div className="flex-grow"/>
123
+ {(property.nullable || property.clearable) && !disabled && (
124
+ <IconButton
125
+ size="small"
126
+ onClick={(e) => {
127
+ e.stopPropagation();
128
+ e.preventDefault();
129
+ setValue(null);
130
+ }}
131
+ >
132
+ <CloseIcon size={"small"}/>
133
+ </IconButton>
134
+ )}
135
+ </div>}
118
136
 
119
137
  <StorageUpload
120
138
  value={internalValue}
@@ -128,14 +146,13 @@ export function StorageUploadFieldBinding({
128
146
  onFileUploadComplete={onFileUploadComplete}
129
147
  storagePathBuilder={storagePathBuilder}
130
148
  storage={storage}
131
- multipleFilesSupported={multipleFilesSupported}/>
149
+ multipleFilesSupported={multipleFilesSupported} />
132
150
 
133
151
  <FieldHelperText includeDescription={includeDescription}
134
- showError={showError}
135
- error={error}
136
- disabled={disabled}
137
- property={property}/>
138
-
152
+ showError={showError}
153
+ error={error}
154
+ disabled={disabled}
155
+ property={property} />
139
156
  </>
140
157
  );
141
158
  }
@@ -154,15 +171,15 @@ interface SortableStorageItemProps {
154
171
  }
155
172
 
156
173
  function SortableStorageItem({
157
- id,
158
- entry,
159
- property,
160
- metadata,
161
- storagePathBuilder,
162
- onFileUploadComplete,
163
- onClear,
164
- disabled,
165
- }: SortableStorageItemProps) {
174
+ id,
175
+ entry,
176
+ property,
177
+ metadata,
178
+ storagePathBuilder,
179
+ onFileUploadComplete,
180
+ onClear,
181
+ disabled,
182
+ }: SortableStorageItemProps) {
166
183
 
167
184
  const {
168
185
  attributes,
@@ -201,7 +218,7 @@ function SortableStorageItem({
201
218
  disabled={disabled}
202
219
  value={entry.storagePathOrDownloadUrl}
203
220
  onRemove={() => onClear(entry.storagePathOrDownloadUrl!)}
204
- size={entry.size}/>
221
+ size={entry.size} />
205
222
  );
206
223
  } else if (entry.file) {
207
224
  child = (
@@ -231,21 +248,21 @@ function SortableStorageItem({
231
248
  }
232
249
 
233
250
  function FileDropComponent({
234
- storage,
235
- disabled,
236
- onFilesAdded,
237
- multipleFilesSupported,
238
- autoFocus,
239
- internalValue,
240
- property,
241
- onClear,
242
- metadata,
243
- storagePathBuilder,
244
- onFileUploadComplete,
245
- name,
246
- helpText,
247
- isDndItemDragging
248
- }: {
251
+ storage,
252
+ disabled,
253
+ onFilesAdded,
254
+ multipleFilesSupported,
255
+ autoFocus,
256
+ internalValue,
257
+ property,
258
+ onClear,
259
+ metadata,
260
+ storagePathBuilder,
261
+ onFileUploadComplete,
262
+ name,
263
+ helpText,
264
+ isDndItemDragging
265
+ }: {
249
266
  storage: StorageConfig,
250
267
  disabled: boolean,
251
268
  onFilesAdded: (acceptedFiles: File[]) => Promise<void>,
@@ -271,33 +288,33 @@ function FileDropComponent({
271
288
  isDragAccept,
272
289
  isDragReject
273
290
  } = useDropzone({
274
- accept: storage.acceptedFiles ? storage.acceptedFiles.reduce((acc, ext) => ({
275
- ...acc,
276
- [ext]: []
277
- }), {}) : undefined,
278
- disabled: disabled || isDndItemDragging,
279
- noDragEventsBubbling: true,
280
- maxSize: storage.maxSize,
281
- onDrop: onFilesAdded,
282
- onDropRejected: (fileRejections) => {
283
- for (const fileRejection of fileRejections) {
284
- for (const error of fileRejection.errors) {
285
- console.error("Error uploading file: ", error);
286
- if (error.code === "file-too-large") {
287
- snackbarContext.open({
288
- type: "error",
289
- message: `Error uploading file: File is larger than ${storage.maxSize} bytes`
290
- });
291
- } else if (error.code === "file-invalid-type") {
292
- snackbarContext.open({
293
- type: "error",
294
- message: "Error uploading file: File type is not supported"
295
- });
296
- }
291
+ accept: storage.acceptedFiles ? storage.acceptedFiles.reduce((acc, ext) => ({
292
+ ...acc,
293
+ [ext]: []
294
+ }), {}) : undefined,
295
+ disabled: disabled || isDndItemDragging,
296
+ noDragEventsBubbling: true,
297
+ maxSize: storage.maxSize,
298
+ onDrop: onFilesAdded,
299
+ onDropRejected: (fileRejections) => {
300
+ for (const fileRejection of fileRejections) {
301
+ for (const error of fileRejection.errors) {
302
+ console.error("Error uploading file: ", error);
303
+ if (error.code === "file-too-large") {
304
+ snackbarContext.open({
305
+ type: "error",
306
+ message: `Error uploading file: File is larger than ${storage.maxSize} bytes`
307
+ });
308
+ } else if (error.code === "file-invalid-type") {
309
+ snackbarContext.open({
310
+ type: "error",
311
+ message: "Error uploading file: File type is not supported"
312
+ });
297
313
  }
298
314
  }
299
315
  }
300
316
  }
317
+ }
301
318
  );
302
319
 
303
320
  return (
@@ -349,8 +366,8 @@ function FileDropComponent({
349
366
  <div
350
367
  className="flex-grow min-h-[38px] box-border m-2 text-center">
351
368
  <Typography align={"center"}
352
- variant={"label"}
353
- className={disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : ""}>
369
+ variant={"label"}
370
+ className={disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : ""}>
354
371
  {helpText}
355
372
  </Typography>
356
373
  </div>
@@ -374,19 +391,20 @@ export interface StorageUploadProps {
374
391
  }
375
392
 
376
393
  export function StorageUpload({
377
- property,
378
- name,
379
- value, // This is internalValue from useStorageUploadController
380
- setInternalValue,
381
- onChange,
382
- multipleFilesSupported,
383
- onFileUploadComplete,
384
- disabled,
385
- onFilesAdded,
386
- autoFocus,
387
- storage,
388
- storagePathBuilder,
389
- }: StorageUploadProps) {
394
+ property,
395
+ name,
396
+ value, // This is internalValue from useStorageUploadController
397
+ setInternalValue,
398
+ onChange,
399
+ multipleFilesSupported,
400
+ onFileUploadComplete,
401
+ disabled,
402
+ onFilesAdded,
403
+ autoFocus,
404
+ storage,
405
+ storagePathBuilder,
406
+ }: StorageUploadProps) {
407
+ const { t } = useTranslation();
390
408
 
391
409
  if (multipleFilesSupported) {
392
410
  const arrayProperty = property as ResolvedArrayProperty<string[]>;
@@ -461,8 +479,8 @@ export function StorageUpload({
461
479
  }, [value, multipleFilesSupported, onChange, setInternalValue]);
462
480
 
463
481
  const helpText = multipleFilesSupported
464
- ? "Drag 'n' drop some files here, or click to select files. Drag to reorder."
465
- : "Drag 'n' drop a file here, or click to select one";
482
+ ? t("drag_drop_multiple")
483
+ : t("drag_drop_single");
466
484
 
467
485
  const renderProperty: ResolvedStringProperty = multipleFilesSupported
468
486
  ? (property as ResolvedArrayProperty<string[]>).of as ResolvedStringProperty
@@ -500,6 +518,6 @@ export function StorageUpload({
500
518
  );
501
519
  } else {
502
520
  // For single file, no D&D context is needed
503
- return <FileDropComponent {...fileDropProps} isDndItemDragging={false}/>;
521
+ return <FileDropComponent {...fileDropProps} isDndItemDragging={false} />;
504
522
  }
505
523
  }
@@ -3,7 +3,7 @@ import React from "react";
3
3
  import { FieldProps } from "../../types";
4
4
  import { getIconForProperty } from "../../util";
5
5
  import { FieldHelperText, LabelWithIcon } from "../components";
6
- import { BooleanSwitchWithLabel } from "@firecms/ui";
6
+ import { BooleanSwitchWithLabel, IconButton, CloseIcon } from "@firecms/ui";
7
7
  import { useClearRestoreValue } from "../useClearRestoreValue";
8
8
  import { PropertyIdCopyTooltip } from "../../components";
9
9
 
@@ -39,19 +39,36 @@ export const SwitchFieldBinding = function SwitchFieldBinding({
39
39
  <>
40
40
 
41
41
  <PropertyIdCopyTooltip propertyKey={propertyKey}>
42
- <BooleanSwitchWithLabel
43
- value={value}
44
- onValueChange={(v) => setValue(v)}
45
- error={showError}
46
- className={property.widthPercentage !== undefined ? "mt-8" : undefined}
47
- label={<LabelWithIcon
48
- icon={getIconForProperty(property, "small")}
49
- required={property.validation?.required}
50
- title={property.name}/>}
51
- disabled={disabled}
52
- autoFocus={autoFocus}
53
- size={size}
54
- />
42
+ <div className="flex items-center">
43
+ <BooleanSwitchWithLabel
44
+ value={value}
45
+ onValueChange={(v) => setValue(v)}
46
+ error={showError}
47
+ className={property.widthPercentage !== undefined ? "mt-8" : undefined}
48
+ label={<LabelWithIcon
49
+ icon={getIconForProperty(property, "small")}
50
+ required={property.validation?.required}
51
+ title={property.name}/>}
52
+ disabled={disabled}
53
+ autoFocus={autoFocus}
54
+ size={size}
55
+ switchAdornment={
56
+ (property.nullable || property.clearable) && !disabled && value !== null && (
57
+ <IconButton
58
+ size="small"
59
+ onClick={(e) => {
60
+ e.stopPropagation();
61
+ e.preventDefault();
62
+ setValue(null);
63
+ }}
64
+ className="mr-2"
65
+ >
66
+ <CloseIcon size={"small"}/>
67
+ </IconButton>
68
+ )
69
+ }
70
+ />
71
+ </div>
55
72
  </PropertyIdCopyTooltip>
56
73
 
57
74
  <FieldHelperText includeDescription={includeDescription}
@@ -100,10 +100,10 @@ export function TextFieldBinding<T extends string | number>({
100
100
  showError && error ? "text-red-500 dark:text-red-600" : ""
101
101
  )}
102
102
  />
103
- {property.clearable && (
103
+ {(property.nullable || property.clearable) && value !== null && value !== undefined && (
104
104
  <div className="flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-4">
105
- <IconButton onClick={handleClearClick}>
106
- <CloseIcon />
105
+ <IconButton size="small" onClick={handleClearClick}>
106
+ <CloseIcon size="small" />
107
107
  </IconButton>
108
108
  </div>
109
109
  )}
@@ -119,10 +119,13 @@ export function TextFieldBinding<T extends string | number>({
119
119
  type={inputType}
120
120
  disabled={disabled}
121
121
  endAdornment={
122
- property.clearable && <IconButton
123
- onClick={handleClearClick}>
124
- <CloseIcon />
125
- </IconButton>
122
+ (property.nullable || property.clearable) && value !== null && value !== undefined ? (
123
+ <IconButton
124
+ size="small"
125
+ onClick={handleClearClick}>
126
+ <CloseIcon size="small" />
127
+ </IconButton>
128
+ ) : undefined
126
129
  }
127
130
  error={showError ? error : undefined}
128
131
  inputClassName={error ? "text-red-500 dark:text-red-600" : ""} />
@@ -59,11 +59,13 @@ export function UserSelectFieldBinding({
59
59
  />
60
60
  </PropertyIdCopyTooltip>}
61
61
  endAdornment={
62
- property.clearable && !disabled && value && <IconButton
63
- size="small"
64
- onClick={handleClearClick}>
65
- <CloseIcon size={"small"}/>
66
- </IconButton>
62
+ (property.nullable || property.clearable) && !disabled && value !== null && value !== undefined ? (
63
+ <IconButton
64
+ size="small"
65
+ onClick={handleClearClick}>
66
+ <CloseIcon size={"small"}/>
67
+ </IconButton>
68
+ ) : undefined
67
69
  }
68
70
  onValueChange={(updatedValue: string) => {
69
71
  const newValue = updatedValue || null;
@@ -31,3 +31,4 @@ export * from "./useBuildLocalConfigurationPersistence";
31
31
  export * from "./useBuildModeController";
32
32
 
33
33
  export * from "./useValidateAuthenticator";
34
+ export * from "./useTranslation";
@@ -34,7 +34,6 @@ import { getParentReferencesFromPath } from "../util/parent_references_from_path
34
34
  const DEFAULT_BASE_PATH = "/";
35
35
  const DEFAULT_COLLECTION_PATH = "/c";
36
36
 
37
- export const NAVIGATION_DEFAULT_GROUP_NAME = "Views";
38
37
  export const NAVIGATION_ADMIN_GROUP_NAME = "Admin";
39
38
 
40
39
  export type BuildNavigationContextProps<EC extends EntityCollection, USER extends User> = {
@@ -122,10 +121,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
122
121
 
123
122
  const navigate = useNavigate();
124
123
 
125
- const collectionsRef = useRef<EntityCollection[] | undefined>();
126
- const viewsRef = useRef<CMSView[] | undefined>();
127
- const adminViewsRef = useRef<CMSView[] | undefined>();
128
- const navigationEntriesOrderRef = useRef<string[] | undefined>();
124
+ const collectionsRef = useRef<EntityCollection[] | undefined>(undefined);
125
+ const viewsRef = useRef<CMSView[] | undefined>(undefined);
126
+ const adminViewsRef = useRef<CMSView[] | undefined>(undefined);
127
+ const navigationEntriesOrderRef = useRef<string[] | undefined>(undefined);
129
128
 
130
129
  const [initialised, setInitialised] = useState<boolean>(false);
131
130
 
@@ -189,7 +188,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
189
188
  path: pathKey,
190
189
  collection,
191
190
  description: collection.description?.trim(),
192
- group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
191
+ group: groupName
193
192
  });
194
193
  return acc;
195
194
  }, [] as NavigationEntry[]),
@@ -217,7 +216,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
217
216
  path: view.path,
218
217
  view,
219
218
  description: view.description?.trim(),
220
- group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
219
+ group: groupName
221
220
  });
222
221
  return acc;
223
222
  }, [] as NavigationEntry[]),
@@ -242,7 +241,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
242
241
  }, [] as NavigationEntry[])
243
242
  ];
244
243
 
245
- const groupOrderValue = (groupName?: string): number => {
244
+ const groupOrderValue = (groupName?: string | null): number => {
246
245
  if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
247
246
  return 0; // Other groups
248
247
  };
@@ -266,7 +265,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
266
265
 
267
266
  const collectedGroupsFromEntries = navigationEntries
268
267
  .map(e => e.group)
269
- .filter(Boolean) as string[];
268
+ .filter((g): g is string => g !== null && Boolean(g));
269
+
270
+ // Check if there are any ungrouped entries
271
+ const hasUngroupedEntries = navigationEntries.some(e => e.group === null && e.type !== "admin");
270
272
 
271
273
  // Preserve order from finalNavigationGroupMappings (persisted order)
272
274
  const groupsFromMappings = finalNavigationGroupMappings.map(g => g.name);
@@ -284,7 +286,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
284
286
  const uniqueGroupsArray = [...new Set(allDefinedGroups)];
285
287
  const adminGroups = uniqueGroupsArray.filter(g => g === NAVIGATION_ADMIN_GROUP_NAME);
286
288
  const nonAdminGroups = uniqueGroupsArray.filter(g => g !== NAVIGATION_ADMIN_GROUP_NAME);
287
- const uniqueGroups = [...nonAdminGroups, ...adminGroups];
289
+ // Place null (ungrouped) first if there are ungrouped entries
290
+ const uniqueGroups: (string | null)[] = [
291
+ ...(hasUngroupedEntries ? [null] : []),
292
+ ...nonAdminGroups,
293
+ ...adminGroups
294
+ ];
288
295
 
289
296
  return {
290
297
  allowDragAndDrop: plugins?.some(plugin => plugin.homePage?.allowDragAndDrop) ?? false,
@@ -487,8 +494,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
487
494
 
488
495
  const urlPathToDataPath = useCallback((path: string): string => {
489
496
  const decodedPath = decodeURIComponent(path);
490
- if (decodedPath.startsWith(fullCollectionPath))
491
- return decodedPath.replace(fullCollectionPath, "");
497
+ const withoutHash = decodedPath.split("#")[0];
498
+ const cleanPath = withoutHash.split("?")[0];
499
+ if (cleanPath.startsWith(fullCollectionPath))
500
+ return cleanPath.replace(fullCollectionPath, "");
492
501
  throw Error("Expected path starting with " + fullCollectionPath);
493
502
  }, [fullCollectionPath]);
494
503
 
@@ -565,9 +574,27 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
565
574
  }
566
575
 
567
576
  function encodePath(input: string) {
568
- return encodeURIComponent(removeInitialAndTrailingSlashes(input))
569
- .replaceAll("%2F", "/")
570
- .replaceAll("%23", "#");
577
+ const cleanInput = removeInitialAndTrailingSlashes(input);
578
+ const [pathPart, rest] = cleanInput.split("?", 2);
579
+
580
+ let encodedPath = encodeURIComponent(pathPart).replaceAll("%2F", "/");
581
+ let result = encodedPath;
582
+
583
+ if (rest !== undefined) {
584
+ const [searchPart, hashPart] = rest.split("#", 2);
585
+ result += `?${searchPart}`;
586
+ if (hashPart !== undefined) {
587
+ result += `#${hashPart}`;
588
+ }
589
+ } else {
590
+ const [pathOnly, hashOnly] = cleanInput.split("#", 2);
591
+ if (hashOnly !== undefined) {
592
+ encodedPath = encodeURIComponent(pathOnly).replaceAll("%2F", "/");
593
+ result = `${encodedPath}#${hashOnly}`;
594
+ }
595
+ }
596
+
597
+ return result;
571
598
  }
572
599
 
573
600
  function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
@@ -662,12 +689,12 @@ async function resolveCMSViews(
662
689
  return resolvedViews;
663
690
  }
664
691
 
665
- function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
692
+ function getGroup(collectionOrView: EntityCollection<any, any> | CMSView): string | null {
666
693
  const trimmed = collectionOrView.group?.trim();
667
694
  if (!trimmed || trimmed === "") {
668
- return NAVIGATION_DEFAULT_GROUP_NAME;
695
+ return null;
669
696
  }
670
- return trimmed ?? NAVIGATION_DEFAULT_GROUP_NAME;
697
+ return trimmed;
671
698
  }
672
699
 
673
700
  function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
@@ -803,7 +830,7 @@ function computeNavigationGroups({
803
830
  (collections ?? []).forEach(collection => {
804
831
  const entry = collection.id ?? collection.path;
805
832
  if (!assignedEntries.has(entry)) {
806
- const groupName = getGroup(collection);
833
+ const groupName = getGroup(collection) ?? "__default__";
807
834
  if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
808
835
  unassignedGroupMap[groupName].push(entry);
809
836
  }
@@ -813,7 +840,7 @@ function computeNavigationGroups({
813
840
  (views ?? []).forEach(view => {
814
841
  const entry = view.path;
815
842
  if (!assignedEntries.has(entry)) {
816
- const groupName = getGroup(view);
843
+ const groupName = getGroup(view) ?? "__default__";
817
844
  if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
818
845
  unassignedGroupMap[groupName].push(entry);
819
846
  }
@@ -841,7 +868,7 @@ function computeNavigationGroups({
841
868
 
842
869
  // Add collections
843
870
  (collections ?? []).forEach(collection => {
844
- const name = getGroup(collection);
871
+ const name = getGroup(collection) ?? "__default__";
845
872
  const entry = collection.id ?? collection.path;
846
873
  if (!groupMap[name]) groupMap[name] = [];
847
874
  groupMap[name].push(entry);
@@ -849,7 +876,7 @@ function computeNavigationGroups({
849
876
 
850
877
  // Add views
851
878
  (views ?? []).forEach(view => {
852
- const name = getGroup(view);
879
+ const name = getGroup(view) ?? "__default__";
853
880
  const entry = view.path;
854
881
  if (!groupMap[name]) groupMap[name] = [];
855
882
  groupMap[name].push(entry);
@@ -10,7 +10,7 @@ const STORAGE_KEY_PREFIX = "firecms-collapsed-groups";
10
10
  * @param groupNames - Array of group names to track
11
11
  * @param namespace - Namespace for localStorage key (e.g., "home", "drawer") to allow independent state
12
12
  */
13
- export function useCollapsedGroups(groupNames: string[], namespace: string = "default") {
13
+ export function useCollapsedGroups(groupNames: (string | null)[], namespace: string = "default") {
14
14
  const storageKey = `${STORAGE_KEY_PREFIX}-${namespace}`;
15
15
 
16
16
  // Load collapsed groups from localStorage on mount
@@ -37,7 +37,7 @@ export function useCollapsedGroups(groupNames: string[], namespace: string = "de
37
37
  // Only clean up if we have actual groups loaded (avoid cleaning up during initial load)
38
38
  if (groupNames.length === 0) return;
39
39
 
40
- const currentGroupNames = new Set(groupNames);
40
+ const currentGroupNames = new Set(groupNames.map(g => g ?? "__default__"));
41
41
 
42
42
  setCollapsedGroups(prev => {
43
43
  const cleaned = Object.fromEntries(
@@ -56,12 +56,13 @@ export function useCollapsedGroups(groupNames: string[], namespace: string = "de
56
56
  });
57
57
  }, [groupNames]);
58
58
 
59
- const isGroupCollapsed = useCallback((name: string) => {
60
- return !!collapsedGroups[name];
59
+ const isGroupCollapsed = useCallback((name?: string | null) => {
60
+ return !!collapsedGroups[name ?? "__default__"];
61
61
  }, [collapsedGroups]);
62
62
 
63
- const toggleGroupCollapsed = useCallback((name: string) => {
64
- setCollapsedGroups(prev => ({ ...prev, [name]: !prev[name] }));
63
+ const toggleGroupCollapsed = useCallback((name?: string | null) => {
64
+ const key = name ?? "__default__";
65
+ setCollapsedGroups(prev => ({ ...prev, [key]: !prev[key] }));
65
66
  }, []);
66
67
 
67
68
  return {
@@ -0,0 +1,31 @@
1
+ import { useTranslation as useI18nTranslation } from "react-i18next";
2
+
3
+ const FIRECMS_NS = "firecms_core";
4
+
5
+ /**
6
+ * Internal hook for translating FireCMS UI strings.
7
+ *
8
+ * Uses the `firecms_core` i18next namespace that is initialised by
9
+ * `FireCMSi18nProvider`. Do NOT use `react-i18next` directly in internal
10
+ * components — always go through this hook so the namespace is consistent.
11
+ *
12
+ * @example
13
+ * const { t } = useTranslation();
14
+ * <Button>{t("save")}</Button>
15
+ *
16
+ * @internal
17
+ */
18
+ export function useTranslation() {
19
+ const { t, i18n } = useI18nTranslation(FIRECMS_NS);
20
+
21
+ /**
22
+ * Typed translation function scoped to FirecmsTranslations keys.
23
+ * Also supports i18next interpolation variables, e.g.
24
+ * t("add_to_field", { fieldName: "Tags" })
25
+ * t("error_deleting", { message: err.message })
26
+ */
27
+ const typedT = (key: string, vars?: Record<string, string>): string =>
28
+ t(key, vars) as string;
29
+
30
+ return { t: typedT, i18n };
31
+ }