@firecms/core 3.1.0-canary.24c8270 → 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 (224) 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/VirtualTable/VirtualTableHeader.d.ts +1 -0
  7. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  8. package/dist/components/VirtualTable/VirtualTableProps.d.ts +6 -1
  9. package/dist/components/VirtualTable/types.d.ts +1 -0
  10. package/dist/components/index.d.ts +1 -0
  11. package/dist/core/DrawerNavigationGroup.d.ts +2 -2
  12. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  13. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  14. package/dist/editor/components/editor-bubble.d.ts +8 -0
  15. package/dist/editor/components/image-bubble.d.ts +5 -0
  16. package/dist/editor/components/index.d.ts +16 -0
  17. package/dist/editor/components/table-bubble.d.ts +5 -0
  18. package/dist/editor/editor.d.ts +30 -0
  19. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  20. package/dist/editor/extensions/Image/index.d.ts +6 -0
  21. package/dist/editor/extensions/Image.d.ts +6 -0
  22. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  23. package/dist/editor/extensions/clipboard.d.ts +7 -0
  24. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  25. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  26. package/dist/editor/hooks/useProseMirror.d.ts +13 -0
  27. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  28. package/dist/editor/index.d.ts +2 -0
  29. package/dist/editor/markdown.d.ts +5 -0
  30. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  31. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  32. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  33. package/dist/editor/nodeViews/index.d.ts +6 -0
  34. package/dist/editor/plugins/index.d.ts +2 -0
  35. package/dist/editor/plugins/inputrules.d.ts +6 -0
  36. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  37. package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
  38. package/dist/editor/schema.d.ts +2 -0
  39. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  40. package/dist/editor/selectors/color-selector.d.ts +10 -0
  41. package/dist/editor/selectors/link-selector.d.ts +8 -0
  42. package/dist/editor/selectors/node-selector.d.ts +15 -0
  43. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  44. package/dist/editor/types.d.ts +5 -0
  45. package/dist/editor/useProseMirror.d.ts +16 -0
  46. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  47. package/dist/editor/utils/remove_classes.d.ts +1 -0
  48. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  49. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  50. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  51. package/dist/hooks/index.d.ts +1 -0
  52. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  53. package/dist/hooks/useCollapsedGroups.d.ts +3 -3
  54. package/dist/hooks/useTranslation.d.ts +17 -0
  55. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  56. package/dist/index.d.ts +5 -0
  57. package/dist/index.es.js +29889 -18645
  58. package/dist/index.es.js.map +1 -1
  59. package/dist/index.umd.js +29883 -18659
  60. package/dist/index.umd.js.map +1 -1
  61. package/dist/locales/de.d.ts +2 -0
  62. package/dist/locales/en.d.ts +10 -0
  63. package/dist/locales/es.d.ts +10 -0
  64. package/dist/locales/fr.d.ts +2 -0
  65. package/dist/locales/hi.d.ts +2 -0
  66. package/dist/locales/it.d.ts +2 -0
  67. package/dist/locales/pt.d.ts +7 -0
  68. package/dist/types/collections.d.ts +38 -0
  69. package/dist/types/customization_controller.d.ts +2 -1
  70. package/dist/types/firecms.d.ts +2 -1
  71. package/dist/types/index.d.ts +1 -0
  72. package/dist/types/navigation.d.ts +2 -2
  73. package/dist/types/plugins.d.ts +7 -0
  74. package/dist/types/properties.d.ts +9 -8
  75. package/dist/types/storage.d.ts +1 -0
  76. package/dist/types/translations.d.ts +669 -0
  77. package/dist/util/index.d.ts +1 -0
  78. package/dist/util/lazy_eager.d.ts +7 -0
  79. package/dist/util/objects.d.ts +1 -0
  80. package/dist/util/useStorageUploadController.d.ts +10 -1
  81. package/package.json +45 -9
  82. package/src/app/Scaffold.tsx +7 -5
  83. package/src/components/AIIcon.tsx +3 -1
  84. package/src/components/ArrayContainer.tsx +6 -4
  85. package/src/components/ClearFilterSortButton.tsx +6 -3
  86. package/src/components/ConfirmationDialog.tsx +4 -2
  87. package/src/components/DeleteEntityDialog.tsx +10 -7
  88. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -3
  89. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  90. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -1
  91. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  92. package/src/components/EntityCollectionView/BoardSortableList.tsx +3 -1
  93. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  94. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +16 -43
  95. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +17 -25
  96. package/src/components/EntityCollectionView/EntityCollectionView.tsx +24 -18
  97. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -3
  98. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +4 -2
  99. package/src/components/EntityCollectionView/FiltersDialog.tsx +8 -5
  100. package/src/components/EntityCollectionView/ViewModeToggle.tsx +11 -8
  101. package/src/components/EntityJsonPreview.tsx +2 -1
  102. package/src/components/EntityView.tsx +3 -2
  103. package/src/components/ErrorBoundary.tsx +27 -15
  104. package/src/components/HomePage/DefaultHomePage.tsx +19 -13
  105. package/src/components/HomePage/HomePageDnD.tsx +3 -1
  106. package/src/components/HomePage/NavigationGroup.tsx +3 -1
  107. package/src/components/HomePage/RenameGroupDialog.tsx +15 -13
  108. package/src/components/LanguageToggle.tsx +66 -0
  109. package/src/components/NotFoundPage.tsx +5 -3
  110. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -7
  111. package/src/components/ReferenceWidget.tsx +3 -2
  112. package/src/components/SearchIconsView.tsx +3 -1
  113. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +11 -0
  114. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +15 -2
  115. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +11 -0
  116. package/src/components/UnsavedChangesDialog.tsx +6 -4
  117. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
  118. package/src/components/VirtualTable/VirtualTable.tsx +5 -3
  119. package/src/components/VirtualTable/VirtualTableHeader.tsx +21 -18
  120. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +8 -3
  121. package/src/components/VirtualTable/VirtualTableProps.tsx +7 -1
  122. package/src/components/VirtualTable/types.tsx +1 -0
  123. package/src/components/common/default_entity_actions.tsx +4 -0
  124. package/src/components/common/useDataSourceTableController.tsx +5 -14
  125. package/src/components/index.tsx +1 -0
  126. package/src/core/DefaultAppBar.tsx +14 -10
  127. package/src/core/DefaultDrawer.tsx +8 -2
  128. package/src/core/DrawerNavigationGroup.tsx +5 -3
  129. package/src/core/EntityEditView.tsx +53 -7
  130. package/src/core/EntityEditViewFormActions.tsx +24 -17
  131. package/src/core/EntitySidePanel.tsx +6 -4
  132. package/src/core/FireCMS.tsx +33 -6
  133. package/src/core/field_configs.tsx +4 -2
  134. package/src/editor/components/SlashCommandMenu.tsx +516 -0
  135. package/src/editor/components/editor-bubble-item.tsx +32 -0
  136. package/src/editor/components/editor-bubble.tsx +118 -0
  137. package/src/editor/components/image-bubble.tsx +156 -0
  138. package/src/editor/components/index.ts +14 -0
  139. package/src/editor/components/table-bubble.tsx +165 -0
  140. package/src/editor/editor.tsx +455 -0
  141. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  142. package/src/editor/extensions/Image/index.ts +133 -0
  143. package/src/editor/extensions/Image.ts +159 -0
  144. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  145. package/src/editor/extensions/clipboard.ts +72 -0
  146. package/src/editor/extensions/custom-keymap.ts +24 -0
  147. package/src/editor/extensions/drag-and-drop.tsx +480 -0
  148. package/src/editor/hooks/useProseMirror.ts +124 -0
  149. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  150. package/src/editor/index.ts +2 -0
  151. package/src/editor/markdown.ts +172 -0
  152. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  153. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  154. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  155. package/src/editor/nodeViews/index.ts +35 -0
  156. package/src/editor/plugins/index.ts +58 -0
  157. package/src/editor/plugins/inputrules.ts +82 -0
  158. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  159. package/src/editor/plugins/slashCommandPlugin.ts +61 -0
  160. package/src/editor/schema.ts +240 -0
  161. package/src/editor/selectors/ai-selector.tsx +111 -0
  162. package/src/editor/selectors/color-selector.tsx +200 -0
  163. package/src/editor/selectors/link-selector.tsx +118 -0
  164. package/src/editor/selectors/node-selector.tsx +157 -0
  165. package/src/editor/selectors/text-buttons.tsx +86 -0
  166. package/src/editor/types.ts +6 -0
  167. package/src/editor/useProseMirror.ts +126 -0
  168. package/src/editor/utils/prosemirror-utils.ts +108 -0
  169. package/src/editor/utils/remove_classes.ts +17 -0
  170. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  171. package/src/form/EntityForm.tsx +80 -7
  172. package/src/form/EntityFormActions.tsx +19 -12
  173. package/src/form/PropertyFieldBinding.tsx +7 -5
  174. package/src/form/components/LocalChangesMenu.tsx +13 -13
  175. package/src/form/components/StorageItemPreview.tsx +3 -2
  176. package/src/form/components/StorageUploadProgress.tsx +18 -3
  177. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
  178. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +22 -9
  179. package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
  180. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  181. package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -24
  182. package/src/form/field_bindings/MapFieldBinding.tsx +27 -11
  183. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +73 -36
  184. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
  185. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
  186. package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
  187. package/src/form/field_bindings/RepeatFieldBinding.tsx +21 -6
  188. package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
  189. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +28 -10
  190. package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
  191. package/src/form/field_bindings/TextFieldBinding.tsx +10 -7
  192. package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
  193. package/src/hooks/index.tsx +1 -0
  194. package/src/hooks/useBuildNavigationController.tsx +20 -13
  195. package/src/hooks/useCollapsedGroups.ts +7 -6
  196. package/src/hooks/useTranslation.ts +31 -0
  197. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  198. package/src/index.ts +5 -0
  199. package/src/locales/de.ts +718 -0
  200. package/src/locales/en.ts +730 -0
  201. package/src/locales/es.ts +730 -0
  202. package/src/locales/fr.ts +718 -0
  203. package/src/locales/hi.ts +718 -0
  204. package/src/locales/it.ts +718 -0
  205. package/src/locales/pt.ts +727 -0
  206. package/src/preview/PropertyPreview.tsx +3 -2
  207. package/src/preview/components/ReferencePreview.tsx +2 -1
  208. package/src/preview/components/UrlComponentPreview.tsx +4 -2
  209. package/src/preview/components/UserPreview.tsx +3 -1
  210. package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
  211. package/src/routes/FireCMSRoute.tsx +63 -54
  212. package/src/types/collections.ts +40 -0
  213. package/src/types/customization_controller.tsx +2 -1
  214. package/src/types/firecms.tsx +2 -1
  215. package/src/types/index.ts +1 -0
  216. package/src/types/navigation.ts +2 -2
  217. package/src/types/plugins.tsx +8 -0
  218. package/src/types/properties.ts +12 -10
  219. package/src/types/storage.ts +2 -1
  220. package/src/types/translations.ts +752 -0
  221. package/src/util/index.ts +1 -0
  222. package/src/util/lazy_eager.tsx +33 -0
  223. package/src/util/objects.ts +15 -0
  224. package/src/util/useStorageUploadController.tsx +23 -29
@@ -126,6 +126,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
126
126
  AddColumnComponent,
127
127
  initialScroll = 0,
128
128
  onColumnsOrderChange,
129
+ headerIconSize,
129
130
  }: VirtualTableProps<T>) {
130
131
 
131
132
  const sortByProperty: string | undefined = sortBy ? sortBy[0] : undefined;
@@ -211,7 +212,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
211
212
  }, [tableRef]);
212
213
 
213
214
  const [measureRef, bounds] = useMeasure({
214
- debounce: 50,
215
+ debounce: 0,
215
216
  polyfill: ResizeObserver,
216
217
  scroll: true,
217
218
  // This is important for handling zooming in react-flow
@@ -369,8 +370,9 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
369
370
  setColumns(newColumns);
370
371
  onColumnsOrderChange(newColumns);
371
372
  } : undefined,
372
- draggingColumnId
373
- }), [data, rowHeight, cellRenderer, columns, currentSort, onRowClick, customView, onColumnResizeInternal, onColumnResizeEndInternal, filterInput, onColumnSort, onFilterUpdateInternal, sortByProperty, hoverRow, createFilterField, rowClassName, endAdornment, AddColumnComponent, onColumnsOrderChange, draggingColumnId]);
373
+ draggingColumnId,
374
+ headerIconSize,
375
+ }), [data, rowHeight, cellRenderer, columns, currentSort, onRowClick, customView, onColumnResizeInternal, onColumnResizeEndInternal, filterInput, onColumnSort, onFilterUpdateInternal, sortByProperty, hoverRow, createFilterField, rowClassName, endAdornment, AddColumnComponent, onColumnsOrderChange, draggingColumnId, headerIconSize]);
374
376
 
375
377
  // Get sortable column keys (excluding frozen columns)
376
378
  const sortableColumnKeys = columns
@@ -2,6 +2,7 @@ import React, { RefObject, useCallback, useEffect, useState } from "react";
2
2
  import equal from "react-fast-compare";
3
3
 
4
4
  import { VirtualTableColumn, VirtualTableSort, VirtualTableWhereFilterOp } from "./VirtualTableProps";
5
+ import { useTranslation } from "../../hooks";
5
6
  import { ErrorBoundary } from "../ErrorBoundary";
6
7
  import {
7
8
  ArrowUpwardIcon,
@@ -47,6 +48,7 @@ type VirtualTableHeaderProps<M extends Record<string, any>> = {
47
48
  AdditionalHeaderWidget?: (props: { onHover: boolean }) => React.ReactNode;
48
49
  isDragging?: boolean;
49
50
  isDraggable?: boolean;
51
+ headerIconSize?: "small" | "smallest";
50
52
  };
51
53
 
52
54
  export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
@@ -63,7 +65,8 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
63
65
  createFilterField,
64
66
  AdditionalHeaderWidget,
65
67
  isDragging,
66
- isDraggable
68
+ isDraggable,
69
+ headerIconSize = "small",
67
70
  }: VirtualTableHeaderProps<M>) {
68
71
 
69
72
  const [onHover, setOnHover] = useState(false);
@@ -135,18 +138,17 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
135
138
  <Badge color="secondary"
136
139
  invisible={!sort}>
137
140
  <IconButton
138
- size={"small"}
141
+ size={headerIconSize}
139
142
  className={onHover || openFilter ? "bg-white dark:bg-surface-950" : undefined}
140
143
  onClick={() => {
141
144
  onColumnSort(column.key as Extract<keyof M, string>);
142
145
  }}
143
146
  >
144
- {!sort &&
145
- <ArrowUpwardIcon />}
146
- {sort === "asc" &&
147
- <ArrowUpwardIcon />}
148
- {sort === "desc" &&
149
- <ArrowUpwardIcon className={"rotate-180"} />}
147
+ <ArrowUpwardIcon size={headerIconSize}
148
+ className={cls(
149
+ "transition-transform duration-200",
150
+ sort === "desc" ? "rotate-180" : "rotate-0"
151
+ )} />
150
152
  </IconButton>
151
153
  </Badge>
152
154
  }
@@ -213,6 +215,8 @@ function FilterForm<M>({
213
215
  setHidden
214
216
  }: FilterFormProps<M>) {
215
217
 
218
+ const { t } = useTranslation();
219
+
216
220
  const id = column.key;
217
221
 
218
222
  const [filterInternal, setFilterInternal] = useState<[VirtualTableWhereFilterOp, any] | undefined>(filter);
@@ -258,16 +262,15 @@ function FilterForm<M>({
258
262
  {filterField && <div className="m-4 w-[400px]">
259
263
  {filterField}
260
264
  </div>}
261
- <div className="flex justify-end m-4">
262
- <Button
263
- className="mr-4"
264
- disabled={!filterIsSet}
265
- variant={"text"}
266
- type="reset"
267
- aria-label="filter clear"
268
- onClick={reset}>Clear</Button>
269
- <Button
270
- type="submit">Filter</Button>
265
+ <div className="flex justify-end p-4 pt-0 gap-2">
266
+ <Button variant={"text"}
267
+ size={"small"}
268
+ aria-label="filter clear"
269
+ onClick={reset}>{t("clear")}</Button>
270
+
271
+ <Button variant={"outlined"}
272
+ size={"small"}
273
+ type="submit">{t("filter")}</Button>
271
274
  </div>
272
275
  </form>
273
276
  );
@@ -22,7 +22,8 @@ const SortableColumnHeader = ({
22
22
  onClickResizeColumn,
23
23
  createFilterField,
24
24
  isDragging,
25
- isDraggable
25
+ isDraggable,
26
+ headerIconSize
26
27
  }: {
27
28
  column: VirtualTableColumn;
28
29
  columnIndex: number;
@@ -37,6 +38,7 @@ const SortableColumnHeader = ({
37
38
  createFilterField: any;
38
39
  isDragging: boolean;
39
40
  isDraggable: boolean;
41
+ headerIconSize?: "small" | "smallest";
40
42
  }) => {
41
43
  const [isPressing, setIsPressing] = useState(false);
42
44
 
@@ -103,7 +105,8 @@ const SortableColumnHeader = ({
103
105
  createFilterField={createFilterField}
104
106
  AdditionalHeaderWidget={column.AdditionalHeaderWidget}
105
107
  isDragging={isDragging || isPressing}
106
- isDraggable={isDraggable} />
108
+ isDraggable={isDraggable}
109
+ headerIconSize={headerIconSize} />
107
110
  </div>
108
111
  );
109
112
  };
@@ -123,7 +126,8 @@ export const VirtualTableHeaderRow = ({
123
126
  data,
124
127
  cellRenderer: CellRenderer,
125
128
  rowHeight = 54,
126
- draggingColumnId
129
+ draggingColumnId,
130
+ headerIconSize,
127
131
  }: VirtualTableContextProps<any>) => {
128
132
 
129
133
  const columnRefs = useMemo(() => columns.map(() => createRef<HTMLDivElement>()), [columns.length]);
@@ -234,6 +238,7 @@ export const VirtualTableHeaderRow = ({
234
238
  createFilterField={createFilterField}
235
239
  isDragging={isDragging}
236
240
  isDraggable={isDraggable}
241
+ headerIconSize={headerIconSize}
237
242
  />
238
243
  </ErrorBoundary>
239
244
  );
@@ -168,6 +168,12 @@ export interface VirtualTableProps<T extends Record<string, any>> {
168
168
  */
169
169
  onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
170
170
 
171
+ /**
172
+ * Size of icons in column headers (sort, filter).
173
+ * @default "small"
174
+ */
175
+ headerIconSize?: "small" | "smallest";
176
+
171
177
  }
172
178
 
173
179
  export type CellRendererParams<T = any> = {
@@ -206,7 +212,7 @@ export interface VirtualTableColumn<CustomProps = any> {
206
212
  /**
207
213
  * Label displayed in the header
208
214
  */
209
- title?: string;
215
+ title?: React.ReactNode;
210
216
 
211
217
  /**
212
218
  * This column is frozen to the left
@@ -42,4 +42,5 @@ export type VirtualTableContextProps<T extends any> = {
42
42
  AddColumnComponent?: React.ComponentType;
43
43
  onColumnsOrderChange?: (columns: VirtualTableColumn[]) => void;
44
44
  draggingColumnId?: string | null;
45
+ headerIconSize?: "small" | "smallest";
45
46
  };
@@ -1,3 +1,7 @@
1
+ // Note: entity action 'name' fields (Edit, Copy, Delete) are plain strings defined
2
+ // at module level. They cannot use hooks. Consumers who need to translate these
3
+ // should override the action name by creating their own EntityAction objects or
4
+ // by using the entityActions prop with custom names for their locale.
1
5
  import { DeleteIcon, EditIcon, FileCopyIcon } from "@firecms/ui";
2
6
  import { EntityAction } from "../../types";
3
7
  import { DeleteEntityDialog } from "../DeleteEntityDialog";
@@ -139,20 +139,6 @@ export function useDataSourceTableController<M extends Record<string, any> = any
139
139
  const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? (updateUrl ? initialFilterUrl : undefined) ?? initialFilter ?? undefined);
140
140
  const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>((updateUrl ? initialSortUrl : undefined) ?? initialSortInternal);
141
141
 
142
- useEffect(() => {
143
- if (updateUrl) {
144
- const { filterValues: urlFilterValues, sortBy: urlSortBy } = parseFilterAndSort(location.search);
145
- if (!forceFilter) {
146
- setFilterValues(urlFilterValues as any);
147
- }
148
- if (urlSortBy && forceFilter && !checkFilterCombination(forceFilter, urlSortBy)) {
149
- console.warn("URL sort is not compatible with the force filter.");
150
- } else {
151
- setSortBy(urlSortBy as any);
152
- }
153
- }
154
- }, [location.search, updateUrl, forceFilter, checkFilterCombination]);
155
-
156
142
  useUpdateUrl(filterValues, sortBy, searchString, updateUrl);
157
143
 
158
144
  const collectionScroll = scrollRestoration?.getCollectionScroll(fullPath, filterValues);
@@ -345,6 +331,11 @@ function encodeFilterAndSort(filterValues?: FilterValues<string>, sortBy?: [stri
345
331
  } else if (val instanceof EntityReference) {
346
332
  encodedValue = encodeRef(val);
347
333
  }
334
+ } else if (typeof val === "string") {
335
+ // JSON.stringify wraps the string in quotes (e.g. "4" → '"4"')
336
+ // so that decodeString's JSON.parse restores the string type,
337
+ // not a number. Without this, "4" round-trips as the number 4.
338
+ encodedValue = JSON.stringify(val);
348
339
  }
349
340
  } catch (e) {
350
341
  encodedValue = val;
@@ -42,3 +42,4 @@ export * from "./FieldCaption";
42
42
  export * from "./EntityPreview";
43
43
 
44
44
  export * from "./AIIcon";
45
+ export * from "./LanguageToggle";
@@ -1,10 +1,11 @@
1
1
  import React from "react";
2
2
 
3
3
  import { Link, useNavigate } from "react-router-dom";
4
- import { ErrorBoundary, FireCMSLogo } from "../components";
4
+ import { ErrorBoundary, FireCMSLogo, LanguageToggle } from "../components";
5
5
  import {
6
6
  Avatar,
7
7
  BrightnessMediumIcon,
8
+ CheckIcon,
8
9
  cls,
9
10
  DarkModeIcon,
10
11
  IconButton,
@@ -15,7 +16,7 @@ import {
15
16
  Skeleton,
16
17
  Typography
17
18
  } from "@firecms/ui";
18
- import { useAuthController, useLargeLayout, useModeController, useNavigationController } from "../hooks";
19
+ import { useAuthController, useLargeLayout, useModeController, useNavigationController, useTranslation } from "../hooks";
19
20
  import { User } from "../types";
20
21
  import { useApp } from "../app/useApp";
21
22
  import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
@@ -85,6 +86,8 @@ export const DefaultAppBar = function DefaultAppBar({
85
86
  mode,
86
87
  setMode
87
88
  } = useModeController();
89
+
90
+ const { i18n, t } = useTranslation();
88
91
 
89
92
  const navigate = useNavigate();
90
93
 
@@ -192,18 +195,19 @@ export const DefaultAppBar = function DefaultAppBar({
192
195
  <Menu
193
196
  trigger={<IconButton
194
197
  color="inherit"
195
- aria-label="Open drawer"
196
- size="large">
198
+ aria-label="Open drawer">
197
199
  {mode === "dark"
198
- ? <DarkModeIcon />
199
- : <LightModeIcon />}
200
+ ? <DarkModeIcon size="small" />
201
+ : <LightModeIcon size="small" />}
200
202
  </IconButton>}>
201
- <MenuItem onClick={() => setMode("dark")}><DarkModeIcon size={"smallest"} /> Dark</MenuItem>
202
- <MenuItem onClick={() => setMode("light")}><LightModeIcon size={"smallest"} /> Light </MenuItem>
203
+ <MenuItem onClick={() => setMode("dark")}><DarkModeIcon size={"smallest"} /> {t("dark_mode")}</MenuItem>
204
+ <MenuItem onClick={() => setMode("light")}><LightModeIcon size={"smallest"} /> {t("light_mode")}</MenuItem>
203
205
  <MenuItem onClick={() => setMode("system")}> <BrightnessMediumIcon
204
- size={"smallest"} />System</MenuItem>
206
+ size={"smallest"} />{t("system_mode")}</MenuItem>
205
207
  </Menu>}
206
208
 
209
+ <LanguageToggle />
210
+
207
211
  <Menu trigger={avatarComponent}>
208
212
  {user && <div className={"px-4 py-2 mb-2"}>
209
213
  {user.displayName && <Typography variant={"body1"} color={"secondary"}>
@@ -222,7 +226,7 @@ export const DefaultAppBar = function DefaultAppBar({
222
226
  navigate("/");
223
227
  }}>
224
228
  <LogoutIcon />
225
- Log Out
229
+ {t("log_out")}
226
230
  </MenuItem>}
227
231
 
228
232
  </Menu>
@@ -1,6 +1,11 @@
1
1
  import React from "react";
2
2
 
3
- import { useCollapsedGroups, useLargeLayout, useNavigationController } from "../hooks";
3
+ import {
4
+ useCollapsedGroups,
5
+ useLargeLayout,
6
+ useNavigationController,
7
+ useTranslation
8
+ } from "../hooks";
4
9
 
5
10
  import { Link, useNavigate } from "react-router-dom";
6
11
  import { CMSAnalyticsEvent, NavigationEntry, NavigationResult } from "../types";
@@ -34,6 +39,7 @@ export function DefaultDrawer({
34
39
 
35
40
  const analyticsController = useAnalyticsController();
36
41
  const navigation = useNavigationController();
42
+ const { t } = useTranslation();
37
43
 
38
44
  const tooltipsOpen = drawerHovered && !drawerOpen && !adminMenuOpen;
39
45
  const largeLayout = useLargeLayout();
@@ -122,7 +128,7 @@ export function DefaultDrawer({
122
128
  }}
123
129
  key={entry.id}>
124
130
  {<IconForView collectionOrView={entry.view} />}
125
- {entry.name}
131
+ {t(entry.name as any)}
126
132
  </MenuItem>)}
127
133
 
128
134
  </Menu>}
@@ -3,12 +3,13 @@ import { cls, ExpandMoreIcon, Typography } from "@firecms/ui";
3
3
  import { NavigationEntry } from "../types";
4
4
  import { IconForView } from "../util";
5
5
  import { DrawerNavigationItem } from "./DrawerNavigationItem";
6
+ import { useTranslation } from "../hooks/useTranslation";
6
7
 
7
8
  export interface DrawerNavigationGroupProps {
8
9
  /**
9
- * Group name to display in header
10
+ * Group name to display in header. When null, uses the translated default group name.
10
11
  */
11
- group: string;
12
+ group: string | null;
12
13
  /**
13
14
  * Navigation entries in this group
14
15
  */
@@ -58,6 +59,7 @@ export function DrawerNavigationGroup({
58
59
  headerActions,
59
60
  onItemClick
60
61
  }: DrawerNavigationGroupProps) {
62
+ const { t } = useTranslation();
61
63
  return (
62
64
  <div
63
65
  className={"bg-surface-50 dark:bg-surface-800/30 my-4 rounded-lg ml-3 mr-1"}
@@ -81,7 +83,7 @@ export function DrawerNavigationGroup({
81
83
  color={"secondary"}
82
84
  className="font-medium flex-grow line-clamp-1"
83
85
  >
84
- {(group || "Views").toUpperCase()}
86
+ {(group && group !== "__default__" ? group : t("views_group")).toUpperCase()}
85
87
  </Typography>
86
88
  {headerActions && (
87
89
  <div onClick={(e) => e.stopPropagation()}>
@@ -26,12 +26,13 @@ import {
26
26
  useFireCMSContext,
27
27
  useLargeLayout
28
28
  } from "../hooks";
29
- import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
29
+ import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography, Menu, MenuItem, ExpandMoreIcon } from "@firecms/ui";
30
30
  import { getEntityFromMemoryCache } from "../util/entity_cache";
31
31
  import { EntityForm, EntityFormProps } from "../form";
32
32
  import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
33
33
  import { EntityJsonPreview } from "../components/EntityJsonPreview";
34
34
  import { createFormexStub } from "../util/createFormexStub";
35
+ import { useTranslation } from "../hooks/useTranslation";
35
36
 
36
37
  export const MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
37
38
  export const JSON_TAB_VALUE = "__json";
@@ -170,6 +171,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
170
171
  }) {
171
172
 
172
173
  const context = useFireCMSContext();
174
+ const { t } = useTranslation();
173
175
 
174
176
  const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
175
177
 
@@ -228,6 +230,10 @@ export function EntityEditViewInner<M extends Record<string, any>>({
228
230
  const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
229
231
  const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
230
232
 
233
+ const groupedViews = useMemo(() => {
234
+ return (collection.viewGroups ?? []).flatMap(g => g.views);
235
+ }, [collection.viewGroups]);
236
+
231
237
  const {
232
238
  resolvedEntityViews,
233
239
  selectedEntityView,
@@ -342,8 +348,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
342
348
  openEntityMode={layout} />
343
349
  : <div className="flex items-center justify-center w-full h-full p-3">
344
350
  <Typography variant={"label"}>
345
- You need to save your entity before
346
- adding additional collections
351
+ {t("youd_need_to_save_before_additional_collections")}
347
352
  </Typography>
348
353
  </div>)
349
354
  }
@@ -418,16 +423,18 @@ export function EntityEditViewInner<M extends Record<string, any>>({
418
423
  Builder={selectedSecondaryForm?.Builder}
419
424
  />;
420
425
 
421
- const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
426
+ const subcollectionTabs = subcollections && subcollections
427
+ .filter(sub => !groupedViews.includes(sub.id ?? sub.path))
428
+ .map((subcollection) =>
422
429
  <Tab
423
430
  className="text-sm min-w-[120px]"
424
- value={subcollection.id}
431
+ value={subcollection.id ?? subcollection.path}
425
432
  key={`entity_detail_collection_tab_${subcollection.name}`}>
426
433
  {subcollection.name}
427
434
  </Tab>
428
435
  );
429
436
 
430
- const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
437
+ const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start" && !groupedViews.includes(view.key))
431
438
  .map((view) =>
432
439
  <Tab
433
440
  className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
@@ -436,7 +443,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
436
443
  {view.tabComponent ?? view.name}
437
444
  </Tab>
438
445
  );
439
- const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
446
+ const customViewTabsEnd = resolvedEntityViews.filter(view => (!view.position || view.position === "end") && !groupedViews.includes(view.key))
440
447
  .map((view) =>
441
448
  <Tab
442
449
  className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
@@ -446,6 +453,43 @@ export function EntityEditViewInner<M extends Record<string, any>>({
446
453
  </Tab>
447
454
  );
448
455
 
456
+ const viewGroupMenus = collection.viewGroups?.map(group => {
457
+ const isActive = group.views.includes(selectedTab);
458
+ return (
459
+ <Menu
460
+ key={`view_group_${group.name}`}
461
+ trigger={
462
+ <button
463
+ type="button"
464
+ className={cls(
465
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all",
466
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
467
+ "disabled:pointer-events-none disabled:opacity-50",
468
+ isActive ? "bg-white text-surface-900 dark:bg-surface-950 dark:text-surface-50" : "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800"
469
+ )}
470
+ >
471
+ {group.name}
472
+ <ExpandMoreIcon className="ml-1 -mr-1" size="small" />
473
+ </button>
474
+ }>
475
+ {group.views.map(viewId => {
476
+ const subcollection = subcollections.find(s => (s.id ?? s.path) === viewId);
477
+ const customView = resolvedEntityViews.find(v => v.key === viewId);
478
+ const name = subcollection?.name ?? customView?.name ?? viewId;
479
+ return (
480
+ <MenuItem
481
+ key={`view_group_${group.name}_${viewId}`}
482
+ onClick={() => onSideTabClick(viewId)}
483
+ className={selectedTab === viewId ? "bg-surface-accent-100 dark:bg-surface-accent-900" : ""}
484
+ >
485
+ {name}
486
+ </MenuItem>
487
+ );
488
+ })}
489
+ </Menu>
490
+ );
491
+ });
492
+
449
493
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
450
494
 
451
495
  let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
@@ -493,6 +537,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
493
537
 
494
538
  {customViewTabsEnd}
495
539
 
540
+ {viewGroupMenus}
541
+
496
542
  {subcollectionTabs}
497
543
  </Tabs>}
498
544
  </div>}
@@ -28,7 +28,8 @@ import {
28
28
  useCustomizationController,
29
29
  useFireCMSContext,
30
30
  useSideEntityController,
31
- useSnackbarController
31
+ useSnackbarController,
32
+ useTranslation
32
33
  } from "../hooks";
33
34
  import { EntityFormActionsProps } from "../form/EntityFormActions";
34
35
  import { SideDialogController, useSideDialogContext } from "./SideDialogs";
@@ -56,6 +57,7 @@ export function EntityEditViewFormActions({
56
57
  const sideEntityController = useSideEntityController();
57
58
  const sideDialogContext = useSideDialogContext();
58
59
  const customizationController = useCustomizationController();
60
+ const { t } = useTranslation();
59
61
 
60
62
  const entityActions = useMemo((): EntityAction[] => {
61
63
  const customEntityActions = (collection.entityActions ?? [])
@@ -90,7 +92,8 @@ export function EntityEditViewFormActions({
90
92
  openEntityMode,
91
93
  navigateBack,
92
94
  formContext,
93
- formex
95
+ formex,
96
+ t
94
97
  })
95
98
  : buildSideActions({
96
99
  savingError,
@@ -106,7 +109,8 @@ export function EntityEditViewFormActions({
106
109
  openEntityMode,
107
110
  navigateBack,
108
111
  formContext,
109
- formex
112
+ formex,
113
+ t
110
114
  });
111
115
  }
112
116
 
@@ -123,8 +127,9 @@ type ActionsViewProps<M extends object> = {
123
127
  pluginActions?: React.ReactNode[],
124
128
  openEntityMode: "side_panel" | "full_screen";
125
129
  navigateBack: () => void;
126
- formContext: FormContext,
130
+ formContext: FormContext;
127
131
  formex: FormexController<any>;
132
+ t: any;
128
133
  };
129
134
 
130
135
  function buildBottomActions<M extends object>({
@@ -141,7 +146,8 @@ function buildBottomActions<M extends object>({
141
146
  openEntityMode,
142
147
  navigateBack,
143
148
  formContext,
144
- formex
149
+ formex,
150
+ t
145
151
  }: ActionsViewProps<M>) {
146
152
 
147
153
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
@@ -191,7 +197,7 @@ function buildBottomActions<M extends object>({
191
197
  color="primary"
192
198
  disabled={disabled || formex.isSubmitting}
193
199
  type="reset">
194
- {status === "existing" ? "Discard" : "Clear"}
200
+ {status === "existing" ? t("discard") : t("clear")}
195
201
  </Button>
196
202
  <Button variant={canClose ? "text" : "filled"}
197
203
  color="primary"
@@ -200,9 +206,9 @@ function buildBottomActions<M extends object>({
200
206
  onClick={() => {
201
207
  sideDialogContext.setPendingClose(false);
202
208
  }}>
203
- {status === "existing" && "Save"}
204
- {status === "copy" && "Create copy"}
205
- {status === "new" && "Create"}
209
+ {status === "existing" && t("save")}
210
+ {status === "copy" && t("create_copy")}
211
+ {status === "new" && t("create")}
206
212
  </Button>
207
213
  {canClose && <LoadingButton variant="filled"
208
214
  color="primary"
@@ -212,9 +218,9 @@ function buildBottomActions<M extends object>({
212
218
  onClick={() => {
213
219
  sideDialogContext.setPendingClose?.(true);
214
220
  }}>
215
- {status === "existing" && "Save and close"}
216
- {status === "copy" && "Create copy and close"}
217
- {status === "new" && "Create and close"}
221
+ {status === "existing" && t("save_and_close")}
222
+ {status === "copy" && t("create_copy_and_close")}
223
+ {status === "new" && t("create_and_close")}
218
224
  </LoadingButton>}
219
225
  </DialogActions>;
220
226
  }
@@ -233,7 +239,8 @@ function buildSideActions<M extends object>({
233
239
  openEntityMode,
234
240
  navigateBack,
235
241
  formContext,
236
- formex
242
+ formex,
243
+ t
237
244
  }: ActionsViewProps<M>) {
238
245
 
239
246
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
@@ -249,13 +256,13 @@ function buildSideActions<M extends object>({
249
256
  onClick={() => {
250
257
  sideDialogContext.setPendingClose?.(false);
251
258
  }}>
252
- {status === "existing" && "Save"}
253
- {status === "copy" && "Create copy"}
254
- {status === "new" && "Create"}
259
+ {status === "existing" && t("save")}
260
+ {status === "copy" && t("create_copy")}
261
+ {status === "new" && t("create")}
255
262
  </LoadingButton>
256
263
 
257
264
  <Button fullWidth={true} variant="text" disabled={disabled || formex.isSubmitting} type="reset">
258
- {status === "existing" ? "Discard" : "Clear"}
265
+ {status === "existing" ? t("discard") : t("clear")}
259
266
  </Button>
260
267
 
261
268
  {pluginActions}