Skip to content

Commit 625d8d9

Browse files
authored
feat(ui): adds new editMenuItems custom component (#12649)
## What Adds a new custom component called `editMenuItems` that can be used in the document view. This options allows users to inject their own custom components into the dropdown menu found in the document controls (the 3 dot menu), the provided component(s) will be added below the default existing actions (Create New, Duplicate, Delete and so on). ## Why To increase flexibility and customization for users who wish to add functionality to this menu. This provides a clean and consistent way to add additional actions without needing to override or duplicate existing UI logic. ## How - Introduced the `editMenuItems` slot in the document controls dropdown (three-dot menu) - in edit and preview tabs. - Added documentation and tests to cover this new custom component #### Testing Use the `admin` test suite and go to the `edit menu items` collection
1 parent a9ff375 commit 625d8d9

File tree

18 files changed

+277
-25
lines changed

18 files changed

+277
-25
lines changed

docs/configuration/collections.mdx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,15 @@ export const MyCollection: CollectionConfig = {
194194

195195
The following options are available:
196196

197-
| Option | Description |
198-
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
199-
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
200-
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
201-
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
202-
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
203-
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
197+
| Option | Description |
198+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
199+
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](../custom-components/edit-view#beforedocumentcontrols). |
200+
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document controls bar. [More details](../custom-components/edit-view#editmenuitems). |
201+
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
202+
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
203+
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
204+
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
205+
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
204206

205207
<Banner type="success">
206208
**Note:** For details on how to build Custom Components, see [Building Custom

docs/custom-components/edit-view.mdx

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

102102
The following options are available:
103103

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). |
104+
| Path | Description |
105+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
106+
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
107+
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
108+
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
109+
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
110+
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
111+
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
112+
| `Description` | A description of the Collection. [More details](#description). |
113+
| `Upload` | A file upload component. [More details](#upload). |
113114

114115
#### Globals
115116

@@ -134,14 +135,15 @@ export const MyGlobal: GlobalConfig = {
134135

135136
The following options are available:
136137

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). |
138+
| Path | Description |
139+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
140+
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
141+
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
142+
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
143+
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
144+
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
145+
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
146+
| `Description` | A description of the Global. [More details](#description). |
145147

146148
### SaveButton
147149

@@ -260,6 +262,92 @@ export function MyCustomDocumentControlButton(
260262
}
261263
```
262264

265+
### editMenuItems
266+
267+
The `editMenuItems` property allows you to inject custom components into the 3-dot menu dropdown located in the document controls bar. This dropdown contains default options including `Create New`, `Duplicate`, `Delete`, and other options when additional features are enabled. Any custom components you add will appear below these default items.
268+
269+
To add `editMenuItems`, use the `components.edit.editMenuItems` property in your [Collection Config](../configuration/collections):
270+
271+
#### Config Example
272+
273+
```ts
274+
import type { CollectionConfig } from 'payload'
275+
276+
export const Pages: CollectionConfig = {
277+
slug: 'pages',
278+
admin: {
279+
components: {
280+
edit: {
281+
// highlight-start
282+
editMenuItems: ['/path/to/CustomEditMenuItem'],
283+
// highlight-end
284+
},
285+
},
286+
},
287+
}
288+
```
289+
290+
Here's an example of a custom `editMenuItems` component:
291+
292+
#### Server Component
293+
294+
```tsx
295+
import React from 'react'
296+
import { PopupList } from '@payloadcms/ui'
297+
298+
import type { EditMenuItemsServerProps } from 'payload'
299+
300+
export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
301+
const href = `/custom-action?id=${props.id}`
302+
303+
return (
304+
<PopupList.ButtonGroup>
305+
<PopupList.Button href={href}>Custom Edit Menu Item</PopupList.Button>
306+
<PopupList.Button href={href}>
307+
Another Custom Edit Menu Item - add as many as you need!
308+
</PopupList.Button>
309+
</PopupList.ButtonGroup>
310+
)
311+
}
312+
```
313+
314+
#### Client Component
315+
316+
```tsx
317+
'use client'
318+
319+
import React from 'react'
320+
import { PopupList } from '@payloadcms/ui'
321+
322+
import type { EditViewMenuItemClientProps } from 'payload'
323+
324+
export const EditMenuItems = (props: EditViewMenuItemClientProps) => {
325+
const handleClick = () => {
326+
console.log('Custom button clicked!')
327+
}
328+
329+
return (
330+
<PopupList.ButtonGroup>
331+
<PopupList.Button onClick={handleClick}>
332+
Custom Edit Menu Item
333+
</PopupList.Button>
334+
<PopupList.Button onClick={handleClick}>
335+
Another Custom Edit Menu Item - add as many as you need!
336+
</PopupList.Button>
337+
</PopupList.ButtonGroup>
338+
)
339+
}
340+
```
341+
342+
<Banner type="info">
343+
**Styling:** Use Payload&apos;s built-in <code>PopupList.Button</code> to
344+
ensure your menu items automatically match the default dropdown styles. If you
345+
want a different look, you can customize the appearance by passing your own{' '}
346+
<code>className</code> to <code>PopupList.Button</code>, or use a completely
347+
custom button built with a standard HTML <code>&lt;button&gt;</code> element
348+
or any other component that fits your design preferences.
349+
</Banner>
350+
263351
### SaveDraftButton
264352

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

docs/custom-components/list-view.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ The following options are available:
9494
| `beforeListTable` | An array of custom components to inject before the table of documents in the List View. [More details](#beforelisttable). |
9595
| `afterList` | An array of custom components to inject after the list of documents in the List View. [More details](#afterlist). |
9696
| `afterListTable` | An array of custom components to inject after the table of documents in the List View. [More details](#afterlisttable). |
97+
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
9798
| `Description` | A component to render a description of the Collection. [More details](#description). |
9899

99100
### beforeList

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
BeforeDocumentControlsServerPropsOnly,
33
DefaultServerFunctionArgs,
44
DocumentSlots,
5+
EditMenuItemsServerPropsOnly,
56
PayloadRequest,
67
PreviewButtonServerPropsOnly,
78
PublishButtonServerPropsOnly,
@@ -55,6 +56,16 @@ export const renderDocumentSlots: (args: {
5556
})
5657
}
5758

59+
const EditMenuItems = collectionConfig?.admin?.components?.edit?.editMenuItems
60+
61+
if (EditMenuItems) {
62+
components.EditMenuItems = RenderServerComponent({
63+
Component: EditMenuItems,
64+
importMap: req.payload.importMap,
65+
serverProps: serverProps satisfies EditMenuItemsServerPropsOnly,
66+
})
67+
}
68+
5869
const CustomPreviewButton =
5970
collectionConfig?.admin?.components?.edit?.PreviewButton ||
6071
globalConfig?.admin?.components?.elements?.PreviewButton

packages/next/src/views/LivePreview/index.client.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const PreviewView: React.FC<Props> = ({
7575
collectionConfig,
7676
config,
7777
Description,
78+
EditMenuItems,
7879
fields,
7980
globalConfig,
8081
PreviewButton,
@@ -504,6 +505,7 @@ const PreviewView: React.FC<Props> = ({
504505
}}
505506
data={initialData}
506507
disableActions={disableActions}
508+
EditMenuItems={EditMenuItems}
507509
hasPublishPermission={hasPublishPermission}
508510
hasSavePermission={hasSavePermission}
509511
id={id}
@@ -610,6 +612,7 @@ export const LivePreviewClient: React.FC<
610612
collectionConfig={collectionConfig}
611613
config={config}
612614
Description={props.Description}
615+
EditMenuItems={props.EditMenuItems}
613616
fields={(collectionConfig || globalConfig)?.fields}
614617
globalConfig={globalConfig}
615618
PreviewButton={props.PreviewButton}

packages/next/src/views/LivePreview/index.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
BeforeDocumentControlsServerPropsOnly,
33
DocumentViewServerProps,
4+
EditMenuItemsServerPropsOnly,
45
LivePreviewConfig,
56
ServerProps,
67
} from 'payload'
@@ -10,7 +11,6 @@ import React from 'react'
1011

1112
import './index.scss'
1213
import { LivePreviewClient } from './index.client.js'
13-
1414
export async function LivePreviewView(props: DocumentViewServerProps) {
1515
const { doc, initPageResult } = props
1616

@@ -43,6 +43,8 @@ export async function LivePreviewView(props: DocumentViewServerProps) {
4343
collectionConfig?.admin?.components?.edit?.beforeDocumentControls ||
4444
globalConfig?.admin?.components?.elements?.beforeDocumentControls
4545

46+
const EditMenuItems = collectionConfig?.admin?.components?.edit?.editMenuItems
47+
4648
const breakpoints: LivePreviewConfig['breakpoints'] = [
4749
...(livePreviewConfig?.breakpoints || []),
4850
{
@@ -82,6 +84,15 @@ export async function LivePreviewView(props: DocumentViewServerProps) {
8284
}
8385
breakpoints={breakpoints}
8486
Description={props.Description}
87+
EditMenuItems={
88+
EditMenuItems
89+
? RenderServerComponent({
90+
Component: EditMenuItems,
91+
importMap: req.payload.importMap,
92+
serverProps: serverProps satisfies EditMenuItemsServerPropsOnly,
93+
})
94+
: null
95+
}
8596
initialData={doc}
8697
PreviewButton={props.PreviewButton}
8798
PublishButton={props.PublishButton}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { ServerProps } from '../../config/types.js'
2+
3+
export type EditMenuItemsClientProps = {}
4+
5+
export type EditMenuItemsServerPropsOnly = {} & ServerProps
6+
7+
export type EditMenuItemsServerProps = EditMenuItemsClientProps & EditMenuItemsServerPropsOnly

packages/payload/src/admin/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ export type {
5353
export type { DefaultCellComponentProps, DefaultServerCellComponentProps } from './elements/Cell.js'
5454
export type { ConditionalDateProps } from './elements/DatePicker.js'
5555
export type { DayPickerProps, SharedProps, TimePickerProps } from './elements/DatePicker.js'
56+
export type {
57+
EditMenuItemsClientProps,
58+
EditMenuItemsServerProps,
59+
EditMenuItemsServerPropsOnly,
60+
} from './elements/EditMenuItems.js'
5661
export type { NavGroupPreferences, NavPreferences } from './elements/Nav.js'
5762
export type {
5863
PreviewButtonClientProps,
@@ -555,6 +560,7 @@ export type FieldRow = {
555560
export type DocumentSlots = {
556561
BeforeDocumentControls?: React.ReactNode
557562
Description?: React.ReactNode
563+
EditMenuItems?: React.ReactNode
558564
PreviewButton?: React.ReactNode
559565
PublishButton?: React.ReactNode
560566
SaveButton?: React.ReactNode

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

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

3939
addToImportMap(collection.admin?.components?.edit?.beforeDocumentControls)
40+
addToImportMap(collection.admin?.components?.edit?.editMenuItems)
4041
addToImportMap(collection.admin?.components?.edit?.PreviewButton)
4142
addToImportMap(collection.admin?.components?.edit?.PublishButton)
4243
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
@@ -290,6 +290,10 @@ export type CollectionAdminOptions = {
290290
* Inject custom components before the document controls
291291
*/
292292
beforeDocumentControls?: CustomComponent[]
293+
/**
294+
* Inject custom components within the 3-dot menu dropdown
295+
*/
296+
editMenuItems?: CustomComponent[]
293297
/**
294298
* Replaces the "Preview" button
295299
*/

0 commit comments

Comments
 (0)