@firecms/core 3.1.0-canary.24c8270 → 3.1.0-canary.75005e4

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 (180) hide show
  1. package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
  2. package/dist/components/ErrorBoundary.d.ts +3 -1
  3. package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
  4. package/dist/components/LanguageToggle.d.ts +1 -0
  5. package/dist/components/UnsavedChangesDialog.d.ts +1 -0
  6. package/dist/components/index.d.ts +1 -0
  7. package/dist/core/DrawerNavigationGroup.d.ts +2 -2
  8. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  9. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  10. package/dist/editor/components/editor-bubble.d.ts +8 -0
  11. package/dist/editor/components/index.d.ts +14 -0
  12. package/dist/editor/editor.d.ts +30 -0
  13. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  14. package/dist/editor/extensions/Image/index.d.ts +6 -0
  15. package/dist/editor/extensions/Image.d.ts +6 -0
  16. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  17. package/dist/editor/extensions/clipboard.d.ts +7 -0
  18. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  19. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  20. package/dist/editor/hooks/useProseMirror.d.ts +14 -0
  21. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  22. package/dist/editor/index.d.ts +2 -0
  23. package/dist/editor/markdown.d.ts +5 -0
  24. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  25. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  26. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  27. package/dist/editor/nodeViews/index.d.ts +6 -0
  28. package/dist/editor/plugins/index.d.ts +2 -0
  29. package/dist/editor/plugins/inputrules.d.ts +6 -0
  30. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  31. package/dist/editor/plugins/slashCommandPlugin.d.ts +11 -0
  32. package/dist/editor/schema.d.ts +2 -0
  33. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  34. package/dist/editor/selectors/color-selector.d.ts +10 -0
  35. package/dist/editor/selectors/link-selector.d.ts +8 -0
  36. package/dist/editor/selectors/node-selector.d.ts +15 -0
  37. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  38. package/dist/editor/types.d.ts +5 -0
  39. package/dist/editor/useProseMirror.d.ts +16 -0
  40. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  41. package/dist/editor/utils/remove_classes.d.ts +1 -0
  42. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  43. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  44. package/dist/hooks/index.d.ts +1 -0
  45. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  46. package/dist/hooks/useCollapsedGroups.d.ts +3 -3
  47. package/dist/hooks/useTranslation.d.ts +17 -0
  48. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  49. package/dist/index.d.ts +4 -0
  50. package/dist/index.es.js +11293 -2142
  51. package/dist/index.es.js.map +1 -1
  52. package/dist/index.umd.js +11274 -2142
  53. package/dist/index.umd.js.map +1 -1
  54. package/dist/locales/de.d.ts +2 -0
  55. package/dist/locales/en.d.ts +10 -0
  56. package/dist/locales/es.d.ts +10 -0
  57. package/dist/locales/fr.d.ts +2 -0
  58. package/dist/locales/hi.d.ts +2 -0
  59. package/dist/locales/it.d.ts +2 -0
  60. package/dist/locales/pt.d.ts +7 -0
  61. package/dist/types/customization_controller.d.ts +2 -1
  62. package/dist/types/firecms.d.ts +2 -1
  63. package/dist/types/index.d.ts +1 -0
  64. package/dist/types/navigation.d.ts +2 -2
  65. package/dist/types/plugins.d.ts +7 -0
  66. package/dist/types/translations.d.ts +646 -0
  67. package/package.json +43 -9
  68. package/src/app/Scaffold.tsx +7 -5
  69. package/src/components/AIIcon.tsx +3 -1
  70. package/src/components/ArrayContainer.tsx +6 -4
  71. package/src/components/ClearFilterSortButton.tsx +6 -3
  72. package/src/components/ConfirmationDialog.tsx +4 -2
  73. package/src/components/DeleteEntityDialog.tsx +10 -7
  74. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  75. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -1
  76. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  77. package/src/components/EntityCollectionView/BoardSortableList.tsx +3 -1
  78. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  79. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +16 -43
  80. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +17 -25
  81. package/src/components/EntityCollectionView/EntityCollectionView.tsx +26 -18
  82. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -3
  83. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -2
  84. package/src/components/EntityCollectionView/FiltersDialog.tsx +8 -5
  85. package/src/components/EntityCollectionView/ViewModeToggle.tsx +11 -8
  86. package/src/components/EntityView.tsx +3 -2
  87. package/src/components/ErrorBoundary.tsx +27 -15
  88. package/src/components/HomePage/DefaultHomePage.tsx +19 -13
  89. package/src/components/HomePage/HomePageDnD.tsx +3 -1
  90. package/src/components/HomePage/NavigationGroup.tsx +3 -1
  91. package/src/components/HomePage/RenameGroupDialog.tsx +15 -13
  92. package/src/components/LanguageToggle.tsx +66 -0
  93. package/src/components/NotFoundPage.tsx +5 -3
  94. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -7
  95. package/src/components/ReferenceWidget.tsx +3 -2
  96. package/src/components/SearchIconsView.tsx +3 -1
  97. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +11 -0
  98. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +15 -2
  99. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +11 -0
  100. package/src/components/UnsavedChangesDialog.tsx +6 -4
  101. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
  102. package/src/components/VirtualTable/VirtualTableHeader.tsx +12 -10
  103. package/src/components/common/default_entity_actions.tsx +4 -0
  104. package/src/components/common/useDataSourceTableController.tsx +5 -14
  105. package/src/components/index.tsx +1 -0
  106. package/src/core/DefaultAppBar.tsx +14 -10
  107. package/src/core/DefaultDrawer.tsx +8 -2
  108. package/src/core/DrawerNavigationGroup.tsx +5 -3
  109. package/src/core/EntityEditView.tsx +3 -2
  110. package/src/core/EntityEditViewFormActions.tsx +24 -17
  111. package/src/core/EntitySidePanel.tsx +4 -3
  112. package/src/core/FireCMS.tsx +33 -6
  113. package/src/editor/components/SlashCommandMenu.tsx +348 -0
  114. package/src/editor/components/editor-bubble-item.tsx +32 -0
  115. package/src/editor/components/editor-bubble.tsx +118 -0
  116. package/src/editor/components/index.ts +12 -0
  117. package/src/editor/editor.tsx +307 -0
  118. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  119. package/src/editor/extensions/Image/index.ts +133 -0
  120. package/src/editor/extensions/Image.ts +144 -0
  121. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  122. package/src/editor/extensions/clipboard.ts +72 -0
  123. package/src/editor/extensions/custom-keymap.ts +24 -0
  124. package/src/editor/extensions/drag-and-drop.tsx +472 -0
  125. package/src/editor/hooks/useProseMirror.ts +115 -0
  126. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  127. package/src/editor/index.ts +2 -0
  128. package/src/editor/markdown.ts +110 -0
  129. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  130. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  131. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  132. package/src/editor/nodeViews/index.ts +35 -0
  133. package/src/editor/plugins/index.ts +55 -0
  134. package/src/editor/plugins/inputrules.ts +82 -0
  135. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  136. package/src/editor/plugins/slashCommandPlugin.ts +49 -0
  137. package/src/editor/schema.ts +228 -0
  138. package/src/editor/selectors/ai-selector.tsx +111 -0
  139. package/src/editor/selectors/color-selector.tsx +200 -0
  140. package/src/editor/selectors/link-selector.tsx +118 -0
  141. package/src/editor/selectors/node-selector.tsx +157 -0
  142. package/src/editor/selectors/text-buttons.tsx +86 -0
  143. package/src/editor/types.ts +6 -0
  144. package/src/editor/useProseMirror.ts +126 -0
  145. package/src/editor/utils/prosemirror-utils.ts +78 -0
  146. package/src/editor/utils/remove_classes.ts +17 -0
  147. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  148. package/src/form/EntityForm.tsx +7 -3
  149. package/src/form/EntityFormActions.tsx +19 -12
  150. package/src/form/PropertyFieldBinding.tsx +3 -2
  151. package/src/form/components/LocalChangesMenu.tsx +13 -13
  152. package/src/form/components/StorageItemPreview.tsx +3 -2
  153. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -4
  154. package/src/form/field_bindings/BlockFieldBinding.tsx +5 -2
  155. package/src/form/field_bindings/KeyValueFieldBinding.tsx +23 -18
  156. package/src/form/field_bindings/MapFieldBinding.tsx +4 -3
  157. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +3 -3
  158. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -1
  159. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +4 -2
  160. package/src/hooks/index.tsx +1 -0
  161. package/src/hooks/useBuildNavigationController.tsx +20 -13
  162. package/src/hooks/useCollapsedGroups.ts +7 -6
  163. package/src/hooks/useTranslation.ts +31 -0
  164. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  165. package/src/index.ts +4 -0
  166. package/src/locales/de.ts +691 -0
  167. package/src/locales/en.ts +703 -0
  168. package/src/locales/es.ts +703 -0
  169. package/src/locales/fr.ts +691 -0
  170. package/src/locales/hi.ts +691 -0
  171. package/src/locales/it.ts +691 -0
  172. package/src/locales/pt.ts +700 -0
  173. package/src/preview/components/UrlComponentPreview.tsx +4 -2
  174. package/src/preview/components/UserPreview.tsx +3 -1
  175. package/src/types/customization_controller.tsx +2 -1
  176. package/src/types/firecms.tsx +2 -1
  177. package/src/types/index.ts +1 -0
  178. package/src/types/navigation.ts +2 -2
  179. package/src/types/plugins.tsx +8 -0
  180. package/src/types/translations.ts +725 -0
@@ -16,7 +16,7 @@ import {
16
16
  WarningIcon
17
17
  } from "@firecms/ui";
18
18
  import { FormexController } from "@firecms/formex";
19
- import { useSnackbarController } from "../../hooks";
19
+ import { useSnackbarController, useTranslation } from "../../hooks";
20
20
  import { mergeDeep } from "../../util";
21
21
  import { flattenKeys, removeEntityFromCache } from "../../util/entity_cache";
22
22
  import { ResolvedProperties } from "../../types";
@@ -39,6 +39,7 @@ export function LocalChangesMenu<M extends object>({
39
39
  }: LocalChangesMenuProps<M>) {
40
40
 
41
41
  const snackbarController = useSnackbarController();
42
+ const { t } = useTranslation();
42
43
  const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
43
44
  const [open, setOpen] = useState(false);
44
45
 
@@ -62,7 +63,7 @@ export function LocalChangesMenu<M extends object>({
62
63
  formex.setValues(mergedValues);
63
64
  snackbarController.open({
64
65
  type: "info",
65
- message: "Local changes applied to the form"
66
+ message: t("local_changes_applied")
66
67
  });
67
68
  handleCloseMenu();
68
69
  onClearLocalChanges?.();
@@ -72,7 +73,7 @@ export function LocalChangesMenu<M extends object>({
72
73
  removeEntityFromCache(cacheKey);
73
74
  snackbarController.open({
74
75
  type: "info",
75
- message: "Local changes discarded"
76
+ message: t("local_changes_discarded")
76
77
  });
77
78
  handleCloseMenu();
78
79
  onClearLocalChanges?.();
@@ -90,7 +91,7 @@ export function LocalChangesMenu<M extends object>({
90
91
  onClick={handleOpenMenu}
91
92
  >
92
93
  <WarningIcon size={"smallest"} className={"mr-1 text-yellow-600 dark:text-yellow-400"}/>
93
- Unsaved Local changes
94
+ {t("unsaved_local_changes")}
94
95
  <KeyboardArrowDownIcon size={"smallest"}/>
95
96
  </Button>
96
97
  }
@@ -98,12 +99,11 @@ export function LocalChangesMenu<M extends object>({
98
99
  onOpenChange={setOpen}
99
100
  >
100
101
  <div className={"max-w-xs px-4 py-4 text-sm text-gray-700 dark:text-gray-300"}>
101
- This document was edited locally and has unsaved changes. These local changes will be lost if you
102
- don't apply them.
102
+ {t("unsaved_local_changes_description")}
103
103
  </div>
104
- <MenuItem dense onClick={handlePreview}><VisibilityIcon size={"small"}/>Preview Changes</MenuItem>
105
- <MenuItem dense onClick={handleApply}><CheckIcon size={"small"}/>Apply Changes</MenuItem>
106
- <MenuItem dense onClick={handleDiscard}><CancelIcon size={"small"}/>Discard Local Changes</MenuItem>
104
+ <MenuItem dense onClick={handlePreview}><VisibilityIcon size={"small"}/>{t("preview_changes")}</MenuItem>
105
+ <MenuItem dense onClick={handleApply}><CheckIcon size={"small"}/>{t("apply_changes")}</MenuItem>
106
+ <MenuItem dense onClick={handleDiscard}><CancelIcon size={"small"}/>{t("discard_local_changes")}</MenuItem>
107
107
  </Menu>
108
108
 
109
109
  <Dialog
@@ -111,10 +111,10 @@ export function LocalChangesMenu<M extends object>({
111
111
  onOpenChange={setPreviewDialogOpen}
112
112
  maxWidth={"4xl"}
113
113
  >
114
- <DialogTitle variant={"h6"}>Preview Local Changes</DialogTitle>
114
+ <DialogTitle variant={"h6"}>{t("preview_local_changes")}</DialogTitle>
115
115
  <DialogContent className={"my-4"}>
116
116
  <Typography variant={"body2"} className={"mb-4"}>
117
- These are the local changes that will be applied to the form.
117
+ {t("preview_local_changes_description")}
118
118
  </Typography>
119
119
  <div className={`border rounded-lg ${defaultBorderMixin}`} style={{
120
120
  maxHeight: 520,
@@ -127,7 +127,7 @@ export function LocalChangesMenu<M extends object>({
127
127
  </div>
128
128
  </DialogContent>
129
129
  <DialogActions>
130
- <Button onClick={() => setPreviewDialogOpen(false)}>Close</Button>
130
+ <Button onClick={() => setPreviewDialogOpen(false)}>{t("close")}</Button>
131
131
  <Button
132
132
  variant={"filled"}
133
133
  onClick={() => {
@@ -135,7 +135,7 @@ export function LocalChangesMenu<M extends object>({
135
135
  setPreviewDialogOpen(false);
136
136
  }}
137
137
  >
138
- Apply changes
138
+ {t("apply_changes")}
139
139
  </Button>
140
140
  </DialogActions>
141
141
  </Dialog>
@@ -5,6 +5,7 @@ import { PreviewSize, PropertyPreview } from "../../preview";
5
5
 
6
6
  import { cls, DescriptionIcon, IconButton, paperMixin, RemoveIcon, Tooltip } from "@firecms/ui";
7
7
  import { ErrorBoundary } from "../../components";
8
+ import { useTranslation } from "../../hooks/useTranslation";
8
9
 
9
10
  interface StorageItemPreviewProps {
10
11
  name: string;
@@ -27,7 +28,7 @@ export function StorageItemPreview({
27
28
  placeholder,
28
29
  className
29
30
  }: StorageItemPreviewProps) {
30
-
31
+ const { t } = useTranslation();
31
32
  return (
32
33
  <div className={cls(
33
34
  "relative border-box flex items-center justify-center",
@@ -41,7 +42,7 @@ export function StorageItemPreview({
41
42
 
42
43
  <Tooltip
43
44
  asChild={true}
44
- title="Remove">
45
+ title={t("remove")}>
45
46
  <IconButton
46
47
  size={"small"}
47
48
  onClick={(event) => {
@@ -4,8 +4,7 @@ import { ReferencePreview } from "../../preview";
4
4
  import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
5
5
  import { ArrayContainer, ArrayEntryParams, ErrorView } from "../../components";
6
6
  import { getIconForProperty, getReferenceFrom } from "../../util";
7
-
8
- import { useNavigationController, useReferenceDialog } from "../../hooks";
7
+ import { useNavigationController, useReferenceDialog, useTranslation } from "../../hooks";
9
8
  import { Button, cls, EditIcon, ExpandablePanel, fieldBackgroundMixin, Typography } from "@firecms/ui";
10
9
  import { useClearRestoreValue } from "../useClearRestoreValue";
11
10
 
@@ -48,6 +47,7 @@ export function ArrayOfReferencesFieldBinding({
48
47
  setValue
49
48
  });
50
49
 
50
+ const { t } = useTranslation();
51
51
  const navigationController = useNavigationController();
52
52
  const collection: EntityCollection | undefined = useMemo(() => {
53
53
  return ofProperty.path ? navigationController.getCollection(ofProperty.path) : undefined;
@@ -84,7 +84,7 @@ export function ArrayOfReferencesFieldBinding({
84
84
  }: ArrayEntryParams) => {
85
85
  const entryValue = value && value.length > index ? value[index] : undefined;
86
86
  if (!entryValue)
87
- return <div>Internal ERROR</div>;
87
+ return <div>{t("internal_error")}</div>;
88
88
  return (
89
89
  <ReferencePreview
90
90
  key={internalId}
@@ -132,7 +132,7 @@ export function ArrayOfReferencesFieldBinding({
132
132
  disabled={isSubmitting}
133
133
  onClick={onEntryClick}>
134
134
  <EditIcon size={"small"}/>
135
- Edit {property.name}
135
+ {t("edit_name", { name: property.name ?? "" })}
136
136
  </Button>
137
137
  </div>}
138
138
  </>;
@@ -11,6 +11,7 @@ import { DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE } from "../../util/common";
11
11
  import { cls, ExpandablePanel, paperMixin, Select, SelectItem, Typography } from "@firecms/ui";
12
12
  import { useClearRestoreValue } from "../useClearRestoreValue";
13
13
  import { ArrayContainer, ArrayEntryParams } from "../../components";
14
+ import { useTranslation } from "../../hooks/useTranslation";
14
15
 
15
16
  /**
16
17
  * If the `oneOf` property is specified, this fields render each array entry as
@@ -37,6 +38,7 @@ export function BlockFieldBinding<T extends Array<any>>({
37
38
  }: FieldProps<T>) {
38
39
 
39
40
  const minimalistView = minimalistViewProp || property.minimalistView;
41
+ const { t } = useTranslation();
40
42
 
41
43
  if (!property.oneOf)
42
44
  throw Error("ArrayOneOfField misconfiguration. Property `oneOf` not set");
@@ -84,7 +86,7 @@ export function BlockFieldBinding<T extends Array<any>>({
84
86
  const body = <ArrayContainer value={value}
85
87
  className={"flex flex-col gap-3"}
86
88
  droppableId={propertyKey}
87
- addLabel={property.name ? "Add entry to " + property.name : "Add entry"}
89
+ addLabel={property.name ? t("add_to_field", { fieldName: property.name }) : t("add_entry")}
88
90
  buildEntry={buildEntry}
89
91
  onInternalIdAdded={setLastAddedId}
90
92
  disabled={isSubmitting || Boolean(property.disabled)}
@@ -167,6 +169,7 @@ function BlockEntry({
167
169
  const [typeInternal, setTypeInternal] = useState<string | undefined>(type ?? undefined);
168
170
 
169
171
  const formex = useFormex();
172
+ const { t } = useTranslation();
170
173
 
171
174
  useEffect(() => {
172
175
  if (!type) {
@@ -227,7 +230,7 @@ function BlockEntry({
227
230
  <Select
228
231
  className="mb-2"
229
232
  placeholder={<Typography variant={"caption"}
230
- className={"px-4 py-2 font-medium"}>Type</Typography>}
233
+ className={"px-4 py-2 font-medium"}>{t("type")}</Typography>}
231
234
  size={"medium"}
232
235
  fullWidth={true}
233
236
  position={"item-aligned"}
@@ -22,6 +22,7 @@ import {
22
22
  import { getDefaultValueForDataType, getIconForProperty } from "../../util";
23
23
  import { useCustomizationController } from "../../hooks";
24
24
  import { getIn } from "@firecms/formex";
25
+ import { useTranslation } from "../../hooks/useTranslation";
25
26
 
26
27
  type MapEditViewRowState = [number, {
27
28
  key: string,
@@ -54,6 +55,7 @@ export function KeyValueFieldBinding({
54
55
  throw Error(`Your property ${propertyKey} needs to have the 'keyValue' prop in order to use this field binding`);
55
56
  }
56
57
 
58
+ const { t } = useTranslation();
57
59
  const initialValues = getIn(context.formex.initialValues, propertyKey);
58
60
 
59
61
  const mapFormView = <MapEditView value={value}
@@ -103,6 +105,7 @@ function MapEditView<T extends Record<string, any>>({
103
105
  fieldName,
104
106
  disabled
105
107
  }: MapEditViewParams<T>) {
108
+ const { t } = useTranslation();
106
109
  const [internalState, setInternalState] = React.useState<MapEditViewRowState[]>(
107
110
  Object.keys(initialValue ?? {}).map((key) => [getRandomId(), {
108
111
  key,
@@ -230,7 +233,7 @@ function MapEditView<T extends Record<string, any>>({
230
233
  }]]);
231
234
  }
232
235
  }>
233
- {fieldName ? `Add to ${fieldName}` : "Add"}
236
+ {fieldName ? t("add_to_field", { fieldName }) : t("add_entry")}
234
237
  </Button>
235
238
 
236
239
  </div>;
@@ -261,12 +264,13 @@ function MapKeyValueRow<T extends Record<string, any>>({
261
264
  }) {
262
265
 
263
266
  const { locale } = useCustomizationController();
267
+ const { t } = useTranslation();
264
268
 
265
269
  function buildInput(entryValue: any, fieldKey: string, dataType: DataType) {
266
270
  if (dataType === "string" || dataType === "number") {
267
271
  return <TextField
268
272
  key={dataType}
269
- placeholder={"value"}
273
+ placeholder={t("value")}
270
274
  value={entryValue}
271
275
  type={dataType === "number" ? "number" : "text"}
272
276
  size={"medium"}
@@ -325,7 +329,7 @@ function MapKeyValueRow<T extends Record<string, any>>({
325
329
  <ArrayContainer value={entryValue}
326
330
  newDefaultEntry={""}
327
331
  droppableId={rowId.toString()}
328
- addLabel={fieldKey ? `Add to ${fieldKey}` : "Add"}
332
+ addLabel={fieldKey ? t("add_to_field", { fieldName: fieldKey }) : t("add_entry")}
329
333
  size={"small"}
330
334
  disabled={disabled || !fieldKey}
331
335
  canAddElements={true}
@@ -370,7 +374,7 @@ function MapKeyValueRow<T extends Record<string, any>>({
370
374
  } else {
371
375
  return <Typography
372
376
  variant={"caption"}>
373
- {`Data type ${dataType} not supported yet`}
377
+ {t("data_type_not_supported", { dataType })}
374
378
  </Typography>;
375
379
  }
376
380
  }
@@ -386,7 +390,7 @@ function MapKeyValueRow<T extends Record<string, any>>({
386
390
  <div className="w-[300px] max-w-[30%]">
387
391
  <TextField
388
392
  value={fieldKey}
389
- placeholder={"key"}
393
+ placeholder={t("key")}
390
394
  disabled={disabled || (entryValue !== undefined && entryValue !== null && entryValue !== "")}
391
395
  size={"medium"}
392
396
  onChange={(event) => {
@@ -404,17 +408,17 @@ function MapKeyValueRow<T extends Record<string, any>>({
404
408
  </IconButton>}
405
409
  >
406
410
  <MenuItem dense
407
- onClick={() => doUpdateDataType("string")}>string</MenuItem>
411
+ onClick={() => doUpdateDataType("string")}>{t("string")}</MenuItem>
408
412
  <MenuItem dense
409
- onClick={() => doUpdateDataType("number")}>number</MenuItem>
413
+ onClick={() => doUpdateDataType("number")}>{t("number")}</MenuItem>
410
414
  <MenuItem dense
411
- onClick={() => doUpdateDataType("boolean")}>boolean</MenuItem>
415
+ onClick={() => doUpdateDataType("boolean")}>{t("boolean")}</MenuItem>
412
416
  <MenuItem dense
413
- onClick={() => doUpdateDataType("date")}>date</MenuItem>
417
+ onClick={() => doUpdateDataType("date")}>{t("date")}</MenuItem>
414
418
  <MenuItem dense
415
- onClick={() => doUpdateDataType("map")}>map</MenuItem>
419
+ onClick={() => doUpdateDataType("map")}>{t("map")}</MenuItem>
416
420
  <MenuItem dense
417
- onClick={() => doUpdateDataType("array")}>array</MenuItem>
421
+ onClick={() => doUpdateDataType("array")}>{t("array")}</MenuItem>
418
422
  </Menu>
419
423
 
420
424
  <IconButton aria-label="delete"
@@ -446,6 +450,7 @@ function ArrayKeyValueRow<T>({
446
450
  }) {
447
451
 
448
452
  const { locale } = useCustomizationController();
453
+ const { t } = useTranslation();
449
454
  const [selectedDataType, setSelectedDataType] = useState<DataType>(getDataType(value) ?? "string");
450
455
 
451
456
  function doUpdateDataType(dataType: DataType) {
@@ -487,7 +492,7 @@ function ArrayKeyValueRow<T>({
487
492
  }}/>;
488
493
  } else if (dataType === "array") {
489
494
  return <Typography variant={"caption"}>
490
- Arrays of arrays are not supported.
495
+ {t("arrays_of_arrays_not_supported")}
491
496
  </Typography>;
492
497
  } else if (dataType === "map") {
493
498
  return <div className={cls(defaultBorderMixin, "ml-2 pl-2 border-l border-solid")}>
@@ -499,7 +504,7 @@ function ArrayKeyValueRow<T>({
499
504
  } else {
500
505
  return <Typography
501
506
  variant={"caption"}>
502
- {`Data type ${dataType} not supported yet`}
507
+ {t("data_type_not_supported", { dataType })}
503
508
  </Typography>;
504
509
  }
505
510
  }
@@ -519,15 +524,15 @@ function ArrayKeyValueRow<T>({
519
524
  <ArrowDropDownIcon/>
520
525
  </IconButton>}>
521
526
  <MenuItem dense
522
- onClick={() => doUpdateDataType("string")}>string</MenuItem>
527
+ onClick={() => doUpdateDataType("string")}>{t("string")}</MenuItem>
523
528
  <MenuItem dense
524
- onClick={() => doUpdateDataType("number")}>number</MenuItem>
529
+ onClick={() => doUpdateDataType("number")}>{t("number")}</MenuItem>
525
530
  <MenuItem dense
526
- onClick={() => doUpdateDataType("boolean")}>boolean</MenuItem>
531
+ onClick={() => doUpdateDataType("boolean")}>{t("boolean")}</MenuItem>
527
532
  <MenuItem dense
528
- onClick={() => doUpdateDataType("map")}>map</MenuItem>
533
+ onClick={() => doUpdateDataType("map")}>{t("map")}</MenuItem>
529
534
  <MenuItem dense
530
- onClick={() => doUpdateDataType("date")}>date</MenuItem>
535
+ onClick={() => doUpdateDataType("date")}>{t("date")}</MenuItem>
531
536
  </Menu>
532
537
 
533
538
  </Typography>
@@ -7,6 +7,7 @@ import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
7
7
  import { FormEntry } from "../components/FormEntry";
8
8
  import { PropertyFieldBinding } from "../PropertyFieldBinding";
9
9
  import { cls, ExpandablePanel, InputLabel, Select, SelectItem } from "@firecms/ui";
10
+ import { useTranslation } from "../../hooks";
10
11
 
11
12
  /**
12
13
  * Field that renders the children property fields
@@ -91,7 +92,7 @@ export function MapFieldBinding({
91
92
  }
92
93
  </div>
93
94
 
94
- {/*{pickOnlySomeKeys && buildPickKeysSelect(disabled, property.properties, setValue, value)}*/}
95
+ {/*{pickOnlySomeKeys && buildPickKeysSelect(disabled, property.properties, setValue, value, t)}*/}
95
96
 
96
97
  </>
97
98
  ;
@@ -128,7 +129,7 @@ export function MapFieldBinding({
128
129
  );
129
130
  }
130
131
 
131
- const buildPickKeysSelect = (disabled: boolean, properties: Properties, setValue: (value: any) => void, value: any) => {
132
+ const buildPickKeysSelect = (disabled: boolean, properties: Properties, setValue: (value: any) => void, value: any, t: any) => {
132
133
 
133
134
  const keys = Object.keys(properties)
134
135
  .filter((key) => !value || !(key in value));
@@ -143,7 +144,7 @@ const buildPickKeysSelect = (disabled: boolean, properties: Properties, setValue
143
144
  if (!keys.length) return <></>;
144
145
 
145
146
  return <div className={"m-4"}>
146
- <InputLabel>Add property</InputLabel>
147
+ <InputLabel>{t("add_property")}</InputLabel>
147
148
  <Select
148
149
  value={""}
149
150
  size={"large"}
@@ -12,7 +12,7 @@ import {
12
12
  useStorageSource
13
13
  } from "../../index";
14
14
  import { cls, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundMixin } from "@firecms/ui";
15
- import { FireCMSEditor, FireCMSEditorProps } from "@firecms/editor";
15
+ import { FireCMSEditor, FireCMSEditorProps } from "../../editor";
16
16
  import { resolveProperty, resolveStorageFilenameString, resolveStoragePathString } from "../../util";
17
17
 
18
18
  interface MarkdownEditorFieldProps {
@@ -55,12 +55,12 @@ export function MarkdownEditorFieldBinding({
55
55
  }
56
56
  internalValue.current = content;
57
57
  setValue(content);
58
- }, [setValue]);
58
+ }, [setValue, value]);
59
59
 
60
60
  useEffect(() => {
61
61
  if (internalValue.current !== value) {
62
62
  internalValue.current = value;
63
- setFieldVersion(fieldVersion + 1);
63
+ setFieldVersion(v => v + 1);
64
64
  }
65
65
  }, [value]);
66
66
 
@@ -7,6 +7,7 @@ import { PropertyFieldBinding } from "../PropertyFieldBinding";
7
7
  import { ExpandablePanel, Typography } from "@firecms/ui";
8
8
  import { useClearRestoreValue } from "../useClearRestoreValue";
9
9
  import { useAuthController } from "../../hooks";
10
+ import { useTranslation } from "../../hooks/useTranslation";
10
11
 
11
12
  /**
12
13
  * Generic array field that allows reordering and renders the child property
@@ -34,6 +35,7 @@ export function RepeatFieldBinding<T extends Array<any>>({
34
35
 
35
36
  const authController = useAuthController();
36
37
  const minimalistView = minimalistViewProp || property.minimalistView;
38
+ const { t } = useTranslation();
37
39
 
38
40
  if (!property.of)
39
41
  throw Error("RepeatFieldBinding misconfiguration. Property `of` not set");
@@ -87,7 +89,7 @@ export function RepeatFieldBinding<T extends Array<any>>({
87
89
  const canAddElements = !property.disabled && !isSubmitting && !disabled && (property.canAddElements || property.canAddElements === undefined);
88
90
  const sortable = property.sortable === undefined ? true : property.sortable;
89
91
  const arrayContainer = <ArrayContainer droppableId={propertyKey}
90
- addLabel={property.name ? "Add entry to " + property.name : "Add entry"}
92
+ addLabel={property.name ? t("add_to_field", { fieldName: property.name }) : t("add_entry")}
91
93
  value={value}
92
94
  buildEntry={buildEntry}
93
95
  onInternalIdAdded={setLastAddedId}
@@ -41,6 +41,7 @@ import {
41
41
  Typography
42
42
  } from "@firecms/ui";
43
43
  import { useClearRestoreValue } from "../useClearRestoreValue";
44
+ import { useTranslation } from "../../hooks/useTranslation";
44
45
 
45
46
  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
47
  const disabledClasses = fieldBackgroundDisabledMixin;
@@ -387,6 +388,7 @@ export function StorageUpload({
387
388
  storage,
388
389
  storagePathBuilder,
389
390
  }: StorageUploadProps) {
391
+ const { t } = useTranslation();
390
392
 
391
393
  if (multipleFilesSupported) {
392
394
  const arrayProperty = property as ResolvedArrayProperty<string[]>;
@@ -461,8 +463,8 @@ export function StorageUpload({
461
463
  }, [value, multipleFilesSupported, onChange, setInternalValue]);
462
464
 
463
465
  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";
466
+ ? t("drag_drop_multiple")
467
+ : t("drag_drop_single");
466
468
 
467
469
  const renderProperty: ResolvedStringProperty = multipleFilesSupported
468
470
  ? (property as ResolvedArrayProperty<string[]>).of as ResolvedStringProperty
@@ -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> = {
@@ -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,
@@ -682,12 +689,12 @@ async function resolveCMSViews(
682
689
  return resolvedViews;
683
690
  }
684
691
 
685
- function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
692
+ function getGroup(collectionOrView: EntityCollection<any, any> | CMSView): string | null {
686
693
  const trimmed = collectionOrView.group?.trim();
687
694
  if (!trimmed || trimmed === "") {
688
- return NAVIGATION_DEFAULT_GROUP_NAME;
695
+ return null;
689
696
  }
690
- return trimmed ?? NAVIGATION_DEFAULT_GROUP_NAME;
697
+ return trimmed;
691
698
  }
692
699
 
693
700
  function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
@@ -823,7 +830,7 @@ function computeNavigationGroups({
823
830
  (collections ?? []).forEach(collection => {
824
831
  const entry = collection.id ?? collection.path;
825
832
  if (!assignedEntries.has(entry)) {
826
- const groupName = getGroup(collection);
833
+ const groupName = getGroup(collection) ?? "__default__";
827
834
  if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
828
835
  unassignedGroupMap[groupName].push(entry);
829
836
  }
@@ -833,7 +840,7 @@ function computeNavigationGroups({
833
840
  (views ?? []).forEach(view => {
834
841
  const entry = view.path;
835
842
  if (!assignedEntries.has(entry)) {
836
- const groupName = getGroup(view);
843
+ const groupName = getGroup(view) ?? "__default__";
837
844
  if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
838
845
  unassignedGroupMap[groupName].push(entry);
839
846
  }
@@ -861,7 +868,7 @@ function computeNavigationGroups({
861
868
 
862
869
  // Add collections
863
870
  (collections ?? []).forEach(collection => {
864
- const name = getGroup(collection);
871
+ const name = getGroup(collection) ?? "__default__";
865
872
  const entry = collection.id ?? collection.path;
866
873
  if (!groupMap[name]) groupMap[name] = [];
867
874
  groupMap[name].push(entry);
@@ -869,7 +876,7 @@ function computeNavigationGroups({
869
876
 
870
877
  // Add views
871
878
  (views ?? []).forEach(view => {
872
- const name = getGroup(view);
879
+ const name = getGroup(view) ?? "__default__";
873
880
  const entry = view.path;
874
881
  if (!groupMap[name]) groupMap[name] = [];
875
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
+ }