Skip to content

Commit f64a0ae

Browse files
authored
fix: remove unsupported path property from default document view configs (#12774)
Customizing the `path` property on default document views is currently not supported, but the types suggest that it is. You can only provide a path to custom views. This PR ensures that `path` cannot be set on default views as expected. For example: ```ts import type { CollectionConfig } from 'payload' export const MyCollectionConfig: CollectionConfig = { // ... admin: { components: { views: { edit: { default: { path: '/' // THIS IS NOT ALLOWED! }, myCustomView: { path: '/edit', // THIS IS ALLOWED! Component: '/collections/CustomViews3/MyEditView.js#MyEditView', }, }, }, }, }, } ``` For background context, this was deeply explored in #12701. This is not planned, however, due to [performance and maintainability concerns](#12701 (comment)), plus [there are alternatives to achieve this](#12772). This PR also fixes and improves various jsdocs, and fixes a typo found in the docs.
1 parent 143aff5 commit f64a0ae

File tree

10 files changed

+110
-68
lines changed

10 files changed

+110
-68
lines changed

docs/custom-components/document-views.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export const MyCollection: CollectionConfig = {
8888

8989
### Edit View
9090

91-
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. the Edit View is keyed under the `default` property in the `views.edit` object.
91+
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. The Edit View is keyed under the `default` property in the `views.edit` object.
9292

9393
For more information on customizing the Edit View, see the [Edit View](./edit-view) documentation.
9494

@@ -107,16 +107,16 @@ export const MyCollection: CollectionConfig = {
107107
components: {
108108
views: {
109109
edit: {
110-
myCustomTab: {
111-
Component: '/path/to/MyCustomTab',
110+
myCustomView: {
111+
Component: '/path/to/MyCustomView',
112112
path: '/my-custom-tab',
113113
// highlight-start
114114
tab: {
115115
Component: '/path/to/MyCustomTabComponent',
116116
},
117117
// highlight-end
118118
},
119-
anotherCustomTab: {
119+
anotherCustomView: {
120120
Component: '/path/to/AnotherCustomView',
121121
path: '/another-custom-view',
122122
// highlight-start

packages/next/src/elements/DocumentHeader/Tabs/getCustomViews.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import type { EditViewConfig, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
1+
import type { DocumentViewConfig, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
22

33
import { documentViewKeys } from './tabs/index.js'
44

55
export const getCustomViews = (args: {
66
collectionConfig: SanitizedCollectionConfig
77
globalConfig: SanitizedGlobalConfig
8-
}): EditViewConfig[] => {
8+
}): DocumentViewConfig[] => {
99
const { collectionConfig, globalConfig } = args
1010

11-
let customViews: EditViewConfig[]
11+
let customViews: DocumentViewConfig[]
1212

1313
if (collectionConfig) {
1414
const collectionViewsConfig =

packages/next/src/elements/DocumentHeader/Tabs/getViewConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { EditViewConfig, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
1+
import type { DocumentViewConfig, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
22

33
export const getViewConfig = (args: {
44
collectionConfig: SanitizedCollectionConfig
55
globalConfig: SanitizedGlobalConfig
66
name: string
7-
}): EditViewConfig => {
7+
}): DocumentViewConfig => {
88
const { name, collectionConfig, globalConfig } = args
99

1010
if (collectionConfig) {

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,11 @@ export const DocumentTabs: React.FC<{
9898

9999
return null
100100
})}
101-
{customViews?.map((CustomView, index) => {
102-
if ('tab' in CustomView) {
103-
const { path, tab } = CustomView
101+
{customViews?.map((customViewConfig, index) => {
102+
if ('tab' in customViewConfig) {
103+
const { tab } = customViewConfig
104+
105+
const path = 'path' in customViewConfig ? customViewConfig.path : ''
104106

105107
if (tab.Component) {
106108
return RenderServerComponent({

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { generateLivePreviewViewMetadata } from '../LivePreview/metadata.js'
1010
import { generateNotFoundViewMetadata } from '../NotFound/metadata.js'
1111
import { generateVersionViewMetadata } from '../Version/metadata.js'
1212
import { generateVersionsViewMetadata } from '../Versions/metadata.js'
13-
import { getViewsFromConfig } from './getViewsFromConfig.js'
13+
import { getViewsFromConfig } from './getDocumentView.js'
1414

1515
export type GenerateEditViewMetadata = (
1616
args: {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ import { logError } from 'payload'
1616
import { formatAdminURL } from 'payload/shared'
1717
import React from 'react'
1818

19+
import type { ViewFromConfig } from './getDocumentView.js'
1920
import type { GenerateEditViewMetadata } from './getMetaBySegment.js'
20-
import type { ViewFromConfig } from './getViewsFromConfig.js'
2121

2222
import { DocumentHeader } from '../../elements/DocumentHeader/index.js'
2323
import { NotFoundView } from '../NotFound/index.js'
2424
import { getDocPreferences } from './getDocPreferences.js'
2525
import { getDocumentData } from './getDocumentData.js'
2626
import { getDocumentPermissions } from './getDocumentPermissions.js'
27+
import { getViewsFromConfig } from './getDocumentView.js'
2728
import { getIsLocked } from './getIsLocked.js'
2829
import { getMetaBySegment } from './getMetaBySegment.js'
2930
import { getVersions } from './getVersions.js'
30-
import { getViewsFromConfig } from './getViewsFromConfig.js'
3131
import { renderDocumentSlots } from './renderDocumentSlots.js'
3232

3333
export const generateMetadata: GenerateEditViewMetadata = async (args) => getMetaBySegment(args)

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,14 @@ export type CollectionAdminOptions = {
328328
listMenuItems?: CustomComponent[]
329329
views?: {
330330
/**
331-
* Set to a React component to replace the entire Edit View, including all nested routes.
332-
* Set to an object to replace or modify individual nested routes, or to add new ones.
331+
* Replace, modify, or add new "document" views.
332+
* @link https://payloadcms.com/docs/custom-components/document-views
333333
*/
334334
edit?: EditConfig
335+
/**
336+
* Replace or modify the "list" view.
337+
* @link https://payloadcms.com/docs/custom-components/list-view
338+
*/
335339
list?: {
336340
actions?: CustomComponent[]
337341
Component?: PayloadComponent

packages/payload/src/config/types.ts

Lines changed: 72 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -340,29 +340,50 @@ export type Endpoint = {
340340
root?: never
341341
}
342342

343-
export type EditViewComponent = PayloadComponent<DocumentViewServerProps>
343+
/**
344+
* @deprecated
345+
* This type will be renamed in v4.
346+
* Use `DocumentViewComponent` instead.
347+
*/
348+
export type EditViewComponent = DocumentViewComponent
344349

345-
export type EditViewConfig = {
350+
export type DocumentViewComponent = PayloadComponent<DocumentViewServerProps>
351+
352+
/**
353+
* @deprecated
354+
* This type will be renamed in v4.
355+
* Use `DocumentViewConfig` instead.
356+
*/
357+
export type EditViewConfig = DocumentViewConfig
358+
359+
type BaseDocumentViewConfig = {
360+
actions?: CustomComponent[]
346361
meta?: MetaConfig
347-
} & (
348-
| {
349-
actions?: CustomComponent[]
350-
}
351-
| {
352-
Component: EditViewComponent
353-
path?: string
354-
}
355-
| {
356-
path?: string
357-
/**
358-
* Add a new Edit View to the admin panel
359-
* i.e. you can render a custom view that has no tab, if desired
360-
* Or override a specific properties of an existing one
361-
* i.e. you can customize the `Default` view tab label, if desired
362-
*/
363-
tab?: DocumentTabConfig
364-
}
365-
)
362+
tab?: DocumentTabConfig
363+
}
364+
365+
/*
366+
If your view does not originate from a "known" key, e.g. `myCustomView`, then it is considered a "custom" view and can accept a `path`, etc.
367+
To render just a tab component without an accompanying view, you can omit the `path` and `Component` properties altogether.
368+
*/
369+
export type CustomDocumentViewConfig =
370+
| ({
371+
Component: DocumentViewComponent
372+
path: string
373+
} & BaseDocumentViewConfig)
374+
| ({
375+
Component?: DocumentViewComponent
376+
path?: never
377+
} & BaseDocumentViewConfig)
378+
379+
/*
380+
If your view does originates from a "known" key, e.g. `api`, then it is considered a "default" view and cannot accept a `path`, etc.
381+
*/
382+
export type DefaultDocumentViewConfig = {
383+
Component?: DocumentViewComponent
384+
} & BaseDocumentViewConfig
385+
386+
export type DocumentViewConfig = CustomDocumentViewConfig | DefaultDocumentViewConfig
366387

367388
export type Params = { [key: string]: string | string[] | undefined }
368389

@@ -1260,46 +1281,46 @@ export type SanitizedConfig = {
12601281

12611282
export type EditConfig = EditConfigWithoutRoot | EditConfigWithRoot
12621283

1284+
/**
1285+
* Replace or modify _all_ nested document views and routes, including the document header, controls, and tabs. This cannot be used in conjunction with other nested views.
1286+
* + `root` - `/admin/collections/:collection/:id/**\/*`
1287+
* @link https://payloadcms.com/docs/custom-components/document-views#document-root
1288+
*/
12631289
export type EditConfigWithRoot = {
12641290
api?: never
12651291
default?: never
12661292
livePreview?: never
1267-
/**
1268-
* Replace or modify _all_ nested document views and routes, including the document header, controls, and tabs. This cannot be used in conjunction with other nested views.
1269-
* + `root` - `/admin/collections/:collection/:id/**\/*`
1270-
*/
1271-
root: Partial<EditViewConfig>
1293+
root: DefaultDocumentViewConfig
12721294
version?: never
12731295
versions?: never
12741296
}
12751297

1298+
type KnownEditKeys = 'api' | 'default' | 'livePreview' | 'root' | 'version' | 'versions'
1299+
1300+
/**
1301+
* Replace or modify individual nested routes, or add new ones:
1302+
* + `default` - `/admin/collections/:collection/:id`
1303+
* + `api` - `/admin/collections/:collection/:id/api`
1304+
* + `livePreview` - `/admin/collections/:collection/:id/preview`
1305+
* + `references` - `/admin/collections/:collection/:id/references`
1306+
* + `relationships` - `/admin/collections/:collection/:id/relationships`
1307+
* + `versions` - `/admin/collections/:collection/:id/versions`
1308+
* + `version` - `/admin/collections/:collection/:id/versions/:version`
1309+
* + `customView` - `/admin/collections/:collection/:id/:path`
1310+
*
1311+
* To override the entire Edit View including all nested views, use the `root` key.
1312+
*
1313+
* @link https://payloadcms.com/docs/custom-components/document-views
1314+
*/
12761315
export type EditConfigWithoutRoot = {
1277-
[key: string]: EditViewConfig
1278-
/**
1279-
* Replace or modify individual nested routes, or add new ones:
1280-
* + `default` - `/admin/collections/:collection/:id`
1281-
* + `api` - `/admin/collections/:collection/:id/api`
1282-
* + `livePreview` - `/admin/collections/:collection/:id/preview`
1283-
* + `references` - `/admin/collections/:collection/:id/references`
1284-
* + `relationships` - `/admin/collections/:collection/:id/relationships`
1285-
* + `versions` - `/admin/collections/:collection/:id/versions`
1286-
* + `version` - `/admin/collections/:collection/:id/versions/:version`
1287-
* + `customView` - `/admin/collections/:collection/:id/:path`
1288-
*
1289-
* To override the entire Edit View including all nested views, use the `root` key.
1290-
*/
1291-
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
1292-
api?: Partial<EditViewConfig>
1293-
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
1294-
default?: Partial<EditViewConfig>
1295-
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
1296-
livePreview?: Partial<EditViewConfig>
1297-
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
1316+
[K in Exclude<string, KnownEditKeys>]: CustomDocumentViewConfig
1317+
} & {
1318+
api?: DefaultDocumentViewConfig
1319+
default?: DefaultDocumentViewConfig
1320+
livePreview?: DefaultDocumentViewConfig
12981321
root?: never
1299-
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
1300-
version?: Partial<EditViewConfig>
1301-
// @ts-expect-error - vestiges of when tsconfig was not strict. Feel free to improve
1302-
versions?: Partial<EditViewConfig>
1322+
version?: DefaultDocumentViewConfig
1323+
versions?: DefaultDocumentViewConfig
13031324
}
13041325

13051326
export type EntityDescriptionComponent = CustomComponent

test/types/types.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type {
22
BulkOperationResult,
3+
CustomDocumentViewConfig,
4+
DefaultDocumentViewConfig,
35
JoinQuery,
46
PaginatedDocs,
57
SelectType,
@@ -158,4 +160,17 @@ describe('Types testing', () => {
158160
})
159161
})
160162
})
163+
164+
describe('views', () => {
165+
test('default view config', () => {
166+
expect<DefaultDocumentViewConfig>().type.not.toBeAssignableWith<{
167+
path: string
168+
}>()
169+
170+
expect<CustomDocumentViewConfig>().type.toBeAssignableWith<{
171+
Component: string
172+
path: string
173+
}>()
174+
})
175+
})
161176
})

0 commit comments

Comments
 (0)