@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
@@ -12,6 +12,7 @@ import { ErrorBoundary } from "../ErrorBoundary";
12
12
  import { ClearFilterSortButton } from "../ClearFilterSortButton";
13
13
  import { FiltersDialog } from "./FiltersDialog";
14
14
  import { Badge, Button, cls, FilterListIcon, IconButton, Tooltip } from "@firecms/ui";
15
+ import { useTranslation } from "../../hooks/useTranslation";
15
16
 
16
17
  export type EntityCollectionViewStartActionsProps<M extends Record<string, any>> = {
17
18
  collection: EntityCollection<M>;
@@ -42,6 +43,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
42
43
  const customizationController = useCustomizationController();
43
44
  const plugins = customizationController.plugins ?? [];
44
45
  const largeLayout = useLargeLayout();
46
+ const { t } = useTranslation();
45
47
 
46
48
  // Filters dialog state
47
49
  const [filtersDialogOpen, setFiltersDialogOpen] = useState(false);
@@ -66,7 +68,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
66
68
 
67
69
  // Filters button
68
70
  const filtersButton = resolvedProperties && tableController.setFilterValues && (
69
- <Tooltip title="Filters"
71
+ <Tooltip title={t("filters")}
70
72
  key={"filters_tooltip"}>
71
73
  <Badge
72
74
  color="primary"
@@ -80,7 +82,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, any>>(
80
82
  startIcon={<FilterListIcon size="small" />}
81
83
  className={cls(activeFilterCount > 0 && "text-primary")}
82
84
  >
83
- Filters{activeFilterCount > 0 ? ` (${activeFilterCount})` : ""}
85
+ {t("filters")}{activeFilterCount > 0 ? ` (${activeFilterCount})` : ""}
84
86
  </Button>
85
87
  ) : (
86
88
  <IconButton
@@ -15,6 +15,7 @@ import {
15
15
  FilterListIcon,
16
16
  Typography
17
17
  } from "@firecms/ui";
18
+ import { useTranslation } from "../../hooks/useTranslation";
18
19
  import { StringNumberFilterField } from "../SelectableTable/filters/StringNumberFilterField";
19
20
  import { BooleanFilterField } from "../SelectableTable/filters/BooleanFilterField";
20
21
  import { DateTimeFilterField } from "../SelectableTable/filters/DateTimeFilterField";
@@ -43,6 +44,8 @@ export function FiltersDialog({
43
44
  setFilterValues,
44
45
  forceFilter
45
46
  }: FiltersDialogProps) {
47
+ const { t } = useTranslation();
48
+
46
49
  // Local state for filters being edited
47
50
  const [localFilters, setLocalFilters] = useState<FilterValues<any>>(filterValues ?? {});
48
51
 
@@ -173,7 +176,7 @@ export function FiltersDialog({
173
176
  containerClassName={isAnyFieldHidden ? "hidden" : undefined}
174
177
  >
175
178
  <DialogTitle className="flex items-center gap-2">
176
- <Typography variant="h6">Filters</Typography>
179
+ <Typography variant="h6">{t("filters")}</Typography>
177
180
  {activeFilterCount > 0 && (
178
181
  <span className="ml-2 px-2 py-0.5 text-xs rounded-full bg-primary text-white">
179
182
  {activeFilterCount}
@@ -184,7 +187,7 @@ export function FiltersDialog({
184
187
  <DialogContent >
185
188
  {filterableProperties.length === 0 ? (
186
189
  <Typography color="secondary" className="py-8 text-center">
187
- No filterable properties available
190
+ {t("no_filterable_properties")}
188
191
  </Typography>
189
192
  ) : (
190
193
  <table className="w-full border-collapse">
@@ -228,20 +231,20 @@ export function FiltersDialog({
228
231
  onClick={handleClearAll}
229
232
  disabled={activeFilterCount === 0}
230
233
  >
231
- Clear all
234
+ {t("clear")}
232
235
  </Button>
233
236
  <div className="flex-grow" />
234
237
  <Button
235
238
  variant="text"
236
239
  onClick={() => onOpenChange(false)}
237
240
  >
238
- Cancel
241
+ {t("cancel")}
239
242
  </Button>
240
243
  <Button
241
244
  variant="filled"
242
245
  onClick={handleApply}
243
246
  >
244
- Apply filters
247
+ {t("apply_filters")}
245
248
  </Button>
246
249
  </DialogActions>
247
250
  </Dialog>
@@ -12,6 +12,7 @@ import {
12
12
  ViewColumnIcon,
13
13
  ViewKanbanIcon
14
14
  } from "@firecms/ui";
15
+ import { useTranslation } from "../../hooks/useTranslation";
15
16
 
16
17
  export type KanbanPropertyOption = {
17
18
  key: string;
@@ -22,16 +23,11 @@ export type ViewModeToggleProps = {
22
23
  viewMode?: ViewMode;
23
24
  onViewModeChange?: (mode: ViewMode) => void;
24
25
  /**
25
- * Whether Kanban view mode is available for this collection.
26
- * Should be true when collection.kanban is set with a valid enum property.
26
+ * Which view modes are enabled for this collection.
27
+ * Only these modes will appear in the toggle.
28
+ * Defaults to all three: ["table", "cards", "kanban"].
27
29
  */
28
- kanbanEnabled?: boolean;
29
- /**
30
- * Whether a plugin exists that can configure Kanban (e.g., collection editor).
31
- * When true, Kanban option is always shown (enabled or not based on kanbanEnabled).
32
- * When false, Kanban option is shown but disabled.
33
- */
34
- hasKanbanConfigPlugin?: boolean;
30
+ enabledViews?: ViewMode[];
35
31
  /**
36
32
  * Current size for card/table views
37
33
  */
@@ -62,11 +58,12 @@ export type ViewModeToggleProps = {
62
58
  onKanbanPropertyChange?: (property: string) => void;
63
59
  }
64
60
 
61
+ const ALL_VIEW_MODES: ViewMode[] = ["table", "cards", "kanban"];
62
+
65
63
  export function ViewModeToggle({
66
64
  viewMode = "table",
67
65
  onViewModeChange,
68
- kanbanEnabled = false,
69
- hasKanbanConfigPlugin = false,
66
+ enabledViews = ALL_VIEW_MODES,
70
67
  size,
71
68
  onSizeChanged,
72
69
  open,
@@ -76,6 +73,8 @@ export function ViewModeToggle({
76
73
  onKanbanPropertyChange
77
74
  }: ViewModeToggleProps) {
78
75
 
76
+ const { t } = useTranslation();
77
+
79
78
  if (!onViewModeChange) {
80
79
  return null;
81
80
  }
@@ -88,44 +87,44 @@ export function ViewModeToggle({
88
87
  };
89
88
 
90
89
  const getViewModeName = () => {
91
- if (viewMode === "kanban") return "Board";
92
- if (viewMode === "cards") return "Cards";
93
- return "List";
90
+ if (viewMode === "kanban") return t("board");
91
+ if (viewMode === "cards") return t("cards");
92
+ return t("list");
94
93
  };
95
94
 
96
- const showKanban = kanbanEnabled || hasKanbanConfigPlugin;
97
95
  const showSizeSelector = size && onSizeChanged && (viewMode === "table" || viewMode === "cards");
98
96
  const showKanbanPropertySelector = viewMode === "kanban" &&
99
97
  kanbanPropertyOptions &&
100
98
  kanbanPropertyOptions.length > 0 &&
101
99
  onKanbanPropertyChange;
102
100
 
103
- // Build toggle options dynamically based on kanban availability
101
+ // Build toggle options based on enabledViews
104
102
  const viewModeOptions: ToggleButtonOption<ViewMode>[] = useMemo(() => {
105
- const options: ToggleButtonOption<ViewMode>[] = [
103
+ const allOptions: ToggleButtonOption<ViewMode>[] = [
106
104
  {
107
105
  value: "table",
108
- label: "List",
106
+ label: t("list"),
109
107
  icon: <ListIcon size="small" />
110
108
  },
111
109
  {
112
110
  value: "cards",
113
- label: "Cards",
111
+ label: t("cards"),
114
112
  icon: <AppsIcon size="small" />
113
+ },
114
+ {
115
+ value: "kanban",
116
+ label: t("board"),
117
+ icon: <ViewKanbanIcon size="small" />
115
118
  }
116
119
  ];
117
120
 
118
- if (showKanban) {
119
- options.push({
120
- value: "kanban",
121
- label: "Board",
122
- icon: <ViewKanbanIcon size="small" />,
123
- disabled: !kanbanEnabled && !hasKanbanConfigPlugin
124
- });
125
- }
121
+ return allOptions.filter(option => enabledViews.includes(option.value));
122
+ }, [enabledViews]);
126
123
 
127
- return options;
128
- }, [showKanban, kanbanEnabled, hasKanbanConfigPlugin]);
124
+ // Don't render if only one view is enabled
125
+ if (viewModeOptions.length <= 1 && !showSizeSelector) {
126
+ return null;
127
+ }
129
128
 
130
129
  return (
131
130
  <Popover
@@ -141,18 +140,20 @@ export function ViewModeToggle({
141
140
  >
142
141
  <div className="p-3 flex flex-col gap-3 min-w-[240px]">
143
142
  {/* View mode toggle using ToggleButtonGroup */}
144
- <ToggleButtonGroup
145
- value={viewMode}
146
- onValueChange={onViewModeChange}
147
- options={viewModeOptions}
148
- />
143
+ {viewModeOptions.length > 1 && (
144
+ <ToggleButtonGroup
145
+ value={viewMode}
146
+ onValueChange={onViewModeChange}
147
+ options={viewModeOptions}
148
+ />
149
+ )}
149
150
 
150
151
  {/* Size selector */}
151
152
  {showSizeSelector && (
152
153
  <div className="flex flex-row items-center justify-between gap-2">
153
154
  <div className="flex items-center gap-2 text-sm text-surface-600 dark:text-surface-300">
154
155
  <ViewColumnIcon size="small" />
155
- <span>Size</span>
156
+ <span>{t("size_label")}</span>
156
157
  </div>
157
158
  <Select
158
159
  value={size}
@@ -175,7 +176,7 @@ export function ViewModeToggle({
175
176
  <div className="flex flex-row items-center justify-between gap-2">
176
177
  <div className="flex items-center gap-2 text-sm text-surface-600 dark:text-surface-300">
177
178
  <ViewKanbanIcon size="small" />
178
- <span>Group by</span>
179
+ <span>{t("group_by")}</span>
179
180
  </div>
180
181
  <Select
181
182
  value={selectedKanbanProperty}
@@ -199,4 +200,3 @@ export function ViewModeToggle({
199
200
  </Popover>
200
201
  );
201
202
  }
202
-
@@ -1,9 +1,10 @@
1
1
  import React, { useRef } from "react";
2
2
  import { Highlight, themes } from "prism-react-renderer";
3
3
  import { useModeController } from "../hooks";
4
+ import { jsonStringifyReplacer } from "../util/objects";
4
5
 
5
6
  export function EntityJsonPreview({ values }: { values: object }) {
6
- const code = JSON.stringify(values, null, "\t");
7
+ const code = JSON.stringify(values, jsonStringifyReplacer, "\t");
7
8
  const { mode } = useModeController();
8
9
  const preRef = useRef<HTMLPreElement>(null);
9
10
 
@@ -4,7 +4,7 @@ import { resolveCollection } from "../util";
4
4
  import { cls, defaultBorderMixin, IconButton, OpenInNewIcon, Typography } from "@firecms/ui";
5
5
  import { CustomizationController } from "../types/customization_controller";
6
6
  import { useCustomizationController } from "../hooks/useCustomizationController";
7
- import { useAuthController } from "../hooks";
7
+ import { useAuthController, useTranslation } from "../hooks";
8
8
  import { PropertyCollectionView } from "./PropertyCollectionView";
9
9
 
10
10
  /**
@@ -26,6 +26,7 @@ export function EntityView<M extends Record<string, any>>(
26
26
  }: EntityViewProps<M>) {
27
27
 
28
28
  const authController = useAuthController();
29
+ const { t } = useTranslation();
29
30
  const customizationController: CustomizationController = useCustomizationController();
30
31
  const resolvedCollection: ResolvedEntityCollection<M> = useMemo(() => resolveCollection<M>({
31
32
  collection,
@@ -48,7 +49,7 @@ export function EntityView<M extends Record<string, any>>(
48
49
  color={"secondary"}
49
50
  component={"span"}
50
51
  className="break-words">
51
- Id
52
+ {t("id")}
52
53
  </Typography>
53
54
  </div>
54
55
  <div className="col-span-8">
@@ -1,18 +1,20 @@
1
1
  import React, { ErrorInfo, PropsWithChildren } from "react";
2
+ import { useTranslation } from "../hooks/useTranslation";
2
3
 
3
4
  import { ErrorIcon, Typography } from "@firecms/ui";
4
5
 
5
6
  export class ErrorBoundary extends React.Component<PropsWithChildren<Record<string, unknown>>, {
6
- error: Error | null
7
+ hasError: boolean,
8
+ error?: Error
7
9
  }> {
8
10
  constructor(props: any) {
9
11
  super(props);
10
- this.state = { error: null };
12
+ this.state = { hasError: false };
11
13
  }
12
14
 
13
15
  // eslint-disable-next-line n/handle-callback-err
14
16
  static getDerivedStateFromError(error: Error) {
15
- return { error };
17
+ return { hasError: true, error };
16
18
  }
17
19
 
18
20
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
@@ -21,20 +23,30 @@ export class ErrorBoundary extends React.Component<PropsWithChildren<Record<stri
21
23
  }
22
24
 
23
25
  render() {
24
- if (this.state.error) {
25
- return (
26
- <div className="flex flex-col m-2">
27
- <div className="flex items-center m-2">
28
- <ErrorIcon color={"error"} size={"small"}/>
29
- <div className="ml-4">Error</div>
30
- </div>
31
- <Typography variant={"caption"}>
32
- {this.state.error?.message ?? "See the error in the console"}
33
- </Typography>
34
- </div>
35
- );
26
+ if (this.state.hasError) {
27
+ // You can render any custom fallback UI
28
+ return <FallbackView message={this.state.error?.message}/>;
36
29
  }
37
30
 
38
31
  return this.props.children;
39
32
  }
40
33
  }
34
+
35
+ function FallbackView({ message }: { message?: string }) {
36
+ const { t } = useTranslation();
37
+ return (
38
+ <div className="h-full w-full bg-slate-100 dark:bg-surface-900 flex items-center justify-center p-4">
39
+ <div
40
+ className="flex flex-col items-center justify-center m-4 bg-white dark:bg-surface-800 p-8 rounded-lg shadow-sm border border-gray-200 dark:border-surface-700">
41
+ <div className="flex items-center mb-4 text-red-500 dark:text-red-400">
42
+ <ErrorIcon/>
43
+ <div className="ml-4">{t("error")}</div>
44
+ </div>
45
+ <div className="flex justify-center text-gray-500 dark:text-gray-400">
46
+ {/* Error message is purposely removed since it's hard to access state here, but typical ErrorBoundary fallback doesn't always show the raw message */}
47
+ {t("see_console_details")}
48
+ </div>
49
+ </div>
50
+ </div>
51
+ );
52
+ }
@@ -30,8 +30,13 @@ import { NavigationCardBinding } from "./NavigationCardBinding";
30
30
  import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
31
31
  import { restrictToVerticalAxis, restrictToWindowEdges } from "@dnd-kit/modifiers";
32
32
  import { RenameGroupDialog } from "./RenameGroupDialog";
33
+ import { useTranslation } from "../../hooks/useTranslation";
33
34
 
34
- export const DEFAULT_GROUP_NAME = "Views";
35
+ /**
36
+ * Internal sentinel key for ungrouped navigation entries.
37
+ * Not displayed — display components use t("views_group") when group is undefined.
38
+ */
39
+ const DEFAULT_GROUP_KEY = "__default__";
35
40
  export const ADMIN_GROUP_NAME = "Admin";
36
41
 
37
42
  export function DefaultHomePage({
@@ -47,6 +52,7 @@ export function DefaultHomePage({
47
52
  const context = useFireCMSContext();
48
53
  const customizationController = useCustomizationController();
49
54
  const navigationController = useNavigationController();
55
+ const { t } = useTranslation();
50
56
 
51
57
  if (!navigationController.topLevelNavigation)
52
58
  throw Error("Navigation not ready");
@@ -108,7 +114,7 @@ export function DefaultHomePage({
108
114
  const g =
109
115
  e.type === "admin"
110
116
  ? ADMIN_GROUP_NAME
111
- : e.group ?? DEFAULT_GROUP_NAME;
117
+ : e.group ?? DEFAULT_GROUP_KEY;
112
118
  (entriesByGroup[g] ??= []).push(e);
113
119
  });
114
120
 
@@ -119,7 +125,7 @@ export function DefaultHomePage({
119
125
 
120
126
  if (performingSearch) {
121
127
  const ordered = [
122
- ...new Set(src.map((e) => e.group ?? DEFAULT_GROUP_NAME))
128
+ ...new Set(src.map((e) => e.group ?? DEFAULT_GROUP_KEY))
123
129
  ];
124
130
  allProcessed = ordered
125
131
  .map((name) => ({
@@ -129,11 +135,11 @@ export function DefaultHomePage({
129
135
  .filter((g) => g.entries.length);
130
136
  } else {
131
137
  allProcessed = groupOrderFromNavController.map((g) => ({
132
- name: g,
133
- entries: entriesByGroup[g] || []
138
+ name: g ?? DEFAULT_GROUP_KEY,
139
+ entries: entriesByGroup[g ?? DEFAULT_GROUP_KEY] || []
134
140
  }));
135
141
  Object.keys(entriesByGroup).forEach((g) => {
136
- if (!groupOrderFromNavController.includes(g))
142
+ if (!groupOrderFromNavController.map(x => x ?? DEFAULT_GROUP_KEY).includes(g))
137
143
  allProcessed.push({
138
144
  name: g,
139
145
  entries: entriesByGroup[g]
@@ -141,9 +147,9 @@ export function DefaultHomePage({
141
147
  });
142
148
 
143
149
  // Ensure default group exists if there are plugin additional cards but no collections
144
- if (hasPluginAdditionalCards && !allProcessed.some(g => g.name === DEFAULT_GROUP_NAME)) {
150
+ if (hasPluginAdditionalCards && !allProcessed.some(g => g.name === DEFAULT_GROUP_KEY)) {
145
151
  allProcessed.push({
146
- name: DEFAULT_GROUP_NAME,
152
+ name: DEFAULT_GROUP_KEY,
147
153
  entries: []
148
154
  });
149
155
  }
@@ -151,7 +157,7 @@ export function DefaultHomePage({
151
157
  allProcessed = allProcessed.filter(
152
158
  (g) =>
153
159
  g.entries.length ||
154
- (g.name === DEFAULT_GROUP_NAME && hasPluginAdditionalCards)
160
+ (g.name === DEFAULT_GROUP_KEY && hasPluginAdditionalCards)
155
161
  );
156
162
  }
157
163
 
@@ -362,7 +368,7 @@ export function DefaultHomePage({
362
368
  >
363
369
  <SearchBar
364
370
  onTextSearch={updateSearch}
365
- placeholder="Search collections"
371
+ placeholder={t("search_collections")}
366
372
  autoFocus
367
373
  innerClassName="w-full"
368
374
  className="w-full flex-grow"
@@ -412,7 +418,7 @@ export function DefaultHomePage({
412
418
 
413
419
  const actionProps: PluginHomePageAdditionalCardsProps = {
414
420
  group:
415
- groupKey === DEFAULT_GROUP_NAME
421
+ groupKey === DEFAULT_GROUP_KEY
416
422
  ? undefined
417
423
  : groupKey,
418
424
  context
@@ -432,7 +438,7 @@ export function DefaultHomePage({
432
438
  >
433
439
  <NavigationGroup
434
440
  group={
435
- groupKey === DEFAULT_GROUP_NAME
441
+ groupKey === DEFAULT_GROUP_KEY
436
442
  ? undefined
437
443
  : groupKey
438
444
  }
@@ -535,7 +541,7 @@ export function DefaultHomePage({
535
541
  <NavigationGroup
536
542
  group={
537
543
  activeGroupData.name ===
538
- DEFAULT_GROUP_NAME
544
+ DEFAULT_GROUP_KEY
539
545
  ? undefined
540
546
  : activeGroupData.name
541
547
  }
@@ -30,6 +30,7 @@ import { CSS } from "@dnd-kit/utilities";
30
30
  import { NavigationCardBinding } from "./NavigationCardBinding";
31
31
  import { NavigationEntry } from "../../types";
32
32
  import { cls, defaultBorderMixin } from "@firecms/ui";
33
+ import { useTranslation } from "../../hooks";
33
34
 
34
35
  const animateLayoutChanges: AnimateLayoutChanges = (args) =>
35
36
  defaultAnimateLayoutChanges({
@@ -669,6 +670,7 @@ export function NewGroupDropZone({
669
670
  disabled: boolean;
670
671
  setIsHovering: (v: boolean) => void;
671
672
  }) {
673
+ const { t } = useTranslation();
672
674
  const {
673
675
  setNodeRef,
674
676
  isOver
@@ -709,7 +711,7 @@ export function NewGroupDropZone({
709
711
  )}>
710
712
  <div className="text-center p-4">
711
713
  <span className="block font-medium text-sm">
712
- Drop here to create a new group
714
+ {t("drop_here_create_group")}
713
715
  </span>
714
716
  </div>
715
717
  </div>
@@ -1,5 +1,6 @@
1
1
  import React, { PropsWithChildren, useState } from "react";
2
2
  import { cls, EditIcon, IconButton, Typography, ExpandablePanel } from "@firecms/ui";
3
+ import { useTranslation } from "../../hooks/useTranslation";
3
4
 
4
5
  export function NavigationGroup({
5
6
  children,
@@ -22,8 +23,9 @@ export function NavigationGroup({
22
23
  onToggleCollapsed?: () => void;
23
24
  }>) {
24
25
 
26
+ const { t } = useTranslation();
25
27
  const [isHovered, setIsHovered] = useState(false);
26
- const currentGroupName = group ?? "Views";
28
+ const currentGroupName = group ?? t("views_group");
27
29
 
28
30
  // Show caret only when not in preview and there is a toggle handler
29
31
  const showCaret = !isPreview && !!onToggleCollapsed;
@@ -1,5 +1,6 @@
1
1
  import React, { useEffect, useRef, useState } from "react";
2
2
  import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField } from "@firecms/ui";
3
+ import { useTranslation } from "../../hooks/useTranslation";
3
4
 
4
5
  interface RenameGroupDialogProps {
5
6
  open: boolean;
@@ -18,7 +19,8 @@ export function RenameGroupDialog({
18
19
  }: RenameGroupDialogProps) {
19
20
  const [name, setName] = useState(initialName);
20
21
  const [error, setError] = useState<string | null>(null);
21
- const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null); // Create a ref for the input
22
+ const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);
23
+ const { t } = useTranslation();
22
24
 
23
25
  useEffect(() => {
24
26
  if (open) {
@@ -38,9 +40,9 @@ export function RenameGroupDialog({
38
40
  const newName = event.target.value;
39
41
  setName(newName);
40
42
  if (!newName.trim()) {
41
- setError("Group name cannot be empty.");
43
+ setError(t("group_name_empty_error"));
42
44
  } else if (existingGroupNames.includes(newName.trim())) {
43
- setError("This group name already exists.");
45
+ setError(t("group_name_exists_error"));
44
46
  } else {
45
47
  setError(null);
46
48
  }
@@ -49,11 +51,11 @@ export function RenameGroupDialog({
49
51
  const handleSave = () => {
50
52
  const trimmedName = name.trim();
51
53
  if (!trimmedName) {
52
- setError("Group name cannot be empty.");
54
+ setError(t("group_name_empty_error"));
53
55
  return;
54
56
  }
55
57
  if (existingGroupNames.includes(trimmedName)) {
56
- setError("This group name already exists.");
58
+ setError(t("group_name_exists_error"));
57
59
  return;
58
60
  }
59
61
  if (!error) {
@@ -70,9 +72,9 @@ export function RenameGroupDialog({
70
72
  // because the error state might not have updated if the user types and immediately hits enter.
71
73
  let currentError = null;
72
74
  if (!trimmedName) {
73
- currentError = "Group name cannot be empty.";
75
+ currentError = t("group_name_empty_error");
74
76
  } else if (existingGroupNames.includes(trimmedName)) {
75
- currentError = "This group name already exists.";
77
+ currentError = t("group_name_exists_error");
76
78
  }
77
79
 
78
80
  if (!currentError && trimmedName) {
@@ -93,14 +95,14 @@ export function RenameGroupDialog({
93
95
 
94
96
  return (
95
97
  <Dialog open={open}>
96
- <DialogTitle>Rename Group</DialogTitle>
98
+ <DialogTitle>{t("rename_group")}</DialogTitle>
97
99
  <DialogContent>
98
100
  <TextField
99
- inputRef={inputRef} // Pass the ref to the TextField
100
- label="Group Name"
101
+ inputRef={inputRef}
102
+ label={t("group_name_label")}
101
103
  value={name}
102
104
  onChange={handleNameChange}
103
- onKeyDown={handleKeyDown} // Added onKeyDown handler
105
+ onKeyDown={handleKeyDown}
104
106
  error={!!error}
105
107
  aria-describedby={error ? "group-name-error" : undefined}
106
108
  />
@@ -109,11 +111,11 @@ export function RenameGroupDialog({
109
111
  <DialogActions>
110
112
  <Button onClick={onClose}
111
113
  variant="text">
112
- Cancel
114
+ {t("cancel")}
113
115
  </Button>
114
116
  <Button onClick={handleSave}
115
117
  disabled={!!error || !name.trim()}>
116
- Save
118
+ {t("save")}
117
119
  </Button>
118
120
  </DialogActions>
119
121
  </Dialog>
@@ -0,0 +1,66 @@
1
+ import React from "react";
2
+ import { CheckIcon, IconButton, Menu, MenuItem, TranslateIcon, Typography } from "@firecms/ui";
3
+ import { useTranslation } from "../hooks";
4
+
5
+ export function LanguageToggle() {
6
+ const { i18n } = useTranslation();
7
+
8
+ return (
9
+ <Menu
10
+ trigger={<IconButton
11
+ color="inherit"
12
+ aria-label="Change language">
13
+ <TranslateIcon size="small" />
14
+ </IconButton>}>
15
+ <MenuItem onClick={() => i18n.changeLanguage("en")}>
16
+ <div className="flex w-full items-center justify-between gap-4">
17
+ {/* eslint-disable-next-line i18next/no-literal-string */}
18
+ <Typography variant="body2" className={i18n.language === "en" ? "font-bold" : ""}>English</Typography>
19
+ {i18n.language === "en" && <CheckIcon size="small" />}
20
+ </div>
21
+ </MenuItem>
22
+ <MenuItem onClick={() => i18n.changeLanguage("es")}>
23
+ <div className="flex w-full items-center justify-between gap-4">
24
+ {/* eslint-disable-next-line i18next/no-literal-string */}
25
+ <Typography variant="body2" className={i18n.language === "es" ? "font-bold" : ""}>Español</Typography>
26
+ {i18n.language === "es" && <CheckIcon size="small" />}
27
+ </div>
28
+ </MenuItem>
29
+ <MenuItem onClick={() => i18n.changeLanguage("de")}>
30
+ <div className="flex w-full items-center justify-between gap-4">
31
+ {/* eslint-disable-next-line i18next/no-literal-string */}
32
+ <Typography variant="body2" className={i18n.language === "de" ? "font-bold" : ""}>Deutsch</Typography>
33
+ {i18n.language === "de" && <CheckIcon size="small" />}
34
+ </div>
35
+ </MenuItem>
36
+ <MenuItem onClick={() => i18n.changeLanguage("fr")}>
37
+ <div className="flex w-full items-center justify-between gap-4">
38
+ {/* eslint-disable-next-line i18next/no-literal-string */}
39
+ <Typography variant="body2" className={i18n.language === "fr" ? "font-bold" : ""}>Français</Typography>
40
+ {i18n.language === "fr" && <CheckIcon size="small" />}
41
+ </div>
42
+ </MenuItem>
43
+ <MenuItem onClick={() => i18n.changeLanguage("it")}>
44
+ <div className="flex w-full items-center justify-between gap-4">
45
+ {/* eslint-disable-next-line i18next/no-literal-string */}
46
+ <Typography variant="body2" className={i18n.language === "it" ? "font-bold" : ""}>Italiano</Typography>
47
+ {i18n.language === "it" && <CheckIcon size="small" />}
48
+ </div>
49
+ </MenuItem>
50
+ <MenuItem onClick={() => i18n.changeLanguage("hi")}>
51
+ <div className="flex w-full items-center justify-between gap-4">
52
+ {/* eslint-disable-next-line i18next/no-literal-string */}
53
+ <Typography variant="body2" className={i18n.language === "hi" ? "font-bold" : ""}>हिन्दी</Typography>
54
+ {i18n.language === "hi" && <CheckIcon size="small" />}
55
+ </div>
56
+ </MenuItem>
57
+ <MenuItem onClick={() => i18n.changeLanguage("pt")}>
58
+ <div className="flex w-full items-center justify-between gap-4">
59
+ {/* eslint-disable-next-line i18next/no-literal-string */}
60
+ <Typography variant="body2" className={i18n.language === "pt" ? "font-bold" : ""}>Português</Typography>
61
+ {i18n.language === "pt" && <CheckIcon size="small" />}
62
+ </div>
63
+ </MenuItem>
64
+ </Menu>
65
+ );
66
+ }