Skip to content

Commit 00667fa

Browse files
feat: folders (#10030)
1 parent 898e97e commit 00667fa

File tree

289 files changed

+12979
-1350
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

289 files changed

+12979
-1350
lines changed

.vscode/launch.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@
118118
"request": "launch",
119119
"type": "node-terminal"
120120
},
121+
{
122+
"command": "pnpm tsx --no-deprecation test/dev.ts folder-view",
123+
"cwd": "${workspaceFolder}",
124+
"name": "Run Dev Folder View",
125+
"request": "launch",
126+
"type": "node-terminal"
127+
},
121128
{
122129
"command": "pnpm tsx --no-deprecation test/dev.ts localization",
123130
"cwd": "${workspaceFolder}",

docs/configuration/collections.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ The following options are available:
132132
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
133133
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
134134
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
135+
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
135136
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
136137
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
137138
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |

docs/configuration/overview.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ The following options are available:
8484
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
8585
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
8686
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
87+
| `folders` | An optional object to configure global folder settings. [More details](../folders/overview). |
8788
| `queryPresets` | An object that to configure Collection Query Presets. [More details](../query-presets/overview). |
8889
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
8990
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |

docs/folders/overview.mdx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
title: Folders
3+
label: Folders
4+
order: 10
5+
desc: Folders allow you to group documents across collections, and are a great way to organize your content.
6+
keywords: folders, folder, content organization
7+
---
8+
9+
Folders allow you to group documents across collections, and are a great way to organize your content. Folders are built on top of relationship fields, when you enable folders on a collection, Payload adds a hidden relationship field `folders`, that relates to a folder — or no folder. Folders also have the `folder` field, allowing folders to be nested within other folders.
10+
11+
The configuration for folders is done in two places, the collection config and the Payload config. The collection config is where you enable folders, and the Payload config is where you configure the global folder settings.
12+
13+
## Folder Configuration
14+
15+
On the payload config, you can configure the following settings under the `folders` property:
16+
17+
```ts
18+
// Type definition
19+
20+
type RootFoldersConfiguration = {
21+
/**
22+
* An array of functions to be ran when the folder collection is initialized
23+
* This allows plugins to modify the collection configuration
24+
*/
25+
collectionOverrides?: (({
26+
collection,
27+
}: {
28+
collection: CollectionConfig
29+
}) => CollectionConfig | Promise<CollectionConfig>)[]
30+
/**
31+
* Ability to view hidden fields and collections related to folders
32+
*
33+
* @default false
34+
*/
35+
debug?: boolean
36+
/**
37+
* The Folder field name
38+
*
39+
* @default "folder"
40+
*/
41+
fieldName?: string
42+
/**
43+
* Slug for the folder collection
44+
*
45+
* @default "payload-folders"
46+
*/
47+
slug?: string
48+
}
49+
```
50+
51+
```ts
52+
// Example usage
53+
54+
import { buildConfig } from 'payload'
55+
56+
const config = buildConfig({
57+
// ...
58+
folders: {
59+
// highlight-start
60+
debug: true, // optional
61+
collectionOverrides: [
62+
async ({ collection }) => {
63+
return collection
64+
},
65+
], // optional
66+
fieldName: 'folder', // optional
67+
slug: 'payload-folders', // optional
68+
// highlight-end
69+
},
70+
})
71+
```
72+
73+
## Collection Configuration
74+
75+
To enable folders on a collection, you need to set the `admin.folders` property to `true` on the collection config. This will add a hidden relationship field to the collection that relates to a folder — or no folder.
76+
77+
```ts
78+
// Type definition
79+
80+
type CollectionFoldersConfiguration = boolean
81+
```
82+
83+
```ts
84+
// Example usage
85+
86+
import { buildConfig } from 'payload'
87+
88+
const config = buildConfig({
89+
collections: [
90+
{
91+
slug: 'pages',
92+
// highlight-start
93+
admin: {
94+
folders: true, // defaults to false
95+
},
96+
// highlight-end
97+
},
98+
],
99+
})
100+
```

docs/getting-started/installation.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
8181

8282
#### 2. Copy Payload files into your Next.js app folder
8383

84-
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
84+
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
8585

8686
```plaintext
8787
app/

packages/next/src/elements/DocumentHeader/Tabs/Tab/TabLink.tsx

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
22
import type { SanitizedConfig } from 'payload'
33

4-
import { Link } from '@payloadcms/ui'
4+
import { Button } from '@payloadcms/ui'
55
import { useParams, usePathname, useSearchParams } from 'next/navigation.js'
66
import { formatAdminURL } from 'payload/shared'
77
import React from 'react'
@@ -13,7 +13,6 @@ export const DocumentTabLink: React.FC<{
1313
children?: React.ReactNode
1414
href: string
1515
isActive?: boolean
16-
isCollection?: boolean
1716
newTab?: boolean
1817
}> = ({
1918
adminRoute,
@@ -54,19 +53,17 @@ export const DocumentTabLink: React.FC<{
5453
isActiveFromProps
5554

5655
return (
57-
<li
56+
<Button
5857
aria-label={ariaLabel}
58+
buttonStyle="tab"
5959
className={[baseClass, isActive && `${baseClass}--active`].filter(Boolean).join(' ')}
60+
disabled={isActive}
61+
el={!isActive || href !== pathname ? 'link' : 'div'}
62+
newTab={newTab}
63+
size="medium"
64+
to={!isActive || href !== pathname ? hrefWithLocale : undefined}
6065
>
61-
<Link
62-
className={`${baseClass}__link`}
63-
href={!isActive || href !== pathname ? hrefWithLocale : ''}
64-
prefetch={false}
65-
{...(newTab && { rel: 'noopener noreferrer', target: '_blank' })}
66-
tabIndex={isActive ? -1 : 0}
67-
>
68-
{children}
69-
</Link>
70-
</li>
66+
{children}
67+
</Button>
7168
)
7269
}
Lines changed: 10 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,24 @@
1-
@import '../../../../scss/styles.scss';
2-
31
@layer payload-default {
42
.doc-tab {
5-
@extend %h5;
6-
position: relative;
7-
8-
&__link {
9-
text-decoration: none;
10-
display: flex;
11-
justify-content: center;
12-
align-items: center;
13-
white-space: nowrap;
14-
15-
// Use a pseudo element for the accessability so that it doesn't take up DOM space
16-
// Also because the parent element has `overflow: hidden` which would clip an outline
17-
&:focus-visible::after {
18-
content: '';
19-
border: var(--accessibility-outline);
20-
position: absolute;
21-
top: 0;
22-
right: 0;
23-
bottom: 0;
24-
left: 0;
25-
pointer-events: none;
26-
}
27-
}
28-
29-
&:focus:not(:focus-visible) {
30-
opacity: 1;
31-
}
32-
33-
&::before {
34-
content: '';
35-
display: block;
36-
position: absolute;
37-
width: 100%;
38-
height: 100%;
39-
border-radius: var(--style-radius-s);
40-
background-color: var(--theme-elevation-50);
41-
opacity: 0;
42-
}
3+
display: flex;
4+
justify-content: center;
5+
align-items: center;
6+
white-space: nowrap;
437

448
&:hover {
45-
&::before {
46-
opacity: 1;
47-
}
48-
49-
.doc-tab__count {
9+
.pill-version-count {
5010
background-color: var(--theme-elevation-150);
5111
}
5212
}
5313

5414
&--active {
55-
font-weight: 600;
56-
&::before {
57-
opacity: 1;
58-
background-color: var(--theme-elevation-100);
59-
}
60-
61-
.doc-tab {
62-
&__count {
63-
background-color: var(--theme-elevation-250);
64-
}
15+
.pill-version-count {
16+
background-color: var(--theme-elevation-250);
6517
}
6618

6719
&:hover {
68-
.doc-tab {
69-
&__count {
70-
background-color: var(--theme-elevation-250);
71-
}
20+
.pill-version-count {
21+
background-color: var(--theme-elevation-250);
7222
}
7323
}
7424
}
@@ -80,16 +30,7 @@
8030
gap: 4px;
8131
width: 100%;
8232
height: 100%;
83-
line-height: base(1.2);
84-
padding: base(0.2) base(0.6);
85-
}
86-
87-
&__count {
88-
line-height: base(0.8);
89-
min-width: base(0.8);
90-
text-align: center;
91-
background-color: var(--theme-elevation-100);
92-
border-radius: var(--style-radius-s);
33+
line-height: calc(var(--base) * 1.2);
9334
}
9435
}
9536
}

packages/next/src/elements/DocumentHeader/Tabs/Tab/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ export const DocumentTab: React.FC<
6868
baseClass={baseClass}
6969
href={href}
7070
isActive={isActive}
71-
isCollection={!!collectionConfig && !globalConfig}
7271
newTab={newTab}
7372
>
7473
<span className={`${baseClass}__label`}>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@layer payload-default {
2+
.pill-version-count {
3+
line-height: calc(var(--base) * 0.8);
4+
min-width: calc(var(--base) * 0.8);
5+
text-align: center;
6+
background-color: var(--theme-elevation-100);
7+
border-radius: var(--style-radius-s);
8+
}
9+
}

packages/next/src/elements/DocumentHeader/Tabs/tabs/VersionsPill/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import { useDocumentInfo } from '@payloadcms/ui'
33
import React from 'react'
44

5-
import { baseClass } from '../../Tab/index.js'
5+
import './index.scss'
6+
7+
const baseClass = 'pill-version-count'
68

79
export const VersionsPill: React.FC = () => {
810
const { versionCount } = useDocumentInfo()
@@ -11,5 +13,5 @@ export const VersionsPill: React.FC = () => {
1113
return null
1214
}
1315

14-
return <span className={`${baseClass}__count`}>{versionCount}</span>
16+
return <span className={baseClass}>{versionCount}</span>
1517
}

0 commit comments

Comments
 (0)