fix(next): dynamic params for custom collection and global views
This commit is contained in:
@@ -17,7 +17,11 @@ import { Settings } from './Settings/index.js'
|
|||||||
|
|
||||||
export { generateAccountMetadata } from './meta.js'
|
export { generateAccountMetadata } from './meta.js'
|
||||||
|
|
||||||
export const Account: React.FC<AdminViewProps> = async ({ initPageResult, searchParams }) => {
|
export const Account: React.FC<AdminViewProps> = async ({
|
||||||
|
initPageResult,
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
locale,
|
locale,
|
||||||
permissions,
|
permissions,
|
||||||
@@ -87,6 +91,7 @@ export const Account: React.FC<AdminViewProps> = async ({ initPageResult, search
|
|||||||
|
|
||||||
const viewComponentProps: ServerSideEditViewProps = {
|
const viewComponentProps: ServerSideEditViewProps = {
|
||||||
initPageResult,
|
initPageResult,
|
||||||
|
params,
|
||||||
routeSegments: [],
|
routeSegments: [],
|
||||||
searchParams,
|
searchParams,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
import type { EditViewComponent } from 'payload/config'
|
import type { EditViewComponent } from 'payload/config'
|
||||||
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types'
|
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types'
|
||||||
|
|
||||||
export const getCustomViewByPath = (
|
import { isPathMatchingRoute } from '../Root/isPathMatchingRoute.js'
|
||||||
|
|
||||||
|
export const getCustomViewByRoute = ({
|
||||||
|
baseRoute,
|
||||||
|
currentRoute,
|
||||||
|
views,
|
||||||
|
}: {
|
||||||
|
baseRoute: string
|
||||||
|
currentRoute: string
|
||||||
views:
|
views:
|
||||||
| SanitizedCollectionConfig['admin']['components']['views']
|
| SanitizedCollectionConfig['admin']['components']['views']
|
||||||
| SanitizedGlobalConfig['admin']['components']['views'],
|
| SanitizedGlobalConfig['admin']['components']['views']
|
||||||
path: string,
|
}): EditViewComponent => {
|
||||||
): EditViewComponent => {
|
|
||||||
if (typeof views?.Edit === 'object' && typeof views?.Edit !== 'function') {
|
if (typeof views?.Edit === 'object' && typeof views?.Edit !== 'function') {
|
||||||
const foundViewConfig = Object.entries(views.Edit).find(([, view]) => {
|
const foundViewConfig = Object.entries(views.Edit).find(([, view]) => {
|
||||||
if (typeof view === 'object' && typeof view !== 'function' && 'path' in view) {
|
if (typeof view === 'object' && typeof view !== 'function' && 'path' in view) {
|
||||||
return view.path === path
|
const viewPath = `${baseRoute}${view.path}`
|
||||||
|
|
||||||
|
return isPathMatchingRoute({
|
||||||
|
currentRoute,
|
||||||
|
exact: true,
|
||||||
|
path: viewPath,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})?.[1]
|
})?.[1]
|
||||||
@@ -8,15 +8,17 @@ import type {
|
|||||||
} from 'payload/types'
|
} from 'payload/types'
|
||||||
|
|
||||||
import { isEntityHidden } from 'payload/utilities'
|
import { isEntityHidden } from 'payload/utilities'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
import { APIView as DefaultAPIView } from '../API/index.js'
|
import { APIView as DefaultAPIView } from '../API/index.js'
|
||||||
import { EditView as DefaultEditView } from '../Edit/index.js'
|
import { EditView as DefaultEditView } from '../Edit/index.js'
|
||||||
import { LivePreviewView as DefaultLivePreviewView } from '../LivePreview/index.js'
|
import { LivePreviewView as DefaultLivePreviewView } from '../LivePreview/index.js'
|
||||||
|
import { NotFoundClient } from '../NotFound/index.client.js'
|
||||||
import { Unauthorized } from '../Unauthorized/index.js'
|
import { Unauthorized } from '../Unauthorized/index.js'
|
||||||
import { VersionView as DefaultVersionView } from '../Version/index.js'
|
import { VersionView as DefaultVersionView } from '../Version/index.js'
|
||||||
import { VersionsView as DefaultVersionsView } from '../Versions/index.js'
|
import { VersionsView as DefaultVersionsView } from '../Versions/index.js'
|
||||||
import { getCustomViewByKey } from './getCustomViewByKey.js'
|
import { getCustomViewByKey } from './getCustomViewByKey.js'
|
||||||
import { getCustomViewByPath } from './getCustomViewByPath.js'
|
import { getCustomViewByRoute } from './getCustomViewByRoute.js'
|
||||||
|
|
||||||
export const getViewsFromConfig = ({
|
export const getViewsFromConfig = ({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
@@ -46,6 +48,10 @@ export const getViewsFromConfig = ({
|
|||||||
let CustomView: EditViewComponent = null
|
let CustomView: EditViewComponent = null
|
||||||
let ErrorView: AdminViewComponent = null
|
let ErrorView: AdminViewComponent = null
|
||||||
|
|
||||||
|
const {
|
||||||
|
routes: { admin: adminRoute },
|
||||||
|
} = config
|
||||||
|
|
||||||
const views =
|
const views =
|
||||||
(collectionConfig && collectionConfig?.admin?.components?.views) ||
|
(collectionConfig && collectionConfig?.admin?.components?.views) ||
|
||||||
(globalConfig && globalConfig?.admin?.components?.views)
|
(globalConfig && globalConfig?.admin?.components?.views)
|
||||||
@@ -65,8 +71,7 @@ export const getViewsFromConfig = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!EditOverride) {
|
if (!EditOverride) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] =
|
||||||
const [collectionEntity, collectionSlug, createOrID, nestedViewSlug, segmentFive] =
|
|
||||||
routeSegments
|
routeSegments
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -78,8 +83,9 @@ export const getViewsFromConfig = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// `../:id`, or `../create`
|
// `../:id`, or `../create`
|
||||||
if (routeSegments.length === 3) {
|
switch (routeSegments.length) {
|
||||||
switch (createOrID) {
|
case 3: {
|
||||||
|
switch (segment3) {
|
||||||
case 'create': {
|
case 'create': {
|
||||||
if ('create' in docPermissions && docPermissions?.create?.permission) {
|
if ('create' in docPermissions && docPermissions?.create?.permission) {
|
||||||
CustomView = getCustomViewByKey(views, 'Default')
|
CustomView = getCustomViewByKey(views, 'Default')
|
||||||
@@ -97,13 +103,15 @@ export const getViewsFromConfig = ({
|
|||||||
} else {
|
} else {
|
||||||
ErrorView = Unauthorized
|
ErrorView = Unauthorized
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// `../:id/api`, `../:id/preview`, `../:id/versions`, etc
|
// `../:id/api`, `../:id/preview`, `../:id/versions`, etc
|
||||||
if (routeSegments?.length === 4) {
|
case 4: {
|
||||||
switch (nestedViewSlug) {
|
switch (segment4) {
|
||||||
case 'api': {
|
case 'api': {
|
||||||
if (collectionConfig?.admin?.hideAPIURL !== true) {
|
if (collectionConfig?.admin?.hideAPIURL !== true) {
|
||||||
CustomView = getCustomViewByKey(views, 'API')
|
CustomView = getCustomViewByKey(views, 'API')
|
||||||
@@ -130,22 +138,55 @@ export const getViewsFromConfig = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
const path = `/${nestedViewSlug}`
|
const baseRoute = [adminRoute, 'collections', collectionSlug, segment3]
|
||||||
CustomView = getCustomViewByPath(views, path)
|
.filter(Boolean)
|
||||||
|
.join('/')
|
||||||
|
|
||||||
|
const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/')
|
||||||
|
|
||||||
|
CustomView = getCustomViewByRoute({
|
||||||
|
baseRoute,
|
||||||
|
currentRoute,
|
||||||
|
views,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!CustomView) ErrorView = () => <NotFoundClient />
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// `../:id/versions/:version`, etc
|
// `../:id/versions/:version`, etc
|
||||||
if (routeSegments.length === 5) {
|
default: {
|
||||||
if (nestedViewSlug === 'versions') {
|
if (segment4 === 'versions') {
|
||||||
if (docPermissions?.readVersions?.permission) {
|
if (docPermissions?.readVersions?.permission) {
|
||||||
CustomView = getCustomViewByKey(views, 'Version')
|
CustomView = getCustomViewByKey(views, 'Version')
|
||||||
DefaultView = DefaultVersionView
|
DefaultView = DefaultVersionView
|
||||||
} else {
|
} else {
|
||||||
ErrorView = Unauthorized
|
ErrorView = Unauthorized
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const baseRoute = [adminRoute, collectionEntity, collectionSlug, segment3]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/')
|
||||||
|
|
||||||
|
const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/')
|
||||||
|
|
||||||
|
CustomView = getCustomViewByRoute({
|
||||||
|
baseRoute,
|
||||||
|
currentRoute,
|
||||||
|
views,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!CustomView) ErrorView = () => <NotFoundClient />
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +202,7 @@ export const getViewsFromConfig = ({
|
|||||||
|
|
||||||
if (!EditOverride) {
|
if (!EditOverride) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const [globalEntity, globalSlug, nestedViewSlug] = routeSegments
|
const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
|
||||||
|
|
||||||
const {
|
const {
|
||||||
admin: { hidden },
|
admin: { hidden },
|
||||||
@@ -171,18 +212,20 @@ export const getViewsFromConfig = ({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeSegments?.length === 2) {
|
switch (routeSegments.length) {
|
||||||
|
case 2: {
|
||||||
if (docPermissions?.read?.permission) {
|
if (docPermissions?.read?.permission) {
|
||||||
CustomView = getCustomViewByKey(views, 'Default')
|
CustomView = getCustomViewByKey(views, 'Default')
|
||||||
DefaultView = DefaultEditView
|
DefaultView = DefaultEditView
|
||||||
} else {
|
} else {
|
||||||
ErrorView = Unauthorized
|
ErrorView = Unauthorized
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeSegments?.length === 3) {
|
case 3: {
|
||||||
// `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
|
// `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
|
||||||
switch (nestedViewSlug) {
|
switch (segment3) {
|
||||||
case 'api': {
|
case 'api': {
|
||||||
if (globalConfig?.admin?.hideAPIURL !== true) {
|
if (globalConfig?.admin?.hideAPIURL !== true) {
|
||||||
CustomView = getCustomViewByKey(views, 'API')
|
CustomView = getCustomViewByKey(views, 'API')
|
||||||
@@ -218,17 +261,34 @@ export const getViewsFromConfig = ({
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (routeSegments?.length === 4) {
|
default: {
|
||||||
// `../:slug/versions/:version`, etc
|
// `../:slug/versions/:version`, etc
|
||||||
if (nestedViewSlug === 'versions') {
|
if (segment3 === 'versions') {
|
||||||
if (docPermissions?.readVersions?.permission) {
|
if (docPermissions?.readVersions?.permission) {
|
||||||
CustomView = getCustomViewByKey(views, 'Version')
|
CustomView = getCustomViewByKey(views, 'Version')
|
||||||
DefaultView = DefaultVersionView
|
DefaultView = DefaultVersionView
|
||||||
} else {
|
} else {
|
||||||
ErrorView = Unauthorized
|
ErrorView = Unauthorized
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const baseRoute = [adminRoute, 'globals', globalSlug].filter(Boolean).join('/')
|
||||||
|
|
||||||
|
const currentRoute = [baseRoute, segment3, ...remainingSegments]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('/')
|
||||||
|
|
||||||
|
CustomView = getCustomViewByRoute({
|
||||||
|
baseRoute,
|
||||||
|
currentRoute,
|
||||||
|
views,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!CustomView) ErrorView = () => <NotFoundClient />
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,14 @@
|
|||||||
import type { EditViewComponent } from 'payload/config'
|
import type { EditViewComponent } from 'payload/config'
|
||||||
import type {
|
import type { AdminViewComponent, ServerSideEditViewProps } from 'payload/types'
|
||||||
AdminViewComponent,
|
|
||||||
DocumentPreferences,
|
|
||||||
Document as DocumentType,
|
|
||||||
Field,
|
|
||||||
ServerSideEditViewProps,
|
|
||||||
} from 'payload/types'
|
|
||||||
import type { DocumentPermissions } from 'payload/types'
|
import type { DocumentPermissions } from 'payload/types'
|
||||||
import type { AdminViewProps } from 'payload/types'
|
import type { AdminViewProps } from 'payload/types'
|
||||||
|
|
||||||
import { DocumentHeader } from '@payloadcms/ui/elements/DocumentHeader'
|
import { DocumentHeader } from '@payloadcms/ui/elements/DocumentHeader'
|
||||||
import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser'
|
import { HydrateClientUser } from '@payloadcms/ui/elements/HydrateClientUser'
|
||||||
import { RenderCustomComponent } from '@payloadcms/ui/elements/RenderCustomComponent'
|
import { RenderCustomComponent } from '@payloadcms/ui/elements/RenderCustomComponent'
|
||||||
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
|
|
||||||
import { DocumentInfoProvider } from '@payloadcms/ui/providers/DocumentInfo'
|
import { DocumentInfoProvider } from '@payloadcms/ui/providers/DocumentInfo'
|
||||||
import { EditDepthProvider } from '@payloadcms/ui/providers/EditDepth'
|
import { EditDepthProvider } from '@payloadcms/ui/providers/EditDepth'
|
||||||
import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParams'
|
import { FormQueryParamsProvider } from '@payloadcms/ui/providers/FormQueryParams'
|
||||||
import { formatDocTitle } from '@payloadcms/ui/utilities/formatDocTitle'
|
|
||||||
import { formatFields } from '@payloadcms/ui/utilities/formatFields'
|
|
||||||
import { docAccessOperation } from 'payload/operations'
|
import { docAccessOperation } from 'payload/operations'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
@@ -222,6 +213,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
|||||||
|
|
||||||
const viewComponentProps: ServerSideEditViewProps = {
|
const viewComponentProps: ServerSideEditViewProps = {
|
||||||
initPageResult,
|
initPageResult,
|
||||||
|
params,
|
||||||
routeSegments: segments,
|
routeSegments: segments,
|
||||||
searchParams,
|
searchParams,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AdminViewComponent, SanitizedConfig } from 'payload/types'
|
import type { AdminViewComponent, SanitizedConfig } from 'payload/types'
|
||||||
|
|
||||||
import { pathToRegexp } from 'path-to-regexp'
|
import { isPathMatchingRoute } from './isPathMatchingRoute.js'
|
||||||
|
|
||||||
export const getCustomViewByRoute = ({
|
export const getCustomViewByRoute = ({
|
||||||
config,
|
config,
|
||||||
@@ -23,22 +23,13 @@ export const getCustomViewByRoute = ({
|
|||||||
typeof views === 'object' &&
|
typeof views === 'object' &&
|
||||||
Object.entries(views).find(([, view]) => {
|
Object.entries(views).find(([, view]) => {
|
||||||
if (typeof view === 'object') {
|
if (typeof view === 'object') {
|
||||||
const { exact, path: viewPath, sensitive, strict } = view
|
return isPathMatchingRoute({
|
||||||
|
currentRoute,
|
||||||
const keys = []
|
exact: view.exact,
|
||||||
|
path: view.path,
|
||||||
// run the view path through `pathToRegexp` to resolve any dynamic segments
|
sensitive: view.sensitive,
|
||||||
// i.e. `/admin/custom-view/:id` -> `/admin/custom-view/123`
|
strict: view.strict,
|
||||||
const regex = pathToRegexp(viewPath, keys, {
|
|
||||||
sensitive,
|
|
||||||
strict,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const match = regex.exec(currentRoute)
|
|
||||||
const viewRoute = match?.[0] || viewPath
|
|
||||||
|
|
||||||
if (exact) return currentRoute === viewRoute
|
|
||||||
if (!exact) return viewRoute.startsWith(currentRoute)
|
|
||||||
}
|
}
|
||||||
})?.[1]
|
})?.[1]
|
||||||
|
|
||||||
|
|||||||
30
packages/next/src/views/Root/isPathMatchingRoute.ts
Normal file
30
packages/next/src/views/Root/isPathMatchingRoute.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { pathToRegexp } from 'path-to-regexp'
|
||||||
|
|
||||||
|
export const isPathMatchingRoute = ({
|
||||||
|
currentRoute,
|
||||||
|
exact,
|
||||||
|
path: viewPath,
|
||||||
|
sensitive,
|
||||||
|
strict,
|
||||||
|
}: {
|
||||||
|
currentRoute: string
|
||||||
|
exact?: boolean
|
||||||
|
path?: string
|
||||||
|
sensitive?: boolean
|
||||||
|
strict?: boolean
|
||||||
|
}) => {
|
||||||
|
const keys = []
|
||||||
|
|
||||||
|
// run the view path through `pathToRegexp` to resolve any dynamic segments
|
||||||
|
// i.e. `/admin/custom-view/:id` -> `/admin/custom-view/123`
|
||||||
|
const regex = pathToRegexp(viewPath, keys, {
|
||||||
|
sensitive,
|
||||||
|
strict,
|
||||||
|
})
|
||||||
|
|
||||||
|
const match = regex.exec(currentRoute)
|
||||||
|
const viewRoute = match?.[0] || viewPath
|
||||||
|
|
||||||
|
if (exact) return currentRoute === viewRoute
|
||||||
|
if (!exact) return viewRoute.startsWith(currentRoute)
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ export type InitPageResult = {
|
|||||||
|
|
||||||
export type ServerSideEditViewProps = {
|
export type ServerSideEditViewProps = {
|
||||||
initPageResult: InitPageResult
|
initPageResult: InitPageResult
|
||||||
|
params: { [key: string]: string | string[] | undefined }
|
||||||
routeSegments: string[]
|
routeSegments: string[]
|
||||||
searchParams: { [key: string]: string | string[] | undefined }
|
searchParams: { [key: string]: string | string[] | undefined }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
SanitizedGlobalConfig,
|
SanitizedGlobalConfig,
|
||||||
} from 'payload/types'
|
} from 'payload/types'
|
||||||
|
|
||||||
import { isPlainObject } from 'packages/payload/src/utilities/isPlainObject.js'
|
import { isPlainObject } from 'payload/utilities'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { ShouldRenderTabs } from './ShouldRenderTabs.js'
|
import { ShouldRenderTabs } from './ShouldRenderTabs.js'
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import type { CollectionConfig } from 'payload/types'
|
import type { CollectionConfig } from 'payload/types'
|
||||||
|
|
||||||
import { CustomTabComponent } from '../components/CustomTabComponent/index.js'
|
import { CustomTabComponent } from '../components/CustomTabComponent/index.js'
|
||||||
import { CustomTabView } from '../components/views/CustomTab/index.js'
|
import { CustomTabComponentView } from '../components/views/CustomTabComponent/index.js'
|
||||||
import { CustomTabView2 } from '../components/views/CustomTab2/index.js'
|
import { CustomTabLabelView } from '../components/views/CustomTabLabel/index.js'
|
||||||
import { CustomNestedTabView } from '../components/views/CustomTabNested/index.js'
|
import { CustomNestedTabView } from '../components/views/CustomTabNested/index.js'
|
||||||
|
import { CustomTabWithParamView } from '../components/views/CustomTabWithParam/index.js'
|
||||||
import { CustomVersionsView } from '../components/views/CustomVersions/index.js'
|
import { CustomVersionsView } from '../components/views/CustomVersions/index.js'
|
||||||
import {
|
import {
|
||||||
|
customCollectionParamViewPath,
|
||||||
|
customCollectionParamViewPathBase,
|
||||||
customEditLabel,
|
customEditLabel,
|
||||||
customNestedTabViewPath,
|
customNestedTabViewPath,
|
||||||
customTabLabel,
|
customTabLabel,
|
||||||
@@ -26,7 +29,7 @@ export const CustomViews2: CollectionConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
MyCustomView: {
|
MyCustomView: {
|
||||||
Component: CustomTabView,
|
Component: CustomTabLabelView,
|
||||||
Tab: {
|
Tab: {
|
||||||
href: '/custom-tab-view',
|
href: '/custom-tab-view',
|
||||||
label: customTabLabel,
|
label: customTabLabel,
|
||||||
@@ -34,15 +37,27 @@ export const CustomViews2: CollectionConfig = {
|
|||||||
path: '/custom-tab-view',
|
path: '/custom-tab-view',
|
||||||
},
|
},
|
||||||
MyCustomViewWithCustomTab: {
|
MyCustomViewWithCustomTab: {
|
||||||
Component: CustomTabView2,
|
Component: CustomTabComponentView,
|
||||||
Tab: CustomTabComponent,
|
Tab: CustomTabComponent,
|
||||||
path: customTabViewPath,
|
path: customTabViewPath,
|
||||||
},
|
},
|
||||||
MyCustomViewWithNestedPath: {
|
MyCustomViewWithNestedPath: {
|
||||||
Component: CustomNestedTabView,
|
Component: CustomNestedTabView,
|
||||||
path: customNestedTabViewPath,
|
path: customNestedTabViewPath,
|
||||||
|
tab: {
|
||||||
|
label: 'Custom Nested Tab View',
|
||||||
|
href: customNestedTabViewPath,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Versions: CustomVersionsView,
|
Versions: CustomVersionsView,
|
||||||
|
CustomViewWithParam: {
|
||||||
|
Component: CustomTabWithParamView,
|
||||||
|
path: customCollectionParamViewPath,
|
||||||
|
Tab: {
|
||||||
|
label: 'Custom Param View',
|
||||||
|
href: `${customCollectionParamViewPathBase}/123`,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import LinkImport from 'next/link'
|
import { useConfig } from '@payloadcms/ui/providers/Config'
|
||||||
import { usePathname } from 'next/navigation'
|
import LinkImport from 'next/link.js'
|
||||||
|
import { useParams } from 'next/navigation'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||||
|
|
||||||
export const CustomTabComponentClient: React.FC<{
|
export const CustomTabComponentClient: React.FC<{
|
||||||
path: string
|
path: string
|
||||||
}> = (props) => {
|
}> = ({ path }) => {
|
||||||
const { path } = props
|
const {
|
||||||
const pathname = usePathname()
|
routes: { admin: adminRoute },
|
||||||
|
} = useConfig()
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
return <Link href={`${pathname}${path}`}>Custom Tab Component</Link>
|
const baseRoute = (params.segments.slice(0, 3) as string[]).join('/')
|
||||||
|
|
||||||
|
return <Link href={`${adminRoute}/${baseRoute}${path}`}>Custom Tab Component</Link>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// As this is the demo folder, we import Payload SCSS functions relatively.
|
// As this is the demo folder, we import Payload SCSS functions relatively.
|
||||||
@import '../../../../../packages/ui/src/exports/scss.scss';
|
@import '../../../../../packages/ui/src/scss/styles.scss';
|
||||||
|
|
||||||
// In your own projects, you'd import as follows:
|
// In your own projects, you'd import as follows:
|
||||||
// @import '~payload/scss';
|
// @import '~payload/scss';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// As this is the demo folder, we import Payload SCSS functions relatively.
|
// As this is the demo folder, we import Payload SCSS functions relatively.
|
||||||
@import '../../../../../packages/ui/src/exports/scss.scss';
|
@import '../../../../../packages/ui/src/scss/styles.scss';
|
||||||
|
|
||||||
// In your own projects, you'd import as follows:
|
// In your own projects, you'd import as follows:
|
||||||
// @import '~payload/scss';
|
// @import '~payload/scss';
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import React, { Fragment } from 'react'
|
|||||||
|
|
||||||
import type { ServerSideEditViewProps } from '../../../../../packages/payload/types.js'
|
import type { ServerSideEditViewProps } from '../../../../../packages/payload/types.js'
|
||||||
|
|
||||||
import { customTabViewTitle } from '../../../shared.js'
|
import { customTabViewComponentTitle } from '../../../shared.js'
|
||||||
|
|
||||||
export const CustomTabView2: React.FC<ServerSideEditViewProps> = ({ initPageResult }) => {
|
export const CustomTabComponentView: React.FC<ServerSideEditViewProps> = ({ initPageResult }) => {
|
||||||
if (!initPageResult) {
|
if (!initPageResult) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ export const CustomTabView2: React.FC<ServerSideEditViewProps> = ({ initPageResu
|
|||||||
paddingRight: 'var(--gutter-h)',
|
paddingRight: 'var(--gutter-h)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1 id="custom-view-title">{customTabViewTitle}</h1>
|
<h1 id="custom-view-title">{customTabViewComponentTitle}</h1>
|
||||||
<p>This custom view was added through the Payload config:</p>
|
<p>This custom view was added through the Payload config:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@@ -4,9 +4,9 @@ import React, { Fragment } from 'react'
|
|||||||
|
|
||||||
import type { ServerSideEditViewProps } from '../../../../../packages/payload/types.js'
|
import type { ServerSideEditViewProps } from '../../../../../packages/payload/types.js'
|
||||||
|
|
||||||
import { customTabViewTitle } from '../../../shared.js'
|
import { customTabLabelViewTitle } from '../../../shared.js'
|
||||||
|
|
||||||
export const CustomTabView: React.FC<ServerSideEditViewProps> = ({ initPageResult }) => {
|
export const CustomTabLabelView: React.FC<ServerSideEditViewProps> = ({ initPageResult }) => {
|
||||||
if (!initPageResult) {
|
if (!initPageResult) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ export const CustomTabView: React.FC<ServerSideEditViewProps> = ({ initPageResul
|
|||||||
paddingRight: 'var(--gutter-h)',
|
paddingRight: 'var(--gutter-h)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1 id="custom-view-title">{customTabViewTitle}</h1>
|
<h1 id="custom-view-title">{customTabLabelViewTitle}</h1>
|
||||||
<p>This custom view was added through the Payload config:</p>
|
<p>This custom view was added through the Payload config:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
24
test/admin/components/views/CustomTabWithParam/index.tsx
Normal file
24
test/admin/components/views/CustomTabWithParam/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { AdminViewProps } from 'payload/types.js'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { customParamViewTitle } from '../../../shared.js'
|
||||||
|
|
||||||
|
export const CustomTabWithParamView: React.FC<AdminViewProps> = ({ params }) => {
|
||||||
|
const paramValue = params?.segments?.[4]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 'calc(var(--base) * 2)',
|
||||||
|
paddingLeft: 'var(--gutter-h)',
|
||||||
|
paddingRight: 'var(--gutter-h)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 id="custom-view-title">{customParamViewTitle}</h1>
|
||||||
|
<p>
|
||||||
|
This custom collection view is using a dynamic URL parameter `slug: {paramValue || 'None'}`
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,7 +2,8 @@ import type { GlobalConfig } from 'payload/types'
|
|||||||
|
|
||||||
import { CustomTabComponent } from '../components/CustomTabComponent/index.js'
|
import { CustomTabComponent } from '../components/CustomTabComponent/index.js'
|
||||||
import { CustomDefaultEditView } from '../components/views/CustomEditDefault/index.js'
|
import { CustomDefaultEditView } from '../components/views/CustomEditDefault/index.js'
|
||||||
import { CustomTabView } from '../components/views/CustomTab/index.js'
|
import { CustomTabComponentView } from '../components/views/CustomTabComponent/index.js'
|
||||||
|
import { CustomTabLabelView } from '../components/views/CustomTabLabel/index.js'
|
||||||
import { CustomVersionsView } from '../components/views/CustomVersions/index.js'
|
import { CustomVersionsView } from '../components/views/CustomVersions/index.js'
|
||||||
import { customGlobalViews2GlobalSlug } from '../slugs.js'
|
import { customGlobalViews2GlobalSlug } from '../slugs.js'
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ export const CustomGlobalViews2: GlobalConfig = {
|
|||||||
Edit: {
|
Edit: {
|
||||||
Default: CustomDefaultEditView,
|
Default: CustomDefaultEditView,
|
||||||
MyCustomView: {
|
MyCustomView: {
|
||||||
Component: CustomTabView,
|
Component: CustomTabLabelView,
|
||||||
Tab: {
|
Tab: {
|
||||||
href: '/custom-tab-view',
|
href: '/custom-tab-view',
|
||||||
label: 'Custom',
|
label: 'Custom',
|
||||||
@@ -22,7 +23,7 @@ export const CustomGlobalViews2: GlobalConfig = {
|
|||||||
path: '/custom-tab-view',
|
path: '/custom-tab-view',
|
||||||
},
|
},
|
||||||
MyCustomViewWithCustomTab: {
|
MyCustomViewWithCustomTab: {
|
||||||
Component: CustomTabView,
|
Component: CustomTabComponentView,
|
||||||
Tab: CustomTabComponent,
|
Tab: CustomTabComponent,
|
||||||
path: '/custom-tab-component',
|
path: '/custom-tab-component',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,8 +22,14 @@ export const customTabLabel = 'Custom Tab Label'
|
|||||||
|
|
||||||
export const customTabViewPath = '/custom-tab-component'
|
export const customTabViewPath = '/custom-tab-component'
|
||||||
|
|
||||||
export const customTabViewTitle = 'Custom View With Tab Component'
|
export const customTabLabelViewTitle = 'Custom Tab Label View'
|
||||||
|
|
||||||
|
export const customTabViewComponentTitle = 'Custom View With Tab Component'
|
||||||
|
|
||||||
export const customNestedTabViewPath = `${customTabViewPath}/nested-view`
|
export const customNestedTabViewPath = `${customTabViewPath}/nested-view`
|
||||||
|
|
||||||
export const customNestedTabViewTitle = 'Custom Nested Tab View'
|
export const customNestedTabViewTitle = 'Custom Nested Tab View'
|
||||||
|
|
||||||
|
export const customCollectionParamViewPathBase = '/custom-param'
|
||||||
|
|
||||||
|
export const customCollectionParamViewPath = `${customCollectionParamViewPathBase}/:slug`
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@payload-config": ["./test/_community/config.ts"],
|
"@payload-config": ["./test/admin/config.ts"],
|
||||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||||
"@payloadcms/ui/assets": ["./packages/ui/src/assets/index.ts"],
|
"@payloadcms/ui/assets": ["./packages/ui/src/assets/index.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user