@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.
- package/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.js +3 -0
- package/dist/exports/i18n.d.ts +3 -0
- package/dist/exports/i18n.js +3 -0
- package/dist/exports/types.d.ts +2 -0
- package/dist/exports/types.js +1 -0
- package/dist/folder/BulkUploadButton.js +84 -0
- package/dist/folder/BulkUploadButton.js.map +1 -0
- package/dist/folder/FolderActions.js +133 -0
- package/dist/folder/FolderActions.js.map +1 -0
- package/dist/folder/FolderBrowser.js +417 -0
- package/dist/folder/FolderBrowser.js.map +1 -0
- package/dist/folder/chosenUploadIds.js +63 -0
- package/dist/folder/chosenUploadIds.js.map +1 -0
- package/dist/folder/folder.css +74 -0
- package/dist/folder/index.d.ts +13 -0
- package/dist/folder/index.js +65 -0
- package/dist/folder/index.js.map +1 -0
- package/dist/folder/isMacPlatform.js +21 -0
- package/dist/folder/isMacPlatform.js.map +1 -0
- package/dist/folder/native.js +391 -0
- package/dist/folder/native.js.map +1 -0
- package/dist/folder/useChosenUploads.js +27 -0
- package/dist/folder/useChosenUploads.js.map +1 -0
- package/dist/folder/useFolderTargets.js +126 -0
- package/dist/folder/useFolderTargets.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/registerFolderListView.js +36 -0
- package/dist/plugin/registerFolderListView.js.map +1 -0
- package/dist/plugin/registerTranslations.js +19 -0
- package/dist/plugin/registerTranslations.js.map +1 -0
- package/dist/translations/de.js +15 -0
- package/dist/translations/de.js.map +1 -0
- package/dist/translations/en.js +20 -0
- package/dist/translations/en.js.map +1 -0
- package/dist/translations/index.d.ts +16 -0
- package/dist/translations/index.js +35 -0
- package/dist/translations/index.js.map +1 -0
- package/dist/translations/keys.d.ts +19 -0
- package/dist/translations/keys.js +19 -0
- package/dist/translations/keys.js.map +1 -0
- package/dist/translations/uk.js +15 -0
- package/dist/translations/uk.js.map +1 -0
- package/dist/translations/useTranslation.js +12 -0
- package/dist/translations/useTranslation.js.map +1 -0
- package/package.json +110 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @10x-media/folder-picker
|
|
2
|
+
|
|
3
|
+
## 0.1.0-beta.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Initial beta of `@10x-media/folder-picker`: folder browsing inside Payload's list drawer.
|
|
8
|
+
|
|
9
|
+
Payload has had folders since 3.39, but only on the collection route. Opening the same collection from an upload field's "choose from existing" gives a flat, paginated list with no folders. This plugin puts them back.
|
|
10
|
+
|
|
11
|
+
- **One view swap, every caller**: replaces `admin.components.views.list.Component` on folder-enabled collections. Payload resolves that component for both the list route and the list drawer, so upload fields, `hasMany` upload fields, relationship fields with `appearance: 'drawer'`, and the lexical upload node are all covered without patching a single field.
|
|
12
|
+
- **Folder browser in the drawer**: subfolder navigation, breadcrumbs, search, grid and list display, and sorting, driven by `get-folder-results-component-and-data` so changing folder never tears the drawer down.
|
|
13
|
+
- **Folder management inline**: create folders and documents, and move, rename or delete folders, without leaving the drawer. Bulk upload lands in the folder being viewed.
|
|
14
|
+
- **The list route is untouched**: the swapped view defers to `DefaultListView` outside a drawer, so Payload's own tabs and behavior come back unchanged.
|
|
15
|
+
- **Respects host customization**: collections that already declare a list view are never overwritten, and collections without `folders: true` keep the stock drawer.
|
|
16
|
+
- **Documented internals**: the three undocumented Payload behaviors this relies on are recorded in the README, along with the version sensitivity of the copied drawer-header markup.
|
|
17
|
+
|
|
18
|
+
- Review round: picking a file is an explicit action, polymorphic fields switch collection without leaving the folder view, and dragging no longer crashes the drawer.
|
|
19
|
+
|
|
20
|
+
- **Collection select above the folder view.** A polymorphic upload field (`relationTo: ['media', 'files']`) previously stripped its collection picker in the folder tab, so reaching the second collection meant going back to the list tab and forward again. The picker now sits above the folders exactly as it does above the list, and switching re-renders the view for the collection that was chosen. Folder state is keyed to the collection it was loaded for, so a switch never leaves the previous collection's folders under the new collection's title, and the header stays mounted while the new results load.
|
|
21
|
+
- **Selection is confirmed, not double-clicked.** Picking a file used to require a double-click, and the only confirmation was a pale pill that appeared for `hasMany` fields alone. One click now selects and **Select** confirms, from the search bar row Payload's own list tab confirms in, but dark rather than white: the folder view has no row checkboxes to hint that selecting is a thing. A single-value field routes through `onSelect`, which it never had a button for; `hasMany` keeps the bulk path and labels the count. Double-clicking a file still picks it outright, and double-clicking a folder still opens it.
|
|
22
|
+
- **Dragging a card no longer crashes the admin panel.** A document view wraps its children in `LivePreviewProvider`, whose `DndContext` looks up a `live-preview-area` droppable and hands the result to `rectIntersection` unchecked; inside a drawer that area does not exist, so the first pointer move threw. The folder cards now register in their own `DndContext` with the collision detection the admin root uses, and a drag overlay follows the cursor as it does on the route.
|
|
23
|
+
- **`folders.fieldName` is honored when moving a folder.** Moving the folder in view PATCHed a hard-coded `folder` field, so a host that renamed the folder field through `config.folders.fieldName` silently wrote nothing. The configured name is threaded through and used.
|
|
24
|
+
- **Translation keys without a namespace are dropped.** `toNested` split every key at `indexOf(':')`, so an override key with no colon was filed under itself with its last character shaved off. Such keys are skipped instead.
|
|
25
|
+
- **The `translations` option is documented**, alongside which strings belong to the plugin and which come from Payload's own admin locale.
|
|
26
|
+
|
|
27
|
+
- The folder tab stops offering what the field cannot take, and says how to pick more than one.
|
|
28
|
+
|
|
29
|
+
- **A file the field already holds no longer appears.** Payload's upload field hides its current value from the list tab by building `filterOptions` with `id: { not_in: [...] }`, but the folder server function takes no filter, so the folder tab kept offering those files. Picking one again stored it twice, because the field appends whatever the drawer hands it. The same rule is now applied to the folder's results, for single-value fields as well as `hasMany`, and removing a value puts the file back among the options.
|
|
30
|
+
- **Multi-selection is limited to `hasMany` fields.** It was enabled everywhere, so on a single-value field Ctrl and Shift built a selection the field could not accept and the confirm pill counted files that were then dropped.
|
|
31
|
+
- **A hint under the header explains the modifiers**, for `hasMany` fields only, where a selection can actually hold several. It draws `⌘` and `⇧` on macOS and spells `Ctrl` and `Shift` out everywhere else.
|
|
32
|
+
- **German and Ukrainian locales**, alongside the English the plugin already shipped. Every string is checked against the English key set, and against the placeholders each one carries.
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- Two fixes to the folder view, both places where the port drifted from the admin's own behaviour.
|
|
37
|
+
|
|
38
|
+
- **Editing with a file selected no longer lands on a "not found" page.** The edit action fed its target's id to a drawer keyed to the folder collection, and a selected target could be a document, so picking a file and choosing edit sent the admin to `/admin/collections/<folders>?notFound=<file id>`. A document is no longer a target for it, which is what Payload's own selection bar does: the action hides when the selection is not a folder, leaving the bulk document actions to handle it. Editing the folder in view, and editing a selected folder, are unchanged.
|
|
39
|
+
- **The dragged card no longer trails from wherever it was grabbed.** dnd-kit preserves the grab point, so a card taken by its lower edge followed the cursor from that edge and covered what was being dragged over. The card now pins its top left just below and right of the cursor however it was picked up, matching the folder view on the route. Payload applies a modifier for this but does not export it, so it is reproduced alongside the card itself.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 10x Media GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# @10x-media/folder-picker
|
|
4
|
+
|
|
5
|
+
Folder browsing inside Payload's list drawer, so any field picks documents by folder. Payload has had folders since 3.39, but only on the collection route: open the same collection from an upload field's "choose from existing" and you get a flat, paginated list. This plugin puts the folders back, for every field that opens a drawer.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@10x-media/folder-picker)
|
|
8
|
+
|
|
9
|
+
Part of the [@10x-media Payload plugins](https://github.com/10x-media/payload-plugins) collection. In beta: published under the `beta` dist-tag until a stable 1.0.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **One view swap, every caller**: upload fields, `hasMany` uploads, relationship fields with `appearance: 'drawer'`, and the lexical upload node, without patching a single field.
|
|
14
|
+
- **Folder browsing in the drawer**: subfolder navigation, breadcrumbs, search, grid or list display, and sorting, none of which tears the drawer down.
|
|
15
|
+
- **Folder management inline**: create, move, rename and delete folders, drag files and folders between them, and create documents, without leaving the document you are editing.
|
|
16
|
+
- **Picking is explicit**: one click selects, **Select** confirms, for single and `hasMany` fields alike.
|
|
17
|
+
- **Polymorphic fields switch collection in place**, through the same collection select the list tab carries.
|
|
18
|
+
- **Bulk upload into the current folder** for `hasMany` fields.
|
|
19
|
+
- **The list route stays Payload's**: outside a drawer the collection renders exactly as before.
|
|
20
|
+
- **Respects host customization**: collections that already declare a list view are never overwritten, and collections without `folders: true` keep the stock drawer.
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @10x-media/folder-picker@beta
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// payload.config.ts
|
|
30
|
+
import { buildConfig } from 'payload'
|
|
31
|
+
import { folderPicker } from '@10x-media/folder-picker'
|
|
32
|
+
|
|
33
|
+
export default buildConfig({
|
|
34
|
+
folders: { browseByFolder: true },
|
|
35
|
+
collections: [{ slug: 'media', folders: true, upload: true, fields: [] }],
|
|
36
|
+
plugins: [folderPicker({})],
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then run `payload generate:importmap`, or the swapped view will not resolve. Open any document with an upload field pointing at `media` and the drawer gains a **By folder** tab.
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
Full documentation at [docs.10xmedia.de](https://docs.10xmedia.de/folder-picker):
|
|
45
|
+
|
|
46
|
+
- [Overview](https://docs.10xmedia.de/folder-picker)
|
|
47
|
+
- [Quick start](https://docs.10xmedia.de/folder-picker/quick-start)
|
|
48
|
+
- [Configuration](https://docs.10xmedia.de/folder-picker/configuration)
|
|
49
|
+
- [Limits](https://docs.10xmedia.de/folder-picker/limits)
|
|
50
|
+
- [i18n](https://docs.10xmedia.de/folder-picker/i18n)
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
[MIT](./LICENSE). Copyright 10x Media GmbH.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Button, Pill, useBulkUpload, useConfig, useFolder, useListDrawerContext, useModal, useTranslation } from "@payloadcms/ui";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/folder/BulkUploadButton.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Opens the bulk upload drawer the upload field already mounts. `BulkUploadProvider` wraps the
|
|
8
|
+
* field and renders `<BulkUploadDrawer />` itself, so only a trigger is missing. Created documents
|
|
9
|
+
* come back through `setOnSuccess` and are handed to the list drawer's `onBulkSelect`, the same
|
|
10
|
+
* path the field uses when several rows are picked from the list.
|
|
11
|
+
*/
|
|
12
|
+
const BulkUploadButton = ({ collectionSlug, enableRowSelections, folderID }) => {
|
|
13
|
+
const { getEntityConfig } = useConfig();
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const { onBulkSelect } = useListDrawerContext();
|
|
16
|
+
const { openModal } = useModal();
|
|
17
|
+
const { drawerSlug, setCollectionSlug, setFolderID, setOnSuccess } = useBulkUpload();
|
|
18
|
+
const canBulkUpload = Boolean(enableRowSelections) && Boolean(onBulkSelect) && Boolean(getEntityConfig({ collectionSlug })?.upload);
|
|
19
|
+
const open = React.useCallback(() => {
|
|
20
|
+
setCollectionSlug(collectionSlug);
|
|
21
|
+
setFolderID(folderID ?? void 0);
|
|
22
|
+
setOnSuccess((uploadedForms) => {
|
|
23
|
+
const selected = new Map(uploadedForms.map((form) => form.doc?.id).filter((id) => id !== void 0).map((id) => [id, true]));
|
|
24
|
+
onBulkSelect?.(selected);
|
|
25
|
+
});
|
|
26
|
+
openModal(drawerSlug);
|
|
27
|
+
}, [
|
|
28
|
+
collectionSlug,
|
|
29
|
+
drawerSlug,
|
|
30
|
+
folderID,
|
|
31
|
+
onBulkSelect,
|
|
32
|
+
openModal,
|
|
33
|
+
setCollectionSlug,
|
|
34
|
+
setFolderID,
|
|
35
|
+
setOnSuccess
|
|
36
|
+
]);
|
|
37
|
+
if (!canBulkUpload) return null;
|
|
38
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
39
|
+
buttonStyle: "pill",
|
|
40
|
+
el: "button",
|
|
41
|
+
onClick: open,
|
|
42
|
+
size: "small",
|
|
43
|
+
children: t("upload:bulkUpload")
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Hands the selection to the field, in the search bar row and as the same `Pill` Payload puts its
|
|
48
|
+
* own `SelectMany` in, so both tabs of the drawer confirm in the same place. Dark rather than white
|
|
49
|
+
* because the folder view has no row checkboxes to hint that selecting is a thing, which is what
|
|
50
|
+
* made picking a file look like it needed a double click.
|
|
51
|
+
*
|
|
52
|
+
* Payload's own version covers `hasMany` alone. A single-value field has no bulk path, so it gets
|
|
53
|
+
* the first selected document through the same `onSelect` a double click uses. Folders are skipped
|
|
54
|
+
* either way: an upload field can only hold documents.
|
|
55
|
+
*/
|
|
56
|
+
const SelectFolderItems = ({ collectionSlug, enableRowSelections }) => {
|
|
57
|
+
const { getSelectedItems } = useFolder();
|
|
58
|
+
const { onBulkSelect, onSelect } = useListDrawerContext();
|
|
59
|
+
const { t } = useTranslation();
|
|
60
|
+
const documents = (getSelectedItems?.() ?? []).filter((item) => item.relationTo === collectionSlug);
|
|
61
|
+
const [first] = documents;
|
|
62
|
+
if (!first) return null;
|
|
63
|
+
const confirm = () => {
|
|
64
|
+
if (enableRowSelections && onBulkSelect) {
|
|
65
|
+
onBulkSelect(new Map(documents.map((item) => [item.value.id, true])));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
onSelect?.({
|
|
69
|
+
collectionSlug,
|
|
70
|
+
doc: first.value,
|
|
71
|
+
docID: String(first.value.id)
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
return /* @__PURE__ */ jsx(Pill, {
|
|
75
|
+
onClick: confirm,
|
|
76
|
+
pillStyle: "dark",
|
|
77
|
+
size: "small",
|
|
78
|
+
children: documents.length > 1 ? `${t("general:select")} ${documents.length}` : t("general:select")
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
export { BulkUploadButton, SelectFolderItems };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=BulkUploadButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BulkUploadButton.js","names":[],"sources":["../../src/folder/BulkUploadButton.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tButton,\n\tPill,\n\tuseBulkUpload,\n\tuseConfig,\n\tuseFolder,\n\tuseListDrawerContext,\n\tuseModal,\n\tuseTranslation,\n} from '@payloadcms/ui'\nimport type { CollectionSlug } from 'payload'\nimport React from 'react'\n\n/**\n * Set to false to drop bulk upload from the drawer. Everything else keeps working; the button and\n * its wiring are the only things gated on it.\n */\nexport const BULK_UPLOAD_IN_DRAWER = true\n\ntype BulkUploadButtonProps = {\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 folderID?: null | number | string\n}\n\n/**\n * Opens the bulk upload drawer the upload field already mounts. `BulkUploadProvider` wraps the\n * field and renders `<BulkUploadDrawer />` itself, so only a trigger is missing. Created documents\n * come back through `setOnSuccess` and are handed to the list drawer's `onBulkSelect`, the same\n * path the field uses when several rows are picked from the list.\n */\nexport const BulkUploadButton: React.FC<BulkUploadButtonProps> = ({\n\tcollectionSlug,\n\tenableRowSelections,\n\tfolderID,\n}) => {\n\tconst { getEntityConfig } = useConfig()\n\tconst { t } = useTranslation()\n\tconst { onBulkSelect } = useListDrawerContext()\n\tconst { openModal } = useModal()\n\tconst { drawerSlug, setCollectionSlug, setFolderID, setOnSuccess } = useBulkUpload()\n\n\t// onBulkSelect exists for every upload field, so it cannot gate this. The field passes\n\t// enableRowSelections straight from `hasMany`, which is the real signal: only then can it hold\n\t// the several documents a bulk upload produces.\n\tconst canBulkUpload =\n\t\tBULK_UPLOAD_IN_DRAWER &&\n\t\tBoolean(enableRowSelections) &&\n\t\tBoolean(onBulkSelect) &&\n\t\tBoolean(getEntityConfig({ collectionSlug })?.upload)\n\n\tconst open = React.useCallback(() => {\n\t\tsetCollectionSlug(collectionSlug)\n\t\tsetFolderID(folderID ?? undefined)\n\t\tsetOnSuccess((uploadedForms) => {\n\t\t\tconst selected = new Map<number | string, boolean>(\n\t\t\t\tuploadedForms\n\t\t\t\t\t.map((form) => form.doc?.id as number | string | undefined)\n\t\t\t\t\t.filter((id): id is number | string => id !== undefined)\n\t\t\t\t\t.map((id) => [id, true])\n\t\t\t)\n\t\t\tvoid onBulkSelect?.(selected)\n\t\t})\n\t\topenModal(drawerSlug)\n\t}, [\n\t\tcollectionSlug,\n\t\tdrawerSlug,\n\t\tfolderID,\n\t\tonBulkSelect,\n\t\topenModal,\n\t\tsetCollectionSlug,\n\t\tsetFolderID,\n\t\tsetOnSuccess,\n\t])\n\n\tif (!canBulkUpload) {\n\t\treturn null\n\t}\n\n\treturn (\n\t\t<Button buttonStyle=\"pill\" el=\"button\" onClick={open} size=\"small\">\n\t\t\t{t('upload:bulkUpload')}\n\t\t</Button>\n\t)\n}\n\n/**\n * Hands the selection to the field, in the search bar row and as the same `Pill` Payload puts its\n * own `SelectMany` in, so both tabs of the drawer confirm in the same place. Dark rather than white\n * because the folder view has no row checkboxes to hint that selecting is a thing, which is what\n * made picking a file look like it needed a double click.\n *\n * Payload's own version covers `hasMany` alone. A single-value field has no bulk path, so it gets\n * the first selected document through the same `onSelect` a double click uses. Folders are skipped\n * either way: an upload field can only hold documents.\n */\nexport const SelectFolderItems: React.FC<{\n\treadonly collectionSlug: CollectionSlug\n\treadonly enableRowSelections?: boolean\n}> = ({ collectionSlug, enableRowSelections }) => {\n\tconst { getSelectedItems } = useFolder()\n\tconst { onBulkSelect, onSelect } = useListDrawerContext()\n\tconst { t } = useTranslation()\n\n\tconst documents = (getSelectedItems?.() ?? []).filter(\n\t\t(item) => item.relationTo === collectionSlug\n\t)\n\tconst [first] = documents\n\n\tif (!first) {\n\t\treturn null\n\t}\n\n\tconst confirm = () => {\n\t\tif (enableRowSelections && onBulkSelect) {\n\t\t\tvoid onBulkSelect(\n\t\t\t\tnew Map<number | string, boolean>(documents.map((item) => [item.value.id, true]))\n\t\t\t)\n\t\t\treturn\n\t\t}\n\n\t\tonSelect?.({ collectionSlug, doc: first.value, docID: String(first.value.id) })\n\t}\n\n\treturn (\n\t\t<Pill onClick={confirm} pillStyle=\"dark\" size=\"small\">\n\t\t\t{documents.length > 1 ? `${t('general:select')} ${documents.length}` : t('general:select')}\n\t\t</Pill>\n\t)\n}\n"],"mappings":";;;;;;;;;;;AAkCA,MAAa,oBAAqD,EACjE,gBACA,qBACA,eACK;CACL,MAAM,EAAE,oBAAoB,UAAU;CACtC,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,EAAE,iBAAiB,qBAAqB;CAC9C,MAAM,EAAE,cAAc,SAAS;CAC/B,MAAM,EAAE,YAAY,mBAAmB,aAAa,iBAAiB,cAAc;CAKnF,MAAM,gBAEL,QAAQ,mBAAmB,KAC3B,QAAQ,YAAY,KACpB,QAAQ,gBAAgB,EAAE,eAAe,CAAC,GAAG,MAAM;CAEpD,MAAM,OAAO,MAAM,kBAAkB;EACpC,kBAAkB,cAAc;EAChC,YAAY,YAAY,KAAA,CAAS;EACjC,cAAc,kBAAkB;GAC/B,MAAM,WAAW,IAAI,IACpB,cACE,KAAK,SAAS,KAAK,KAAK,EAAiC,EACzD,QAAQ,OAA8B,OAAO,KAAA,CAAS,EACtD,KAAK,OAAO,CAAC,IAAI,IAAI,CAAC,CACzB;GACA,eAAoB,QAAQ;EAC7B,CAAC;EACD,UAAU,UAAU;CACrB,GAAG;EACF;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACD,CAAC;CAED,IAAI,CAAC,eACJ,OAAO;CAGR,OACC,oBAAC,QAAD;EAAQ,aAAY;EAAO,IAAG;EAAS,SAAS;EAAM,MAAK;YACzD,EAAE,mBAAmB;CACf,CAAA;AAEV;;;;;;;;;;;AAYA,MAAa,qBAGP,EAAE,gBAAgB,0BAA0B;CACjD,MAAM,EAAE,qBAAqB,UAAU;CACvC,MAAM,EAAE,cAAc,aAAa,qBAAqB;CACxD,MAAM,EAAE,MAAM,eAAe;CAE7B,MAAM,aAAa,mBAAmB,KAAK,CAAC,GAAG,QAC7C,SAAS,KAAK,eAAe,cAC/B;CACA,MAAM,CAAC,SAAS;CAEhB,IAAI,CAAC,OACJ,OAAO;CAGR,MAAM,gBAAgB;EACrB,IAAI,uBAAuB,cAAc;GACxC,aACC,IAAI,IAA8B,UAAU,KAAK,SAAS,CAAC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,CACjF;GACA;EACD;EAEA,WAAW;GAAE;GAAgB,KAAK,MAAM;GAAO,OAAO,OAAO,MAAM,MAAM,EAAE;EAAE,CAAC;CAC/E;CAEA,OACC,oBAAC,MAAD;EAAM,SAAS;EAAS,WAAU;EAAO,MAAK;YAC5C,UAAU,SAAS,IAAI,GAAG,EAAE,gBAAgB,EAAE,GAAG,UAAU,WAAW,EAAE,gBAAgB;CACpF,CAAA;AAER"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Dots, ListSelectionBar, ListSelectionButton } from "./native.js";
|
|
3
|
+
import { useFolderTargets } from "./useFolderTargets.js";
|
|
4
|
+
import { ConfirmationModal, MoveDocToFolderButton, Popup, PopupList, useDocumentDrawer, useModal } from "@payloadcms/ui";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/folder/FolderActions.tsx
|
|
8
|
+
const deleteModalSlug = "folder-browser-confirm-delete";
|
|
9
|
+
const moveModalSlug = "folder-browser-move";
|
|
10
|
+
/**
|
|
11
|
+
* Edit / move / delete, driven by useFolderTargets so the menu and the selection bar can never
|
|
12
|
+
* disagree on what they act on. The edit drawer is keyed on its target because
|
|
13
|
+
* `useDocumentDrawer` derives its slug from `id` on every render but builds the drawer once, and
|
|
14
|
+
* would otherwise keep pointing at whatever was open when it was created.
|
|
15
|
+
*/
|
|
16
|
+
const FolderActionsMenu = (props) => {
|
|
17
|
+
const { clearSelections, count, editTarget, folderLabel, hasTarget, move, remove, selected, t, viewFolderID, viewFolderName } = useFolderTargets(props);
|
|
18
|
+
const { openModal } = useModal();
|
|
19
|
+
const moveButtonRef = React.useRef(null);
|
|
20
|
+
const openMove = React.useCallback(() => {
|
|
21
|
+
moveButtonRef.current?.querySelector("button")?.click();
|
|
22
|
+
}, []);
|
|
23
|
+
const pickerDoc = editTarget ?? (selected[0] ? {
|
|
24
|
+
id: selected[0].value.id,
|
|
25
|
+
name: String(selected[0].value._folderOrDocumentTitle ?? "")
|
|
26
|
+
} : {
|
|
27
|
+
id: viewFolderID ?? "",
|
|
28
|
+
name: viewFolderName
|
|
29
|
+
});
|
|
30
|
+
const [EditDrawer, , { closeDrawer: closeEditDrawer, openDrawer: openEditDrawer }] = useDocumentDrawer({
|
|
31
|
+
id: editTarget ? String(editTarget.id) : void 0,
|
|
32
|
+
collectionSlug: props.folderCollectionSlug
|
|
33
|
+
});
|
|
34
|
+
props.handlersRef.current = {
|
|
35
|
+
openDelete: () => openModal(deleteModalSlug),
|
|
36
|
+
openEdit: openEditDrawer,
|
|
37
|
+
openMove
|
|
38
|
+
};
|
|
39
|
+
if (!hasTarget) return null;
|
|
40
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
41
|
+
/* @__PURE__ */ jsx(Popup, {
|
|
42
|
+
button: /* @__PURE__ */ jsx(Dots, { ariaLabel: t("general:moreOptions") }),
|
|
43
|
+
className: "current-folder-actions",
|
|
44
|
+
horizontalAlign: "right",
|
|
45
|
+
verticalAlign: "bottom",
|
|
46
|
+
children: /* @__PURE__ */ jsxs(PopupList.ButtonGroup, { children: [
|
|
47
|
+
count > 0 ? /* @__PURE__ */ jsx(PopupList.Button, {
|
|
48
|
+
onClick: () => clearSelections(),
|
|
49
|
+
children: t("general:clearAll")
|
|
50
|
+
}) : null,
|
|
51
|
+
editTarget ? /* @__PURE__ */ jsx(PopupList.Button, {
|
|
52
|
+
onClick: openEditDrawer,
|
|
53
|
+
children: t("general:editLabel", { label: folderLabel })
|
|
54
|
+
}) : null,
|
|
55
|
+
/* @__PURE__ */ jsx(PopupList.Button, {
|
|
56
|
+
onClick: openMove,
|
|
57
|
+
children: t("general:move")
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ jsx(PopupList.Button, {
|
|
60
|
+
onClick: () => openModal(deleteModalSlug),
|
|
61
|
+
children: t("general:delete")
|
|
62
|
+
})
|
|
63
|
+
] })
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ jsx("div", {
|
|
66
|
+
hidden: true,
|
|
67
|
+
ref: moveButtonRef,
|
|
68
|
+
children: /* @__PURE__ */ jsx(MoveDocToFolderButton, {
|
|
69
|
+
collectionSlug: props.collectionSlug,
|
|
70
|
+
docData: {
|
|
71
|
+
_folderOrDocumentTitle: pickerDoc.name,
|
|
72
|
+
folderType: [props.collectionSlug],
|
|
73
|
+
id: pickerDoc.id
|
|
74
|
+
},
|
|
75
|
+
docID: pickerDoc.id,
|
|
76
|
+
docTitle: pickerDoc.name,
|
|
77
|
+
folderCollectionSlug: props.folderCollectionSlug,
|
|
78
|
+
folderFieldName: props.folderFieldName,
|
|
79
|
+
fromFolderID: viewFolderID,
|
|
80
|
+
fromFolderName: viewFolderName,
|
|
81
|
+
modalSlug: moveModalSlug,
|
|
82
|
+
onConfirm: move
|
|
83
|
+
})
|
|
84
|
+
}),
|
|
85
|
+
editTarget ? /* @__PURE__ */ jsx(EditDrawer, { onSave: async () => {
|
|
86
|
+
closeEditDrawer();
|
|
87
|
+
await props.onChanged(viewFolderID ?? null);
|
|
88
|
+
} }, String(editTarget.id)) : null,
|
|
89
|
+
/* @__PURE__ */ jsx(ConfirmationModal, {
|
|
90
|
+
body: t("general:aboutToDeleteCount", {
|
|
91
|
+
count: Math.max(count, 1),
|
|
92
|
+
label: folderLabel
|
|
93
|
+
}),
|
|
94
|
+
confirmingLabel: t("general:deleting"),
|
|
95
|
+
heading: t("general:confirmDeletion"),
|
|
96
|
+
modalSlug: deleteModalSlug,
|
|
97
|
+
onConfirm: remove
|
|
98
|
+
})
|
|
99
|
+
] });
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* The horizontal bar the route view shows while items are selected: same actions and the same
|
|
103
|
+
* conditions as the menu. Hidden on small screens, where the menu is the usable form.
|
|
104
|
+
*/
|
|
105
|
+
const FolderSelectionBar = (props) => {
|
|
106
|
+
const { clearSelections, count, editTarget, folderLabel, t } = useFolderTargets(props);
|
|
107
|
+
if (count === 0) return null;
|
|
108
|
+
return /* @__PURE__ */ jsx(ListSelectionBar, {
|
|
109
|
+
count,
|
|
110
|
+
ListActions: [/* @__PURE__ */ jsx(ListSelectionButton, {
|
|
111
|
+
onClick: () => clearSelections(),
|
|
112
|
+
children: t("general:clearAll")
|
|
113
|
+
}, "clear-all")],
|
|
114
|
+
SelectionActions: [
|
|
115
|
+
editTarget ? /* @__PURE__ */ jsx(ListSelectionButton, {
|
|
116
|
+
onClick: () => props.handlersRef.current?.openEdit(),
|
|
117
|
+
children: t("general:editLabel", { label: folderLabel })
|
|
118
|
+
}, "edit") : null,
|
|
119
|
+
/* @__PURE__ */ jsx(ListSelectionButton, {
|
|
120
|
+
onClick: () => props.handlersRef.current?.openMove(),
|
|
121
|
+
children: t("general:move")
|
|
122
|
+
}, "move"),
|
|
123
|
+
/* @__PURE__ */ jsx(ListSelectionButton, {
|
|
124
|
+
onClick: () => props.handlersRef.current?.openDelete(),
|
|
125
|
+
children: t("general:delete")
|
|
126
|
+
}, "delete")
|
|
127
|
+
].filter(Boolean)
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { FolderActionsMenu, FolderSelectionBar };
|
|
132
|
+
|
|
133
|
+
//# sourceMappingURL=FolderActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FolderActions.js","names":[],"sources":["../../src/folder/FolderActions.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tConfirmationModal,\n\tMoveDocToFolderButton,\n\tPopup,\n\tPopupList,\n\tuseDocumentDrawer,\n\tuseModal,\n} from '@payloadcms/ui'\nimport type { CollectionSlug } from 'payload'\nimport React from 'react'\nimport { Dots, ListSelectionBar, ListSelectionButton } from './native'\nimport { useFolderTargets } from './useFolderTargets'\n\nexport type FolderActionHandlers = {\n\topenDelete: () => void\n\topenEdit: () => void\n\topenMove: () => void\n}\n\ntype SharedProps = {\n\t/** The menu owns the drawers; the bar unmounts with the selection, so it calls in through this. */\n\treadonly handlersRef: React.RefObject<FolderActionHandlers | null>\n\treadonly collectionSlug: CollectionSlug\n\treadonly currentFolderName: string\n\treadonly folderCollectionSlug: CollectionSlug\n\treadonly folderFieldName: string\n\treadonly onChanged: (nextFolderID: null | number | string) => Promise<void> | void\n\treadonly parentFolderID?: number | string\n}\n\nconst deleteModalSlug = 'folder-browser-confirm-delete'\nconst moveModalSlug = 'folder-browser-move'\n\n/**\n * Edit / move / delete, driven by useFolderTargets so the menu and the selection bar can never\n * disagree on what they act on. The edit drawer is keyed on its target because\n * `useDocumentDrawer` derives its slug from `id` on every render but builds the drawer once, and\n * would otherwise keep pointing at whatever was open when it was created.\n */\nexport const FolderActionsMenu: React.FC<SharedProps> = (props) => {\n\tconst {\n\t\tclearSelections,\n\t\tcount,\n\t\teditTarget,\n\t\tfolderLabel,\n\t\thasTarget,\n\t\tmove,\n\t\tremove,\n\t\tselected,\n\t\tt,\n\t\tviewFolderID,\n\t\tviewFolderName,\n\t} = useFolderTargets(props)\n\tconst { openModal } = useModal()\n\tconst moveButtonRef = React.useRef<HTMLDivElement>(null)\n\tconst openMove = React.useCallback(() => {\n\t\tmoveButtonRef.current?.querySelector('button')?.click()\n\t}, [])\n\n\t// The picker only labels one document, so with several selected it names the first; the move is\n\t// applied to all of them in `move`.\n\tconst pickerDoc =\n\t\teditTarget ??\n\t\t(selected[0]\n\t\t\t? { id: selected[0].value.id, name: String(selected[0].value._folderOrDocumentTitle ?? '') }\n\t\t\t: { id: viewFolderID ?? '', name: viewFolderName })\n\n\tconst [EditDrawer, , { closeDrawer: closeEditDrawer, openDrawer: openEditDrawer }] =\n\t\tuseDocumentDrawer({\n\t\t\tid: editTarget ? String(editTarget.id) : undefined,\n\t\t\tcollectionSlug: props.folderCollectionSlug,\n\t\t})\n\n\tprops.handlersRef.current = {\n\t\topenDelete: () => openModal(deleteModalSlug),\n\t\topenEdit: openEditDrawer,\n\t\topenMove,\n\t}\n\n\tif (!hasTarget) {\n\t\treturn null\n\t}\n\n\treturn (\n\t\t<React.Fragment>\n\t\t\t<Popup\n\t\t\t\tbutton={<Dots ariaLabel={t('general:moreOptions')} />}\n\t\t\t\tclassName=\"current-folder-actions\"\n\t\t\t\thorizontalAlign=\"right\"\n\t\t\t\tverticalAlign=\"bottom\"\n\t\t\t>\n\t\t\t\t<PopupList.ButtonGroup>\n\t\t\t\t\t{count > 0 ? (\n\t\t\t\t\t\t<PopupList.Button onClick={() => clearSelections()}>\n\t\t\t\t\t\t\t{t('general:clearAll')}\n\t\t\t\t\t\t</PopupList.Button>\n\t\t\t\t\t) : null}\n\t\t\t\t\t{/* Editing needs exactly one target, so it drops out once several are selected. */}\n\t\t\t\t\t{editTarget ? (\n\t\t\t\t\t\t<PopupList.Button onClick={openEditDrawer}>\n\t\t\t\t\t\t\t{t('general:editLabel', { label: folderLabel })}\n\t\t\t\t\t\t</PopupList.Button>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<PopupList.Button onClick={openMove}>{t('general:move')}</PopupList.Button>\n\t\t\t\t\t<PopupList.Button onClick={() => openModal(deleteModalSlug)}>\n\t\t\t\t\t\t{t('general:delete')}\n\t\t\t\t\t</PopupList.Button>\n\t\t\t\t</PopupList.ButtonGroup>\n\t\t\t</Popup>\n\n\t\t\t{/* MoveItemsToFolderDrawer, which owns the picker, is missing from the bundled client entry.\n\t\t\t\tReaching it through the ./elements/* subpath does not work: Payload ships its client\n\t\t\t\tcomponents twice, and the unbundled copy carries its own React contexts, so the drawer\n\t\t\t\tloads with no modal provider above it and reads an undefined modalState.\n\n\t\t\t\tMoveDocToFolderButton bundles the same drawer and does come from the bundle, but labels itself for a doc's edit\n\t\t\t\tview and cannot take children, so its trigger stays hidden and is clicked from the menu.\n\t\t\t\tIt only knows one document, so it is used purely to pick a destination; the move is then\n\t\t\t\tapplied to every target. The picker opens on the folder in view. */}\n\t\t\t<div hidden ref={moveButtonRef}>\n\t\t\t\t<MoveDocToFolderButton\n\t\t\t\t\tcollectionSlug={props.collectionSlug}\n\t\t\t\t\tdocData={{\n\t\t\t\t\t\t_folderOrDocumentTitle: pickerDoc.name,\n\t\t\t\t\t\tfolderType: [props.collectionSlug],\n\t\t\t\t\t\tid: pickerDoc.id,\n\t\t\t\t\t}}\n\t\t\t\t\tdocID={pickerDoc.id}\n\t\t\t\t\tdocTitle={pickerDoc.name}\n\t\t\t\t\tfolderCollectionSlug={props.folderCollectionSlug}\n\t\t\t\t\tfolderFieldName={props.folderFieldName}\n\t\t\t\t\tfromFolderID={viewFolderID}\n\t\t\t\t\tfromFolderName={viewFolderName}\n\t\t\t\t\tmodalSlug={moveModalSlug}\n\t\t\t\t\tonConfirm={move}\n\t\t\t\t/>\n\t\t\t</div>\n\n\t\t\t{editTarget ? (\n\t\t\t\t<EditDrawer\n\t\t\t\t\tkey={String(editTarget.id)}\n\t\t\t\t\tonSave={async () => {\n\t\t\t\t\t\tcloseEditDrawer()\n\t\t\t\t\t\tawait props.onChanged(viewFolderID ?? null)\n\t\t\t\t\t}}\n\t\t\t\t/>\n\t\t\t) : null}\n\n\t\t\t<ConfirmationModal\n\t\t\t\tbody={t('general:aboutToDeleteCount', { count: Math.max(count, 1), label: folderLabel })}\n\t\t\t\tconfirmingLabel={t('general:deleting')}\n\t\t\t\theading={t('general:confirmDeletion')}\n\t\t\t\tmodalSlug={deleteModalSlug}\n\t\t\t\tonConfirm={remove}\n\t\t\t/>\n\t\t</React.Fragment>\n\t)\n}\n\n/**\n * The horizontal bar the route view shows while items are selected: same actions and the same\n * conditions as the menu. Hidden on small screens, where the menu is the usable form.\n */\nexport const FolderSelectionBar: React.FC<SharedProps> = (props) => {\n\tconst { clearSelections, count, editTarget, folderLabel, t } = useFolderTargets(props)\n\n\tif (count === 0) {\n\t\treturn null\n\t}\n\n\treturn (\n\t\t<ListSelectionBar\n\t\t\tcount={count}\n\t\t\tListActions={[\n\t\t\t\t<ListSelectionButton key=\"clear-all\" onClick={() => clearSelections()}>\n\t\t\t\t\t{t('general:clearAll')}\n\t\t\t\t</ListSelectionButton>,\n\t\t\t]}\n\t\t\tSelectionActions={[\n\t\t\t\teditTarget ? (\n\t\t\t\t\t<ListSelectionButton key=\"edit\" onClick={() => props.handlersRef.current?.openEdit()}>\n\t\t\t\t\t\t{t('general:editLabel', { label: folderLabel })}\n\t\t\t\t\t</ListSelectionButton>\n\t\t\t\t) : null,\n\t\t\t\t<ListSelectionButton key=\"move\" onClick={() => props.handlersRef.current?.openMove()}>\n\t\t\t\t\t{t('general:move')}\n\t\t\t\t</ListSelectionButton>,\n\t\t\t\t<ListSelectionButton key=\"delete\" onClick={() => props.handlersRef.current?.openDelete()}>\n\t\t\t\t\t{t('general:delete')}\n\t\t\t\t</ListSelectionButton>,\n\t\t\t].filter(Boolean)}\n\t\t/>\n\t)\n}\n"],"mappings":"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"}
|