Skip to content

Commit 28a43f8

Browse files
PatrikKozakkendelljoseph
authored andcommitted
feat: adds beforeDocumentControls slot to allow custom component injection next to document controls (#12104)
### What This PR introduces a new `beforeDocumentControls` slot to the edit view of both collections and globals. It allows injecting one or more custom components next to the document control buttons (e.g., Save, Publish, Save Draft) in the admin UI — useful for adding context, additional buttons, or custom UI elements. #### Usage ##### For collections: ``` admin: { components: { edit: { beforeDocumentControls: ['/path/to/CustomComponent'], }, }, }, ``` ##### For globals: ``` admin: { components: { elements: { beforeDocumentControls: ['/path/to/CustomComponent'], }, }, }, ```
1 parent cae41c4 commit 28a43f8

File tree

14 files changed

+216
-21
lines changed

14 files changed

+216
-21
lines changed

docs/custom-components/edit-view.mdx

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,15 @@ export const MyCollection: CollectionConfig = {
101101

102102
The following options are available:
103103

104-
| Path | Description |
105-
| ----------------- | -------------------------------------------------------------------------------------- |
106-
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
107-
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
108-
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
109-
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
110-
| `Description` | A description of the Collection. [More details](#description). |
111-
| `Upload` | A file upload component. [More details](#upload). |
104+
| Path | Description |
105+
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
106+
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
107+
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
108+
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
109+
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
110+
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
111+
| `Description` | A description of the Collection. [More details](#description). |
112+
| `Upload` | A file upload component. [More details](#upload). |
112113

113114
#### Globals
114115

@@ -133,13 +134,14 @@ export const MyGlobal: GlobalConfig = {
133134

134135
The following options are available:
135136

136-
| Path | Description |
137-
| ----------------- | -------------------------------------------------------------------------------------- |
138-
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
139-
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
140-
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
141-
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
142-
| `Description` | A description of the Global. [More details](#description). |
137+
| Path | Description |
138+
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
139+
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
140+
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
141+
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
142+
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
143+
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
144+
| `Description` | A description of the Global. [More details](#description). |
143145

144146
### SaveButton
145147

@@ -191,6 +193,73 @@ export function MySaveButton(props: SaveButtonClientProps) {
191193
}
192194
```
193195

196+
### beforeDocumentControls
197+
198+
The `beforeDocumentControls` property allows you to render custom components just before the default document action buttons (like Save, Publish, or Preview). This is useful for injecting custom buttons, status indicators, or any other UI elements before the built-in controls.
199+
200+
To add `beforeDocumentControls` components, use the `components.edit.beforeDocumentControls` property in you [Collection Config](../configuration/collections) or `components.elements.beforeDocumentControls` in your [Global Config](../configuration/globals):
201+
202+
#### Collections
203+
204+
```
205+
export const MyCollection: CollectionConfig = {
206+
admin: {
207+
components: {
208+
edit: {
209+
// highlight-start
210+
beforeDocumentControls: ['/path/to/CustomComponent'],
211+
// highlight-end
212+
},
213+
},
214+
},
215+
}
216+
```
217+
218+
#### Globals
219+
220+
```
221+
export const MyGlobal: GlobalConfig = {
222+
admin: {
223+
components: {
224+
elements: {
225+
// highlight-start
226+
beforeDocumentControls: ['/path/to/CustomComponent'],
227+
// highlight-end
228+
},
229+
},
230+
},
231+
}
232+
```
233+
234+
Here's an example of a custom `beforeDocumentControls` component:
235+
236+
#### Server Component
237+
238+
```tsx
239+
import React from 'react'
240+
import type { BeforeDocumentControlsServerProps } from 'payload'
241+
242+
export function MyCustomDocumentControlButton(
243+
props: BeforeDocumentControlsServerProps,
244+
) {
245+
return <div>This is a custom beforeDocumentControl button (Server)</div>
246+
}
247+
```
248+
249+
#### Client Component
250+
251+
```tsx
252+
'use client'
253+
import React from 'react'
254+
import type { BeforeDocumentControlsClientProps } from 'payload'
255+
256+
export function MyCustomDocumentControlButton(
257+
props: BeforeDocumentControlsClientProps,
258+
) {
259+
return <div>This is a custom beforeDocumentControl button (Client)</div>
260+
}
261+
```
262+
194263
### SaveDraftButton
195264

196265
The `SaveDraftButton` property allows you to render a custom Save Draft Button in the Edit View.

packages/next/src/views/Document/renderDocumentSlots.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
BeforeDocumentControlsServerPropsOnly,
23
DefaultServerFunctionArgs,
34
DocumentSlots,
45
PayloadRequest,
@@ -42,6 +43,18 @@ export const renderDocumentSlots: (args: {
4243
// TODO: Add remaining serverProps
4344
}
4445

46+
const BeforeDocumentControls =
47+
collectionConfig?.admin?.components?.edit?.beforeDocumentControls ||
48+
globalConfig?.admin?.components?.elements?.beforeDocumentControls
49+
50+
if (BeforeDocumentControls) {
51+
components.BeforeDocumentControls = RenderServerComponent({
52+
Component: BeforeDocumentControls,
53+
importMap: req.payload.importMap,
54+
serverProps: serverProps satisfies BeforeDocumentControlsServerPropsOnly,
55+
})
56+
}
57+
4558
const CustomPreviewButton =
4659
collectionConfig?.admin?.components?.edit?.PreviewButton ||
4760
globalConfig?.admin?.components?.elements?.PreviewButton

packages/payload/src/admin/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ export type FieldRow = {
553553
}
554554

555555
export type DocumentSlots = {
556+
BeforeDocumentControls?: React.ReactNode
556557
Description?: React.ReactNode
557558
PreviewButton?: React.ReactNode
558559
PublishButton?: React.ReactNode
@@ -579,6 +580,9 @@ export type { LanguageOptions } from './LanguageOptions.js'
579580
export type { RichTextAdapter, RichTextAdapterProvider, RichTextHooks } from './RichText.js'
580581

581582
export type {
583+
BeforeDocumentControlsClientProps,
584+
BeforeDocumentControlsServerProps,
585+
BeforeDocumentControlsServerPropsOnly,
582586
DocumentSubViewTypes,
583587
DocumentTabClientProps,
584588
/**

packages/payload/src/admin/views/document.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ export type DocumentTabServerPropsOnly = {
3636
readonly permissions: SanitizedPermissions
3737
} & ServerProps
3838

39-
export type DocumentTabServerProps = DocumentTabClientProps & DocumentTabServerPropsOnly
40-
4139
export type DocumentTabClientProps = {
4240
path: string
4341
}
4442

43+
export type DocumentTabServerProps = DocumentTabClientProps & DocumentTabServerPropsOnly
44+
4545
export type DocumentTabCondition = (args: {
4646
collectionConfig: SanitizedCollectionConfig
4747
config: SanitizedConfig
@@ -75,3 +75,10 @@ export type DocumentTabConfig = {
7575
export type DocumentTabComponent = PayloadComponent<{
7676
path: string
7777
}>
78+
79+
// BeforeDocumentControls
80+
81+
export type BeforeDocumentControlsClientProps = {}
82+
export type BeforeDocumentControlsServerPropsOnly = {} & ServerProps
83+
export type BeforeDocumentControlsServerProps = BeforeDocumentControlsClientProps &
84+
BeforeDocumentControlsServerPropsOnly

packages/payload/src/bin/generateImportMap/iterateCollections.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export function iterateCollections({
3636
addToImportMap(collection.admin?.components?.beforeListTable)
3737
addToImportMap(collection.admin?.components?.Description)
3838

39+
addToImportMap(collection.admin?.components?.edit?.beforeDocumentControls)
3940
addToImportMap(collection.admin?.components?.edit?.PreviewButton)
4041
addToImportMap(collection.admin?.components?.edit?.PublishButton)
4142
addToImportMap(collection.admin?.components?.edit?.SaveButton)

packages/payload/src/collections/config/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ export type CollectionAdminOptions = {
279279
* Components within the edit view
280280
*/
281281
edit?: {
282+
/**
283+
* Inject custom components before the document controls
284+
*/
285+
beforeDocumentControls?: CustomComponent[]
282286
/**
283287
* Replaces the "Preview" button
284288
*/

packages/payload/src/globals/config/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
} from '../../admin/types.js'
1010
import type {
1111
Access,
12+
CustomComponent,
1213
EditConfig,
1314
Endpoint,
1415
EntityDescription,
@@ -80,6 +81,10 @@ export type GlobalAdminOptions = {
8081
*/
8182
components?: {
8283
elements?: {
84+
/**
85+
* Inject custom components before the document controls
86+
*/
87+
beforeDocumentControls?: CustomComponent[]
8388
Description?: EntityDescriptionComponent
8489
/**
8590
* Replaces the "Preview" button

packages/ui/src/elements/DocumentControls/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const baseClass = 'doc-controls'
3737

3838
export const DocumentControls: React.FC<{
3939
readonly apiURL: string
40+
readonly BeforeDocumentControls?: React.ReactNode
4041
readonly customComponents?: {
4142
readonly PreviewButton?: React.ReactNode
4243
readonly PublishButton?: React.ReactNode
@@ -67,6 +68,7 @@ export const DocumentControls: React.FC<{
6768
const {
6869
id,
6970
slug,
71+
BeforeDocumentControls,
7072
customComponents: {
7173
PreviewButton: CustomPreviewButton,
7274
PublishButton: CustomPublishButton,
@@ -222,6 +224,7 @@ export const DocumentControls: React.FC<{
222224
</div>
223225
<div className={`${baseClass}__controls-wrapper`}>
224226
<div className={`${baseClass}__controls`}>
227+
{BeforeDocumentControls}
225228
{(collectionConfig?.admin.preview || globalConfig?.admin.preview) && (
226229
<RenderCustomComponent
227230
CustomComponent={CustomPreviewButton}

packages/ui/src/views/Edit/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const baseClass = 'collection-edit'
4343
// When rendered within a drawer, props are empty
4444
// This is solely to support custom edit views which get server-rendered
4545
export function DefaultEditView({
46+
BeforeDocumentControls,
4647
Description,
4748
PreviewButton,
4849
PublishButton,
@@ -512,6 +513,7 @@ export function DefaultEditView({
512513
/>
513514
<DocumentControls
514515
apiURL={apiURL}
516+
BeforeDocumentControls={BeforeDocumentControls}
515517
customComponents={{
516518
PreviewButton,
517519
PublishButton,

test/admin/collections/Posts.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ export const Posts: CollectionConfig = {
6969
},
7070
},
7171
],
72+
edit: {
73+
beforeDocumentControls: [
74+
'/components/BeforeDocumentControls/CustomDraftButton/index.js#CustomDraftButton',
75+
'/components/BeforeDocumentControls/CustomSaveButton/index.js#CustomSaveButton',
76+
],
77+
},
7278
},
7379
pagination: {
7480
defaultLimit: 5,

0 commit comments

Comments
 (0)