feat: view component types (#11126)
It is currently very difficult to build custom edit and list views or inject custom components into these views because these views and components are not explicitly typed. Instances of these components were not fully type safe as well, i.e. when rendering them via `RenderServerComponent`, there was little to no type-checking in most cases. There is now a 1:1 type match for all views and view components and they now receive type-checking at render time. The following types have been newly added and/or improved: List View: - `ListViewClientProps` - `ListViewServerProps` - `BeforeListClientProps` - `BeforeListServerProps` - `BeforeListTableClientProps` - `BeforeListTableServerProps` - `AfterListClientProps` - `AfterListServerProps` - `AfterListTableClientProps` - `AfterListTableServerProps` - `ListViewSlotSharedClientProps` Document View: - `DocumentViewClientProps` - `DocumentViewServerProps` - `SaveButtonClientProps` - `SaveButtonServerProps` - `SaveDraftButtonClientProps` - `SaveDraftButtonServerProps` - `PublishButtonClientProps` - `PublishButtonServerProps` - `PreviewButtonClientProps` - `PreviewButtonServerProps` Root View: - `AdminViewClientProps` - `AdminViewServerProps` General: - `ViewDescriptionClientProps` - `ViewDescriptionServerProps` A few other changes were made in a non-breaking way: - `Column` is now exported from `payload` - `ListPreferences` is now exported from `payload` - `ListViewSlots` is now exported from `payload` - `ListViewClientProps` is now exported from `payload` - `AdminViewProps` is now an alias of `AdminViewServerProps` (listed above) - `ClientSideEditViewProps` is now an alias of `DocumentViewClientProps` (listed above) - `ServerSideEditViewProps` is now an alias of `DocumentViewServerProps` (listed above) - `ListComponentClientProps` is now an alias of `ListViewClientProps` (listed above) - `ListComponentServerProps` is now an alias of `ListViewServerProps` (listed above) - `CustomSaveButton` is now marked as deprecated because this is only relevant to the config (see correct type above) - `CustomSaveDraftButton` is now marked as deprecated because this is only relevant to the config (see correct type above) - `CustomPublishButton` is now marked as deprecated because this is only relevant to the config (see correct type above) - `CustomPreviewButton` is now marked as deprecated because this is only relevant to the config (see correct type above) This PR _does not_ apply these changes to _root_ components, i.e. `afterNavLinks`. Those will come in a future PR. Related: #10987.
This commit is contained in:
@@ -9,14 +9,14 @@ export const Geo: CollectionConfig = {
|
||||
views: {
|
||||
edit: {
|
||||
api: {
|
||||
actions: ['/components/CollectionAPIButton/index.js#CollectionAPIButton'],
|
||||
actions: ['/components/actions/CollectionAPIButton/index.js#CollectionAPIButton'],
|
||||
},
|
||||
default: {
|
||||
actions: ['/components/CollectionEditButton/index.js#CollectionEditButton'],
|
||||
actions: ['/components/actions/CollectionEditButton/index.js#CollectionEditButton'],
|
||||
},
|
||||
},
|
||||
list: {
|
||||
actions: ['/components/CollectionListButton/index.js#CollectionListButton'],
|
||||
actions: ['/components/actions/CollectionListButton/index.js#CollectionListButton'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { DocumentTabClientProps } from 'payload'
|
||||
|
||||
import { useConfig } from '@payloadcms/ui'
|
||||
import LinkImport from 'next/link.js'
|
||||
import { useParams } from 'next/navigation.js'
|
||||
@@ -7,9 +9,7 @@ import React from 'react'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
export const CustomTabComponentClient: React.FC<{
|
||||
readonly path: string
|
||||
}> = ({ path }) => {
|
||||
export function CustomTabComponentClient({ path }: DocumentTabClientProps) {
|
||||
const {
|
||||
config: {
|
||||
routes: { admin: adminRoute },
|
||||
@@ -18,7 +18,7 @@ export const CustomTabComponentClient: React.FC<{
|
||||
|
||||
const params = useParams()
|
||||
|
||||
const baseRoute = (params.segments.slice(0, 3) as string[]).join('/')
|
||||
const baseRoute = (params.segments?.slice(0, 3) as string[]).join('/')
|
||||
|
||||
return <Link href={`${adminRoute}/${baseRoute}${path}`}>Custom Tab Component</Link>
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { DocumentTabComponent } from 'payload'
|
||||
import type { DocumentTabServerProps } from 'payload'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { CustomTabComponentClient } from './client.js'
|
||||
import './index.scss'
|
||||
|
||||
export const CustomTabComponent: DocumentTabComponent = (props) => {
|
||||
export function CustomTabComponent(props: DocumentTabServerProps) {
|
||||
const { path } = props
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use client'
|
||||
|
||||
import type { StaticDescription } from 'payload'
|
||||
import type { ViewDescriptionClientProps } from 'payload'
|
||||
|
||||
import { ViewDescription as DefaultViewDescription } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import { Banner } from '../Banner/index.js'
|
||||
|
||||
export const ViewDescription: React.FC<{ description: StaticDescription }> = ({
|
||||
export function ViewDescription({
|
||||
description = 'This is a custom view description component.',
|
||||
}) => {
|
||||
}: ViewDescriptionClientProps) {
|
||||
return (
|
||||
<Banner>
|
||||
<DefaultViewDescription description={description} />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AdminViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
export const CustomAccountView: PayloadServerReactComponent<AdminViewComponent> = () => {
|
||||
export function CustomAccountView(props: AdminViewServerProps) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AdminViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
export const CustomDashboardView: PayloadServerReactComponent<AdminViewComponent> = () => {
|
||||
export function CustomDashboardView(props: AdminViewServerProps) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
|
||||
@@ -5,19 +5,16 @@ import React from 'react'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import { Button, SetStepNav } from '@payloadcms/ui'
|
||||
|
||||
import { customViewPath } from '../../../shared.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'custom-default-view'
|
||||
|
||||
export const CustomDefaultView: React.FC<AdminViewProps> = ({
|
||||
initPageResult,
|
||||
params,
|
||||
searchParams,
|
||||
}) => {
|
||||
export function CustomDefaultView({ initPageResult, params, searchParams }: AdminViewServerProps) {
|
||||
const {
|
||||
permissions,
|
||||
req: {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { EditViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
import type { DocumentViewServerProps } from 'payload'
|
||||
|
||||
import { SetStepNav } from '@payloadcms/ui'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
export const CustomEditView: PayloadServerReactComponent<EditViewComponent> = ({
|
||||
initPageResult,
|
||||
}) => {
|
||||
export function CustomEditView({ initPageResult }: DocumentViewServerProps) {
|
||||
if (!initPageResult) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { EditViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
import type { DocumentViewServerProps } from 'payload'
|
||||
|
||||
import { SetStepNav } from '@payloadcms/ui'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
export const CustomDefaultEditView: PayloadServerReactComponent<EditViewComponent> = ({
|
||||
initPageResult,
|
||||
}) => {
|
||||
export function CustomDefaultEditView({ initPageResult }: DocumentViewServerProps) {
|
||||
if (!initPageResult) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.
|
||||
// import { Button } from 'payload/components/elements';
|
||||
// import { useConfig } from 'payload/components/utilities';
|
||||
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import { MinimalTemplate } from '@payloadcms/next/templates'
|
||||
import { Button } from '@payloadcms/ui'
|
||||
@@ -20,7 +20,7 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'custom-minimal-view'
|
||||
|
||||
export const CustomMinimalView: React.FC<AdminViewProps> = ({ initPageResult }) => {
|
||||
export function CustomMinimalView({ initPageResult }: AdminViewServerProps) {
|
||||
const {
|
||||
req: {
|
||||
payload: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import { Button } from '@payloadcms/ui'
|
||||
import LinkImport from 'next/link.js'
|
||||
@@ -10,7 +10,7 @@ import { settingsGlobalSlug } from '../../../slugs.js'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
export const CustomProtectedView: React.FC<AdminViewProps> = async ({ initPageResult }) => {
|
||||
export async function CustomProtectedView({ initPageResult }: AdminViewServerProps) {
|
||||
const {
|
||||
req: {
|
||||
payload: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ServerSideEditViewProps } from 'payload'
|
||||
import type { DocumentViewServerProps } from 'payload'
|
||||
|
||||
import { SetStepNav } from '@payloadcms/ui'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
@@ -6,7 +6,7 @@ import React, { Fragment } from 'react'
|
||||
|
||||
import { customTabViewComponentTitle } from '../../../shared.js'
|
||||
|
||||
export const CustomTabComponentView: React.FC<ServerSideEditViewProps> = ({ initPageResult }) => {
|
||||
export function CustomTabComponentView({ initPageResult }: DocumentViewServerProps) {
|
||||
if (!initPageResult) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EditViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
import type { DocumentViewServerProps } from 'payload'
|
||||
|
||||
import { SetStepNav } from '@payloadcms/ui'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
@@ -6,9 +6,7 @@ import React, { Fragment } from 'react'
|
||||
|
||||
import { customTabLabelViewTitle } from '../../../shared.js'
|
||||
|
||||
export const CustomTabLabelView: PayloadServerReactComponent<EditViewComponent> = ({
|
||||
initPageResult,
|
||||
}) => {
|
||||
export function CustomTabLabelView({ initPageResult }: DocumentViewServerProps) {
|
||||
if (!initPageResult) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EditViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
import type { DocumentViewServerProps } from 'payload'
|
||||
|
||||
import { SetStepNav } from '@payloadcms/ui'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
@@ -6,9 +6,7 @@ import React, { Fragment } from 'react'
|
||||
|
||||
import { customNestedTabViewTitle } from '../../../shared.js'
|
||||
|
||||
export const CustomNestedTabView: PayloadServerReactComponent<EditViewComponent> = ({
|
||||
initPageResult,
|
||||
}) => {
|
||||
export function CustomNestedTabView({ initPageResult }: DocumentViewServerProps) {
|
||||
if (!initPageResult) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { DocumentViewServerProps } from 'payload'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { customParamViewTitle } from '../../../shared.js'
|
||||
|
||||
export const CustomTabWithParamView: React.FC<AdminViewProps> = ({ params }) => {
|
||||
export function CustomTabWithParamView({ params }: DocumentViewServerProps) {
|
||||
const paramValue = params?.segments?.[4]
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import type { EditViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
import type { DocumentViewServerProps } from 'payload'
|
||||
|
||||
import { SetStepNav } from '@payloadcms/ui'
|
||||
import { notFound, redirect } from 'next/navigation.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
export const CustomVersionsView: PayloadServerReactComponent<EditViewComponent> = ({
|
||||
initPageResult,
|
||||
}) => {
|
||||
export function CustomVersionsView({ initPageResult }: DocumentViewServerProps) {
|
||||
if (!initPageResult) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import LinkImport from 'next/link.js'
|
||||
import React from 'react'
|
||||
@@ -10,7 +10,7 @@ import { Button } from '@payloadcms/ui'
|
||||
import { customNestedViewPath, customViewTitle } from '../../../shared.js'
|
||||
import { ClientForm } from './index.client.js'
|
||||
|
||||
export const CustomView: React.FC<AdminViewProps> = ({ initPageResult }) => {
|
||||
export function CustomView({ initPageResult }: AdminViewServerProps) {
|
||||
const {
|
||||
req: {
|
||||
payload: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import { Button } from '@payloadcms/ui'
|
||||
import LinkImport from 'next/link.js'
|
||||
@@ -8,7 +8,7 @@ import { customNestedViewTitle, customViewPath } from '../../../shared.js'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
export const CustomNestedView: React.FC<AdminViewProps> = ({ initPageResult }) => {
|
||||
export function CustomNestedView({ initPageResult }: AdminViewServerProps) {
|
||||
const {
|
||||
req: {
|
||||
payload: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from 'react'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
|
||||
import type { AdminViewProps } from 'payload'
|
||||
import type { AdminViewServerProps } from 'payload'
|
||||
|
||||
import {
|
||||
customParamViewPath,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
customParamViewTitle,
|
||||
} from '../../../shared.js'
|
||||
|
||||
export const CustomViewWithParam: React.FC<AdminViewProps> = ({ initPageResult, params }) => {
|
||||
export function CustomViewWithParam({ initPageResult, params }: AdminViewServerProps) {
|
||||
const {
|
||||
req: {
|
||||
payload: {
|
||||
|
||||
@@ -16,7 +16,6 @@ import { CollectionGroup2B } from './collections/Group2B.js'
|
||||
import { CollectionHidden } from './collections/Hidden.js'
|
||||
import { CollectionNoApiView } from './collections/NoApiView.js'
|
||||
import { CollectionNotInView } from './collections/NotInView.js'
|
||||
import { Orders } from './collections/Orders.js'
|
||||
import { Posts } from './collections/Posts.js'
|
||||
import { UploadCollection } from './collections/Upload.js'
|
||||
import { Users } from './collections/Users.js'
|
||||
@@ -47,7 +46,7 @@ export default buildConfigWithDefaults({
|
||||
baseDir: path.resolve(dirname),
|
||||
},
|
||||
components: {
|
||||
actions: ['/components/AdminButton/index.js#AdminButton'],
|
||||
actions: ['/components/actions/AdminButton/index.js#AdminButton'],
|
||||
afterDashboard: [
|
||||
'/components/AfterDashboard/index.js#AfterDashboard',
|
||||
'/components/AfterDashboardClient/index.js#AfterDashboardClient',
|
||||
|
||||
@@ -9,10 +9,10 @@ export const Global: GlobalConfig = {
|
||||
views: {
|
||||
edit: {
|
||||
api: {
|
||||
actions: ['/components/GlobalAPIButton/index.js#GlobalAPIButton'],
|
||||
actions: ['/components/actions/GlobalAPIButton/index.js#GlobalAPIButton'],
|
||||
},
|
||||
default: {
|
||||
actions: ['/components/GlobalEditButton/index.js#GlobalEditButton'],
|
||||
actions: ['/components/actions/GlobalEditButton/index.js#GlobalEditButton'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,65 +6,10 @@
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji';
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
uploads: Upload;
|
||||
posts: Post;
|
||||
@@ -332,6 +277,9 @@ export interface CustomField {
|
||||
* Static field description.
|
||||
*/
|
||||
descriptionAsString?: string | null;
|
||||
/**
|
||||
* Function description
|
||||
*/
|
||||
descriptionAsFunction?: string | null;
|
||||
descriptionAsComponent?: string | null;
|
||||
customSelectField?: string | null;
|
||||
|
||||
Reference in New Issue
Block a user