@firecms/core 3.0.0-canary.258 → 3.0.0-canary.259

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 (39) hide show
  1. package/dist/core/EntityEditViewFormActions.d.ts +1 -1
  2. package/dist/form/EntityFormActions.d.ts +4 -2
  3. package/dist/index.es.js +339 -155
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +339 -155
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/types/collections.d.ts +4 -1
  8. package/dist/types/customization_controller.d.ts +8 -0
  9. package/dist/types/entity_actions.d.ts +45 -6
  10. package/dist/types/firecms.d.ts +8 -0
  11. package/dist/types/plugins.d.ts +8 -1
  12. package/dist/util/resolutions.d.ts +2 -1
  13. package/package.json +5 -5
  14. package/src/components/ConfirmationDialog.tsx +1 -0
  15. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -0
  16. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +2 -2
  17. package/src/components/EntityCollectionView/EntityCollectionView.tsx +5 -2
  18. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +3 -2
  19. package/src/components/HomePage/DefaultHomePage.tsx +48 -33
  20. package/src/components/HomePage/HomePageDnD.tsx +22 -36
  21. package/src/components/HomePage/RenameGroupDialog.tsx +6 -2
  22. package/src/components/UnsavedChangesDialog.tsx +6 -2
  23. package/src/components/common/default_entity_actions.tsx +18 -5
  24. package/src/components/common/useDataSourceTableController.tsx +1 -1
  25. package/src/core/EntityEditView.tsx +35 -12
  26. package/src/core/EntityEditViewFormActions.tsx +154 -29
  27. package/src/core/EntitySidePanel.tsx +1 -1
  28. package/src/core/FireCMS.tsx +2 -0
  29. package/src/form/EntityForm.tsx +31 -5
  30. package/src/form/EntityFormActions.tsx +37 -8
  31. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -2
  32. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
  33. package/src/types/collections.ts +4 -1
  34. package/src/types/customization_controller.tsx +9 -0
  35. package/src/types/entity_actions.tsx +56 -6
  36. package/src/types/firecms.tsx +9 -0
  37. package/src/types/plugins.tsx +9 -1
  38. package/src/util/join_collections.ts +3 -1
  39. package/src/util/resolutions.ts +13 -1
@@ -173,8 +173,11 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
173
173
  * }
174
174
  * }
175
175
  * ```
176
+ *
177
+ * You can also pass the action as a string that represents the `key`, in which case it will
178
+ * use the action defined in the main configuration under `entityActions`.
176
179
  */
177
- entityActions?: EntityAction<M, USER>[];
180
+ entityActions?: (EntityAction<M, USER> | string)[];
178
181
  /**
179
182
  * Pass your own selection controller if you want to control selected
180
183
  * entities externally.
@@ -4,6 +4,7 @@ import { FireCMSPlugin } from "./plugins";
4
4
  import { EntityCustomView } from "./collections";
5
5
  import { Locale } from "./locales";
6
6
  import { PropertyConfig } from "./property_config";
7
+ import { EntityAction } from "./entity_actions";
7
8
  export type CustomizationController = {
8
9
  /**
9
10
  * Builder for generating utility links for entities
@@ -21,6 +22,13 @@ export type CustomizationController = {
21
22
  * You can also define an entity view from the UI.
22
23
  */
23
24
  entityViews?: EntityCustomView[];
25
+ /**
26
+ * List of actions that can be performed on entities.
27
+ * These actions are displayed in the entity view and in the collection view.
28
+ * You can later reuse these actions in the `entityActions` prop of a collection,
29
+ * by specifying the `key` of the action.
30
+ */
31
+ entityActions?: EntityAction<any, any>[];
24
32
  /**
25
33
  * Format of the dates in the CMS.
26
34
  * Defaults to 'MMMM dd, yyyy, HH:mm:ss'
@@ -4,6 +4,7 @@ import { Entity } from "./entities";
4
4
  import { EntityCollection, SelectionController } from "./collections";
5
5
  import { User } from "./user";
6
6
  import { SideEntityController } from "./side_entity_controller";
7
+ import { FormContext } from "./fields";
7
8
  /**
8
9
  * An entity action is a custom action that can be performed on an entity.
9
10
  * They are displayed in the entity view and in the collection view.
@@ -15,7 +16,10 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
15
16
  name: string;
16
17
  /**
17
18
  * Key of the action. You only need to provide this if you want to
18
- * override the default actions.
19
+ * override the default actions, or if you are not passing the action
20
+ * directly to the `entityActions` prop of a collection.
21
+ * You can define your actions at the app level, in which case you
22
+ * must provide a key.
19
23
  * The default actions are:
20
24
  * - edit
21
25
  * - delete
@@ -31,6 +35,11 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
31
35
  * @param props
32
36
  */
33
37
  onClick: (props: EntityActionClickProps<M, USER>) => Promise<void> | void;
38
+ /**
39
+ * Optional callback in case you want to disable the action
40
+ * @param props
41
+ */
42
+ isEnabled?: (props: EntityActionClickProps<M, USER>) => boolean;
34
43
  /**
35
44
  * Show this action collapsed in the menu of the collection view.
36
45
  * Defaults to true
@@ -43,18 +52,48 @@ export type EntityAction<M extends object = any, USER extends User = User> = {
43
52
  includeInForm?: boolean;
44
53
  };
45
54
  export type EntityActionClickProps<M extends object, USER extends User = User> = {
46
- entity: Entity<M>;
55
+ entity?: Entity<M>;
47
56
  context: FireCMSContext<USER>;
48
57
  fullPath?: string;
49
58
  fullIdPath?: string;
50
59
  collection?: EntityCollection<M>;
60
+ /**
61
+ * Optional form context, present if the action is being called from a form.
62
+ * This allows you to access the form state and methods, including modifying the form values.
63
+ */
64
+ formContext?: FormContext;
65
+ /**
66
+ * Present if this actions is being called from a side dialog only
67
+ */
68
+ sideEntityController?: SideEntityController;
69
+ /**
70
+ * Is the action being called from the collection view or from the entity form view?
71
+ */
72
+ view: "collection" | "form";
73
+ /**
74
+ * If the action is rendered in the form, is it open in a side panel or full screen?
75
+ */
76
+ openEntityMode: "side_panel" | "full_screen";
77
+ /**
78
+ * Optional selection controller, present if the action is being called from a collection view
79
+ */
51
80
  selectionController?: SelectionController;
81
+ /**
82
+ * Optional highlight function to highlight the entity in the collection view
83
+ * @param entity
84
+ */
52
85
  highlightEntity?: (entity: Entity<any>) => void;
86
+ /**
87
+ * Optional unhighlight function to remove the highlight from the entity in the collection view
88
+ * @param entity
89
+ */
53
90
  unhighlightEntity?: (entity: Entity<any>) => void;
54
- onCollectionChange?: () => void;
55
91
  /**
56
- * If this actions is being called from a side dialog
92
+ * Optional function to navigate back (e.g. when deleting an entity or navigating from a form)
57
93
  */
58
- sideEntityController?: SideEntityController;
59
- openEntityMode: "side_panel" | "full_screen";
94
+ navigateBack?: () => void;
95
+ /**
96
+ * Callback to be called when the collection changes, e.g. after an entity is deleted or created.
97
+ */
98
+ onCollectionChange?: () => void;
60
99
  };
@@ -12,6 +12,7 @@ import { EntityLinkBuilder } from "./entity_link_builder";
12
12
  import { UserConfigurationPersistence } from "./local_config_persistence";
13
13
  import { FireCMSPlugin } from "./plugins";
14
14
  import { CMSAnalyticsEvent } from "./analytics";
15
+ import { EntityAction } from "./entity_actions";
15
16
  /**
16
17
  * Use this callback to build entity collections dynamically.
17
18
  * You can use the user to decide which collections to show.
@@ -85,6 +86,13 @@ export type FireCMSProps<USER extends User> = {
85
86
  * You can also define an entity view from the UI.
86
87
  */
87
88
  entityViews?: EntityCustomView[];
89
+ /**
90
+ * List of actions that can be performed on entities.
91
+ * These actions are displayed in the entity view and in the collection view.
92
+ * You can later reuse these actions in the `entityActions` prop of a collection,
93
+ * by specifying the `key` of the action.
94
+ */
95
+ entityActions?: EntityAction[];
88
96
  /**
89
97
  * Format of the dates in the CMS.
90
98
  * Defaults to 'MMMM dd, yyyy, HH:mm:ss'
@@ -139,7 +139,14 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
139
139
  Component: React.ComponentType<PropsWithChildren<FORM_PROPS & PluginFormActionProps<any, EC>>>;
140
140
  props?: FORM_PROPS;
141
141
  };
142
+ /**
143
+ * Add custom actions to the default ones ("Save", "Discard"...)
144
+ */
142
145
  Actions?: React.ComponentType<PluginFormActionProps<any, EC>>;
146
+ /**
147
+ * Add custom actions to the top of the form
148
+ */
149
+ ActionsTop?: React.ComponentType<PluginFormActionProps<any, EC>>;
143
150
  fieldBuilder?: <T extends CMSType = CMSType>(props: PluginFieldBuilderParams<T, any, EC>) => React.ComponentType<FieldProps<T>> | null;
144
151
  fieldBuilderEnabled?: <T extends CMSType = CMSType>(props: PluginFieldBuilderParams<T>) => boolean;
145
152
  };
@@ -181,12 +188,12 @@ export interface PluginHomePageActionsProps<EP extends object = object, M extend
181
188
  export interface PluginFormActionProps<USER extends User = User, EC extends EntityCollection = EntityCollection> {
182
189
  entityId?: string;
183
190
  path: string;
191
+ parentCollectionIds: string[];
184
192
  status: EntityStatus;
185
193
  collection: EC;
186
194
  disabled: boolean;
187
195
  formContext?: FormContext<any>;
188
196
  context: FireCMSContext<USER>;
189
- currentEntityId?: string;
190
197
  openEntityMode: "side_panel" | "full_screen";
191
198
  }
192
199
  export type PluginFieldBuilderParams<T extends CMSType = CMSType, M extends Record<string, any> = any, EC extends EntityCollection<M> = EntityCollection<M>> = {
@@ -1,4 +1,4 @@
1
- import { ArrayProperty, AuthController, CMSType, CustomizationController, EntityCollection, EntityCustomView, EntityValues, EnumValueConfig, EnumValues, NumberProperty, PropertiesOrBuilders, PropertyConfig, PropertyOrBuilder, ResolvedArrayProperty, ResolvedEntityCollection, ResolvedNumberProperty, ResolvedProperties, ResolvedProperty, ResolvedStringProperty, StringProperty, UserConfigurationPersistence } from "../types";
1
+ import { ArrayProperty, AuthController, CMSType, CustomizationController, EntityAction, EntityCollection, EntityCustomView, EntityValues, EnumValueConfig, EnumValues, NumberProperty, PropertiesOrBuilders, PropertyConfig, PropertyOrBuilder, ResolvedArrayProperty, ResolvedEntityCollection, ResolvedNumberProperty, ResolvedProperties, ResolvedProperty, ResolvedStringProperty, StringProperty, UserConfigurationPersistence } from "../types";
2
2
  export declare const resolveCollection: <M extends Record<string, any>>({ collection, path, entityId, values, previousValues, userConfigPersistence, propertyConfigs, ignoreMissingFields, authController }: {
3
3
  collection: EntityCollection<M> | ResolvedEntityCollection<M>;
4
4
  path: string;
@@ -81,6 +81,7 @@ export declare function resolveProperties<M extends Record<string, any>>({ prope
81
81
  export declare function resolvePropertyEnum(property: StringProperty | NumberProperty, fromBuilder?: boolean): ResolvedStringProperty | ResolvedNumberProperty;
82
82
  export declare function resolveEnumValues(input: EnumValues): EnumValueConfig[] | undefined;
83
83
  export declare function resolveEntityView(entityView: string | EntityCustomView<any>, contextEntityViews?: EntityCustomView<any>[]): EntityCustomView<any> | undefined;
84
+ export declare function resolveEntityAction<M extends Record<string, any>>(entityAction: string | EntityAction<M>, contextEntityActions?: EntityAction<M>[]): EntityAction<M> | undefined;
84
85
  export declare function resolvedSelectedEntityView<M extends Record<string, any>>(customViews: (string | EntityCustomView<M>)[] | undefined, customizationController: CustomizationController, selectedTab?: string, canEdit?: boolean): {
85
86
  resolvedEntityViews: EntityCustomView<M>[];
86
87
  selectedEntityView: EntityCustomView<M> | undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.258",
4
+ "version": "3.0.0-canary.259",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -53,9 +53,9 @@
53
53
  "@dnd-kit/core": "^6.3.1",
54
54
  "@dnd-kit/modifiers": "^9.0.0",
55
55
  "@dnd-kit/sortable": "^10.0.0",
56
- "@firecms/editor": "^3.0.0-canary.258",
57
- "@firecms/formex": "^3.0.0-canary.258",
58
- "@firecms/ui": "^3.0.0-canary.258",
56
+ "@firecms/editor": "^3.0.0-canary.259",
57
+ "@firecms/formex": "^3.0.0-canary.259",
58
+ "@firecms/ui": "^3.0.0-canary.259",
59
59
  "@radix-ui/react-portal": "^1.1.3",
60
60
  "clsx": "^2.1.1",
61
61
  "date-fns": "^3.6.0",
@@ -107,7 +107,7 @@
107
107
  "dist",
108
108
  "src"
109
109
  ],
110
- "gitHead": "2d408ab25c6f29305fa6052d2aff07a5ab3daa14",
110
+ "gitHead": "a4ab5e6aacfb9e10ee8c1c72354c939fb2275ab0",
111
111
  "publishConfig": {
112
112
  "access": "public"
113
113
  },
@@ -29,6 +29,7 @@ export function ConfirmationDialog({
29
29
 
30
30
  <DialogActions>
31
31
  <Button
32
+ color={"primary"}
32
33
  variant={"text"}
33
34
  onClick={onCancel}
34
35
  autoFocus>Cancel</Button>
@@ -107,6 +107,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
107
107
  onClick={(event: MouseEvent) => {
108
108
  event.stopPropagation();
109
109
  action.onClick({
110
+ view: "collection",
110
111
  entity,
111
112
  fullPath,
112
113
  fullIdPath,
@@ -137,6 +138,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
137
138
  onClick={(e) => {
138
139
  e.stopPropagation();
139
140
  action.onClick({
141
+ view: "collection",
140
142
  entity,
141
143
  fullPath,
142
144
  fullIdPath,
@@ -73,7 +73,7 @@ export function CollectionTableToolbar({
73
73
  <div
74
74
  className={cls(defaultBorderMixin, "no-scrollbar min-h-[56px] overflow-x-auto px-2 md:px-4 bg-surface-50 dark:bg-surface-900 border-b flex flex-row justify-between items-center w-full")}>
75
75
 
76
- <div className="flex items-center gap-2 md:mr-4 mr-2">
76
+ <div className="flex items-center gap-1 md:mr-4 mr-2">
77
77
 
78
78
  {title && <div className={"hidden lg:block"}>
79
79
  {title}
@@ -85,7 +85,7 @@ export function CollectionTableToolbar({
85
85
 
86
86
  </div>
87
87
 
88
- <div className="flex items-center gap-2">
88
+ <div className="flex items-center gap-1">
89
89
 
90
90
  {largeLayout && <div className="w-[22px] mr-4">
91
91
  {loading &&
@@ -31,6 +31,7 @@ import {
31
31
  mergeEntityActions,
32
32
  navigateToEntity,
33
33
  resolveCollection,
34
+ resolveEntityAction,
34
35
  resolveProperty
35
36
  } from "../../util";
36
37
  import { ReferencePreview } from "../../preview";
@@ -151,7 +152,6 @@ export const EntityCollectionView = React.memo(
151
152
  }: EntityCollectionViewProps<M>
152
153
  ) {
153
154
 
154
-
155
155
  const context = useFireCMSContext();
156
156
  const navigation = useNavigationController();
157
157
  const fullPath = fullPathProp ?? collectionProp.path;
@@ -521,10 +521,13 @@ export const EntityCollectionView = React.memo(
521
521
  }) => {
522
522
 
523
523
  const isSelected = Boolean(usedSelectionController.selectedEntities.find(e => e.id == entity.id && e.path == entity.path));
524
+ const customEntityActions = (collection.entityActions ?? [])
525
+ .map(action => resolveEntityAction(action, customizationController.entityActions))
526
+ .filter(Boolean) as EntityAction[];
524
527
 
525
528
  const actions = getActionsForEntity({
526
529
  entity,
527
- customEntityActions: collection.entityActions
530
+ customEntityActions
528
531
  });
529
532
 
530
533
  return (
@@ -71,7 +71,7 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
71
71
  ? <Button
72
72
  variant={"text"}
73
73
  disabled={!(selectedEntities?.length) || !multipleDeleteEnabled}
74
- startIcon={<DeleteIcon/>}
74
+ startIcon={<DeleteIcon size={"small"}/>}
75
75
  onClick={onMultipleDeleteClick}
76
76
  color={"primary"}
77
77
  className="lg:w-20"
@@ -79,10 +79,11 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
79
79
  ({selectedEntities?.length})
80
80
  </Button>
81
81
  : <IconButton
82
+ size={"small"}
82
83
  color={"primary"}
83
84
  disabled={!(selectedEntities?.length) || !multipleDeleteEnabled}
84
85
  onClick={onMultipleDeleteClick}>
85
- <DeleteIcon/>
86
+ <DeleteIcon size={"small"}/>
86
87
  </IconButton>;
87
88
  multipleDeleteButton =
88
89
  <Tooltip
@@ -38,6 +38,7 @@ export function DefaultHomePage({
38
38
  additionalChildrenStart?: React.ReactNode;
39
39
  additionalChildrenEnd?: React.ReactNode;
40
40
  }) {
41
+
41
42
  const context = useFireCMSContext();
42
43
  const customizationController = useCustomizationController();
43
44
  const navigationController = useNavigationController();
@@ -56,9 +57,12 @@ export function DefaultHomePage({
56
57
  const [filteredUrls, setFilteredUrls] = useState<string[] | null>(null);
57
58
  const performingSearch = Boolean(filteredUrls);
58
59
 
59
- const filteredNavigationEntries = filteredUrls
60
- ? rawNavigationEntries.filter((e) => filteredUrls.includes(e.url))
61
- : rawNavigationEntries;
60
+ // Memoize filtered navigation entries to prevent unnecessary recalculations
61
+ const filteredNavigationEntries = useMemo(() => {
62
+ return filteredUrls
63
+ ? rawNavigationEntries.filter((e) => filteredUrls.includes(e.url))
64
+ : rawNavigationEntries;
65
+ }, [filteredUrls, rawNavigationEntries]);
62
66
 
63
67
  useEffect(() => {
64
68
  fuse.current = new Fuse(rawNavigationEntries, {
@@ -67,9 +71,12 @@ export function DefaultHomePage({
67
71
  }, [rawNavigationEntries]);
68
72
 
69
73
  const updateSearch = useCallback((v?: string) => {
70
- if (!v?.trim()) return setFilteredUrls(null);
71
- const r = fuse.current?.search(v.trim());
72
- setFilteredUrls(r ? r.map((x) => x.item.url) : []);
74
+ if (!v?.trim()) {
75
+ setFilteredUrls(null);
76
+ return;
77
+ }
78
+ const results = fuse.current?.search(v.trim());
79
+ setFilteredUrls(results ? results.map((x) => x.item.url) : []);
73
80
  }, []);
74
81
 
75
82
  /* ───────────────────────────────────────────────────────────────
@@ -83,12 +90,11 @@ export function DefaultHomePage({
83
90
  entries: NavigationEntry[];
84
91
  } | null>(null);
85
92
 
86
- useEffect(() => {
87
- const src = performingSearch
88
- ? filteredNavigationEntries
89
- : rawNavigationEntries;
90
-
93
+ // Memoize the processed groups to avoid unnecessary recalculations
94
+ const processedGroups = useMemo(() => {
95
+ const src = filteredNavigationEntries;
91
96
  const entriesByGroup: Record<string, NavigationEntry[]> = {};
97
+
92
98
  src.forEach((e) => {
93
99
  const g =
94
100
  e.type === "admin"
@@ -97,6 +103,9 @@ export function DefaultHomePage({
97
103
  (entriesByGroup[g] ??= []).push(e);
98
104
  });
99
105
 
106
+ // Check if there are custom actions from plugins that should show in the default group
107
+ const hasPluginAdditionalCards = customizationController.plugins?.some(p => p.homePage?.AdditionalCards);
108
+
100
109
  let allProcessed: { name: string; entries: NavigationEntry[] }[];
101
110
 
102
111
  if (performingSearch) {
@@ -121,27 +130,35 @@ export function DefaultHomePage({
121
130
  entries: entriesByGroup[g]
122
131
  });
123
132
  });
133
+
134
+ // Ensure default group exists if there are plugin additional cards but no collections
135
+ if (hasPluginAdditionalCards && !allProcessed.some(g => g.name === DEFAULT_GROUP_NAME)) {
136
+ allProcessed.push({
137
+ name: DEFAULT_GROUP_NAME,
138
+ entries: []
139
+ });
140
+ }
141
+
124
142
  allProcessed = allProcessed.filter(
125
143
  (g) =>
126
144
  g.entries.length ||
127
- groupOrderFromNavController.includes(g.name)
145
+ groupOrderFromNavController.includes(g.name) ||
146
+ (g.name === DEFAULT_GROUP_NAME && hasPluginAdditionalCards)
128
147
  );
129
148
  }
130
149
 
131
150
  const admin = allProcessed.find((g) => g.name === ADMIN_GROUP_NAME);
132
- if (admin) {
133
- setAdminGroupData(admin);
134
- setItems(allProcessed.filter((g) => g.name !== ADMIN_GROUP_NAME));
135
- } else {
136
- setAdminGroupData(null);
137
- setItems(allProcessed);
138
- }
139
- }, [
140
- performingSearch,
141
- filteredNavigationEntries,
142
- rawNavigationEntries,
143
- groupOrderFromNavController
144
- ]);
151
+ return {
152
+ adminGroupData: admin || null,
153
+ items: allProcessed.filter((g) => g.name !== ADMIN_GROUP_NAME)
154
+ };
155
+ }, [filteredNavigationEntries, performingSearch, groupOrderFromNavController, customizationController.plugins]);
156
+
157
+ // Update state only when processedGroups actually changes
158
+ useEffect(() => {
159
+ setAdminGroupData(processedGroups.adminGroupData);
160
+ setItems(processedGroups.items);
161
+ }, [processedGroups]);
145
162
 
146
163
  /* ───────────────────────────────────────────────────────────────
147
164
  Local update vs. persistence helpers
@@ -177,9 +194,9 @@ export function DefaultHomePage({
177
194
  onNavigationEntriesUpdate(all);
178
195
  };
179
196
 
180
- /* ───────────────────────────────────────────────────────────────
197
+ /* ─────────────────────────────────────────────────────���─────────
181
198
  Hook for DnD
182
- ─────────────────────────────────────────────────────────────── */
199
+ ───�����────────────────────────────────────────────────────────── */
183
200
  const {
184
201
  sensors,
185
202
  collisionDetection,
@@ -225,11 +242,9 @@ export function DefaultHomePage({
225
242
 
226
243
  const dndDisabled = !allowDragAndDrop || performingSearch;
227
244
 
228
- const dndModifiers = useMemo(() => {
229
- if (dndKitActiveNode?.data.current?.type === "group")
230
- return [restrictToVerticalAxis, restrictToWindowEdges];
231
- return [restrictToWindowEdges];
232
- }, [dndKitActiveNode]);
245
+ const dndModifiers = dndKitActiveNode?.data.current?.type === "group"
246
+ ? [restrictToVerticalAxis, restrictToWindowEdges]
247
+ : [restrictToWindowEdges];
233
248
 
234
249
  /* ───────────────────────────────────────────────────────────────
235
250
  Plugin extras
@@ -288,7 +303,7 @@ export function DefaultHomePage({
288
303
 
289
304
  /* ───────────────────────────────────────────────────────────────
290
305
  Render
291
- ─────────────────────────────────────────────────────────────── */
306
+ ─────────���───────────────────────────────────────────────────── */
292
307
  return (
293
308
  <div ref={containerRef} className="py-2 overflow-auto h-full w-full">
294
309
  <Container maxWidth="6xl">
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
2
  import {
3
3
  Active,
4
4
  closestCenter,
@@ -84,14 +84,11 @@ export function SortableNavigationCard({
84
84
  animateLayoutChanges
85
85
  });
86
86
 
87
- const style = useMemo(
88
- () => ({
89
- transform: transform ? CSS.Transform.toString(transform) : undefined,
90
- transition,
91
- opacity: isDragging ? 0 : 1
92
- }),
93
- [transform, transition, isDragging]
94
- );
87
+ const style = {
88
+ transform: transform ? CSS.Transform.toString(transform) : undefined,
89
+ transition,
90
+ opacity: isDragging ? 0 : 1
91
+ };
95
92
 
96
93
  return (
97
94
  <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
@@ -153,14 +150,11 @@ export function SortableNavigationGroup({
153
150
  disabled
154
151
  });
155
152
 
156
- const style = useMemo(
157
- () => ({
158
- transform: transform ? CSS.Transform.toString(transform) : undefined,
159
- transition,
160
- opacity: isDragging ? 0 : 1
161
- }),
162
- [transform, transition, isDragging]
163
- );
153
+ const style = {
154
+ transform: transform ? CSS.Transform.toString(transform) : undefined,
155
+ transition,
156
+ opacity: isDragging ? 0 : 1
157
+ };
164
158
 
165
159
  return (
166
160
  <div ref={setNodeRef} style={style} {...attributes} {...listeners}>
@@ -224,15 +218,10 @@ export function useHomePageDnd({
224
218
  }
225
219
  });
226
220
  const keyboardSensor = useSensor(KeyboardSensor);
227
- const sensors = useSensors(
228
- ...(disabled ? [] : [mouseSensor, touchSensor, keyboardSensor])
229
- );
221
+ const sensors = useSensors(mouseSensor, touchSensor, keyboardSensor);
230
222
 
231
223
  /* ---------------- helpers ---------------- */
232
- const dndContainers = useMemo(
233
- () => dndItems.map((g) => g.name),
234
- [dndItems]
235
- );
224
+ const dndContainers = dndItems.map((g) => g.name);
236
225
 
237
226
  const findDndContainer = useCallback(
238
227
  (id: UniqueIdentifier): string | undefined => {
@@ -518,18 +507,15 @@ export function useHomePageDnd({
518
507
  };
519
508
 
520
509
  /* ---------------- public API ---------------- */
521
- const activeItemForOverlay = useMemo(() => {
522
- if (disabled || !activeId || activeIsGroup) return null;
523
- return (
524
- dndItems.flatMap((g) => g.entries).find((e) => e.url === activeId) ||
525
- null
526
- );
527
- }, [activeId, dndItems, disabled, activeIsGroup]);
528
-
529
- const activeGroupData = useMemo(() => {
530
- if (disabled || !activeId || !activeIsGroup) return null;
531
- return dndItems.find((g) => g.name === activeId) || null;
532
- }, [activeId, dndItems, disabled, activeIsGroup]);
510
+ const activeItemForOverlay =
511
+ disabled || !activeId || activeIsGroup
512
+ ? null
513
+ : dndItems.flatMap((g) => g.entries).find((e) => e.url === activeId) || null;
514
+
515
+ const activeGroupData =
516
+ disabled || !activeId || !activeIsGroup
517
+ ? null
518
+ : dndItems.find((g) => g.name === activeId) || null;
533
519
 
534
520
  return {
535
521
  sensors,
@@ -101,10 +101,14 @@ export function RenameGroupDialog({ open, initialName, existingGroupNames, onClo
101
101
  {error && <p id="group-name-error" style={{ display: "none" }}>{error}</p>}
102
102
  </DialogContent>
103
103
  <DialogActions>
104
- <Button onClick={onClose} variant="text">
104
+ <Button onClick={onClose}
105
+ color={"primary"}
106
+ variant="text">
105
107
  Cancel
106
108
  </Button>
107
- <Button onClick={handleSave} disabled={!!error || !name.trim()}>
109
+ <Button onClick={handleSave}
110
+ color={"primary"}
111
+ disabled={!!error || !name.trim()}>
108
112
  Save
109
113
  </Button>
110
114
  </DialogActions>
@@ -34,8 +34,12 @@ export function UnsavedChangesDialog({
34
34
 
35
35
  </DialogContent>
36
36
  <DialogActions>
37
- <Button variant="text" onClick={handleCancel} autoFocus> Cancel </Button>
38
- <Button onClick={handleOk}> Ok </Button>
37
+ <Button variant="text"
38
+ color={"primary"}
39
+ onClick={handleCancel} autoFocus> Cancel </Button>
40
+ <Button
41
+ color={"primary"}
42
+ onClick={handleOk}> Ok </Button>
39
43
  </DialogActions>
40
44
  </Dialog>
41
45
  );