@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
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { BulkUploadButton } from "./BulkUploadButton.js";
|
|
3
|
+
import { FolderBrowser } from "./FolderBrowser.js";
|
|
4
|
+
import { getTranslation } from "@payloadcms/translations";
|
|
5
|
+
import { Button, DefaultListView, Gutter, useConfig, useListDrawerContext, useTranslation } from "@payloadcms/ui";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import "./folder.css";
|
|
9
|
+
//#region src/folder/index.tsx
|
|
10
|
+
const tabsClass = "default-list-view-tabs";
|
|
11
|
+
/**
|
|
12
|
+
* List view for folder-enabled collections. On a real list route it renders Payload's own view
|
|
13
|
+
* untouched, since the route already carries the List/Folders tabs. Inside a list drawer (the
|
|
14
|
+
* upload field's "choose from existing") those tabs are replaced by the drawer header, so this
|
|
15
|
+
* adds them back and swaps in a folder picker that does not navigate.
|
|
16
|
+
*/
|
|
17
|
+
const FolderListView = (props) => {
|
|
18
|
+
const { collectionSlug } = props;
|
|
19
|
+
const { isInDrawer } = useListDrawerContext();
|
|
20
|
+
const { getEntityConfig } = useConfig();
|
|
21
|
+
const { i18n, t } = useTranslation();
|
|
22
|
+
const [viewType, setViewType] = React.useState("list");
|
|
23
|
+
if (!isInDrawer) return /* @__PURE__ */ jsx(DefaultListView, { ...props });
|
|
24
|
+
const collectionConfig = getEntityConfig({ collectionSlug });
|
|
25
|
+
const tabs = /* @__PURE__ */ jsxs("div", {
|
|
26
|
+
className: tabsClass,
|
|
27
|
+
children: [/* @__PURE__ */ jsxs(Button, {
|
|
28
|
+
buttonStyle: "tab",
|
|
29
|
+
className: [`${tabsClass}__button`, viewType === "list" && `${tabsClass}__button--active`].filter(Boolean).join(" "),
|
|
30
|
+
disabled: viewType === "list",
|
|
31
|
+
el: "button",
|
|
32
|
+
onClick: () => setViewType("list"),
|
|
33
|
+
children: [
|
|
34
|
+
t("general:all"),
|
|
35
|
+
" ",
|
|
36
|
+
getTranslation(collectionConfig?.labels?.plural ?? "", i18n)
|
|
37
|
+
]
|
|
38
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
39
|
+
buttonStyle: "tab",
|
|
40
|
+
className: [`${tabsClass}__button`, viewType === "folders" && `${tabsClass}__button--active`].filter(Boolean).join(" "),
|
|
41
|
+
disabled: viewType === "folders",
|
|
42
|
+
el: "button",
|
|
43
|
+
onClick: () => setViewType("folders"),
|
|
44
|
+
children: t("folder:byFolder")
|
|
45
|
+
})]
|
|
46
|
+
}, "tabs");
|
|
47
|
+
return /* @__PURE__ */ jsx(React.Fragment, { children: viewType === "folders" ? /* @__PURE__ */ jsx(FolderBrowser, {
|
|
48
|
+
collectionSlug,
|
|
49
|
+
enableRowSelections: props.enableRowSelections,
|
|
50
|
+
Tabs: tabs
|
|
51
|
+
}) : /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx(Gutter, {
|
|
52
|
+
className: `${tabsClass}__drawer-gutter`,
|
|
53
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
54
|
+
className: `${tabsClass}__drawer-row`,
|
|
55
|
+
children: [tabs, /* @__PURE__ */ jsx(BulkUploadButton, {
|
|
56
|
+
collectionSlug,
|
|
57
|
+
enableRowSelections: props.enableRowSelections
|
|
58
|
+
})]
|
|
59
|
+
})
|
|
60
|
+
}), /* @__PURE__ */ jsx(DefaultListView, { ...props })] }) });
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
export { FolderListView as default };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/folder/index.tsx"],"sourcesContent":["'use client'\n\nimport { getTranslation } from '@payloadcms/translations'\nimport {\n\tButton,\n\tDefaultListView,\n\tGutter,\n\tuseConfig,\n\tuseListDrawerContext,\n\tuseTranslation,\n} from '@payloadcms/ui'\nimport type { ListViewClientProps } from 'payload'\nimport React from 'react'\nimport { BulkUploadButton } from './BulkUploadButton'\nimport { FolderBrowser } from './FolderBrowser'\nimport './folder.css'\nconst tabsClass = 'default-list-view-tabs'\n\n/**\n * List view for folder-enabled collections. On a real list route it renders Payload's own view\n * untouched, since the route already carries the List/Folders tabs. Inside a list drawer (the\n * upload field's \"choose from existing\") those tabs are replaced by the drawer header, so this\n * adds them back and swaps in a folder picker that does not navigate.\n */\nexport const FolderListView: React.FC<ListViewClientProps> = (props) => {\n\tconst { collectionSlug } = props\n\tconst { isInDrawer } = useListDrawerContext()\n\tconst { getEntityConfig } = useConfig()\n\tconst { i18n, t } = useTranslation()\n\tconst [viewType, setViewType] = React.useState<'folders' | 'list'>('list')\n\n\tif (!isInDrawer) {\n\t\treturn <DefaultListView {...props} />\n\t}\n\n\tconst collectionConfig = getEntityConfig({ collectionSlug })\n\n\t// Same markup as Payload's DefaultListViewTabs, which is internal and always pushes a route.\n\tconst tabs = (\n\t\t<div className={tabsClass} key=\"tabs\">\n\t\t\t<Button\n\t\t\t\tbuttonStyle=\"tab\"\n\t\t\t\tclassName={[`${tabsClass}__button`, viewType === 'list' && `${tabsClass}__button--active`]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\tdisabled={viewType === 'list'}\n\t\t\t\tel=\"button\"\n\t\t\t\tonClick={() => setViewType('list')}\n\t\t\t>\n\t\t\t\t{t('general:all')} {getTranslation(collectionConfig?.labels?.plural ?? '', i18n)}\n\t\t\t</Button>\n\t\t\t<Button\n\t\t\t\tbuttonStyle=\"tab\"\n\t\t\t\tclassName={[\n\t\t\t\t\t`${tabsClass}__button`,\n\t\t\t\t\tviewType === 'folders' && `${tabsClass}__button--active`,\n\t\t\t\t]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\tdisabled={viewType === 'folders'}\n\t\t\t\tel=\"button\"\n\t\t\t\tonClick={() => setViewType('folders')}\n\t\t\t>\n\t\t\t\t{t('folder:byFolder')}\n\t\t\t</Button>\n\t\t</div>\n\t)\n\n\t// Both modes render their own header and gutter below this, so the tabs sit above in their own\n\t// gutter. Rendering them identically in both is what keeps placement and spacing the same.\n\treturn (\n\t\t<React.Fragment>\n\t\t\t{viewType === 'folders' ? (\n\t\t\t\t// The folder view renders the tabs row itself so the breadcrumb can sit beside them.\n\t\t\t\t<FolderBrowser\n\t\t\t\t\tcollectionSlug={collectionSlug}\n\t\t\t\t\tenableRowSelections={props.enableRowSelections}\n\t\t\t\t\tTabs={tabs}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<React.Fragment>\n\t\t\t\t\t{/* DefaultListView owns the drawer header, so bulk upload cannot go beside its Create\n\t\t\t\t\t\tNew pill without forking it. It rides with the tabs instead, which is the nearest\n\t\t\t\t\t\tslot this view actually controls. */}\n\t\t\t\t\t<Gutter className={`${tabsClass}__drawer-gutter`}>\n\t\t\t\t\t\t<div className={`${tabsClass}__drawer-row`}>\n\t\t\t\t\t\t\t{tabs}\n\t\t\t\t\t\t\t<BulkUploadButton\n\t\t\t\t\t\t\t\tcollectionSlug={collectionSlug}\n\t\t\t\t\t\t\t\tenableRowSelections={props.enableRowSelections}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</Gutter>\n\t\t\t\t\t<DefaultListView {...props} />\n\t\t\t\t</React.Fragment>\n\t\t\t)}\n\t\t</React.Fragment>\n\t)\n}\n\nexport default FolderListView\n"],"mappings":";;;;;;;;;AAgBA,MAAM,YAAY;;;;;;;AAQlB,MAAa,kBAAiD,UAAU;CACvE,MAAM,EAAE,mBAAmB;CAC3B,MAAM,EAAE,eAAe,qBAAqB;CAC5C,MAAM,EAAE,oBAAoB,UAAU;CACtC,MAAM,EAAE,MAAM,MAAM,eAAe;CACnC,MAAM,CAAC,UAAU,eAAe,MAAM,SAA6B,MAAM;CAEzE,IAAI,CAAC,YACJ,OAAO,oBAAC,iBAAD,EAAiB,GAAI,MAAQ,CAAA;CAGrC,MAAM,mBAAmB,gBAAgB,EAAE,eAAe,CAAC;CAG3D,MAAM,OACL,qBAAC,OAAD;EAAK,WAAW;YAAhB,CACC,qBAAC,QAAD;GACC,aAAY;GACZ,WAAW,CAAC,GAAG,UAAU,WAAW,aAAa,UAAU,GAAG,UAAU,iBAAiB,EACvF,OAAO,OAAO,EACd,KAAK,GAAG;GACV,UAAU,aAAa;GACvB,IAAG;GACH,eAAe,YAAY,MAAM;aAPlC;IASE,EAAE,aAAa;IAAE;IAAE,eAAe,kBAAkB,QAAQ,UAAU,IAAI,IAAI;GACxE;MACR,oBAAC,QAAD;GACC,aAAY;GACZ,WAAW,CACV,GAAG,UAAU,WACb,aAAa,aAAa,GAAG,UAAU,iBACxC,EACE,OAAO,OAAO,EACd,KAAK,GAAG;GACV,UAAU,aAAa;GACvB,IAAG;GACH,eAAe,YAAY,SAAS;aAEnC,EAAE,iBAAiB;EACb,CAAA,CACJ;IA1B0B,MA0B1B;CAKN,OACC,oBAAC,MAAM,UAAP,EAAA,UACE,aAAa,YAEb,oBAAC,eAAD;EACiB;EAChB,qBAAqB,MAAM;EAC3B,MAAM;CACN,CAAA,IAED,qBAAC,MAAM,UAAP,EAAA,UAAA,CAIC,oBAAC,QAAD;EAAQ,WAAW,GAAG,UAAU;YAC/B,qBAAC,OAAD;GAAK,WAAW,GAAG,UAAU;aAA7B,CACE,MACD,oBAAC,kBAAD;IACiB;IAChB,qBAAqB,MAAM;GAC3B,CAAA,CACG;;CACE,CAAA,GACR,oBAAC,iBAAD,EAAiB,GAAI,MAAQ,CAAA,CACd,EAAA,CAAA,EAEF,CAAA;AAElB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/folder/isMacPlatform.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether the shortcut labels shown to the reader should use Apple's modifier keys.
|
|
4
|
+
*
|
|
5
|
+
* Read from the user agent rather than from the keyboard event, because the hint is drawn
|
|
6
|
+
* before anything is pressed. iPhones and iPads report a Mac-like agent and have no
|
|
7
|
+
* modifier keys to speak of, so they are excluded.
|
|
8
|
+
*/
|
|
9
|
+
const isMacPlatform = (userAgent) => {
|
|
10
|
+
if (!userAgent) return false;
|
|
11
|
+
return /mac/i.test(userAgent) && !/iphone|ipad|ipod/i.test(userAgent);
|
|
12
|
+
};
|
|
13
|
+
/** Labels for the two modifiers the folder view's selection responds to. */
|
|
14
|
+
const modifierLabels = (isMac) => ({
|
|
15
|
+
modifier: isMac ? "⌘" : "Ctrl",
|
|
16
|
+
range: isMac ? "⇧" : "Shift"
|
|
17
|
+
});
|
|
18
|
+
//#endregion
|
|
19
|
+
export { isMacPlatform, modifierLabels };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=isMacPlatform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isMacPlatform.js","names":[],"sources":["../../src/folder/isMacPlatform.ts"],"sourcesContent":["/**\n * Whether the shortcut labels shown to the reader should use Apple's modifier keys.\n *\n * Read from the user agent rather than from the keyboard event, because the hint is drawn\n * before anything is pressed. iPhones and iPads report a Mac-like agent and have no\n * modifier keys to speak of, so they are excluded.\n */\nexport const isMacPlatform = (userAgent: string | undefined): boolean => {\n\tif (!userAgent) {\n\t\treturn false\n\t}\n\treturn /mac/i.test(userAgent) && !/iphone|ipad|ipod/i.test(userAgent)\n}\n\n/** Labels for the two modifiers the folder view's selection responds to. */\nexport const modifierLabels = (isMac: boolean): { modifier: string; range: string } => ({\n\tmodifier: isMac ? '⌘' : 'Ctrl',\n\trange: isMac ? '⇧' : 'Shift',\n})\n"],"mappings":";;;;;;;;AAOA,MAAa,iBAAiB,cAA2C;CACxE,IAAI,CAAC,WACJ,OAAO;CAER,OAAO,OAAO,KAAK,SAAS,KAAK,CAAC,oBAAoB,KAAK,SAAS;AACrE;;AAGA,MAAa,kBAAkB,WAAyD;CACvF,UAAU,QAAQ,MAAM;CACxB,OAAO,QAAQ,MAAM;AACtB"}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { keys } from "../translations/keys.js";
|
|
3
|
+
import { useTranslation as useTranslation$1 } from "../translations/useTranslation.js";
|
|
4
|
+
import { getTranslation } from "@payloadcms/translations";
|
|
5
|
+
import { Button, ChevronIcon, FieldLabel, FolderIcon, GridViewIcon, ListViewIcon, Pill, Popup, PopupList, ReactSelect, SearchIcon, XIcon, useConfig, useListDrawerContext, useModal } from "@payloadcms/ui";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { DragOverlay, useDndMonitor } from "@dnd-kit/core";
|
|
9
|
+
//#region src/folder/native.tsx
|
|
10
|
+
/**
|
|
11
|
+
* Faithful copies of the folder view's header pieces. `ListHeader`, `SearchBar`, `SearchFilter`,
|
|
12
|
+
* `SortByPill`, `ToggleViewButtons` and `NoListResults` are all internal to @payloadcms/ui, but the
|
|
13
|
+
* admin loads that package's whole stylesheet through `@payloadcms/next/css`, so reproducing their
|
|
14
|
+
* markup and class names reproduces their appearance exactly. Only the behaviour differs: these
|
|
15
|
+
* take callbacks instead of pushing an admin route, which a drawer cannot survive.
|
|
16
|
+
*/
|
|
17
|
+
const listHeaderClass = "list-header";
|
|
18
|
+
const ListHeader = ({ Actions = [], AfterListHeaderContent, className, title, TitleActions = [] }) => /* @__PURE__ */ jsxs("header", {
|
|
19
|
+
className: [listHeaderClass, className].filter(Boolean).join(" "),
|
|
20
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
21
|
+
className: `${listHeaderClass}__content`,
|
|
22
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
23
|
+
className: `${listHeaderClass}__title-and-actions`,
|
|
24
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
25
|
+
className: `${listHeaderClass}__title`,
|
|
26
|
+
children: title
|
|
27
|
+
}), TitleActions.length ? /* @__PURE__ */ jsx("div", {
|
|
28
|
+
className: `${listHeaderClass}__title-actions`,
|
|
29
|
+
children: TitleActions
|
|
30
|
+
}) : null]
|
|
31
|
+
}), Actions.length ? /* @__PURE__ */ jsx("div", {
|
|
32
|
+
className: `${listHeaderClass}__actions`,
|
|
33
|
+
children: Actions
|
|
34
|
+
}) : null]
|
|
35
|
+
}), AfterListHeaderContent ? /* @__PURE__ */ jsx("div", {
|
|
36
|
+
className: `${listHeaderClass}__after-header-content`,
|
|
37
|
+
children: AfterListHeaderContent
|
|
38
|
+
}) : null]
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Copy of the internal `DrawerRelationshipSelect`, which the drawer's list header renders for a
|
|
42
|
+
* polymorphic upload field and hides when only one collection is allowed. Picking an option calls
|
|
43
|
+
* the drawer's own `setSelectedOption`, which re-renders the list server side for that collection,
|
|
44
|
+
* so switching needs nothing from the folder view itself.
|
|
45
|
+
*/
|
|
46
|
+
const DrawerRelationshipSelect = () => {
|
|
47
|
+
const { config } = useConfig();
|
|
48
|
+
const { enabledCollections, selectedOption, setSelectedOption } = useListDrawerContext();
|
|
49
|
+
const { i18n, t } = useTranslation$1();
|
|
50
|
+
const enabled = config.collections.filter(({ slug }) => enabledCollections?.includes(slug));
|
|
51
|
+
if (enabled.length < 2) return null;
|
|
52
|
+
const active = enabled.find(({ slug }) => slug === selectedOption?.value);
|
|
53
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
54
|
+
className: "list-drawer__select-collection-wrap",
|
|
55
|
+
children: [/* @__PURE__ */ jsx(FieldLabel, { label: t("upload:selectCollectionToBrowse") }), /* @__PURE__ */ jsx(ReactSelect, {
|
|
56
|
+
className: `${listHeaderClass}__select-collection`,
|
|
57
|
+
isClearable: false,
|
|
58
|
+
onChange: (option) => {
|
|
59
|
+
const picked = Array.isArray(option) ? option[0] : option;
|
|
60
|
+
const target = enabled.find(({ slug }) => slug === picked?.value);
|
|
61
|
+
if (target) setSelectedOption?.({
|
|
62
|
+
label: getTranslation(target.labels.singular, i18n),
|
|
63
|
+
value: target.slug
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
options: enabled.map((collection) => ({
|
|
67
|
+
label: getTranslation(collection.labels.singular, i18n),
|
|
68
|
+
value: collection.slug
|
|
69
|
+
})),
|
|
70
|
+
value: {
|
|
71
|
+
label: active ? getTranslation(active.labels.singular, i18n) : "",
|
|
72
|
+
value: active?.slug
|
|
73
|
+
}
|
|
74
|
+
})]
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
/** Copy of the internal CloseModalButton the drawer's list header renders. */
|
|
78
|
+
const CloseModalButton = ({ className, slug }) => {
|
|
79
|
+
const { closeModal } = useModal();
|
|
80
|
+
const { t } = useTranslation$1();
|
|
81
|
+
return /* @__PURE__ */ jsx("button", {
|
|
82
|
+
"aria-label": t("general:close"),
|
|
83
|
+
className: ["close-modal-button", className].filter(Boolean).join(" "),
|
|
84
|
+
onClick: () => closeModal(slug),
|
|
85
|
+
type: "button",
|
|
86
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
/** Copy of the internal Dots icon the folder actions menu uses as its trigger. */
|
|
90
|
+
const Dots = ({ ariaLabel }) => /* @__PURE__ */ jsxs("div", {
|
|
91
|
+
"aria-label": ariaLabel,
|
|
92
|
+
className: "dots dots--vertical",
|
|
93
|
+
role: "img",
|
|
94
|
+
children: [
|
|
95
|
+
/* @__PURE__ */ jsx("div", {}),
|
|
96
|
+
/* @__PURE__ */ jsx("div", {}),
|
|
97
|
+
/* @__PURE__ */ jsx("div", {})
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
const searchFilterClass = "search-filter";
|
|
101
|
+
/**
|
|
102
|
+
* Controlled from above rather than holding the typed text itself. This sits inside the folder
|
|
103
|
+
* provider, which is rebuilt whenever the view or the folder changes, and local state would be
|
|
104
|
+
* wiped by that while the search it had already reported went on filtering the results.
|
|
105
|
+
*/
|
|
106
|
+
const SearchFilter = ({ label, onChange, value }) => /* @__PURE__ */ jsx("div", {
|
|
107
|
+
className: searchFilterClass,
|
|
108
|
+
children: /* @__PURE__ */ jsx("input", {
|
|
109
|
+
"aria-label": label,
|
|
110
|
+
className: `${searchFilterClass}__input`,
|
|
111
|
+
onChange: (event) => onChange(event.target.value),
|
|
112
|
+
placeholder: label,
|
|
113
|
+
type: "text",
|
|
114
|
+
value
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
const searchBarClass = "search-bar";
|
|
118
|
+
const SearchBar = ({ Actions = [], label, onSearchChange, search }) => /* @__PURE__ */ jsxs("div", {
|
|
119
|
+
className: searchBarClass,
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsx(SearchIcon, {}),
|
|
122
|
+
/* @__PURE__ */ jsx(SearchFilter, {
|
|
123
|
+
label,
|
|
124
|
+
onChange: onSearchChange,
|
|
125
|
+
value: search
|
|
126
|
+
}),
|
|
127
|
+
Actions.length ? /* @__PURE__ */ jsx("div", {
|
|
128
|
+
className: `${searchBarClass}__actions`,
|
|
129
|
+
children: Actions
|
|
130
|
+
}) : null
|
|
131
|
+
]
|
|
132
|
+
});
|
|
133
|
+
const toggleClass = "folder-view-toggle-button";
|
|
134
|
+
/**
|
|
135
|
+
* Both buttons are icon only, so without a name a screen reader announces two unlabelled buttons
|
|
136
|
+
* and neither can be told from the other. `aria-pressed` carries the active state that the class
|
|
137
|
+
* shows sighted users.
|
|
138
|
+
*/
|
|
139
|
+
const ToggleViewButtons = ({ activeView, setActiveView }) => {
|
|
140
|
+
const { t } = useTranslation$1();
|
|
141
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
142
|
+
"aria-label": t(keys.gridView),
|
|
143
|
+
"aria-pressed": activeView === "grid",
|
|
144
|
+
buttonStyle: "pill",
|
|
145
|
+
className: [toggleClass, activeView === "grid" && `${toggleClass}--active`].filter(Boolean).join(" "),
|
|
146
|
+
icon: /* @__PURE__ */ jsx(GridViewIcon, {}),
|
|
147
|
+
margin: false,
|
|
148
|
+
onClick: () => setActiveView("grid")
|
|
149
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
150
|
+
"aria-label": t(keys.listView),
|
|
151
|
+
"aria-pressed": activeView === "list",
|
|
152
|
+
buttonStyle: "pill",
|
|
153
|
+
className: [toggleClass, activeView === "list" && `${toggleClass}--active`].filter(Boolean).join(" "),
|
|
154
|
+
icon: /* @__PURE__ */ jsx(ListViewIcon, {}),
|
|
155
|
+
margin: false,
|
|
156
|
+
onClick: () => setActiveView("list")
|
|
157
|
+
})] });
|
|
158
|
+
};
|
|
159
|
+
const SortDownIcon = ({ className }) => /* @__PURE__ */ jsx("svg", {
|
|
160
|
+
"aria-hidden": "true",
|
|
161
|
+
className: ["icon icon--sort", className].filter(Boolean).join(" "),
|
|
162
|
+
fill: "none",
|
|
163
|
+
height: "20",
|
|
164
|
+
viewBox: "0 0 20 20",
|
|
165
|
+
width: "20",
|
|
166
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
167
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
168
|
+
className: "fill",
|
|
169
|
+
d: "M2.5 13.3333L5.83333 16.6667M5.83333 16.6667L9.16667 13.3333M5.83333 16.6667V3.33333M9.16667 7.08333H17.5M9.16667 10.4167H15M11.6667 13.75H12.5",
|
|
170
|
+
stroke: "#2F2F2F",
|
|
171
|
+
strokeLinecap: "round",
|
|
172
|
+
strokeLinejoin: "round"
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
const SortUpIcon = ({ className }) => /* @__PURE__ */ jsx("svg", {
|
|
176
|
+
"aria-hidden": "true",
|
|
177
|
+
className: ["icon icon--sort", className].filter(Boolean).join(" "),
|
|
178
|
+
fill: "none",
|
|
179
|
+
height: "20",
|
|
180
|
+
viewBox: "0 0 20 20",
|
|
181
|
+
width: "20",
|
|
182
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
183
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
184
|
+
className: "fill",
|
|
185
|
+
d: "M2.5 6.66668L5.83333 3.33334M5.83333 3.33334L9.16667 6.66668M5.83333 3.33334V16.6667M11.6667 7.08354H17.5M9.16667 10.4169H15M9.16667 13.7502H12.5",
|
|
186
|
+
stroke: "#2F2F2F",
|
|
187
|
+
strokeLinecap: "round",
|
|
188
|
+
strokeLinejoin: "round"
|
|
189
|
+
})
|
|
190
|
+
});
|
|
191
|
+
const sortClass = "sort-by-pill";
|
|
192
|
+
const SORT_FIELDS = [
|
|
193
|
+
{
|
|
194
|
+
key: "name",
|
|
195
|
+
label: "general:name"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
key: "createdAt",
|
|
199
|
+
label: "general:createdAt"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: "updatedAt",
|
|
203
|
+
label: "general:updatedAt"
|
|
204
|
+
}
|
|
205
|
+
];
|
|
206
|
+
const SortByPill = ({ onChange, sort, t }) => {
|
|
207
|
+
const descending = sort.startsWith("-");
|
|
208
|
+
const field = sort.replace("-", "");
|
|
209
|
+
const activeLabel = SORT_FIELDS.find((option) => option.key === field)?.label ?? "general:name";
|
|
210
|
+
return /* @__PURE__ */ jsx(Popup, {
|
|
211
|
+
button: /* @__PURE__ */ jsxs(Pill, {
|
|
212
|
+
className: `${sortClass}__trigger`,
|
|
213
|
+
icon: /* @__PURE__ */ jsx(ChevronIcon, {}),
|
|
214
|
+
size: "small",
|
|
215
|
+
children: [descending ? /* @__PURE__ */ jsx(SortDownIcon, { className: `${sortClass}__sort-icon` }) : /* @__PURE__ */ jsx(SortUpIcon, { className: `${sortClass}__sort-icon` }), t(activeLabel)]
|
|
216
|
+
}),
|
|
217
|
+
className: sortClass,
|
|
218
|
+
horizontalAlign: "right",
|
|
219
|
+
render: ({ close }) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
220
|
+
/* @__PURE__ */ jsx(PopupList.GroupLabel, { label: t(keys.sortByLabel) }),
|
|
221
|
+
/* @__PURE__ */ jsx(PopupList.ButtonGroup, { children: SORT_FIELDS.map((option) => /* @__PURE__ */ jsx(PopupList.Button, {
|
|
222
|
+
active: field === option.key,
|
|
223
|
+
onClick: () => {
|
|
224
|
+
onChange(descending ? `-${option.key}` : option.key);
|
|
225
|
+
close();
|
|
226
|
+
},
|
|
227
|
+
children: t(option.label)
|
|
228
|
+
}, option.key)) }),
|
|
229
|
+
/* @__PURE__ */ jsx(PopupList.Divider, {}),
|
|
230
|
+
/* @__PURE__ */ jsx(PopupList.GroupLabel, { label: t(keys.orderLabel) }),
|
|
231
|
+
/* @__PURE__ */ jsxs(PopupList.ButtonGroup, { children: [/* @__PURE__ */ jsxs(PopupList.Button, {
|
|
232
|
+
active: !descending,
|
|
233
|
+
className: `${sortClass}__order-option`,
|
|
234
|
+
onClick: () => {
|
|
235
|
+
onChange(field);
|
|
236
|
+
close();
|
|
237
|
+
},
|
|
238
|
+
children: [/* @__PURE__ */ jsx(SortUpIcon, {}), t("general:ascending")]
|
|
239
|
+
}), /* @__PURE__ */ jsxs(PopupList.Button, {
|
|
240
|
+
active: descending,
|
|
241
|
+
className: `${sortClass}__order-option`,
|
|
242
|
+
onClick: () => {
|
|
243
|
+
onChange(`-${field}`);
|
|
244
|
+
close();
|
|
245
|
+
},
|
|
246
|
+
children: [/* @__PURE__ */ jsx(SortDownIcon, {}), t("general:descending")]
|
|
247
|
+
})] })
|
|
248
|
+
] }),
|
|
249
|
+
size: "medium"
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
const noResultsClass = "no-results";
|
|
253
|
+
const NoListResults = ({ Actions = [], Message }) => /* @__PURE__ */ jsxs("div", {
|
|
254
|
+
className: noResultsClass,
|
|
255
|
+
children: [Message, Actions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
256
|
+
className: `${noResultsClass}__actions`,
|
|
257
|
+
children: Actions.map((action, index) => /* @__PURE__ */ jsx(React.Fragment, { children: action }, index))
|
|
258
|
+
})]
|
|
259
|
+
});
|
|
260
|
+
const listSelectionClass = "list-selection";
|
|
261
|
+
/** Copy of ListSelection_v4, the bar the route view puts in its header when items are selected. */
|
|
262
|
+
const ListSelectionBar = ({ count, ListActions = [], SelectionActions = [] }) => {
|
|
263
|
+
const { t } = useTranslation$1();
|
|
264
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
265
|
+
className: listSelectionClass,
|
|
266
|
+
children: [
|
|
267
|
+
/* @__PURE__ */ jsx("span", { children: t("general:selectedCount", {
|
|
268
|
+
count,
|
|
269
|
+
label: ""
|
|
270
|
+
}) }),
|
|
271
|
+
ListActions.length > 0 && /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx("span", { children: "—" }), /* @__PURE__ */ jsx("div", {
|
|
272
|
+
className: `${listSelectionClass}__actions`,
|
|
273
|
+
children: ListActions
|
|
274
|
+
})] }),
|
|
275
|
+
SelectionActions.length > 0 && /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx("span", { children: "—" }), /* @__PURE__ */ jsx("div", {
|
|
276
|
+
className: `${listSelectionClass}__actions`,
|
|
277
|
+
children: SelectionActions
|
|
278
|
+
})] })
|
|
279
|
+
]
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
const ListSelectionButton = ({ children, onClick }) => /* @__PURE__ */ jsx(Button, {
|
|
283
|
+
buttonStyle: "none",
|
|
284
|
+
className: `${listSelectionClass}__button`,
|
|
285
|
+
el: "button",
|
|
286
|
+
onClick,
|
|
287
|
+
children
|
|
288
|
+
});
|
|
289
|
+
const dragOverlayClass = "drag-overlay-selection";
|
|
290
|
+
/** Distance the card is held away from the cursor, matching Payload's own overlay. */
|
|
291
|
+
const cursorGap = 5;
|
|
292
|
+
/**
|
|
293
|
+
* `getEventCoordinates` from `@dnd-kit/utilities`, which reaches this package only through
|
|
294
|
+
* `@dnd-kit/core` and would have to be declared a second peer dependency to import. Reading two
|
|
295
|
+
* numbers off an event does not justify that.
|
|
296
|
+
*/
|
|
297
|
+
const eventCoordinates = (event) => {
|
|
298
|
+
if (typeof TouchEvent !== "undefined" && event instanceof TouchEvent) {
|
|
299
|
+
const touch = event.touches[0] ?? event.changedTouches[0];
|
|
300
|
+
return touch ? {
|
|
301
|
+
x: touch.clientX,
|
|
302
|
+
y: touch.clientY
|
|
303
|
+
} : null;
|
|
304
|
+
}
|
|
305
|
+
return event instanceof MouseEvent ? {
|
|
306
|
+
x: event.clientX,
|
|
307
|
+
y: event.clientY
|
|
308
|
+
} : null;
|
|
309
|
+
};
|
|
310
|
+
/**
|
|
311
|
+
* Pins the card's top left corner just below and to the right of the cursor, whatever part of it
|
|
312
|
+
* was grabbed. dnd-kit otherwise preserves the grab point, so a card taken by its bottom edge
|
|
313
|
+
* trails from that edge and covers what is being dragged over.
|
|
314
|
+
*
|
|
315
|
+
* Payload applies the same modifier to its own folder overlay but does not export it.
|
|
316
|
+
*/
|
|
317
|
+
const snapTopLeftToCursor = ({ activatorEvent, draggingNodeRect, transform }) => {
|
|
318
|
+
if (!draggingNodeRect || !activatorEvent) return transform;
|
|
319
|
+
const coordinates = eventCoordinates(activatorEvent);
|
|
320
|
+
if (!coordinates) return transform;
|
|
321
|
+
return {
|
|
322
|
+
...transform,
|
|
323
|
+
x: transform.x + coordinates.x - draggingNodeRect.left + cursorGap,
|
|
324
|
+
y: transform.y + coordinates.y - draggingNodeRect.top + cursorGap
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
/** Hoisted so the overlay is not handed a new array on every render. */
|
|
328
|
+
const dragOverlayModifiers = [snapTopLeftToCursor];
|
|
329
|
+
/**
|
|
330
|
+
* The card that follows the cursor while dragging. `DragOverlaySelection` and the `FolderFileCard`
|
|
331
|
+
* it renders are both internal, so this reproduces the folder variant of that card: an icon and a
|
|
332
|
+
* title, which is all the original draws once its drop area and popup are left out.
|
|
333
|
+
*/
|
|
334
|
+
const DragOverlaySelection = ({ selectedCount, title }) => /* @__PURE__ */ jsx(DragOverlay, {
|
|
335
|
+
dropAnimation: null,
|
|
336
|
+
modifiers: dragOverlayModifiers,
|
|
337
|
+
style: {
|
|
338
|
+
height: "unset",
|
|
339
|
+
maxWidth: "220px"
|
|
340
|
+
},
|
|
341
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
342
|
+
className: `${dragOverlayClass}__cards`,
|
|
343
|
+
children: [Array.from({ length: selectedCount > 1 ? 2 : 1 }).map((_, index) => /* @__PURE__ */ jsx("div", {
|
|
344
|
+
className: `${dragOverlayClass}__card`,
|
|
345
|
+
style: {
|
|
346
|
+
right: `${index * 3}px`,
|
|
347
|
+
top: `-${index * 3}px`
|
|
348
|
+
},
|
|
349
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
350
|
+
className: "folder-file-card folder-file-card--folder folder-file-card--selected",
|
|
351
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
352
|
+
className: "folder-file-card__titlebar-area",
|
|
353
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
354
|
+
className: "folder-file-card__icon-wrap",
|
|
355
|
+
children: /* @__PURE__ */ jsx(FolderIcon, { className: "colored-folder-icon" })
|
|
356
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
357
|
+
className: "folder-file-card__titlebar-labels",
|
|
358
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
359
|
+
className: "folder-file-card__name",
|
|
360
|
+
title,
|
|
361
|
+
children: /* @__PURE__ */ jsx("span", { children: title })
|
|
362
|
+
})
|
|
363
|
+
})]
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
}, index)), selectedCount > 1 ? /* @__PURE__ */ jsx("span", {
|
|
367
|
+
className: `${dragOverlayClass}__card-count`,
|
|
368
|
+
children: selectedCount
|
|
369
|
+
}) : null]
|
|
370
|
+
})
|
|
371
|
+
});
|
|
372
|
+
/**
|
|
373
|
+
* Copy of the listener `DefaultCollectionFolderView` declares inline, which is why it is not
|
|
374
|
+
* importable. Without it a drop never reaches `moveToFolder` and the provider's drag flag is
|
|
375
|
+
* never cleared.
|
|
376
|
+
*/
|
|
377
|
+
const DndEventListener = ({ onDragEnd, setIsDragging }) => {
|
|
378
|
+
useDndMonitor({
|
|
379
|
+
onDragCancel: () => setIsDragging(false),
|
|
380
|
+
onDragEnd: (event) => {
|
|
381
|
+
setIsDragging(false);
|
|
382
|
+
onDragEnd(event);
|
|
383
|
+
},
|
|
384
|
+
onDragStart: () => setIsDragging(true)
|
|
385
|
+
});
|
|
386
|
+
return null;
|
|
387
|
+
};
|
|
388
|
+
//#endregion
|
|
389
|
+
export { CloseModalButton, DndEventListener, Dots, DragOverlaySelection, DrawerRelationshipSelect, ListHeader, ListSelectionBar, ListSelectionButton, NoListResults, SearchBar, SortByPill, ToggleViewButtons };
|
|
390
|
+
|
|
391
|
+
//# sourceMappingURL=native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.js","names":["useTranslation"],"sources":["../../src/folder/native.tsx"],"sourcesContent":["'use client'\n\nimport { type DragEndEvent, DragOverlay, type Modifier, useDndMonitor } from '@dnd-kit/core'\nimport { getTranslation } from '@payloadcms/translations'\nimport {\n\tButton,\n\tChevronIcon,\n\tFieldLabel,\n\tFolderIcon,\n\tGridViewIcon,\n\tListViewIcon,\n\tPill,\n\tPopup,\n\tPopupList,\n\tReactSelect,\n\tSearchIcon,\n\tuseConfig,\n\tuseListDrawerContext,\n\tuseModal,\n\tXIcon,\n} from '@payloadcms/ui'\nimport type { FolderSortKeys } from 'payload'\nimport React from 'react'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\n/**\n * Faithful copies of the folder view's header pieces. `ListHeader`, `SearchBar`, `SearchFilter`,\n * `SortByPill`, `ToggleViewButtons` and `NoListResults` are all internal to @payloadcms/ui, but the\n * admin loads that package's whole stylesheet through `@payloadcms/next/css`, so reproducing their\n * markup and class names reproduces their appearance exactly. Only the behaviour differs: these\n * take callbacks instead of pushing an admin route, which a drawer cannot survive.\n */\n\nexport const listHeaderClass = 'list-header'\n\nexport const ListHeader: React.FC<{\n\treadonly Actions?: React.ReactNode[]\n\treadonly AfterListHeaderContent?: React.ReactNode\n\treadonly className?: string\n\treadonly title: string\n\treadonly TitleActions?: React.ReactNode[]\n}> = ({ Actions = [], AfterListHeaderContent, className, title, TitleActions = [] }) => (\n\t<header className={[listHeaderClass, className].filter(Boolean).join(' ')}>\n\t\t<div className={`${listHeaderClass}__content`}>\n\t\t\t<div className={`${listHeaderClass}__title-and-actions`}>\n\t\t\t\t<h1 className={`${listHeaderClass}__title`}>{title}</h1>\n\t\t\t\t{TitleActions.length ? (\n\t\t\t\t\t<div className={`${listHeaderClass}__title-actions`}>{TitleActions}</div>\n\t\t\t\t) : null}\n\t\t\t</div>\n\t\t\t{Actions.length ? <div className={`${listHeaderClass}__actions`}>{Actions}</div> : null}\n\t\t</div>\n\t\t{AfterListHeaderContent ? (\n\t\t\t<div className={`${listHeaderClass}__after-header-content`}>{AfterListHeaderContent}</div>\n\t\t) : null}\n\t</header>\n)\n\n/**\n * Copy of the internal `DrawerRelationshipSelect`, which the drawer's list header renders for a\n * polymorphic upload field and hides when only one collection is allowed. Picking an option calls\n * the drawer's own `setSelectedOption`, which re-renders the list server side for that collection,\n * so switching needs nothing from the folder view itself.\n */\nexport const DrawerRelationshipSelect: React.FC = () => {\n\tconst { config } = useConfig()\n\tconst { enabledCollections, selectedOption, setSelectedOption } = useListDrawerContext()\n\tconst { i18n, t } = useTranslation()\n\n\tconst enabled = config.collections.filter(({ slug }) => enabledCollections?.includes(slug))\n\n\tif (enabled.length < 2) {\n\t\treturn null\n\t}\n\n\tconst active = enabled.find(({ slug }) => slug === selectedOption?.value)\n\n\treturn (\n\t\t<div className=\"list-drawer__select-collection-wrap\">\n\t\t\t<FieldLabel label={t('upload:selectCollectionToBrowse')} />\n\t\t\t<ReactSelect\n\t\t\t\tclassName={`${listHeaderClass}__select-collection`}\n\t\t\t\tisClearable={false}\n\t\t\t\tonChange={(option) => {\n\t\t\t\t\tconst picked = Array.isArray(option) ? option[0] : option\n\t\t\t\t\tconst target = enabled.find(({ slug }) => slug === picked?.value)\n\n\t\t\t\t\tif (target) {\n\t\t\t\t\t\tsetSelectedOption?.({\n\t\t\t\t\t\t\tlabel: getTranslation(target.labels.singular, i18n),\n\t\t\t\t\t\t\tvalue: target.slug,\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\toptions={enabled.map((collection) => ({\n\t\t\t\t\tlabel: getTranslation(collection.labels.singular, i18n),\n\t\t\t\t\tvalue: collection.slug,\n\t\t\t\t}))}\n\t\t\t\tvalue={{\n\t\t\t\t\tlabel: active ? getTranslation(active.labels.singular, i18n) : '',\n\t\t\t\t\tvalue: active?.slug,\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>\n\t)\n}\n\n/** Copy of the internal CloseModalButton the drawer's list header renders. */\nexport const CloseModalButton: React.FC<{ readonly className?: string; readonly slug: string }> = ({\n\tclassName,\n\tslug,\n}) => {\n\tconst { closeModal } = useModal()\n\tconst { t } = useTranslation()\n\n\treturn (\n\t\t<button\n\t\t\taria-label={t('general:close')}\n\t\t\tclassName={['close-modal-button', className].filter(Boolean).join(' ')}\n\t\t\tonClick={() => closeModal(slug)}\n\t\t\ttype=\"button\"\n\t\t>\n\t\t\t<XIcon />\n\t\t</button>\n\t)\n}\n\n/** Copy of the internal Dots icon the folder actions menu uses as its trigger. */\nexport const Dots: React.FC<{ readonly ariaLabel?: string }> = ({ ariaLabel }) => (\n\t<div aria-label={ariaLabel} className=\"dots dots--vertical\" role=\"img\">\n\t\t<div />\n\t\t<div />\n\t\t<div />\n\t</div>\n)\n\nconst searchFilterClass = 'search-filter'\n\n/**\n * Controlled from above rather than holding the typed text itself. This sits inside the folder\n * provider, which is rebuilt whenever the view or the folder changes, and local state would be\n * wiped by that while the search it had already reported went on filtering the results.\n */\nconst SearchFilter: React.FC<{\n\treadonly label: string\n\treadonly onChange: (search: string) => void\n\treadonly value: string\n}> = ({ label, onChange, value }) => (\n\t<div className={searchFilterClass}>\n\t\t{/* No id: Payload's own search filter hard-codes `search-filter-input`, and two of those in\n\t\t one document break every label and aria association pointing at it. `aria-label` names\n\t\t this input without one. */}\n\t\t<input\n\t\t\taria-label={label}\n\t\t\tclassName={`${searchFilterClass}__input`}\n\t\t\tonChange={(event) => onChange(event.target.value)}\n\t\t\tplaceholder={label}\n\t\t\ttype=\"text\"\n\t\t\tvalue={value}\n\t\t/>\n\t</div>\n)\n\nconst searchBarClass = 'search-bar'\n\nexport const SearchBar: React.FC<{\n\treadonly Actions?: React.ReactNode[]\n\treadonly label: string\n\treadonly onSearchChange: (search: string) => void\n\treadonly search: string\n}> = ({ Actions = [], label, onSearchChange, search }) => (\n\t<div className={searchBarClass}>\n\t\t<SearchIcon />\n\t\t<SearchFilter label={label} onChange={onSearchChange} value={search} />\n\t\t{Actions.length ? <div className={`${searchBarClass}__actions`}>{Actions}</div> : null}\n\t</div>\n)\n\nconst toggleClass = 'folder-view-toggle-button'\n\n/**\n * Both buttons are icon only, so without a name a screen reader announces two unlabelled buttons\n * and neither can be told from the other. `aria-pressed` carries the active state that the class\n * shows sighted users.\n */\nexport const ToggleViewButtons: React.FC<{\n\treadonly activeView: 'grid' | 'list'\n\treadonly setActiveView: (view: 'grid' | 'list') => void\n}> = ({ activeView, setActiveView }) => {\n\tconst { t } = useTranslation()\n\n\treturn (\n\t\t<React.Fragment>\n\t\t\t<Button\n\t\t\t\taria-label={t(keys.gridView)}\n\t\t\t\taria-pressed={activeView === 'grid'}\n\t\t\t\tbuttonStyle=\"pill\"\n\t\t\t\tclassName={[toggleClass, activeView === 'grid' && `${toggleClass}--active`]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\ticon={<GridViewIcon />}\n\t\t\t\tmargin={false}\n\t\t\t\tonClick={() => setActiveView('grid')}\n\t\t\t/>\n\t\t\t<Button\n\t\t\t\taria-label={t(keys.listView)}\n\t\t\t\taria-pressed={activeView === 'list'}\n\t\t\t\tbuttonStyle=\"pill\"\n\t\t\t\tclassName={[toggleClass, activeView === 'list' && `${toggleClass}--active`]\n\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t.join(' ')}\n\t\t\t\ticon={<ListViewIcon />}\n\t\t\t\tmargin={false}\n\t\t\t\tonClick={() => setActiveView('list')}\n\t\t\t/>\n\t\t</React.Fragment>\n\t)\n}\n\nconst SortDownIcon: React.FC<{ className?: string }> = ({ className }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={['icon icon--sort', className].filter(Boolean).join(' ')}\n\t\tfill=\"none\"\n\t\theight=\"20\"\n\t\tviewBox=\"0 0 20 20\"\n\t\twidth=\"20\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<path\n\t\t\tclassName=\"fill\"\n\t\t\td=\"M2.5 13.3333L5.83333 16.6667M5.83333 16.6667L9.16667 13.3333M5.83333 16.6667V3.33333M9.16667 7.08333H17.5M9.16667 10.4167H15M11.6667 13.75H12.5\"\n\t\t\tstroke=\"#2F2F2F\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t/>\n\t</svg>\n)\n\nconst SortUpIcon: React.FC<{ className?: string }> = ({ className }) => (\n\t<svg\n\t\taria-hidden=\"true\"\n\t\tclassName={['icon icon--sort', className].filter(Boolean).join(' ')}\n\t\tfill=\"none\"\n\t\theight=\"20\"\n\t\tviewBox=\"0 0 20 20\"\n\t\twidth=\"20\"\n\t\txmlns=\"http://www.w3.org/2000/svg\"\n\t>\n\t\t<path\n\t\t\tclassName=\"fill\"\n\t\t\td=\"M2.5 6.66668L5.83333 3.33334M5.83333 3.33334L9.16667 6.66668M5.83333 3.33334V16.6667M11.6667 7.08354H17.5M9.16667 10.4169H15M9.16667 13.7502H12.5\"\n\t\t\tstroke=\"#2F2F2F\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t/>\n\t</svg>\n)\n\nconst sortClass = 'sort-by-pill'\n\nconst SORT_FIELDS: { key: 'createdAt' | 'name' | 'updatedAt'; label: string }[] = [\n\t{ key: 'name', label: 'general:name' },\n\t{ key: 'createdAt', label: 'general:createdAt' },\n\t{ key: 'updatedAt', label: 'general:updatedAt' },\n]\n\nexport const SortByPill: React.FC<{\n\treadonly onChange: (sort: FolderSortKeys) => void\n\treadonly sort: FolderSortKeys\n\treadonly t: (key: string) => string\n}> = ({ onChange, sort, t }) => {\n\tconst descending = sort.startsWith('-')\n\tconst field = sort.replace('-', '') as 'createdAt' | 'name' | 'updatedAt'\n\tconst activeLabel = SORT_FIELDS.find((option) => option.key === field)?.label ?? 'general:name'\n\n\treturn (\n\t\t<Popup\n\t\t\tbutton={\n\t\t\t\t<Pill className={`${sortClass}__trigger`} icon={<ChevronIcon />} size=\"small\">\n\t\t\t\t\t{descending ? (\n\t\t\t\t\t\t<SortDownIcon className={`${sortClass}__sort-icon`} />\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<SortUpIcon className={`${sortClass}__sort-icon`} />\n\t\t\t\t\t)}\n\t\t\t\t\t{t(activeLabel)}\n\t\t\t\t</Pill>\n\t\t\t}\n\t\t\tclassName={sortClass}\n\t\t\thorizontalAlign=\"right\"\n\t\t\trender={({ close }) => (\n\t\t\t\t<React.Fragment>\n\t\t\t\t\t<PopupList.GroupLabel label={t(keys.sortByLabel)} />\n\t\t\t\t\t<PopupList.ButtonGroup>\n\t\t\t\t\t\t{SORT_FIELDS.map((option) => (\n\t\t\t\t\t\t\t<PopupList.Button\n\t\t\t\t\t\t\t\tactive={field === option.key}\n\t\t\t\t\t\t\t\tkey={option.key}\n\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\tonChange((descending ? `-${option.key}` : option.key) as FolderSortKeys)\n\t\t\t\t\t\t\t\t\tclose()\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{t(option.label)}\n\t\t\t\t\t\t\t</PopupList.Button>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</PopupList.ButtonGroup>\n\t\t\t\t\t<PopupList.Divider />\n\t\t\t\t\t<PopupList.GroupLabel label={t(keys.orderLabel)} />\n\t\t\t\t\t<PopupList.ButtonGroup>\n\t\t\t\t\t\t<PopupList.Button\n\t\t\t\t\t\t\tactive={!descending}\n\t\t\t\t\t\t\tclassName={`${sortClass}__order-option`}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tonChange(field as FolderSortKeys)\n\t\t\t\t\t\t\t\tclose()\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<SortUpIcon />\n\t\t\t\t\t\t\t{t('general:ascending')}\n\t\t\t\t\t\t</PopupList.Button>\n\t\t\t\t\t\t<PopupList.Button\n\t\t\t\t\t\t\tactive={descending}\n\t\t\t\t\t\t\tclassName={`${sortClass}__order-option`}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tonChange(`-${field}` as FolderSortKeys)\n\t\t\t\t\t\t\t\tclose()\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<SortDownIcon />\n\t\t\t\t\t\t\t{t('general:descending')}\n\t\t\t\t\t\t</PopupList.Button>\n\t\t\t\t\t</PopupList.ButtonGroup>\n\t\t\t\t</React.Fragment>\n\t\t\t)}\n\t\t\tsize=\"medium\"\n\t\t/>\n\t)\n}\n\nconst noResultsClass = 'no-results'\n\nexport const NoListResults: React.FC<{\n\treadonly Actions?: React.ReactNode[]\n\treadonly Message: React.ReactNode\n}> = ({ Actions = [], Message }) => (\n\t<div className={noResultsClass}>\n\t\t{Message}\n\t\t{Actions.length > 0 && (\n\t\t\t<div className={`${noResultsClass}__actions`}>\n\t\t\t\t{Actions.map((action, index) => (\n\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: mirrors Payload's own NoListResults\n\t\t\t\t\t<React.Fragment key={index}>{action}</React.Fragment>\n\t\t\t\t))}\n\t\t\t</div>\n\t\t)}\n\t</div>\n)\n\nconst listSelectionClass = 'list-selection'\n\n/** Copy of ListSelection_v4, the bar the route view puts in its header when items are selected. */\nexport const ListSelectionBar: React.FC<{\n\treadonly count: number\n\treadonly ListActions?: React.ReactNode[]\n\treadonly SelectionActions?: React.ReactNode[]\n}> = ({ count, ListActions = [], SelectionActions = [] }) => {\n\tconst { t } = useTranslation()\n\n\treturn (\n\t\t<div className={listSelectionClass}>\n\t\t\t<span>{t('general:selectedCount', { count, label: '' })}</span>\n\t\t\t{ListActions.length > 0 && (\n\t\t\t\t<React.Fragment>\n\t\t\t\t\t<span>—</span>\n\t\t\t\t\t<div className={`${listSelectionClass}__actions`}>{ListActions}</div>\n\t\t\t\t</React.Fragment>\n\t\t\t)}\n\t\t\t{SelectionActions.length > 0 && (\n\t\t\t\t<React.Fragment>\n\t\t\t\t\t<span>—</span>\n\t\t\t\t\t<div className={`${listSelectionClass}__actions`}>{SelectionActions}</div>\n\t\t\t\t</React.Fragment>\n\t\t\t)}\n\t\t</div>\n\t)\n}\n\nexport const ListSelectionButton: React.FC<{\n\treadonly children: React.ReactNode\n\treadonly onClick: () => void\n}> = ({ children, onClick }) => (\n\t<Button\n\t\tbuttonStyle=\"none\"\n\t\tclassName={`${listSelectionClass}__button`}\n\t\tel=\"button\"\n\t\tonClick={onClick}\n\t>\n\t\t{children}\n\t</Button>\n)\n\nconst dragOverlayClass = 'drag-overlay-selection'\n\n/** Distance the card is held away from the cursor, matching Payload's own overlay. */\nconst cursorGap = 5\n\n/**\n * `getEventCoordinates` from `@dnd-kit/utilities`, which reaches this package only through\n * `@dnd-kit/core` and would have to be declared a second peer dependency to import. Reading two\n * numbers off an event does not justify that.\n */\nconst eventCoordinates = (event: Event): null | { x: number; y: number } => {\n\tif (typeof TouchEvent !== 'undefined' && event instanceof TouchEvent) {\n\t\tconst touch = event.touches[0] ?? event.changedTouches[0]\n\t\treturn touch ? { x: touch.clientX, y: touch.clientY } : null\n\t}\n\n\treturn event instanceof MouseEvent ? { x: event.clientX, y: event.clientY } : null\n}\n\n/**\n * Pins the card's top left corner just below and to the right of the cursor, whatever part of it\n * was grabbed. dnd-kit otherwise preserves the grab point, so a card taken by its bottom edge\n * trails from that edge and covers what is being dragged over.\n *\n * Payload applies the same modifier to its own folder overlay but does not export it.\n */\nexport const snapTopLeftToCursor: Modifier = ({ activatorEvent, draggingNodeRect, transform }) => {\n\tif (!draggingNodeRect || !activatorEvent) {\n\t\treturn transform\n\t}\n\n\tconst coordinates = eventCoordinates(activatorEvent)\n\tif (!coordinates) {\n\t\treturn transform\n\t}\n\n\treturn {\n\t\t...transform,\n\t\tx: transform.x + coordinates.x - draggingNodeRect.left + cursorGap,\n\t\ty: transform.y + coordinates.y - draggingNodeRect.top + cursorGap,\n\t}\n}\n\n/** Hoisted so the overlay is not handed a new array on every render. */\nconst dragOverlayModifiers = [snapTopLeftToCursor]\n\n/**\n * The card that follows the cursor while dragging. `DragOverlaySelection` and the `FolderFileCard`\n * it renders are both internal, so this reproduces the folder variant of that card: an icon and a\n * title, which is all the original draws once its drop area and popup are left out.\n */\nexport const DragOverlaySelection: React.FC<{\n\treadonly selectedCount: number\n\treadonly title: string\n}> = ({ selectedCount, title }) => (\n\t<DragOverlay\n\t\tdropAnimation={null}\n\t\tmodifiers={dragOverlayModifiers}\n\t\tstyle={{ height: 'unset', maxWidth: '220px' }}\n\t>\n\t\t<div className={`${dragOverlayClass}__cards`}>\n\t\t\t{Array.from({ length: selectedCount > 1 ? 2 : 1 }).map((_, index) => (\n\t\t\t\t<div\n\t\t\t\t\tclassName={`${dragOverlayClass}__card`}\n\t\t\t\t\t// biome-ignore lint/suspicious/noArrayIndexKey: mirrors Payload's own overlay\n\t\t\t\t\tkey={index}\n\t\t\t\t\tstyle={{ right: `${index * 3}px`, top: `-${index * 3}px` }}\n\t\t\t\t>\n\t\t\t\t\t<div className=\"folder-file-card folder-file-card--folder folder-file-card--selected\">\n\t\t\t\t\t\t<div className=\"folder-file-card__titlebar-area\">\n\t\t\t\t\t\t\t<div className=\"folder-file-card__icon-wrap\">\n\t\t\t\t\t\t\t\t<FolderIcon className=\"colored-folder-icon\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div className=\"folder-file-card__titlebar-labels\">\n\t\t\t\t\t\t\t\t<p className=\"folder-file-card__name\" title={title}>\n\t\t\t\t\t\t\t\t\t<span>{title}</span>\n\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t))}\n\t\t\t{selectedCount > 1 ? (\n\t\t\t\t<span className={`${dragOverlayClass}__card-count`}>{selectedCount}</span>\n\t\t\t) : null}\n\t\t</div>\n\t</DragOverlay>\n)\n\n/**\n * Copy of the listener `DefaultCollectionFolderView` declares inline, which is why it is not\n * importable. Without it a drop never reaches `moveToFolder` and the provider's drag flag is\n * never cleared.\n */\nexport const DndEventListener: React.FC<{\n\treadonly onDragEnd: (event: DragEndEvent) => void\n\treadonly setIsDragging: (isDragging: boolean) => void\n}> = ({ onDragEnd, setIsDragging }) => {\n\tuseDndMonitor({\n\t\tonDragCancel: () => setIsDragging(false),\n\t\tonDragEnd: (event) => {\n\t\t\tsetIsDragging(false)\n\t\t\tonDragEnd(event)\n\t\t},\n\t\tonDragStart: () => setIsDragging(true),\n\t})\n\n\treturn null\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { chosenUploadIds, packChosenIds, unpackChosenIds } from "./chosenUploadIds.js";
|
|
3
|
+
import { useFieldPath, useFormFields } from "@payloadcms/ui";
|
|
4
|
+
import React from "react";
|
|
5
|
+
//#region src/folder/useChosenUploads.ts
|
|
6
|
+
/**
|
|
7
|
+
* The ids the upload field already holds, for one collection.
|
|
8
|
+
*
|
|
9
|
+
* Payload's upload field appends whatever the drawer hands it, in both paths: `onSelect`
|
|
10
|
+
* for a double click and `onBulkSelect` for the selection pill. Neither checks what is
|
|
11
|
+
* already there, so picking a file a second time stores it twice. On the list tab that
|
|
12
|
+
* never comes up, because the field hides what it holds through `filterOptions`. The
|
|
13
|
+
* drawer is rendered inside the field, so the same value is reachable from here and the
|
|
14
|
+
* folder tab can hide the same files.
|
|
15
|
+
*
|
|
16
|
+
* `useFieldPath` is marked experimental upstream. If it ever stops resolving, the set comes
|
|
17
|
+
* back empty and the folder tab lists everything, as it did before.
|
|
18
|
+
*/
|
|
19
|
+
const useChosenUploads = (collectionSlug) => {
|
|
20
|
+
const path = useFieldPath();
|
|
21
|
+
const packed = useFormFields(([fields]) => packChosenIds(chosenUploadIds(path ? fields?.[path]?.value : void 0, collectionSlug)));
|
|
22
|
+
return React.useMemo(() => new Set(packed ? unpackChosenIds(packed) : []), [packed]);
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { useChosenUploads };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=useChosenUploads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChosenUploads.js","names":[],"sources":["../../src/folder/useChosenUploads.ts"],"sourcesContent":["'use client'\n\nimport { useFieldPath, useFormFields } from '@payloadcms/ui'\nimport React from 'react'\n\nimport { chosenUploadIds, packChosenIds, unpackChosenIds } from './chosenUploadIds'\n\n/**\n * The ids the upload field already holds, for one collection.\n *\n * Payload's upload field appends whatever the drawer hands it, in both paths: `onSelect`\n * for a double click and `onBulkSelect` for the selection pill. Neither checks what is\n * already there, so picking a file a second time stores it twice. On the list tab that\n * never comes up, because the field hides what it holds through `filterOptions`. The\n * drawer is rendered inside the field, so the same value is reachable from here and the\n * folder tab can hide the same files.\n *\n * `useFieldPath` is marked experimental upstream. If it ever stops resolving, the set comes\n * back empty and the folder tab lists everything, as it did before.\n */\nexport const useChosenUploads = (collectionSlug: string): Set<string> => {\n\tconst path = useFieldPath()\n\n\tconst packed = useFormFields(([fields]) =>\n\t\tpackChosenIds(chosenUploadIds(path ? fields?.[path]?.value : undefined, collectionSlug))\n\t)\n\n\treturn React.useMemo(() => new Set(packed ? unpackChosenIds(packed) : []), [packed])\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,MAAa,oBAAoB,mBAAwC;CACxE,MAAM,OAAO,aAAa;CAE1B,MAAM,SAAS,eAAe,CAAC,YAC9B,cAAc,gBAAgB,OAAO,SAAS,OAAO,QAAQ,KAAA,GAAW,cAAc,CAAC,CACxF;CAEA,OAAO,MAAM,cAAc,IAAI,IAAI,SAAS,gBAAgB,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;AACpF"}
|