@firecms/core 3.0.0-canary.248 → 3.0.0-canary.249

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 (45) hide show
  1. package/dist/components/HomePage/DefaultHomePage.d.ts +2 -15
  2. package/dist/components/HomePage/HomePageDnD.d.ts +76 -0
  3. package/dist/components/HomePage/NavigationCard.d.ts +3 -1
  4. package/dist/components/HomePage/NavigationCardBinding.d.ts +3 -2
  5. package/dist/components/HomePage/NavigationGroup.d.ts +7 -1
  6. package/dist/components/HomePage/RenameGroupDialog.d.ts +9 -0
  7. package/dist/core/field_configs.d.ts +1 -1
  8. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +9 -0
  9. package/dist/form/index.d.ts +1 -0
  10. package/dist/hooks/useBuildNavigationController.d.ts +51 -2
  11. package/dist/index.es.js +1726 -778
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +1723 -775
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/types/analytics.d.ts +1 -1
  16. package/dist/types/collections.d.ts +3 -0
  17. package/dist/types/navigation.d.ts +20 -4
  18. package/dist/types/plugins.d.ts +12 -0
  19. package/dist/types/properties.d.ts +7 -0
  20. package/dist/types/property_config.d.ts +1 -1
  21. package/dist/util/icons.d.ts +1 -1
  22. package/package.json +5 -5
  23. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +25 -3
  24. package/src/components/HomePage/DefaultHomePage.tsx +476 -157
  25. package/src/components/HomePage/FavouritesView.tsx +3 -3
  26. package/src/components/HomePage/HomePageDnD.tsx +613 -0
  27. package/src/components/HomePage/NavigationCard.tsx +47 -38
  28. package/src/components/HomePage/NavigationCardBinding.tsx +10 -6
  29. package/src/components/HomePage/NavigationGroup.tsx +63 -29
  30. package/src/components/HomePage/RenameGroupDialog.tsx +113 -0
  31. package/src/core/DefaultDrawer.tsx +8 -8
  32. package/src/core/DrawerNavigationItem.tsx +1 -1
  33. package/src/core/field_configs.tsx +15 -1
  34. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +135 -0
  35. package/src/form/field_bindings/RepeatFieldBinding.tsx +0 -1
  36. package/src/form/index.tsx +1 -0
  37. package/src/hooks/useBuildNavigationController.tsx +273 -84
  38. package/src/preview/PropertyPreview.tsx +14 -0
  39. package/src/types/analytics.ts +3 -0
  40. package/src/types/collections.ts +3 -0
  41. package/src/types/navigation.ts +27 -5
  42. package/src/types/plugins.tsx +15 -0
  43. package/src/types/properties.ts +8 -0
  44. package/src/types/property_config.tsx +1 -0
  45. package/src/util/icons.tsx +7 -3
@@ -1,222 +1,541 @@
1
- import React, { useCallback, useEffect, useRef, useState } from "react";
2
-
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import Fuse from "fuse.js";
3
+ import { Container, SearchBar } from "@firecms/ui";
3
4
  import { useCustomizationController, useFireCMSContext, useNavigationController } from "../../hooks";
4
5
  import {
5
6
  CMSAnalyticsEvent,
7
+ NavigationEntry,
8
+ NavigationGroupMapping,
6
9
  PluginGenericProps,
7
- PluginHomePageAdditionalCardsProps,
8
- TopNavigationEntry
10
+ PluginHomePageAdditionalCardsProps
9
11
  } from "../../types";
10
-
11
12
  import { toArray } from "../../util/arrays";
12
- import { NavigationGroup } from "./NavigationGroup";
13
- import { NavigationCardBinding } from "./NavigationCardBinding";
14
-
15
- import Fuse from "fuse.js"
16
-
17
- import { Container, SearchBar } from "@firecms/ui";
18
13
  import { FavouritesView } from "./FavouritesView";
19
14
  import { useRestoreScroll } from "../../internal/useRestoreScroll";
15
+ import { NavigationGroup } from "./NavigationGroup";
16
+ import {
17
+ NavigationGroupDroppable,
18
+ NewGroupDropZone,
19
+ SortableNavigationCard,
20
+ SortableNavigationGroup,
21
+ useHomePageDnd
22
+ } from "./HomePageDnD";
23
+ import { DndContext, DragOverlay, MeasuringStrategy } from "@dnd-kit/core";
24
+ import { NavigationCardBinding } from "./NavigationCardBinding";
25
+ import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
26
+ import { restrictToVerticalAxis, restrictToWindowEdges } from "@dnd-kit/modifiers";
27
+ import { RenameGroupDialog } from "./RenameGroupDialog";
20
28
 
21
- /**
22
- * Default entry view for the CMS. This component renders navigation cards
23
- * for each collection defined in the navigation.
29
+ export const DEFAULT_GROUP_NAME = "Views";
30
+ export const ADMIN_GROUP_NAME = "Admin";
24
31
 
25
- * @group Components
26
- */
27
32
  export function DefaultHomePage({
28
33
  additionalActions,
29
34
  additionalChildrenStart,
30
35
  additionalChildrenEnd
31
36
  }: {
32
- /**
33
- * Additional actions to be rendered in the home page, close to the search bar.
34
- */
35
37
  additionalActions?: React.ReactNode;
36
- /**
37
- * Additional children to be rendered in the beginning of the home page.
38
- */
39
38
  additionalChildrenStart?: React.ReactNode;
40
- /**
41
- * Additional children to be rendered at the end of the home page.
42
- */
43
39
  additionalChildrenEnd?: React.ReactNode;
44
40
  }) {
45
-
46
41
  const context = useFireCMSContext();
47
42
  const customizationController = useCustomizationController();
48
43
  const navigationController = useNavigationController();
49
44
 
50
- const fuse = useRef<Fuse<TopNavigationEntry> | null>(null);
51
-
52
45
  if (!navigationController.topLevelNavigation)
53
- throw Error("Navigation not ready in FireCMSHomePage");
46
+ throw Error("Navigation not ready");
54
47
 
55
48
  const {
56
- containerRef,
57
- scroll,
58
- direction
59
- } = useRestoreScroll();
60
-
61
- const {
62
- navigationEntries,
63
- groups
49
+ allowDragAndDrop,
50
+ navigationEntries: rawNavigationEntries,
51
+ groups: groupOrderFromNavController,
52
+ onNavigationEntriesUpdate
64
53
  } = navigationController.topLevelNavigation;
65
54
 
55
+ const fuse = useRef<Fuse<NavigationEntry> | null>(null);
66
56
  const [filteredUrls, setFilteredUrls] = useState<string[] | null>(null);
67
57
  const performingSearch = Boolean(filteredUrls);
68
58
 
69
59
  const filteredNavigationEntries = filteredUrls
70
- ? filteredUrls.map(url => navigationEntries.find(e => e.url === url)).filter(Boolean) as TopNavigationEntry[]
71
- : navigationEntries;
60
+ ? rawNavigationEntries.filter((e) => filteredUrls.includes(e.url))
61
+ : rawNavigationEntries;
72
62
 
73
63
  useEffect(() => {
74
- fuse.current = new Fuse(navigationEntries, {
64
+ fuse.current = new Fuse(rawNavigationEntries, {
75
65
  keys: ["name", "description", "group", "path"]
76
66
  });
77
- }, [navigationEntries]);
78
-
79
- const updateSearchResults = useCallback(
80
- (value?: string) => {
81
- if (!value || value === "") {
82
- setFilteredUrls(null);
83
- } else {
84
- const searchResult = fuse.current?.search(value);
85
- if (searchResult) {
86
- setFilteredUrls(searchResult.map((e) => e.item.url));
67
+ }, [rawNavigationEntries]);
68
+
69
+ 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) : []);
73
+ }, []);
74
+
75
+ /* ───────────────────────────────────────────────────────────────
76
+ Build groups (all items) + isolate Admin
77
+ ─────────────────────────────────────────────────────────────── */
78
+ const [items, setItems] = useState<
79
+ { name: string; entries: NavigationEntry[] }[]
80
+ >([]);
81
+ const [adminGroupData, setAdminGroupData] = useState<{
82
+ name: string;
83
+ entries: NavigationEntry[];
84
+ } | null>(null);
85
+
86
+ useEffect(() => {
87
+ const src = performingSearch
88
+ ? filteredNavigationEntries
89
+ : rawNavigationEntries;
90
+
91
+ const entriesByGroup: Record<string, NavigationEntry[]> = {};
92
+ src.forEach((e) => {
93
+ const g =
94
+ e.type === "admin"
95
+ ? ADMIN_GROUP_NAME
96
+ : e.group ?? DEFAULT_GROUP_NAME;
97
+ (entriesByGroup[g] ??= []).push(e);
98
+ });
99
+
100
+ let allProcessed: { name: string; entries: NavigationEntry[] }[];
101
+
102
+ if (performingSearch) {
103
+ const ordered = [
104
+ ...new Set(src.map((e) => e.group ?? DEFAULT_GROUP_NAME))
105
+ ];
106
+ allProcessed = ordered
107
+ .map((name) => ({
108
+ name,
109
+ entries: entriesByGroup[name] || []
110
+ }))
111
+ .filter((g) => g.entries.length);
112
+ } else {
113
+ allProcessed = groupOrderFromNavController.map((g) => ({
114
+ name: g,
115
+ entries: entriesByGroup[g] || []
116
+ }));
117
+ Object.keys(entriesByGroup).forEach((g) => {
118
+ if (!groupOrderFromNavController.includes(g))
119
+ allProcessed.push({
120
+ name: g,
121
+ entries: entriesByGroup[g]
122
+ });
123
+ });
124
+ allProcessed = allProcessed.filter(
125
+ (g) =>
126
+ g.entries.length ||
127
+ groupOrderFromNavController.includes(g.name)
128
+ );
129
+ }
130
+
131
+ 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
+ ]);
145
+
146
+ /* ───────────────────────────────────────────────────────────────
147
+ Local update vs. persistence helpers
148
+ ─────────────────────────────────────────────────────────────── */
149
+ const updateItems = (
150
+ updater:
151
+ | { name: string; entries: NavigationEntry[] }[]
152
+ | ((
153
+ prev: { name: string; entries: NavigationEntry[] }[]
154
+ ) => { name: string; entries: NavigationEntry[] }[])
155
+ ) => {
156
+ setItems(updater); // local only
157
+ };
158
+
159
+ const persistNavigationGroups = (
160
+ latest: { name: string; entries: NavigationEntry[] }[]
161
+ ) => {
162
+ const draggable: NavigationGroupMapping[] = latest.map((g) => ({
163
+ name: g.name,
164
+ entries: g.entries.map((e) => e.path)
165
+ }));
166
+
167
+ const all: NavigationGroupMapping[] = adminGroupData
168
+ ? [
169
+ ...draggable,
170
+ {
171
+ name: adminGroupData.name,
172
+ entries: adminGroupData.entries.map((e) => e.path)
87
173
  }
88
- }
89
- }, []);
174
+ ]
175
+ : draggable;
90
176
 
91
- const filteredGroups = filteredUrls ? filteredNavigationEntries.map(entry => entry.group) : [];
92
- const allGroups: Array<string | undefined> = filteredUrls ? filteredGroups.filter((group, index) => filteredGroups.indexOf(group) === index) : [...groups];
93
- if (filteredNavigationEntries.filter(e => !e.group).length > 0 || filteredNavigationEntries.length === 0) {
94
- allGroups.push(undefined);
95
- }
177
+ onNavigationEntriesUpdate(all);
178
+ };
179
+
180
+ /* ───────────────────────────────────────────────────────────────
181
+ Hook for DnD
182
+ ─────────────────────────────────────────────────────────────── */
183
+ const {
184
+ sensors,
185
+ collisionDetection,
186
+ onDragStart,
187
+ onDragOver,
188
+ onDragEnd,
189
+ dropAnimation,
190
+ activeItemForOverlay,
191
+ activeGroupData,
192
+ draggingGroupId,
193
+ containers,
194
+ dndKitActiveNode,
195
+ onDragCancel,
196
+ isDraggingCardOnly,
197
+ dialogOpenForGroup,
198
+ setDialogOpenForGroup,
199
+ handleRenameGroup,
200
+ isHoveringNewGroupDropZone,
201
+ setIsHoveringNewGroupDropZone
202
+ } = useHomePageDnd({
203
+ items,
204
+ setItems: updateItems,
205
+ disabled: !allowDragAndDrop || performingSearch,
206
+ onPersist: persistNavigationGroups, // ——► persistence here
207
+ onGroupMoved: (g) =>
208
+ context.analyticsController?.onAnalyticsEvent?.("home_move_group", {
209
+ name: g
210
+ }),
211
+ onCardMovedBetweenGroups: (card) =>
212
+ context.analyticsController?.onAnalyticsEvent?.("home_move_card", {
213
+ id: card.id
214
+ }),
215
+ onNewGroupDrop: () =>
216
+ context.analyticsController?.onAnalyticsEvent?.(
217
+ "home_drop_new_group"
218
+ )
219
+ });
96
220
 
221
+ const {
222
+ containerRef,
223
+ direction
224
+ } = useRestoreScroll();
225
+
226
+ const dndDisabled = !allowDragAndDrop || performingSearch;
227
+
228
+ const dndModifiers = useMemo(() => {
229
+ if (dndKitActiveNode?.data.current?.type === "group")
230
+ return [restrictToVerticalAxis, restrictToWindowEdges];
231
+ return [restrictToWindowEdges];
232
+ }, [dndKitActiveNode]);
233
+
234
+ /* ───────────────────────────────────────────────────────────────
235
+ Plugin extras
236
+ ─────────────────────────────────────────────────────────────── */
97
237
  let additionalPluginChildrenStart: React.ReactNode | undefined;
98
238
  let additionalPluginChildrenEnd: React.ReactNode | undefined;
99
239
  let additionalPluginSections: React.ReactNode | undefined;
240
+
100
241
  if (customizationController.plugins) {
101
- const sectionProps: PluginGenericProps = {
102
- context
103
- };
104
- additionalPluginSections = <>
105
- {customizationController.plugins.filter(plugin => plugin.homePage?.includeSection)
106
- .map((plugin, i) => {
107
- const section = plugin.homePage!.includeSection!(sectionProps)
108
- return (
109
- <NavigationGroup
110
- group={section.title}
111
- key={`plugin_section_${plugin.key}`}>
112
- {section.children}
113
- </NavigationGroup>
114
- );
115
- })}
116
- </>;
117
-
118
- additionalPluginChildrenStart = <div className={"flex flex-col gap-2"}>
119
- {customizationController.plugins.filter(plugin => plugin.homePage?.additionalChildrenStart)
120
- .map((plugin, i) => {
121
- return <div key={`plugin_children_start_${i}`}>{plugin.homePage!.additionalChildrenStart}</div>;
122
- })}
123
- </div>;
124
-
125
- additionalPluginChildrenEnd = <div className={"flex flex-col gap-2"}>
126
- {customizationController.plugins.filter(plugin => plugin.homePage?.additionalChildrenEnd)
127
- .map((plugin, i) => {
128
- return <div key={`plugin_children_start_${i}`}>{plugin.homePage!.additionalChildrenEnd}</div>;
129
- })}
130
- </div>;
242
+ const sectionProps: PluginGenericProps = { context };
243
+
244
+ additionalPluginSections = (
245
+ <>
246
+ {customizationController.plugins
247
+ .filter((p) => p.homePage?.includeSection)
248
+ .map((plugin) => {
249
+ const section = plugin.homePage!.includeSection!(
250
+ sectionProps
251
+ );
252
+ return (
253
+ <NavigationGroup
254
+ group={section.title}
255
+ key={`plugin_section_${plugin.key}`}
256
+ >
257
+ {section.children}
258
+ </NavigationGroup>
259
+ );
260
+ })}
261
+ </>
262
+ );
263
+
264
+ additionalPluginChildrenStart = (
265
+ <div className="flex flex-col gap-2">
266
+ {customizationController.plugins
267
+ .filter((p) => p.homePage?.additionalChildrenStart)
268
+ .map((plugin, i) => (
269
+ <div key={`plugin_children_start_${i}`}>
270
+ {plugin.homePage!.additionalChildrenStart}
271
+ </div>
272
+ ))}
273
+ </div>
274
+ );
275
+
276
+ additionalPluginChildrenEnd = (
277
+ <div className="flex flex-col gap-2">
278
+ {customizationController.plugins
279
+ .filter((p) => p.homePage?.additionalChildrenEnd)
280
+ .map((plugin, i) => (
281
+ <div key={`plugin_children_end_${i}`}>
282
+ {plugin.homePage!.additionalChildrenEnd}
283
+ </div>
284
+ ))}
285
+ </div>
286
+ );
131
287
  }
132
288
 
289
+ /* ───────────────────────────────────────────────────────────────
290
+ Render
291
+ ─────────────────────────────────────────────────────────────── */
133
292
  return (
134
- <div id="home_page"
135
- ref={containerRef}
136
- className="py-2 overflow-auto h-full w-full">
137
- <Container maxWidth={"6xl"}>
293
+ <div ref={containerRef} className="py-2 overflow-auto h-full w-full">
294
+ <Container maxWidth="6xl">
295
+ {/* search & actions */}
138
296
  <div
139
297
  className="w-full sticky py-4 transition-all duration-400 ease-in-out top-0 z-10 flex flex-row gap-4"
140
- style={{ top: direction === "down" ? -84 : 0 }}>
141
- <SearchBar onTextSearch={updateSearchResults}
142
- placeholder={"Search collections"}
143
- large={false}
144
- autoFocus={true}
145
- innerClassName={"w-full"}
146
- className={"w-full flex-grow"}/>
298
+ style={{ top: direction === "down" ? -84 : 0 }}
299
+ >
300
+ <SearchBar
301
+ onTextSearch={updateSearch}
302
+ placeholder="Search collections"
303
+ autoFocus
304
+ innerClassName="w-full"
305
+ className="w-full flex-grow"
306
+ />
147
307
  {additionalActions}
148
308
  </div>
149
309
 
150
310
  <FavouritesView hidden={performingSearch}/>
151
311
 
152
312
  {additionalChildrenStart}
153
-
154
313
  {additionalPluginChildrenStart}
155
314
 
156
- {allGroups.map((group, index) => {
157
-
158
- const AdditionalCards: React.ComponentType<PluginHomePageAdditionalCardsProps>[] = [];
159
- const actionProps: PluginHomePageAdditionalCardsProps = {
160
- group,
161
- context
162
- };
163
-
164
- if (customizationController.plugins) {
165
- customizationController.plugins.forEach(plugin => {
166
- if (plugin.homePage?.AdditionalCards) {
167
- AdditionalCards.push(...toArray(plugin.homePage?.AdditionalCards));
168
- }
169
- });
170
- }
171
-
172
- const thisGroupCollections = filteredNavigationEntries
173
- .filter((entry) => entry.group === group || (!entry.group && group === undefined));
174
- if (thisGroupCollections.length === 0 && (AdditionalCards.length === 0 || performingSearch))
175
- return null;
176
- return (
177
- <NavigationGroup
178
- group={group}
179
- key={`plugin_section_${group}`}>
180
-
181
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
182
- {thisGroupCollections.map((entry) => (
183
- <div key={`nav_${entry.group}_${entry.path}_${entry.name}`} className="col-span-1">
184
- <NavigationCardBinding
185
- {...entry}
186
- onClick={() => {
187
- let event: CMSAnalyticsEvent;
188
- if (entry.type === "collection") {
189
- event = "home_navigate_to_collection";
190
- } else if (entry.type === "view") {
191
- event = "home_navigate_to_view";
192
- } else if (entry.type === "admin") {
193
- event = "home_navigate_to_admin_view";
194
- } else {
195
- event = "unmapped_event";
196
- }
197
- context.analyticsController?.onAnalyticsEvent?.(event, { path: entry.path });
198
- }}
199
- />
200
- </div>
201
- ))}
202
- {group?.toLowerCase() !== "admin" && AdditionalCards &&
203
- AdditionalCards.map((AdditionalCard, i) => (
204
- <div key={`nav_${group}_add_${i}`}>
205
- <AdditionalCard {...actionProps} />
206
- </div>
207
- ))}
315
+ {/* ─────── DND context ─────── */}
316
+ <DndContext
317
+ sensors={sensors}
318
+ collisionDetection={collisionDetection}
319
+ measuring={{
320
+ droppable: {
321
+ strategy: MeasuringStrategy.Always,
322
+ frequency: 500
323
+ }
324
+ }}
325
+ onDragStart={onDragStart}
326
+ onDragOver={onDragOver}
327
+ onDragEnd={onDragEnd}
328
+ onDragCancel={onDragCancel}
329
+ modifiers={dndModifiers}
330
+ >
331
+ <SortableContext
332
+ key={JSON.stringify(containers)}
333
+ items={containers}
334
+ strategy={verticalListSortingStrategy}
335
+ >
336
+ {items.map((groupData) => {
337
+ const groupKey = groupData.name;
338
+ const entriesInGroup = groupData.entries;
339
+
340
+ const AdditionalCards: React.ComponentType<PluginHomePageAdditionalCardsProps>[] =
341
+ [];
342
+ customizationController.plugins?.forEach((p) => {
343
+ if (p.homePage?.AdditionalCards)
344
+ AdditionalCards.push(
345
+ ...toArray(p.homePage.AdditionalCards)
346
+ );
347
+ });
348
+
349
+ const actionProps: PluginHomePageAdditionalCardsProps = {
350
+ group:
351
+ groupKey === DEFAULT_GROUP_NAME
352
+ ? undefined
353
+ : groupKey,
354
+ context
355
+ };
356
+
357
+ if (
358
+ entriesInGroup.length === 0 &&
359
+ (AdditionalCards.length === 0 || performingSearch) &&
360
+ !groupOrderFromNavController.includes(groupKey)
361
+ )
362
+ return null;
363
+
364
+ return (
365
+ <SortableNavigationGroup
366
+ key={groupKey}
367
+ groupName={groupKey}
368
+ disabled={dndDisabled}
369
+ >
370
+ <NavigationGroup
371
+ group={
372
+ groupKey === DEFAULT_GROUP_NAME
373
+ ? undefined
374
+ : groupKey
375
+ }
376
+ minimised={
377
+ draggingGroupId === groupKey &&
378
+ !isDraggingCardOnly
379
+ }
380
+ isPotentialCardDropTarget={
381
+ isDraggingCardOnly
382
+ }
383
+ dndDisabled={dndDisabled}
384
+ onEditGroup={() => {
385
+ if (dndDisabled) return;
386
+ setDialogOpenForGroup(groupKey);
387
+ }}
388
+ >
389
+ <NavigationGroupDroppable
390
+ id={groupKey}
391
+ itemIds={entriesInGroup.map(
392
+ (e) => e.url
393
+ )}
394
+ isPotentialCardDropTarget={
395
+ isDraggingCardOnly
396
+ }
397
+ >
398
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ">
399
+ {entriesInGroup.map(
400
+ (entry) => (
401
+ <SortableNavigationCard
402
+ key={entry.url}
403
+ entry={entry}
404
+ onClick={() => {
405
+ let event: CMSAnalyticsEvent =
406
+ "unmapped_event";
407
+ if (
408
+ entry.type ===
409
+ "collection"
410
+ )
411
+ event =
412
+ "home_navigate_to_collection";
413
+ else if (
414
+ entry.type ===
415
+ "view"
416
+ )
417
+ event =
418
+ "home_navigate_to_view";
419
+ else if (
420
+ entry.type ===
421
+ "admin"
422
+ )
423
+ event =
424
+ "home_navigate_to_admin_view";
425
+
426
+ context.analyticsController?.onAnalyticsEvent?.(
427
+ event,
428
+ {
429
+ path: entry.path
430
+ }
431
+ );
432
+ }}
433
+ />
434
+ )
435
+ )}
436
+ {!performingSearch &&
437
+ groupKey.toLowerCase() !==
438
+ ADMIN_GROUP_NAME.toLowerCase() &&
439
+ AdditionalCards.map(
440
+ (C, i) => (
441
+ <C
442
+ key={`extra_${groupKey}_${i}`}
443
+ {...actionProps}
444
+ />
445
+ )
446
+ )}
447
+ </div>
448
+ </NavigationGroupDroppable>
449
+ </NavigationGroup>
450
+ </SortableNavigationGroup>
451
+ );
452
+ })}
453
+ </SortableContext>
454
+
455
+ <NewGroupDropZone
456
+ disabled={dndDisabled}
457
+ setIsHovering={setIsHoveringNewGroupDropZone}
458
+ />
459
+
460
+ <DragOverlay adjustScale={false} dropAnimation={dropAnimation}>
461
+ {activeGroupData &&
462
+ draggingGroupId === activeGroupData.name ? (
463
+ <div
464
+ className="rounded-lg bg-transparent"
465
+ style={{
466
+ padding: 0,
467
+ margin: 0
468
+ }}
469
+ >
470
+ <NavigationGroup
471
+ group={
472
+ activeGroupData.name ===
473
+ DEFAULT_GROUP_NAME
474
+ ? undefined
475
+ : activeGroupData.name
476
+ }
477
+ isPreview={false}
478
+ minimised
479
+ />
208
480
  </div>
209
- </NavigationGroup>
210
- );
211
- })}
481
+ ) : activeItemForOverlay ? (
482
+ <NavigationCardBinding
483
+ {...activeItemForOverlay}
484
+ shrink={isHoveringNewGroupDropZone}
485
+ />
486
+ ) : null}
487
+ </DragOverlay>
488
+ </DndContext>
489
+
490
+ {!performingSearch && adminGroupData && (
491
+ <NavigationGroup group={adminGroupData.name}>
492
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ">
493
+ {adminGroupData.entries.map((entry) => (
494
+ <NavigationCardBinding
495
+ key={entry.url}
496
+ {...entry}
497
+ onClick={() => {
498
+ let event: CMSAnalyticsEvent =
499
+ "unmapped_event";
500
+ if (entry.type === "collection")
501
+ event =
502
+ "home_navigate_to_collection";
503
+ else if (entry.type === "view")
504
+ event = "home_navigate_to_view";
505
+ else if (entry.type === "admin")
506
+ event =
507
+ "home_navigate_to_admin_view";
508
+
509
+ context.analyticsController?.onAnalyticsEvent?.(
510
+ event,
511
+ { path: entry.path }
512
+ );
513
+ }}
514
+ />
515
+ ))}
516
+ </div>
517
+ </NavigationGroup>
518
+ )}
212
519
 
213
520
  {additionalPluginSections}
214
-
215
521
  {additionalPluginChildrenEnd}
216
-
217
522
  {additionalChildrenEnd}
218
-
219
523
  </Container>
524
+
525
+ {dialogOpenForGroup && (
526
+ <RenameGroupDialog
527
+ open
528
+ initialName={dialogOpenForGroup}
529
+ existingGroupNames={items
530
+ .map((g) => g.name)
531
+ .filter((n) => n !== dialogOpenForGroup)}
532
+ onClose={() => setDialogOpenForGroup(null)}
533
+ onRename={(newName) => {
534
+ handleRenameGroup(dialogOpenForGroup, newName);
535
+ setDialogOpenForGroup(null);
536
+ }}
537
+ />
538
+ )}
220
539
  </div>
221
540
  );
222
541
  }