@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
@@ -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,
@@ -34,7 +35,7 @@ export type FilterFormFieldProps<CustomProps> = {
34
35
  };
35
36
 
36
37
  type VirtualTableHeaderProps<M extends Record<string, any>> = {
37
- resizeHandleRef: RefObject<HTMLDivElement>;
38
+ resizeHandleRef: RefObject<HTMLDivElement | null>;
38
39
  columnIndex: number;
39
40
  isResizingIndex: number;
40
41
  column: VirtualTableColumn<any>;
@@ -47,24 +48,26 @@ 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>>(
53
55
  function VirtualTableHeader<M extends Record<string, any>>({
54
- resizeHandleRef,
55
- columnIndex,
56
- isResizingIndex,
57
- sort,
58
- onColumnSort,
59
- onFilterUpdate,
60
- filter,
61
- column,
62
- onClickResizeColumn,
63
- createFilterField,
64
- AdditionalHeaderWidget,
65
- isDragging,
66
- isDraggable
67
- }: VirtualTableHeaderProps<M>) {
56
+ resizeHandleRef,
57
+ columnIndex,
58
+ isResizingIndex,
59
+ sort,
60
+ onColumnSort,
61
+ onFilterUpdate,
62
+ filter,
63
+ column,
64
+ onClickResizeColumn,
65
+ createFilterField,
66
+ AdditionalHeaderWidget,
67
+ isDragging,
68
+ isDraggable,
69
+ headerIconSize = "small",
70
+ }: VirtualTableHeaderProps<M>) {
68
71
 
69
72
  const [onHover, setOnHover] = useState(false);
70
73
 
@@ -129,24 +132,23 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
129
132
  <>
130
133
 
131
134
  {AdditionalHeaderWidget &&
132
- <AdditionalHeaderWidget onHover={onHover || openFilter}/>}
135
+ <AdditionalHeaderWidget onHover={onHover || openFilter} />}
133
136
 
134
137
  {column.sortable && (sort || hovered || openFilter) &&
135
138
  <Badge color="secondary"
136
- invisible={!sort}>
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
  }
@@ -154,7 +156,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
154
156
 
155
157
  {column.filter && createFilterField && <div>
156
158
  <Badge color="secondary"
157
- invisible={!filter}>
159
+ invisible={!filter}>
158
160
 
159
161
  <Popover
160
162
  open={openFilter}
@@ -166,16 +168,16 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
166
168
  className={onHover || openFilter ? "bg-white dark:bg-surface-950" : undefined}
167
169
  size={"small"}
168
170
  onClick={handleSettingsClick}>
169
- <FilterListIcon size={"small"}/>
171
+ <FilterListIcon size={"small"} />
170
172
  </IconButton>}
171
173
  >
172
174
  <FilterForm column={column}
173
- filter={filter}
174
- onHover={onHover}
175
- onFilterUpdate={update}
176
- createFilterField={createFilterField}
177
- hidden={hidden}
178
- setHidden={setHidden}/>
175
+ filter={filter}
176
+ onHover={onHover}
177
+ onFilterUpdate={update}
178
+ createFilterField={createFilterField}
179
+ hidden={hidden}
180
+ setHidden={setHidden} />
179
181
 
180
182
  </Popover>
181
183
 
@@ -204,14 +206,16 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
204
206
  }, equal) as React.FunctionComponent<VirtualTableHeaderProps<any>>;
205
207
 
206
208
  function FilterForm<M>({
207
- column,
208
- onFilterUpdate,
209
- filter,
210
- onHover,
211
- createFilterField,
212
- hidden,
213
- setHidden
214
- }: FilterFormProps<M>) {
209
+ column,
210
+ onFilterUpdate,
211
+ filter,
212
+ onHover,
213
+ createFilterField,
214
+ hidden,
215
+ setHidden
216
+ }: FilterFormProps<M>) {
217
+
218
+ const { t } = useTranslation();
215
219
 
216
220
  const id = column.key;
217
221
 
@@ -245,12 +249,12 @@ function FilterForm<M>({
245
249
  if (!filterField) return null;
246
250
  return (
247
251
  <form noValidate={true}
248
- onSubmit={(e) => {
249
- e.stopPropagation();
250
- e.preventDefault();
251
- submit();
252
- }}
253
- className={"text-surface-900 dark:text-white"}>
252
+ onSubmit={(e) => {
253
+ e.stopPropagation();
254
+ e.preventDefault();
255
+ submit();
256
+ }}
257
+ className={"text-surface-900 dark:text-white"}>
254
258
  <div
255
259
  className={cls(defaultBorderMixin, "py-4 px-6 typography-label border-b")}>
256
260
  {column.title ?? id}
@@ -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,11 +22,12 @@ 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;
29
- columnRefs: React.RefObject<HTMLDivElement>[];
30
+ columnRefs: React.RefObject<HTMLDivElement | null>[];
30
31
  isResizing: number;
31
32
  onFilterUpdate: any;
32
33
  filter: [VirtualTableWhereFilterOp, any] | undefined;
@@ -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
@@ -79,7 +79,7 @@ export function VirtualTableSelect(props: {
79
79
  multiple
80
80
  ? <MultiSelect
81
81
  inputRef={ref}
82
- className="w-full h-full p-0 bg-transparent"
82
+ className="w-full h-full p-0 bg-transparent outline-none"
83
83
  position={"item-aligned"}
84
84
  disabled={disabled}
85
85
  includeClear={false}
@@ -104,8 +104,8 @@ export function VirtualTableSelect(props: {
104
104
  inputRef={ref}
105
105
  size={"large"}
106
106
  fullWidth={true}
107
- className="w-full h-full p-0 bg-transparent"
108
- inputClassName="focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none"
107
+ className="w-full h-full p-0 bg-transparent outline-none [&_button]:ring-0 [&_button]:ring-offset-0"
108
+ inputClassName="ring-0 ring-offset-0 focus:ring-0 focus-visible:ring-0 outline-none focus:outline-none focus-visible:outline-none focus-visible:ring-offset-0"
109
109
  position={"item-aligned"}
110
110
  disabled={disabled}
111
111
  padding={false}
@@ -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";
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from "react";
2
+ import { useLocation } from "react-router-dom";
2
3
 
3
4
  import { useDataSource, useFireCMSContext, useNavigationController } from "../../hooks";
4
5
  import { useDataOrder } from "../../hooks/data/useDataOrder";
@@ -94,7 +95,7 @@ export function useDataSourceTableController<M extends Record<string, any> = any
94
95
  const [searchString, setSearchString] = React.useState<string | undefined>();
95
96
 
96
97
  const checkFilterCombination = useCallback((filterValues: FilterValues<any>,
97
- sortBy?: [string, "asc" | "desc"]) => {
98
+ sortBy?: [string, "asc" | "desc"]) => {
98
99
  if (!dataSource.isFilterCombinationValid)
99
100
  return true;
100
101
  return dataSource.isFilterCombinationValid({
@@ -106,8 +107,8 @@ export function useDataSourceTableController<M extends Record<string, any> = any
106
107
  }, []);
107
108
 
108
109
  const onScroll = ({
109
- scrollOffset
110
- }: {
110
+ scrollOffset
111
+ }: {
111
112
  scrollOffset: number
112
113
  }) => {
113
114
  if (scrollRestoration) {
@@ -128,10 +129,12 @@ export function useDataSourceTableController<M extends Record<string, any> = any
128
129
  return initialSort;
129
130
  }, [initialSort, forceFilter]);
130
131
 
132
+ const location = useLocation();
133
+
131
134
  const {
132
135
  filterValues: initialFilterUrl,
133
136
  sortBy: initialSortUrl,
134
- } = parseFilterAndSort(window.location.search);
137
+ } = parseFilterAndSort(location.search);
135
138
 
136
139
  const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? (updateUrl ? initialFilterUrl : undefined) ?? initialFilter ?? undefined);
137
140
  const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>((updateUrl ? initialSortUrl : undefined) ?? initialSortInternal);
@@ -328,6 +331,11 @@ function encodeFilterAndSort(filterValues?: FilterValues<string>, sortBy?: [stri
328
331
  } else if (val instanceof EntityReference) {
329
332
  encodedValue = encodeRef(val);
330
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);
331
339
  }
332
340
  } catch (e) {
333
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
 
@@ -92,7 +95,7 @@ export const DefaultAppBar = function DefaultAppBar({
92
95
 
93
96
  const user = userProp ?? authController.user;
94
97
 
95
- let avatarComponent: JSX.Element | null;
98
+ let avatarComponent: React.ReactElement | null;
96
99
 
97
100
  if (user) {
98
101
  const initial = user?.displayName
@@ -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,12 +453,49 @@ 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">
452
496
 
453
497
  {shouldShowTopBar && <div
454
- className={cls("h-14 items-center overflow-visible overflow-x-scroll w-full no-scrollbar border-b pl-2 pr-2 flex gap-2 bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
498
+ className={cls("h-14 items-center overflow-hidden w-full border-b pl-2 pr-2 flex gap-2 bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
455
499
 
456
500
  {barActions?.({
457
501
  path: fullIdPath ?? path,
@@ -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>}