@10x-media/folder-picker 0.1.0-beta.0

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 (51) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -0
  4. package/dist/exports/client.d.ts +2 -0
  5. package/dist/exports/client.js +3 -0
  6. package/dist/exports/i18n.d.ts +3 -0
  7. package/dist/exports/i18n.js +3 -0
  8. package/dist/exports/types.d.ts +2 -0
  9. package/dist/exports/types.js +1 -0
  10. package/dist/folder/BulkUploadButton.js +84 -0
  11. package/dist/folder/BulkUploadButton.js.map +1 -0
  12. package/dist/folder/FolderActions.js +133 -0
  13. package/dist/folder/FolderActions.js.map +1 -0
  14. package/dist/folder/FolderBrowser.js +417 -0
  15. package/dist/folder/FolderBrowser.js.map +1 -0
  16. package/dist/folder/chosenUploadIds.js +63 -0
  17. package/dist/folder/chosenUploadIds.js.map +1 -0
  18. package/dist/folder/folder.css +74 -0
  19. package/dist/folder/index.d.ts +13 -0
  20. package/dist/folder/index.js +65 -0
  21. package/dist/folder/index.js.map +1 -0
  22. package/dist/folder/isMacPlatform.js +21 -0
  23. package/dist/folder/isMacPlatform.js.map +1 -0
  24. package/dist/folder/native.js +391 -0
  25. package/dist/folder/native.js.map +1 -0
  26. package/dist/folder/useChosenUploads.js +27 -0
  27. package/dist/folder/useChosenUploads.js.map +1 -0
  28. package/dist/folder/useFolderTargets.js +126 -0
  29. package/dist/folder/useFolderTargets.js.map +1 -0
  30. package/dist/index.d.ts +33 -0
  31. package/dist/index.js +24 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/plugin/registerFolderListView.js +36 -0
  34. package/dist/plugin/registerFolderListView.js.map +1 -0
  35. package/dist/plugin/registerTranslations.js +19 -0
  36. package/dist/plugin/registerTranslations.js.map +1 -0
  37. package/dist/translations/de.js +15 -0
  38. package/dist/translations/de.js.map +1 -0
  39. package/dist/translations/en.js +20 -0
  40. package/dist/translations/en.js.map +1 -0
  41. package/dist/translations/index.d.ts +16 -0
  42. package/dist/translations/index.js +35 -0
  43. package/dist/translations/index.js.map +1 -0
  44. package/dist/translations/keys.d.ts +19 -0
  45. package/dist/translations/keys.js +19 -0
  46. package/dist/translations/keys.js.map +1 -0
  47. package/dist/translations/uk.js +15 -0
  48. package/dist/translations/uk.js.map +1 -0
  49. package/dist/translations/useTranslation.js +12 -0
  50. package/dist/translations/useTranslation.js.map +1 -0
  51. package/package.json +110 -0
@@ -0,0 +1,417 @@
1
+ "use client";
2
+ import { keys } from "../translations/keys.js";
3
+ import { BulkUploadButton, SelectFolderItems } from "./BulkUploadButton.js";
4
+ import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
5
+ import { CloseModalButton, DndEventListener, DragOverlaySelection, DrawerRelationshipSelect, ListHeader, NoListResults, SearchBar, SortByPill, ToggleViewButtons } from "./native.js";
6
+ import { FolderActionsMenu, FolderSelectionBar } from "./FolderActions.js";
7
+ import { isMacPlatform, modifierLabels } from "./isMacPlatform.js";
8
+ import { useChosenUploads } from "./useChosenUploads.js";
9
+ import { getTranslation } from "@payloadcms/translations";
10
+ import { Button, FolderIcon, FolderProvider, Gutter, ItemCardGrid, LoadingOverlay, Popup, PopupList, toast, useAuth, useConfig, useDebounce, useDocumentDrawer, useFolder, useListDrawerContext, useServerFunctions, useWindowInfo } from "@payloadcms/ui";
11
+ import React from "react";
12
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
+ import { DndContext, pointerWithin } from "@dnd-kit/core";
14
+ //#region src/folder/FolderBrowser.tsx
15
+ const baseClass = "collection-folder-list";
16
+ /**
17
+ * Dragging, the way the route view wires it: a listener that turns a drop into a move, and the card
18
+ * that follows the cursor. Lives inside the provider because that is where the selection and
19
+ * `moveToFolder` are, and reloads rather than clearing the route cache, since a drawer changes no
20
+ * route.
21
+ */
22
+ const FolderDragLayer = ({ onMoved }) => {
23
+ const { dragOverlayItem, getSelectedItems, moveToFolder, selectedItemKeys, setIsDragging } = useFolder();
24
+ const handleDragEnd = React.useCallback(async (event) => {
25
+ const target = event.over?.data.current;
26
+ if (target?.type !== "folder" || !("id" in target)) return;
27
+ await moveToFolder({
28
+ itemsToMove: getSelectedItems?.() ?? [],
29
+ toFolderID: target.id
30
+ });
31
+ await onMoved();
32
+ }, [
33
+ getSelectedItems,
34
+ moveToFolder,
35
+ onMoved
36
+ ]);
37
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx(DndEventListener, {
38
+ onDragEnd: handleDragEnd,
39
+ setIsDragging
40
+ }), selectedItemKeys.size > 0 && dragOverlayItem ? /* @__PURE__ */ jsx(DragOverlaySelection, {
41
+ selectedCount: selectedItemKeys.size,
42
+ title: String(dragOverlayItem.value._folderOrDocumentTitle ?? "")
43
+ }) : null] });
44
+ };
45
+ /**
46
+ * Folder picker for the list drawer, composed the way `DefaultCollectionFolderView` composes the
47
+ * route view. That view changes folder by pushing an admin route, which would tear a drawer down,
48
+ * so this drives `get-folder-results-component-and-data` directly the way Payload's own
49
+ * MoveToFolder drawer does and keeps the current folder in local state.
50
+ */
51
+ const FolderBrowser = ({ collectionSlug, enableRowSelections, Tabs }) => {
52
+ const { config, getEntityConfig } = useConfig();
53
+ const { permissions } = useAuth();
54
+ const { i18n, t } = useTranslation$1();
55
+ const { getFolderResultsComponentAndData } = useServerFunctions();
56
+ const { drawerSlug, onSelect } = useListDrawerContext();
57
+ const alreadyChosen = useChosenUploads(collectionSlug);
58
+ const { breakpoints: { s: smallBreak } } = useWindowInfo();
59
+ const folderCollectionSlug = config.folders ? config.folders.slug : void 0;
60
+ const folderFieldName = config.folders ? config.folders.fieldName : void 0;
61
+ const folderCollectionConfig = folderCollectionSlug ? getEntityConfig({ collectionSlug: folderCollectionSlug }) : void 0;
62
+ const targetConfig = getEntityConfig({ collectionSlug });
63
+ const [isMac, setIsMac] = React.useState(false);
64
+ React.useEffect(() => {
65
+ setIsMac(isMacPlatform(navigator.userAgent));
66
+ }, []);
67
+ const [folderID, setFolderID] = React.useState(null);
68
+ const [breadcrumbs, setBreadcrumbs] = React.useState([]);
69
+ const [subfolders, setSubfolders] = React.useState([]);
70
+ const [documents, setDocuments] = React.useState([]);
71
+ const [ResultsComponent, setResultsComponent] = React.useState(null);
72
+ const [loadedFor, setLoadedFor] = React.useState(null);
73
+ const [loadError, setLoadError] = React.useState(false);
74
+ const [displayAs, setDisplayAs] = React.useState("grid");
75
+ const [sort, setSort] = React.useState("name");
76
+ const [searchInput, setSearchInput] = React.useState("");
77
+ const search = useDebounce(searchInput, 300);
78
+ /**
79
+ * Breadcrumbs, cards, the sort pill, the view toggle and the mount effect all load, and each
80
+ * load writes six pieces of state. A slow early request resolving after a fast later one would
81
+ * otherwise put one folder's contents under another folder's breadcrumb, so every response
82
+ * checks that it is still the one being waited for.
83
+ */
84
+ const latestRequest = React.useRef(0);
85
+ const loadFolder = React.useCallback(async (args) => {
86
+ if (!folderCollectionSlug) return;
87
+ const request = ++latestRequest.current;
88
+ try {
89
+ const result = await getFolderResultsComponentAndData({
90
+ browseByFolder: false,
91
+ collectionsToDisplay: [folderCollectionSlug, collectionSlug],
92
+ displayAs: args.displayAs,
93
+ folderAssignedCollections: [collectionSlug],
94
+ folderID: args.folderID ?? void 0,
95
+ sort: args.sort
96
+ });
97
+ if (request !== latestRequest.current) return;
98
+ setLoadError(false);
99
+ setBreadcrumbs(result?.breadcrumbs || []);
100
+ setSubfolders(result?.subfolders || []);
101
+ setDocuments(result?.documents || []);
102
+ setResultsComponent(result?.FolderResultsComponent || null);
103
+ setFolderID(args.folderID);
104
+ setLoadedFor(collectionSlug);
105
+ } catch (error) {
106
+ if (request !== latestRequest.current) return;
107
+ toast.error(error instanceof Error ? error.message : String(error));
108
+ setLoadError(true);
109
+ setLoadedFor(collectionSlug);
110
+ }
111
+ }, [
112
+ collectionSlug,
113
+ folderCollectionSlug,
114
+ getFolderResultsComponentAndData
115
+ ]);
116
+ const reload = React.useCallback(() => loadFolder({
117
+ displayAs,
118
+ folderID,
119
+ sort
120
+ }), [
121
+ displayAs,
122
+ folderID,
123
+ loadFolder,
124
+ sort
125
+ ]);
126
+ const actionHandlersRef = React.useRef(null);
127
+ const requestedFor = React.useRef(null);
128
+ React.useEffect(() => {
129
+ if (requestedFor.current !== collectionSlug) {
130
+ requestedFor.current = collectionSlug;
131
+ loadFolder({
132
+ displayAs,
133
+ folderID: null,
134
+ sort
135
+ });
136
+ }
137
+ }, [
138
+ collectionSlug,
139
+ displayAs,
140
+ loadFolder,
141
+ sort
142
+ ]);
143
+ const currentFolder = breadcrumbs[breadcrumbs.length - 1];
144
+ const parentFolder = breadcrumbs[breadcrumbs.length - 2];
145
+ const [CreateFolderDrawer, , { closeDrawer: closeCreateFolderDrawer, openDrawer: openCreateFolderDrawer }] = useDocumentDrawer({ collectionSlug: folderCollectionSlug ?? "" });
146
+ const [CreateDocumentDrawer, , { closeDrawer: closeCreateDocumentDrawer, openDrawer: openCreateDocumentDrawer }] = useDocumentDrawer({ collectionSlug });
147
+ const handleItemClick = React.useCallback(async (item) => {
148
+ if (item.relationTo === folderCollectionSlug) {
149
+ await loadFolder({
150
+ displayAs,
151
+ folderID: item.value.id,
152
+ sort
153
+ });
154
+ return;
155
+ }
156
+ onSelect?.({
157
+ collectionSlug: item.relationTo,
158
+ doc: item.value,
159
+ docID: String(item.value.id)
160
+ });
161
+ }, [
162
+ displayAs,
163
+ folderCollectionSlug,
164
+ loadFolder,
165
+ onSelect,
166
+ sort
167
+ ]);
168
+ if (!folderCollectionSlug || !folderFieldName) return null;
169
+ if (loadedFor === null) return /* @__PURE__ */ jsx(LoadingOverlay, {});
170
+ const isSwitchingCollection = loadedFor !== collectionSlug;
171
+ const term = search.trim().toLowerCase();
172
+ const matches = (item) => !term || String(item.value._folderOrDocumentTitle ?? "").toLowerCase().includes(term);
173
+ const visibleSubfolders = subfolders.filter(matches);
174
+ /**
175
+ * A document the field already holds is dropped, the way Payload's own list tab drops it:
176
+ * the upload field builds `filterOptions` with `id: { not_in: [...] }` from its value, so a
177
+ * file that is already attached never appears among the options. The folder server function
178
+ * takes no filter argument, so the same rule is applied to what came back.
179
+ *
180
+ * Without it the file can be picked a second time, and the upload field appends whatever it
181
+ * is handed, storing the same upload twice.
182
+ */
183
+ const visibleDocuments = documents.filter((item) => matches(item) && !(item.relationTo === collectionSlug && alreadyChosen.has(String(item.value.id))));
184
+ const totalVisible = visibleSubfolders.length + visibleDocuments.length;
185
+ const folderLabel = getTranslation(folderCollectionConfig?.labels?.singular ?? "", i18n);
186
+ const folderPluralLabel = getTranslation(folderCollectionConfig?.labels?.plural ?? "", i18n);
187
+ const pluralLabel = getTranslation(targetConfig?.labels?.plural ?? collectionSlug, i18n);
188
+ /**
189
+ * The server bakes the items into its grid, so narrowing the provider reaches the table view
190
+ * alone and the grid keeps drawing everything: searching would leave the count and the cards
191
+ * disagreeing. The grid is rebuilt here from the same filtered arrays, using the card grid
192
+ * Payload builds it from, so only the data differs. The table needs none of this because it
193
+ * takes no items and reads the provider itself.
194
+ */
195
+ const Results = displayAs === "grid" ? /* @__PURE__ */ jsxs("div", { children: [visibleSubfolders.length ? /* @__PURE__ */ jsx(ItemCardGrid, {
196
+ items: visibleSubfolders,
197
+ title: folderPluralLabel,
198
+ type: "folder"
199
+ }) : null, visibleDocuments.length ? /* @__PURE__ */ jsx(ItemCardGrid, {
200
+ items: visibleDocuments,
201
+ subfolderCount: visibleSubfolders.length,
202
+ title: pluralLabel,
203
+ type: "file"
204
+ }) : null] }) : ResultsComponent;
205
+ const canCreateFolder = Boolean(permissions?.collections?.[folderCollectionSlug]?.create);
206
+ const canCreateDocument = Boolean(permissions?.collections?.[collectionSlug]?.create) && folderID !== null;
207
+ const creatable = [canCreateFolder ? {
208
+ label: folderLabel,
209
+ onClick: openCreateFolderDrawer,
210
+ slug: folderCollectionSlug
211
+ } : null, canCreateDocument ? {
212
+ label: getTranslation(targetConfig?.labels?.singular ?? collectionSlug, i18n),
213
+ onClick: openCreateDocumentDrawer,
214
+ slug: collectionSlug
215
+ } : null].filter(Boolean);
216
+ const [onlyCreatable] = creatable;
217
+ const createAction = creatable.length === 0 ? null : onlyCreatable && creatable.length === 1 ? /* @__PURE__ */ jsx(Button, {
218
+ buttonStyle: "pill",
219
+ el: "div",
220
+ onClick: onlyCreatable.onClick,
221
+ size: "small",
222
+ children: `${t("general:create")} ${onlyCreatable.label.toLowerCase()}`
223
+ }, "create-new") : /* @__PURE__ */ jsx(Popup, {
224
+ button: /* @__PURE__ */ jsx(Button, {
225
+ buttonStyle: "pill",
226
+ el: "div",
227
+ icon: "chevron",
228
+ size: "small",
229
+ children: t("general:createNew")
230
+ }),
231
+ buttonType: "default",
232
+ children: /* @__PURE__ */ jsx(PopupList.ButtonGroup, { children: creatable.map((option) => /* @__PURE__ */ jsx(PopupList.Button, {
233
+ onClick: option.onClick,
234
+ children: option.label
235
+ }, option.slug)) })
236
+ }, "create-new");
237
+ const crumbs = [{
238
+ id: null,
239
+ name: pluralLabel
240
+ }, ...breadcrumbs];
241
+ const trail = breadcrumbs.length > 0 ? /* @__PURE__ */ jsxs("nav", {
242
+ "aria-label": pluralLabel,
243
+ className: `${baseClass}__trail`,
244
+ children: [/* @__PURE__ */ jsx(FolderIcon, {}), crumbs.map((crumb, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [index > 0 && /* @__PURE__ */ jsx("span", {
245
+ "aria-hidden": "true",
246
+ className: `${baseClass}__trail-sep`,
247
+ children: "/"
248
+ }), /* @__PURE__ */ jsx(Button, {
249
+ buttonStyle: "none",
250
+ className: `${baseClass}__trail-crumb`,
251
+ el: "button",
252
+ onClick: () => {
253
+ loadFolder({
254
+ displayAs,
255
+ folderID: crumb.id,
256
+ sort
257
+ });
258
+ },
259
+ children: crumb.name
260
+ })] }, String(crumb.id ?? "root")))]
261
+ }, "breadcrumbs") : null;
262
+ return /* @__PURE__ */ jsx(DndContext, {
263
+ collisionDetection: pointerWithin,
264
+ children: /* @__PURE__ */ jsxs("div", {
265
+ className: `${baseClass} ${baseClass}--${collectionSlug}`,
266
+ children: [Tabs || trail ? /* @__PURE__ */ jsx(Gutter, {
267
+ className: "default-list-view-tabs__drawer-gutter",
268
+ children: /* @__PURE__ */ jsxs("div", {
269
+ className: `${baseClass}__tabs-row`,
270
+ children: [Tabs, trail]
271
+ })
272
+ }) : null, /* @__PURE__ */ jsx(FolderProvider, {
273
+ allCollectionFolderSlugs: [folderCollectionSlug],
274
+ allowCreateCollectionSlugs: canCreateFolder ? [folderCollectionSlug] : [],
275
+ allowMultiSelection: Boolean(enableRowSelections),
276
+ breadcrumbs,
277
+ documents: visibleDocuments,
278
+ folderFieldName,
279
+ folderID: folderID ?? void 0,
280
+ FolderResultsComponent: ResultsComponent,
281
+ onItemClick: handleItemClick,
282
+ subfolders: visibleSubfolders,
283
+ children: /* @__PURE__ */ jsxs(Gutter, {
284
+ className: `${baseClass}__wrap`,
285
+ children: [
286
+ /* @__PURE__ */ jsx(ListHeader, {
287
+ Actions: [smallBreak ? null : /* @__PURE__ */ jsx(FolderSelectionBar, {
288
+ collectionSlug,
289
+ currentFolderName: String(currentFolder?.name ?? ""),
290
+ handlersRef: actionHandlersRef,
291
+ folderCollectionSlug,
292
+ folderFieldName,
293
+ onChanged: (next) => loadFolder({
294
+ displayAs,
295
+ folderID: next,
296
+ sort
297
+ }),
298
+ parentFolderID: parentFolder?.id ?? void 0
299
+ }, "selection-bar"), drawerSlug ? /* @__PURE__ */ jsx(CloseModalButton, {
300
+ className: "list-drawer__header-close",
301
+ slug: drawerSlug
302
+ }, "close-button") : null].filter(Boolean),
303
+ AfterListHeaderContent: /* @__PURE__ */ jsx(DrawerRelationshipSelect, {}),
304
+ className: "list-drawer__header",
305
+ title: pluralLabel,
306
+ TitleActions: [createAction, /* @__PURE__ */ jsx(BulkUploadButton, {
307
+ collectionSlug,
308
+ enableRowSelections,
309
+ folderID
310
+ }, "bulk-upload")].filter(Boolean)
311
+ }),
312
+ /* @__PURE__ */ jsx(SearchBar, {
313
+ Actions: [
314
+ /* @__PURE__ */ jsx(SelectFolderItems, {
315
+ collectionSlug,
316
+ enableRowSelections
317
+ }, "select-items"),
318
+ /* @__PURE__ */ jsx(SortByPill, {
319
+ onChange: (next) => {
320
+ setSort(next);
321
+ loadFolder({
322
+ displayAs,
323
+ folderID,
324
+ sort: next
325
+ });
326
+ },
327
+ sort,
328
+ t
329
+ }, "sort-by-pill"),
330
+ /* @__PURE__ */ jsx(ToggleViewButtons, {
331
+ activeView: displayAs,
332
+ setActiveView: (view) => {
333
+ setDisplayAs(view);
334
+ loadFolder({
335
+ displayAs: view,
336
+ folderID,
337
+ sort
338
+ });
339
+ }
340
+ }, "toggle-view-buttons"),
341
+ /* @__PURE__ */ jsx(FolderActionsMenu, {
342
+ collectionSlug,
343
+ currentFolderName: String(currentFolder?.name ?? ""),
344
+ handlersRef: actionHandlersRef,
345
+ folderCollectionSlug,
346
+ folderFieldName,
347
+ onChanged: (next) => loadFolder({
348
+ displayAs,
349
+ folderID: next,
350
+ sort
351
+ }),
352
+ parentFolderID: parentFolder?.id ?? void 0
353
+ }, "current-folder-actions")
354
+ ].filter(Boolean),
355
+ label: t("general:searchBy", { label: t("general:name") }),
356
+ onSearchChange: setSearchInput,
357
+ search: searchInput
358
+ }),
359
+ enableRowSelections ? /* @__PURE__ */ jsx("p", {
360
+ className: `${baseClass}__pick-many-hint`,
361
+ children: t(keys.pickManyHint, modifierLabels(isMac))
362
+ }) : null,
363
+ isSwitchingCollection ? /* @__PURE__ */ jsx(LoadingOverlay, {}) : loadError ? /* @__PURE__ */ jsx(NoListResults, {
364
+ Actions: [/* @__PURE__ */ jsx(Button, {
365
+ buttonStyle: "primary",
366
+ el: "button",
367
+ onClick: () => void reload(),
368
+ size: "medium",
369
+ children: t(keys.retry)
370
+ }, "retry")],
371
+ Message: /* @__PURE__ */ jsx("p", { children: t("error:unknown") })
372
+ }) : totalVisible > 0 ? Results : /* @__PURE__ */ jsx(NoListResults, {
373
+ Actions: [canCreateFolder ? /* @__PURE__ */ jsx(Button, {
374
+ buttonStyle: "primary",
375
+ el: "button",
376
+ onClick: openCreateFolderDrawer,
377
+ size: "medium",
378
+ children: `${t("general:create")} ${folderLabel.toLowerCase()}`
379
+ }, "create-folder") : null, canCreateDocument ? /* @__PURE__ */ jsx(Button, {
380
+ buttonStyle: "primary",
381
+ el: "button",
382
+ onClick: openCreateDocumentDrawer,
383
+ size: "medium",
384
+ children: `${t("general:create")} ${t("general:document").toLowerCase()}`
385
+ }, "create-document") : null].filter(Boolean),
386
+ Message: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("h3", { children: t("general:noResultsFound") }), /* @__PURE__ */ jsx("p", { children: t("general:noResultsDescription") })] })
387
+ }),
388
+ /* @__PURE__ */ jsx(CreateFolderDrawer, {
389
+ initialData: {
390
+ [folderFieldName]: folderID,
391
+ folderType: [collectionSlug]
392
+ },
393
+ onSave: async () => {
394
+ closeCreateFolderDrawer();
395
+ await reload();
396
+ },
397
+ redirectAfterCreate: false
398
+ }),
399
+ /* @__PURE__ */ jsx(CreateDocumentDrawer, {
400
+ initialData: { [folderFieldName]: folderID },
401
+ onSave: async () => {
402
+ closeCreateDocumentDrawer();
403
+ await reload();
404
+ },
405
+ redirectAfterCreate: false
406
+ }),
407
+ /* @__PURE__ */ jsx(FolderDragLayer, { onMoved: reload })
408
+ ]
409
+ })
410
+ }, `${String(folderID)}-${displayAs}`)]
411
+ })
412
+ });
413
+ };
414
+ //#endregion
415
+ export { FolderBrowser };
416
+
417
+ //# sourceMappingURL=FolderBrowser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FolderBrowser.js","names":["useTranslation"],"sources":["../../src/folder/FolderBrowser.tsx"],"sourcesContent":["'use client'\n\nimport { DndContext, type DragEndEvent, pointerWithin } from '@dnd-kit/core'\nimport { getTranslation } from '@payloadcms/translations'\nimport {\n\tButton,\n\tFolderIcon,\n\tFolderProvider,\n\tGutter,\n\tItemCardGrid,\n\tLoadingOverlay,\n\tPopup,\n\tPopupList,\n\ttoast,\n\tuseAuth,\n\tuseConfig,\n\tuseDebounce,\n\tuseDocumentDrawer,\n\tuseFolder,\n\tuseListDrawerContext,\n\tuseServerFunctions,\n\tuseWindowInfo,\n} from '@payloadcms/ui'\nimport type { CollectionSlug, FolderSortKeys } from 'payload'\nimport type { FolderBreadcrumb, FolderOrDocument } from 'payload/shared'\nimport React from 'react'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\nimport { BulkUploadButton, SelectFolderItems } from './BulkUploadButton'\nimport type { FolderActionHandlers } from './FolderActions'\nimport { FolderActionsMenu, FolderSelectionBar } from './FolderActions'\nimport { isMacPlatform, modifierLabels } from './isMacPlatform'\nimport {\n\tCloseModalButton,\n\tDndEventListener,\n\tDragOverlaySelection,\n\tDrawerRelationshipSelect,\n\tListHeader,\n\tNoListResults,\n\tSearchBar,\n\tSortByPill,\n\tToggleViewButtons,\n} from './native'\nimport { useChosenUploads } from './useChosenUploads'\n\nconst baseClass = 'collection-folder-list'\n\n/**\n * Dragging, the way the route view wires it: a listener that turns a drop into a move, and the card\n * that follows the cursor. Lives inside the provider because that is where the selection and\n * `moveToFolder` are, and reloads rather than clearing the route cache, since a drawer changes no\n * route.\n */\nconst FolderDragLayer: React.FC<{ readonly onMoved: () => Promise<void> }> = ({ onMoved }) => {\n\tconst { dragOverlayItem, getSelectedItems, moveToFolder, selectedItemKeys, setIsDragging } =\n\t\tuseFolder()\n\n\tconst handleDragEnd = React.useCallback(\n\t\tasync (event: DragEndEvent) => {\n\t\t\tconst target = event.over?.data.current\n\t\t\tif (target?.type !== 'folder' || !('id' in target)) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tawait moveToFolder({ itemsToMove: getSelectedItems?.() ?? [], toFolderID: target.id })\n\t\t\tawait onMoved()\n\t\t},\n\t\t[getSelectedItems, moveToFolder, onMoved]\n\t)\n\n\treturn (\n\t\t<React.Fragment>\n\t\t\t<DndEventListener onDragEnd={handleDragEnd} setIsDragging={setIsDragging} />\n\t\t\t{selectedItemKeys.size > 0 && dragOverlayItem ? (\n\t\t\t\t<DragOverlaySelection\n\t\t\t\t\tselectedCount={selectedItemKeys.size}\n\t\t\t\t\ttitle={String(dragOverlayItem.value._folderOrDocumentTitle ?? '')}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t</React.Fragment>\n\t)\n}\n\ntype FolderBrowserProps = {\n\treadonly collectionSlug: CollectionSlug\n\t/** The upload field sets this from `hasMany`, so it is the honest test for a multi-file flow. */\n\treadonly enableRowSelections?: boolean\n\treadonly Tabs?: React.ReactNode\n}\n\n/**\n * Folder picker for the list drawer, composed the way `DefaultCollectionFolderView` composes the\n * route view. That view changes folder by pushing an admin route, which would tear a drawer down,\n * so this drives `get-folder-results-component-and-data` directly the way Payload's own\n * MoveToFolder drawer does and keeps the current folder in local state.\n */\nexport const FolderBrowser: React.FC<FolderBrowserProps> = ({\n\tcollectionSlug,\n\tenableRowSelections,\n\tTabs,\n}) => {\n\tconst { config, getEntityConfig } = useConfig()\n\tconst { permissions } = useAuth()\n\tconst { i18n, t } = useTranslation()\n\tconst { getFolderResultsComponentAndData } = useServerFunctions()\n\tconst { drawerSlug, onSelect } = useListDrawerContext()\n\tconst alreadyChosen = useChosenUploads(collectionSlug)\n\tconst {\n\t\tbreakpoints: { s: smallBreak },\n\t} = useWindowInfo()\n\n\tconst folderCollectionSlug = config.folders ? (config.folders.slug as CollectionSlug) : undefined\n\tconst folderFieldName = config.folders ? config.folders.fieldName : undefined\n\tconst folderCollectionConfig = folderCollectionSlug\n\t\t? getEntityConfig({ collectionSlug: folderCollectionSlug })\n\t\t: undefined\n\tconst targetConfig = getEntityConfig({ collectionSlug })\n\n\t// Read after mount: `navigator` does not exist while the tree is rendered on the server,\n\t// and branching on it during the first client render would not match what was sent.\n\tconst [isMac, setIsMac] = React.useState(false)\n\tReact.useEffect(() => {\n\t\tsetIsMac(isMacPlatform(navigator.userAgent))\n\t}, [])\n\n\tconst [folderID, setFolderID] = React.useState<null | number | string>(null)\n\tconst [breadcrumbs, setBreadcrumbs] = React.useState<FolderBreadcrumb[]>([])\n\tconst [subfolders, setSubfolders] = React.useState<FolderOrDocument[]>([])\n\tconst [documents, setDocuments] = React.useState<FolderOrDocument[]>([])\n\tconst [ResultsComponent, setResultsComponent] = React.useState<React.ReactNode>(null)\n\tconst [loadedFor, setLoadedFor] = React.useState<CollectionSlug | null>(null)\n\tconst [loadError, setLoadError] = React.useState(false)\n\tconst [displayAs, setDisplayAs] = React.useState<'grid' | 'list'>('grid')\n\tconst [sort, setSort] = React.useState<FolderSortKeys>('name')\n\tconst [searchInput, setSearchInput] = React.useState('')\n\t// The typed text lives here rather than in the input, which is rebuilt with the provider on\n\t// every view or folder change. Debounced here for the same reason.\n\tconst search = useDebounce(searchInput, 300)\n\n\t/**\n\t * Breadcrumbs, cards, the sort pill, the view toggle and the mount effect all load, and each\n\t * load writes six pieces of state. A slow early request resolving after a fast later one would\n\t * otherwise put one folder's contents under another folder's breadcrumb, so every response\n\t * checks that it is still the one being waited for.\n\t */\n\tconst latestRequest = React.useRef(0)\n\n\tconst loadFolder = React.useCallback(\n\t\tasync (args: {\n\t\t\tdisplayAs: 'grid' | 'list'\n\t\t\tfolderID: null | number | string\n\t\t\tsort: FolderSortKeys\n\t\t}) => {\n\t\t\tif (!folderCollectionSlug) return\n\n\t\t\tconst request = ++latestRequest.current\n\n\t\t\ttry {\n\t\t\t\tconst result = await getFolderResultsComponentAndData({\n\t\t\t\t\tbrowseByFolder: false,\n\t\t\t\t\t// The folders collection has to be listed here or getFolderResultsComponentAndData\n\t\t\t\t\t// never builds its folderWhere, and no subfolder is ever returned.\n\t\t\t\t\tcollectionsToDisplay: [folderCollectionSlug, collectionSlug],\n\t\t\t\t\tdisplayAs: args.displayAs,\n\t\t\t\t\tfolderAssignedCollections: [collectionSlug],\n\t\t\t\t\tfolderID: args.folderID ?? undefined,\n\t\t\t\t\tsort: args.sort,\n\t\t\t\t})\n\n\t\t\t\tif (request !== latestRequest.current) return\n\n\t\t\t\tsetLoadError(false)\n\t\t\t\tsetBreadcrumbs(result?.breadcrumbs || [])\n\t\t\t\tsetSubfolders(result?.subfolders || [])\n\t\t\t\tsetDocuments(result?.documents || [])\n\t\t\t\tsetResultsComponent(result?.FolderResultsComponent || null)\n\t\t\t\tsetFolderID(args.folderID)\n\t\t\t\tsetLoadedFor(collectionSlug)\n\t\t\t} catch (error) {\n\t\t\t\tif (request !== latestRequest.current) return\n\n\t\t\t\t// Every call site fires this and forgets it, so a rejection would surface as nothing but\n\t\t\t\t// an unhandled promise while the drawer sat under its loading overlay for good.\n\t\t\t\ttoast.error(error instanceof Error ? error.message : String(error))\n\t\t\t\tsetLoadError(true)\n\t\t\t\tsetLoadedFor(collectionSlug)\n\t\t\t}\n\t\t},\n\t\t[collectionSlug, folderCollectionSlug, getFolderResultsComponentAndData]\n\t)\n\n\tconst reload = React.useCallback(\n\t\t() => loadFolder({ displayAs, folderID, sort }),\n\t\t[displayAs, folderID, loadFolder, sort]\n\t)\n\n\tconst actionHandlersRef = React.useRef<FolderActionHandlers | null>(null)\n\t// The drawer's collection select re-renders this view in place rather than remounting it, so a\n\t// switch has to be caught here. Requesting per collection rather than once also keeps the effect\n\t// idempotent, which the toggles below rely on since they load on their own.\n\tconst requestedFor = React.useRef<CollectionSlug | null>(null)\n\tReact.useEffect(() => {\n\t\tif (requestedFor.current !== collectionSlug) {\n\t\t\trequestedFor.current = collectionSlug\n\t\t\tvoid loadFolder({ displayAs, folderID: null, sort })\n\t\t}\n\t}, [collectionSlug, displayAs, loadFolder, sort])\n\n\tconst currentFolder = breadcrumbs[breadcrumbs.length - 1]\n\tconst parentFolder = breadcrumbs[breadcrumbs.length - 2]\n\n\tconst [\n\t\tCreateFolderDrawer,\n\t\t,\n\t\t{ closeDrawer: closeCreateFolderDrawer, openDrawer: openCreateFolderDrawer },\n\t] = useDocumentDrawer({ collectionSlug: folderCollectionSlug ?? '' })\n\n\tconst [\n\t\tCreateDocumentDrawer,\n\t\t,\n\t\t{ closeDrawer: closeCreateDocumentDrawer, openDrawer: openCreateDocumentDrawer },\n\t] = useDocumentDrawer({ collectionSlug })\n\n\tconst handleItemClick = React.useCallback(\n\t\tasync (item: FolderOrDocument) => {\n\t\t\tif (item.relationTo === folderCollectionSlug) {\n\t\t\t\tawait loadFolder({ displayAs, folderID: item.value.id, sort })\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// The upload field re-fetches from the id, so the folder item's partial doc is enough.\n\t\t\tonSelect?.({\n\t\t\t\tcollectionSlug: item.relationTo,\n\t\t\t\tdoc: item.value,\n\t\t\t\tdocID: String(item.value.id),\n\t\t\t})\n\t\t},\n\t\t[displayAs, folderCollectionSlug, loadFolder, onSelect, sort]\n\t)\n\n\tif (!folderCollectionSlug || !folderFieldName) {\n\t\treturn null\n\t}\n\n\t// Only the very first load has nothing to draw. A collection switch keeps the header up and\n\t// swaps the results alone, since blanking a drawer the user is still reading it looks like it\n\t// closed and reopened.\n\tif (loadedFor === null) {\n\t\treturn <LoadingOverlay />\n\t}\n\n\tconst isSwitchingCollection = loadedFor !== collectionSlug\n\n\t// The server function takes no search argument (the route view reads it off the request), so the\n\t// current folder's contents are narrowed in the browser instead. Same scope, no round trip.\n\tconst term = search.trim().toLowerCase()\n\tconst matches = (item: FolderOrDocument) =>\n\t\t!term ||\n\t\tString(item.value._folderOrDocumentTitle ?? '')\n\t\t\t.toLowerCase()\n\t\t\t.includes(term)\n\tconst visibleSubfolders = subfolders.filter(matches)\n\t/**\n\t * A document the field already holds is dropped, the way Payload's own list tab drops it:\n\t * the upload field builds `filterOptions` with `id: { not_in: [...] }` from its value, so a\n\t * file that is already attached never appears among the options. The folder server function\n\t * takes no filter argument, so the same rule is applied to what came back.\n\t *\n\t * Without it the file can be picked a second time, and the upload field appends whatever it\n\t * is handed, storing the same upload twice.\n\t */\n\tconst visibleDocuments = documents.filter(\n\t\t(item) =>\n\t\t\tmatches(item) &&\n\t\t\t!(item.relationTo === collectionSlug && alreadyChosen.has(String(item.value.id)))\n\t)\n\tconst totalVisible = visibleSubfolders.length + visibleDocuments.length\n\n\tconst folderLabel = getTranslation(folderCollectionConfig?.labels?.singular ?? '', i18n)\n\tconst folderPluralLabel = getTranslation(folderCollectionConfig?.labels?.plural ?? '', i18n)\n\tconst pluralLabel = getTranslation(targetConfig?.labels?.plural ?? collectionSlug, i18n)\n\n\t/**\n\t * The server bakes the items into its grid, so narrowing the provider reaches the table view\n\t * alone and the grid keeps drawing everything: searching would leave the count and the cards\n\t * disagreeing. The grid is rebuilt here from the same filtered arrays, using the card grid\n\t * Payload builds it from, so only the data differs. The table needs none of this because it\n\t * takes no items and reads the provider itself.\n\t */\n\tconst Results =\n\t\tdisplayAs === 'grid' ? (\n\t\t\t<div>\n\t\t\t\t{visibleSubfolders.length ? (\n\t\t\t\t\t<ItemCardGrid items={visibleSubfolders} title={folderPluralLabel} type=\"folder\" />\n\t\t\t\t) : null}\n\t\t\t\t{visibleDocuments.length ? (\n\t\t\t\t\t<ItemCardGrid\n\t\t\t\t\t\titems={visibleDocuments}\n\t\t\t\t\t\tsubfolderCount={visibleSubfolders.length}\n\t\t\t\t\t\ttitle={pluralLabel}\n\t\t\t\t\t\ttype=\"file\"\n\t\t\t\t\t/>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t) : (\n\t\t\tResultsComponent\n\t\t)\n\tconst canCreateFolder = Boolean(permissions?.collections?.[folderCollectionSlug]?.create)\n\t// A document needs a folder to live in, so it is only creatable once inside one. Mirrors the\n\t// route view, where the root offers folders alone and nested folders offer both.\n\tconst canCreateDocument =\n\t\tBoolean(permissions?.collections?.[collectionSlug]?.create) && folderID !== null\n\n\tconst creatable = [\n\t\tcanCreateFolder\n\t\t\t? { label: folderLabel, onClick: openCreateFolderDrawer, slug: folderCollectionSlug }\n\t\t\t: null,\n\t\tcanCreateDocument\n\t\t\t? {\n\t\t\t\t\tlabel: getTranslation(targetConfig?.labels?.singular ?? collectionSlug, i18n),\n\t\t\t\t\tonClick: openCreateDocumentDrawer,\n\t\t\t\t\tslug: collectionSlug,\n\t\t\t\t}\n\t\t\t: null,\n\t].filter(Boolean) as { label: string; onClick: () => void; slug: string }[]\n\n\t// One option renders as a plain button, several as a chevron popup, the same shape\n\t// ListCreateNewDocInFolderButton uses. Read out rather than indexed twice, so\n\t// noUncheckedIndexedAccess narrows once for the whole branch.\n\tconst [onlyCreatable] = creatable\n\n\tconst createAction =\n\t\tcreatable.length === 0 ? null : onlyCreatable && creatable.length === 1 ? (\n\t\t\t<Button\n\t\t\t\tbuttonStyle=\"pill\"\n\t\t\t\tel=\"div\"\n\t\t\t\tkey=\"create-new\"\n\t\t\t\tonClick={onlyCreatable.onClick}\n\t\t\t\tsize=\"small\"\n\t\t\t>\n\t\t\t\t{`${t('general:create')} ${onlyCreatable.label.toLowerCase()}`}\n\t\t\t</Button>\n\t\t) : (\n\t\t\t<Popup\n\t\t\t\tbutton={\n\t\t\t\t\t<Button buttonStyle=\"pill\" el=\"div\" icon=\"chevron\" size=\"small\">\n\t\t\t\t\t\t{t('general:createNew')}\n\t\t\t\t\t</Button>\n\t\t\t\t}\n\t\t\t\tbuttonType=\"default\"\n\t\t\t\tkey=\"create-new\"\n\t\t\t>\n\t\t\t\t<PopupList.ButtonGroup>\n\t\t\t\t\t{creatable.map((option) => (\n\t\t\t\t\t\t<PopupList.Button key={option.slug} onClick={option.onClick}>\n\t\t\t\t\t\t\t{option.label}\n\t\t\t\t\t\t</PopupList.Button>\n\t\t\t\t\t))}\n\t\t\t\t</PopupList.ButtonGroup>\n\t\t\t</Popup>\n\t\t)\n\tconst crumbs = [{ id: null, name: pluralLabel }, ...breadcrumbs]\n\n\t// Sits in the title row rather than a row of its own, so entering a folder does not shift\n\t// everything below it down.\n\tconst trail =\n\t\tbreadcrumbs.length > 0 ? (\n\t\t\t<nav aria-label={pluralLabel} className={`${baseClass}__trail`} key=\"breadcrumbs\">\n\t\t\t\t<FolderIcon />\n\t\t\t\t{crumbs.map((crumb, index) => (\n\t\t\t\t\t<React.Fragment key={String(crumb.id ?? 'root')}>\n\t\t\t\t\t\t{index > 0 && (\n\t\t\t\t\t\t\t<span aria-hidden=\"true\" className={`${baseClass}__trail-sep`}>\n\t\t\t\t\t\t\t\t/\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\tbuttonStyle=\"none\"\n\t\t\t\t\t\t\tclassName={`${baseClass}__trail-crumb`}\n\t\t\t\t\t\t\tel=\"button\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tvoid loadFolder({ displayAs, folderID: crumb.id, sort })\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{crumb.name}\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t</React.Fragment>\n\t\t\t\t))}\n\t\t\t</nav>\n\t\t) : null\n\n\t// A document view wraps its children in LivePreviewProvider, whose DndContext looks up a\n\t// `live-preview-area` droppable and hands the result to rectIntersection unchecked. In a drawer\n\t// that area does not exist, so the first pointer move throws. Registering the cards here keeps\n\t// them out of that context, and matches the collision detection the admin root uses.\n\treturn (\n\t\t<DndContext collisionDetection={pointerWithin}>\n\t\t\t<div className={`${baseClass} ${baseClass}--${collectionSlug}`}>\n\t\t\t\t{Tabs || trail ? (\n\t\t\t\t\t<Gutter className=\"default-list-view-tabs__drawer-gutter\">\n\t\t\t\t\t\t<div className={`${baseClass}__tabs-row`}>\n\t\t\t\t\t\t\t{Tabs}\n\t\t\t\t\t\t\t{trail}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Gutter>\n\t\t\t\t) : null}\n\n\t\t\t\t<FolderProvider\n\t\t\t\t\tallCollectionFolderSlugs={[folderCollectionSlug]}\n\t\t\t\t\tallowCreateCollectionSlugs={canCreateFolder ? [folderCollectionSlug] : []}\n\t\t\t\t\t// Only a `hasMany` field can take more than one document. Left on, Ctrl and Shift\n\t\t\t\t\t// build a selection the field cannot accept, and the confirm pill counts files\n\t\t\t\t\t// that will never be added.\n\t\t\t\t\tallowMultiSelection={Boolean(enableRowSelections)}\n\t\t\t\t\tbreadcrumbs={breadcrumbs}\n\t\t\t\t\tdocuments={visibleDocuments}\n\t\t\t\t\tfolderFieldName={folderFieldName}\n\t\t\t\t\tfolderID={folderID ?? undefined}\n\t\t\t\t\tFolderResultsComponent={ResultsComponent}\n\t\t\t\t\tkey={`${String(folderID)}-${displayAs}`}\n\t\t\t\t\tonItemClick={handleItemClick}\n\t\t\t\t\tsubfolders={visibleSubfolders}\n\t\t\t\t>\n\t\t\t\t\t<Gutter className={`${baseClass}__wrap`}>\n\t\t\t\t\t\t<ListHeader\n\t\t\t\t\t\t\tActions={[\n\t\t\t\t\t\t\t\t// Hidden on small screens, where the actions menu in the search bar is the\n\t\t\t\t\t\t\t\t// usable form. Both are available above that break, as in the route view.\n\t\t\t\t\t\t\t\tsmallBreak ? null : (\n\t\t\t\t\t\t\t\t\t<FolderSelectionBar\n\t\t\t\t\t\t\t\t\t\tcollectionSlug={collectionSlug}\n\t\t\t\t\t\t\t\t\t\tcurrentFolderName={String(currentFolder?.name ?? '')}\n\t\t\t\t\t\t\t\t\t\thandlersRef={actionHandlersRef}\n\t\t\t\t\t\t\t\t\t\tfolderCollectionSlug={folderCollectionSlug}\n\t\t\t\t\t\t\t\t\t\tfolderFieldName={folderFieldName}\n\t\t\t\t\t\t\t\t\t\tkey=\"selection-bar\"\n\t\t\t\t\t\t\t\t\t\tonChanged={(next: null | number | string) =>\n\t\t\t\t\t\t\t\t\t\t\tloadFolder({ displayAs, folderID: next, sort })\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tparentFolderID={parentFolder?.id ?? undefined}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tdrawerSlug ? (\n\t\t\t\t\t\t\t\t\t<CloseModalButton\n\t\t\t\t\t\t\t\t\t\tclassName=\"list-drawer__header-close\"\n\t\t\t\t\t\t\t\t\t\tkey=\"close-button\"\n\t\t\t\t\t\t\t\t\t\tslug={drawerSlug}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t) : null,\n\t\t\t\t\t\t\t].filter(Boolean)}\n\t\t\t\t\t\t\tAfterListHeaderContent={<DrawerRelationshipSelect />}\n\t\t\t\t\t\t\t// Same class the drawer's own list header carries, so both views lay their header\n\t\t\t\t\t\t\t// out identically and the close button lands in the same place.\n\t\t\t\t\t\t\tclassName=\"list-drawer__header\"\n\t\t\t\t\t\t\ttitle={pluralLabel}\n\t\t\t\t\t\t\tTitleActions={[\n\t\t\t\t\t\t\t\tcreateAction,\n\t\t\t\t\t\t\t\t<BulkUploadButton\n\t\t\t\t\t\t\t\t\tcollectionSlug={collectionSlug}\n\t\t\t\t\t\t\t\t\tenableRowSelections={enableRowSelections}\n\t\t\t\t\t\t\t\t\tfolderID={folderID}\n\t\t\t\t\t\t\t\t\tkey=\"bulk-upload\"\n\t\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t\t].filter(Boolean)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<SearchBar\n\t\t\t\t\t\t\tActions={[\n\t\t\t\t\t\t\t\t<SelectFolderItems\n\t\t\t\t\t\t\t\t\tcollectionSlug={collectionSlug}\n\t\t\t\t\t\t\t\t\tenableRowSelections={enableRowSelections}\n\t\t\t\t\t\t\t\t\tkey=\"select-items\"\n\t\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t\t\t<SortByPill\n\t\t\t\t\t\t\t\t\tkey=\"sort-by-pill\"\n\t\t\t\t\t\t\t\t\tonChange={(next) => {\n\t\t\t\t\t\t\t\t\t\tsetSort(next)\n\t\t\t\t\t\t\t\t\t\tvoid loadFolder({ displayAs, folderID, sort: next })\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\tsort={sort}\n\t\t\t\t\t\t\t\t\tt={t as unknown as (key: string) => string}\n\t\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t\t\t<ToggleViewButtons\n\t\t\t\t\t\t\t\t\tactiveView={displayAs}\n\t\t\t\t\t\t\t\t\tkey=\"toggle-view-buttons\"\n\t\t\t\t\t\t\t\t\tsetActiveView={(view) => {\n\t\t\t\t\t\t\t\t\t\tsetDisplayAs(view)\n\t\t\t\t\t\t\t\t\t\tvoid loadFolder({ displayAs: view, folderID, sort })\n\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t\t\t<FolderActionsMenu\n\t\t\t\t\t\t\t\t\tcollectionSlug={collectionSlug}\n\t\t\t\t\t\t\t\t\tcurrentFolderName={String(currentFolder?.name ?? '')}\n\t\t\t\t\t\t\t\t\thandlersRef={actionHandlersRef}\n\t\t\t\t\t\t\t\t\tfolderCollectionSlug={folderCollectionSlug}\n\t\t\t\t\t\t\t\t\tfolderFieldName={folderFieldName}\n\t\t\t\t\t\t\t\t\tkey=\"current-folder-actions\"\n\t\t\t\t\t\t\t\t\tonChanged={(next: null | number | string) =>\n\t\t\t\t\t\t\t\t\t\tloadFolder({ displayAs, folderID: next, sort })\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tparentFolderID={parentFolder?.id ?? undefined}\n\t\t\t\t\t\t\t\t/>,\n\t\t\t\t\t\t\t].filter(Boolean)}\n\t\t\t\t\t\t\tlabel={t('general:searchBy', { label: t('general:name') })}\n\t\t\t\t\t\t\tonSearchChange={setSearchInput}\n\t\t\t\t\t\t\tsearch={searchInput}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{enableRowSelections ? (\n\t\t\t\t\t\t\t<p className={`${baseClass}__pick-many-hint`}>\n\t\t\t\t\t\t\t\t{t(keys.pickManyHint, modifierLabels(isMac))}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t) : null}\n\t\t\t\t\t\t{isSwitchingCollection ? (\n\t\t\t\t\t\t\t<LoadingOverlay />\n\t\t\t\t\t\t) : loadError ? (\n\t\t\t\t\t\t\t<NoListResults\n\t\t\t\t\t\t\t\tActions={[\n\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\tbuttonStyle=\"primary\"\n\t\t\t\t\t\t\t\t\t\tel=\"button\"\n\t\t\t\t\t\t\t\t\t\tkey=\"retry\"\n\t\t\t\t\t\t\t\t\t\tonClick={() => void reload()}\n\t\t\t\t\t\t\t\t\t\tsize=\"medium\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{t(keys.retry)}\n\t\t\t\t\t\t\t\t\t</Button>,\n\t\t\t\t\t\t\t\t]}\n\t\t\t\t\t\t\t\tMessage={<p>{t('error:unknown')}</p>}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t) : totalVisible > 0 ? (\n\t\t\t\t\t\t\tResults\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t<NoListResults\n\t\t\t\t\t\t\t\tActions={[\n\t\t\t\t\t\t\t\t\tcanCreateFolder ? (\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tbuttonStyle=\"primary\"\n\t\t\t\t\t\t\t\t\t\t\tel=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tkey=\"create-folder\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={openCreateFolderDrawer}\n\t\t\t\t\t\t\t\t\t\t\tsize=\"medium\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{`${t('general:create')} ${folderLabel.toLowerCase()}`}\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t) : null,\n\t\t\t\t\t\t\t\t\tcanCreateDocument ? (\n\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\tbuttonStyle=\"primary\"\n\t\t\t\t\t\t\t\t\t\t\tel=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tkey=\"create-document\"\n\t\t\t\t\t\t\t\t\t\t\tonClick={openCreateDocumentDrawer}\n\t\t\t\t\t\t\t\t\t\t\tsize=\"medium\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{`${t('general:create')} ${t('general:document').toLowerCase()}`}\n\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t) : null,\n\t\t\t\t\t\t\t\t].filter(Boolean)}\n\t\t\t\t\t\t\t\tMessage={\n\t\t\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t\t\t<h3>{t('general:noResultsFound')}</h3>\n\t\t\t\t\t\t\t\t\t\t<p>{t('general:noResultsDescription')}</p>\n\t\t\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{/* Mirrors ListCreateNewDocInFolderButton: a new folder lands in the folder being\n\t\t\t\t\tviewed and is typed to the collection this drawer is picking for, so the editor\n\t\t\t\t\tnever has to set Folder Type by hand. */}\n\t\t\t\t\t\t<CreateFolderDrawer\n\t\t\t\t\t\t\tinitialData={{ [folderFieldName]: folderID, folderType: [collectionSlug] }}\n\t\t\t\t\t\t\tonSave={async () => {\n\t\t\t\t\t\t\t\tcloseCreateFolderDrawer()\n\t\t\t\t\t\t\t\tawait reload()\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tredirectAfterCreate={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<CreateDocumentDrawer\n\t\t\t\t\t\t\tinitialData={{ [folderFieldName]: folderID }}\n\t\t\t\t\t\t\tonSave={async () => {\n\t\t\t\t\t\t\t\tcloseCreateDocumentDrawer()\n\t\t\t\t\t\t\t\tawait reload()\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tredirectAfterCreate={false}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t<FolderDragLayer onMoved={reload} />\n\t\t\t\t\t</Gutter>\n\t\t\t\t</FolderProvider>\n\t\t\t</div>\n\t\t</DndContext>\n\t)\n}\n"],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ //#region src/folder/chosenUploadIds.ts
2
+ /** An id as a string, or nothing when the candidate cannot stand for one. */
3
+ const readId = (candidate) => {
4
+ if (candidate === null || candidate === void 0) return;
5
+ const id = String(candidate);
6
+ return id === "" ? void 0 : id;
7
+ };
8
+ /**
9
+ * The ids an upload field's value refers to, narrowed to one collection.
10
+ *
11
+ * Mirrors how Payload's upload field reads its own value when it builds the `filterOptions`
12
+ * that hide already-picked files from the list tab: a single value counts the same as an
13
+ * array of one, and polymorphic entries are grouped by the collection they point at.
14
+ *
15
+ * The shapes are checked in the only order that cannot confuse them:
16
+ *
17
+ * 1. `{ relationTo, value }` is a polymorphic pair, and never carries an `id`
18
+ * 2. `{ id }` is a populated document, which always carries one
19
+ * 3. `{ value }` alone is the non-polymorphic object form
20
+ *
21
+ * Checking `value` first would read a populated document that happens to have a field named
22
+ * `value` as a polymorphic pair, and store that field's contents instead of the id.
23
+ */
24
+ const chosenUploadIds = (value, collectionSlug) => {
25
+ const entries = Array.isArray(value) ? value : [value];
26
+ const ids = [];
27
+ const collect = (candidate) => {
28
+ const id = readId(candidate);
29
+ if (id !== void 0) ids.push(id);
30
+ };
31
+ for (const entry of entries) {
32
+ if (entry === null || entry === void 0) continue;
33
+ if (typeof entry !== "object") {
34
+ collect(entry);
35
+ continue;
36
+ }
37
+ if ("relationTo" in entry && "value" in entry) {
38
+ const pair = entry;
39
+ if (pair.relationTo === collectionSlug) collect(pair.value);
40
+ continue;
41
+ }
42
+ if ("id" in entry) {
43
+ collect(entry.id);
44
+ continue;
45
+ }
46
+ if ("value" in entry) collect(entry.value);
47
+ }
48
+ return ids;
49
+ };
50
+ /**
51
+ * The ids as one string, for `useFormFields`: its selector compares what it returns, so a
52
+ * fresh array would count as a change on every render.
53
+ *
54
+ * JSON rather than a separator, because Payload allows custom text ids and any character a
55
+ * separator could use is legal inside one.
56
+ */
57
+ const packChosenIds = (ids) => JSON.stringify(ids);
58
+ /** The inverse of {@link packChosenIds}. */
59
+ const unpackChosenIds = (packed) => JSON.parse(packed);
60
+ //#endregion
61
+ export { chosenUploadIds, packChosenIds, unpackChosenIds };
62
+
63
+ //# sourceMappingURL=chosenUploadIds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chosenUploadIds.js","names":[],"sources":["../../src/folder/chosenUploadIds.ts"],"sourcesContent":["type PolymorphicPair = { relationTo: unknown; value: unknown }\n\n/** An id as a string, or nothing when the candidate cannot stand for one. */\nconst readId = (candidate: unknown): string | undefined => {\n\tif (candidate === null || candidate === undefined) {\n\t\treturn undefined\n\t}\n\tconst id = String(candidate)\n\treturn id === '' ? undefined : id\n}\n\n/**\n * The ids an upload field's value refers to, narrowed to one collection.\n *\n * Mirrors how Payload's upload field reads its own value when it builds the `filterOptions`\n * that hide already-picked files from the list tab: a single value counts the same as an\n * array of one, and polymorphic entries are grouped by the collection they point at.\n *\n * The shapes are checked in the only order that cannot confuse them:\n *\n * 1. `{ relationTo, value }` is a polymorphic pair, and never carries an `id`\n * 2. `{ id }` is a populated document, which always carries one\n * 3. `{ value }` alone is the non-polymorphic object form\n *\n * Checking `value` first would read a populated document that happens to have a field named\n * `value` as a polymorphic pair, and store that field's contents instead of the id.\n */\nexport const chosenUploadIds = (value: unknown, collectionSlug: string): string[] => {\n\tconst entries = Array.isArray(value) ? value : [value]\n\tconst ids: string[] = []\n\n\tconst collect = (candidate: unknown) => {\n\t\tconst id = readId(candidate)\n\t\tif (id !== undefined) {\n\t\t\tids.push(id)\n\t\t}\n\t}\n\n\tfor (const entry of entries) {\n\t\tif (entry === null || entry === undefined) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif (typeof entry !== 'object') {\n\t\t\tcollect(entry)\n\t\t\tcontinue\n\t\t}\n\n\t\tif ('relationTo' in entry && 'value' in entry) {\n\t\t\tconst pair = entry as PolymorphicPair\n\t\t\tif (pair.relationTo === collectionSlug) {\n\t\t\t\tcollect(pair.value)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif ('id' in entry) {\n\t\t\tcollect((entry as { id: unknown }).id)\n\t\t\tcontinue\n\t\t}\n\n\t\tif ('value' in entry) {\n\t\t\tcollect((entry as { value: unknown }).value)\n\t\t}\n\t}\n\n\treturn ids\n}\n\n/**\n * The ids as one string, for `useFormFields`: its selector compares what it returns, so a\n * fresh array would count as a change on every render.\n *\n * JSON rather than a separator, because Payload allows custom text ids and any character a\n * separator could use is legal inside one.\n */\nexport const packChosenIds = (ids: string[]): string => JSON.stringify(ids)\n\n/** The inverse of {@link packChosenIds}. */\nexport const unpackChosenIds = (packed: string): string[] => JSON.parse(packed) as string[]\n"],"mappings":";;AAGA,MAAM,UAAU,cAA2C;CAC1D,IAAI,cAAc,QAAQ,cAAc,KAAA,GACvC;CAED,MAAM,KAAK,OAAO,SAAS;CAC3B,OAAO,OAAO,KAAK,KAAA,IAAY;AAChC;;;;;;;;;;;;;;;;;AAkBA,MAAa,mBAAmB,OAAgB,mBAAqC;CACpF,MAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CACrD,MAAM,MAAgB,CAAC;CAEvB,MAAM,WAAW,cAAuB;EACvC,MAAM,KAAK,OAAO,SAAS;EAC3B,IAAI,OAAO,KAAA,GACV,IAAI,KAAK,EAAE;CAEb;CAEA,KAAK,MAAM,SAAS,SAAS;EAC5B,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC/B;EAGD,IAAI,OAAO,UAAU,UAAU;GAC9B,QAAQ,KAAK;GACb;EACD;EAEA,IAAI,gBAAgB,SAAS,WAAW,OAAO;GAC9C,MAAM,OAAO;GACb,IAAI,KAAK,eAAe,gBACvB,QAAQ,KAAK,KAAK;GAEnB;EACD;EAEA,IAAI,QAAQ,OAAO;GAClB,QAAS,MAA0B,EAAE;GACrC;EACD;EAEA,IAAI,WAAW,OACd,QAAS,MAA6B,KAAK;CAE7C;CAEA,OAAO;AACR;;;;;;;;AASA,MAAa,iBAAiB,QAA0B,KAAK,UAAU,GAAG;;AAG1E,MAAa,mBAAmB,WAA6B,KAAK,MAAM,MAAM"}
@@ -0,0 +1,74 @@
1
+ /* Styles for the folder browsing the plugin adds to Payload's list drawer. Class names
2
+ deliberately match Payload's own (.list-drawer__header, .collection-folder-list) rather than
3
+ carrying a plugin prefix: this restyles Payload's markup, it does not introduce its own. */
4
+
5
+ /* Both drawer views stack below the All Media / By Folder tabs, so neither header should keep the
6
+ top margin it carries when it leads the drawer (base(2.5) on .list-drawer__header). Zeroing both
7
+ leaves the h1's own leading as the gap, which matches the space below the title and keeps the
8
+ two views identical. Keyed on markup only FolderListView renders, the folder view's own root and
9
+ the tabs gutter that precedes DefaultListView, so every other list drawer (a relationship field
10
+ with appearance: 'drawer', whose header does lead the drawer) keeps its margin. Two rules rather
11
+ than one selector group so the weaker selector comes first (noDescendingSpecificity). */
12
+ .collection-folder-list .list-drawer__header {
13
+ margin-top: 0;
14
+ }
15
+
16
+ .default-list-view-tabs__drawer-gutter ~ .collection-list .list-drawer__header {
17
+ margin-top: 0;
18
+ }
19
+
20
+ /* The drawer folder view puts its trail beside the All Media / By Folder tabs, which the route
21
+ view cannot do because its own trail lives in the admin step nav. Slash separators rather than
22
+ the chevron the folder breadcrumbs use, and a muted icon that follows the active theme. */
23
+ .collection-folder-list__tabs-row {
24
+ align-items: center;
25
+ display: flex;
26
+ flex-wrap: nowrap;
27
+ gap: calc(var(--base) * 0.75);
28
+ min-width: 0;
29
+ }
30
+
31
+ .collection-folder-list__trail {
32
+ align-items: center;
33
+ color: var(--theme-elevation-500);
34
+ display: flex;
35
+ flex-wrap: nowrap;
36
+ gap: calc(var(--base) * 0.25);
37
+ min-width: 0;
38
+ overflow: hidden;
39
+ white-space: nowrap;
40
+ }
41
+
42
+ .collection-folder-list__trail .icon--folder {
43
+ flex-shrink: 0;
44
+ }
45
+
46
+ .collection-folder-list__trail .btn {
47
+ margin: 0;
48
+ }
49
+
50
+ .collection-folder-list__trail-sep {
51
+ color: var(--theme-elevation-400);
52
+ }
53
+
54
+ .collection-folder-list__trail-crumb {
55
+ color: var(--theme-elevation-800);
56
+ min-width: 0;
57
+ overflow: hidden;
58
+ text-overflow: ellipsis;
59
+ }
60
+
61
+ /* The All Media tab's own header is owned by DefaultListView, so bulk upload sits with the view
62
+ tabs instead. Scoped to the drawer so no other list view is touched. */
63
+ .list-drawer .default-list-view-tabs__drawer-row {
64
+ align-items: center;
65
+ display: flex;
66
+ gap: calc(var(--base) * 0.5);
67
+ }
68
+
69
+ /* Styled like Payload's own field descriptions so the hint reads as part of the admin
70
+ rather than as an addition to it. */
71
+ .collection-folder-list__pick-many-hint {
72
+ color: var(--theme-elevation-400);
73
+ font-size: 1rem;
74
+ }
@@ -0,0 +1,13 @@
1
+ import { ListViewClientProps } from "payload";
2
+ import React from "react";
3
+ //#region src/folder/index.d.ts
4
+ /**
5
+ * List view for folder-enabled collections. On a real list route it renders Payload's own view
6
+ * untouched, since the route already carries the List/Folders tabs. Inside a list drawer (the
7
+ * upload field's "choose from existing") those tabs are replaced by the drawer header, so this
8
+ * adds them back and swaps in a folder picker that does not navigate.
9
+ */
10
+ declare const FolderListView: React.FC<ListViewClientProps>;
11
+ //#endregion
12
+ export { FolderListView };
13
+ //# sourceMappingURL=index.d.ts.map