feat(ui): provides payload as prop to all custom server components (#5775)

This commit is contained in:
Jacob Fletcher
2024-04-11 11:16:15 -04:00
committed by GitHub
parent d0869d9087
commit 1275c70187
35 changed files with 324 additions and 133 deletions

View File

@@ -12,6 +12,7 @@ import { createClientConfig } from 'payload/config'
import React from 'react'
import 'react-toastify/dist/ReactToastify.css'
import { getPayloadHMR } from '../../utilities/getPayloadHMR.js'
import { getRequestLanguage } from '../../utilities/getRequestLanguage.js'
import { DefaultEditView } from '../../views/Edit/Default/index.js'
import { DefaultListView } from '../../views/List/Default/index.js'
@@ -39,6 +40,7 @@ export const RootLayout = async ({
headers,
})
const payload = await getPayloadHMR({ config })
const i18n = await initI18n({ config: config.i18n, context: 'client', language: languageCode })
const clientConfig = await createClientConfig({ config, t: i18n.t })
@@ -76,6 +78,7 @@ export const RootLayout = async ({
children,
config,
i18n,
payload,
})
return (

View File

@@ -1,9 +1,10 @@
import type { Field } from 'payload/types'
import type { Field, WithServerSideProps as WithServerSidePropsType } from 'payload/types'
import type { AdminViewProps } from 'payload/types'
import { Form } from '@payloadcms/ui/forms/Form'
import { FormSubmit } from '@payloadcms/ui/forms/Submit'
import { buildStateFromSchema } from '@payloadcms/ui/forms/buildStateFromSchema'
import { WithServerSideProps as WithServerSidePropsGeneric } from '@payloadcms/ui/providers/ComponentMap'
import { mapFields } from '@payloadcms/ui/utilities/buildComponentMap'
import React from 'react'
@@ -17,6 +18,7 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
req,
req: {
i18n,
payload,
payload: {
config,
config: {
@@ -49,7 +51,12 @@ export const CreateFirstUserView: React.FC<AdminViewProps> = async ({ initPageRe
},
]
const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => {
return <WithServerSidePropsGeneric Component={Component} payload={payload} {...rest} />
}
const createFirstUserFieldMap = mapFields({
WithServerSideProps,
config,
fieldSchema: fields,
i18n,

View File

@@ -4,6 +4,7 @@ import type { JSONSchema4 } from 'json-schema'
import type { SanitizedConfig } from '../config/types.js'
import type { Field, RichTextField, Validate } from '../fields/config/types.js'
import type { PayloadRequest, RequestContext } from '../types/index.js'
import type { WithServerSideProps } from './elements/WithServerSideProps.js'
export type RichTextFieldProps<
Value extends object,
@@ -28,6 +29,7 @@ type RichTextAdapterBase<
siblingDoc: Record<string, unknown>
}) => Promise<void> | null
generateComponentMap: (args: {
WithServerSideProps: WithServerSideProps
config: SanitizedConfig
i18n: I18n
schemaPath: string

View File

@@ -1 +1,3 @@
export type CustomPreviewButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomPreviewButton = CustomComponent

View File

@@ -1 +1,3 @@
export type CustomPublishButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomPublishButton = CustomComponent

View File

@@ -1 +1,3 @@
export type CustomSaveButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomSaveButton = CustomComponent

View File

@@ -1 +1,3 @@
export type CustomSaveDraftButton = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type CustomSaveDraftButton = CustomComponent

View File

@@ -0,0 +1,4 @@
export type WithServerSideProps = (args: {
[key: string]: any
Component: React.ComponentType<any>
}) => React.ReactNode

View File

@@ -1,10 +1,11 @@
import type React from 'react'
import type { LabelFunction } from '../../config/types.js'
import type { CustomComponent, LabelFunction } from '../../config/types.js'
import type { Payload } from '../../index.js'
export type DescriptionFunction = LabelFunction
export type DescriptionComponent = React.ComponentType<FieldDescriptionProps>
export type DescriptionComponent = CustomComponent<FieldDescriptionProps>
export type Description =
| DescriptionComponent
@@ -17,4 +18,5 @@ export type FieldDescriptionProps = {
className?: string
description?: Record<string, string> | string
marginPlacement?: 'bottom' | 'top'
payload?: Payload
}

View File

@@ -1,3 +1,5 @@
export type RowLabelComponent = React.ComponentType
import type { CustomComponent } from '../../config/types.js'
export type RowLabelComponent = CustomComponent
export type RowLabel = Record<string, string> | RowLabelComponent | string

View File

@@ -13,6 +13,7 @@ export type {
DocumentTabConfig,
DocumentTabProps,
} from './elements/Tab.js'
export type { WithServerSideProps } from './elements/WithServerSideProps.js'
export type { ErrorProps } from './forms/Error.js'
export type {
Description,

View File

@@ -10,6 +10,7 @@ import type {
import type { Auth, ClientUser, IncomingAuthType } from '../../auth/types.js'
import type {
Access,
CustomComponent,
EditConfig,
Endpoint,
EntityDescription,
@@ -201,10 +202,10 @@ export type CollectionAdminOptions = {
* Custom admin components
*/
components?: {
AfterList?: React.ComponentType<any>[]
AfterListTable?: React.ComponentType<any>[]
BeforeList?: React.ComponentType<any>[]
BeforeListTable?: React.ComponentType<any>[]
AfterList?: CustomComponent[]
AfterListTable?: CustomComponent[]
BeforeList?: CustomComponent[]
BeforeListTable?: CustomComponent[]
/**
* Components within the edit view
*/
@@ -239,7 +240,7 @@ export type CollectionAdminOptions = {
List?:
| {
Component?: React.ComponentType<any>
actions?: React.ComponentType<any>[]
actions?: CustomComponent[]
}
| React.ComponentType<any>
}

View File

@@ -279,7 +279,7 @@ export type EditViewConfig =
path: string
}
| {
actions?: React.ComponentType<any>[]
actions?: CustomComponent[]
}
/**
@@ -291,6 +291,14 @@ export type EditViewConfig =
*/
export type EditView = EditViewComponent | EditViewConfig
export type ServerProps = {
payload: Payload
}
export const serverProps: (keyof ServerProps)[] = ['payload']
export type CustomComponent<T extends any = any> = React.ComponentType<T & ServerProps>
export type Locale = {
/**
* value of supported locale
@@ -420,46 +428,46 @@ export type Config = {
/**
* Replace the navigation with a custom component
*/
Nav?: React.ComponentType<any>
Nav?: CustomComponent
/**
* Add custom components to the top right of the Admin Panel
*/
actions?: React.ComponentType<any>[]
actions?: CustomComponent[]
/**
* Add custom components after the collection overview
*/
afterDashboard?: React.ComponentType<any>[]
afterDashboard?: CustomComponent[]
/**
* Add custom components after the email/password field
*/
afterLogin?: React.ComponentType<any>[]
afterLogin?: CustomComponent[]
/**
* Add custom components after the navigation links
*/
afterNavLinks?: React.ComponentType<any>[]
afterNavLinks?: CustomComponent[]
/**
* Add custom components before the collection overview
*/
beforeDashboard?: React.ComponentType<any>[]
beforeDashboard?: CustomComponent[]
/**
* Add custom components before the email/password field
*/
beforeLogin?: React.ComponentType<any>[]
beforeLogin?: CustomComponent[]
/**
* Add custom components before the navigation links
*/
beforeNavLinks?: React.ComponentType<any>[]
beforeNavLinks?: CustomComponent[]
/** Replace graphical components */
graphics?: {
/** Replace the icon in the navigation */
Icon?: React.ComponentType<any>
Icon?: CustomComponent
/** Replace the logo on the login page */
Logo?: React.ComponentType<any>
Logo?: CustomComponent
}
/** Replace logout related components */
logout?: {
/** Replace the logout button */
Button?: React.ComponentType<any>
Button?: CustomComponent
}
/**
* Wrap the admin dashboard in custom context providers
@@ -716,7 +724,7 @@ export type EditConfig =
)
| EditViewComponent
export type EntityDescriptionComponent = React.ComponentType<any>
export type EntityDescriptionComponent = CustomComponent
export type EntityDescriptionFunction = () => string

View File

@@ -34,7 +34,12 @@ export { isEntityHidden } from '../utilities/isEntityHidden.js'
export { isNumber } from '../utilities/isNumber.js'
export { isPlainObject } from '../utilities/isPlainObject.js'
export { isPlainFunction, isReactComponent } from '../utilities/isReactComponent.js'
export {
isPlainFunction,
isReactClientComponent,
isReactComponent,
isReactServerComponent,
} from '../utilities/isReactComponent.js'
export { isValidID } from '../utilities/isValidID.js'
export { default as isolateObjectProperty } from '../utilities/isolateObjectProperty.js'

View File

@@ -17,7 +17,7 @@ import type {
} from '../../admin/types.js'
import type { User } from '../../auth/index.js'
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js'
import type { LabelFunction } from '../../config/types.js'
import type { CustomComponent, LabelFunction } from '../../config/types.js'
import type { DBIdentifierName } from '../../database/types.js'
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
import type { Operation, PayloadRequest, RequestContext, Where } from '../../types/index.js'
@@ -115,8 +115,8 @@ export type FilterOptions<T = any> =
type Admin = {
className?: string
components?: {
Cell?: React.ComponentType<any>
Field?: React.ComponentType<any>
Cell?: CustomComponent
Field?: CustomComponent
Filter?: React.ComponentType<any>
}
/**
@@ -208,10 +208,10 @@ export type NumberField = FieldBase & {
/** Set this property to a string that will be used for browser autocomplete. */
autoComplete?: string
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
}
/** Set this property to define a placeholder string for the field. */
placeholder?: Record<string, string> | string
@@ -246,10 +246,10 @@ export type TextField = FieldBase & {
admin?: Admin & {
autoComplete?: string
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
}
placeholder?: Record<string, string> | string
rtl?: boolean
@@ -280,10 +280,10 @@ export type EmailField = FieldBase & {
admin?: Admin & {
autoComplete?: string
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
}
placeholder?: Record<string, string> | string
}
@@ -293,10 +293,10 @@ export type EmailField = FieldBase & {
export type TextareaField = FieldBase & {
admin?: Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
}
placeholder?: Record<string, string> | string
rows?: number
@@ -310,10 +310,10 @@ export type TextareaField = FieldBase & {
export type CheckboxField = FieldBase & {
admin?: Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
}
}
type: 'checkbox'
@@ -322,10 +322,10 @@ export type CheckboxField = FieldBase & {
export type DateField = FieldBase & {
admin?: Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
afterInput?: React.ComponentType<any>[]
beforeInput?: React.ComponentType<any>[]
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
}
date?: ConditionalDateProps
placeholder?: Record<string, string> | string
@@ -416,8 +416,8 @@ export type TabAsField = Tab & {
export type UIField = {
admin: {
components?: {
Cell?: React.ComponentType<any>
Field: React.ComponentType<any>
Cell?: CustomComponent
Field: CustomComponent
Filter?: React.ComponentType<any>
}
condition?: Condition
@@ -435,8 +435,8 @@ export type UIField = {
export type UploadField = FieldBase & {
admin?: {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
}
}
filterOptions?: FilterOptions
@@ -447,8 +447,8 @@ export type UploadField = FieldBase & {
type CodeAdmin = Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
}
editorOptions?: EditorProps['options']
language?: string
@@ -463,8 +463,8 @@ export type CodeField = Omit<FieldBase, 'admin'> & {
type JSONAdmin = Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
}
editorOptions?: EditorProps['options']
}
@@ -477,8 +477,8 @@ export type JSONField = Omit<FieldBase, 'admin'> & {
export type SelectField = FieldBase & {
admin?: Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
}
isClearable?: boolean
isSortable?: boolean
@@ -533,8 +533,8 @@ type SharedRelationshipProperties = FieldBase & {
type RelationshipAdmin = Admin & {
allowCreate?: boolean
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
}
isSortable?: boolean
}
@@ -574,8 +574,8 @@ export type RichTextField<
> = FieldBase & {
admin?: Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
}
}
editor?: RichTextAdapter<Value, AdapterProps, AdapterProps>
@@ -618,8 +618,8 @@ export type ArrayField = FieldBase & {
export type RadioField = FieldBase & {
admin?: Admin & {
components?: {
Error?: React.ComponentType<ErrorProps>
Label?: React.ComponentType<LabelProps>
Error?: CustomComponent<ErrorProps>
Label?: CustomComponent<LabelProps>
}
layout?: 'horizontal' | 'vertical'
}

View File

@@ -4,14 +4,23 @@ import { isValidElement } from 'react'
import { isPlainObject } from './isPlainObject.js'
export function isReactServerComponent<T extends any>(
component: React.ComponentType | any,
): component is T {
return typeof component === 'function' && isValidElement(component)
}
export function isReactClientComponent<T extends any>(
component: React.ComponentType | any,
): component is T {
// Do this to test for client components (`use client` directive) bc they import as empty objects
return typeof component === 'object' && !isPlainObject(component)
}
export function isReactComponent<T extends any>(
component: React.ComponentType | any,
): component is T {
return (
(typeof component === 'function' && isValidElement(component)) ||
// Do this to test for client components (`use client` directive) bc they import as empty objects
(typeof component === 'object' && !isPlainObject(component))
)
return isReactServerComponent(component) || isReactClientComponent(component)
}
export function isPlainFunction<T extends Function>(fn: any): fn is T {

View File

@@ -13,7 +13,7 @@ export const getGenerateComponentMap =
(args: {
resolvedFeatureMap: ResolvedServerFeatureMap
}): RichTextAdapter['generateComponentMap'] =>
({ config, i18n, schemaPath }) => {
({ WithServerSideProps, config, i18n, schemaPath }) => {
const validRelationships = config.collections.map((c) => c.slug) || []
const componentMap = new Map()
@@ -50,7 +50,8 @@ export const getGenerateComponentMap =
if (Component) {
componentMap.set(
`feature.${featureKey}.components.${componentKey}`,
<Component
<WithServerSideProps
Component={Component}
componentKey={componentKey}
featureKey={resolvedFeature.key}
key={`${resolvedFeature.key}-${componentKey}`}
@@ -86,6 +87,7 @@ export const getGenerateComponentMap =
})
const mappedFields = mapFields({
WithServerSideProps,
config,
disableAddingID: true,
fieldSchema: sanitizedFields,

View File

@@ -14,7 +14,7 @@ import { defaultLeaves as leafTypes } from './field/leaves/index.js'
export const getGenerateComponentMap =
(args: AdapterArguments): RichTextAdapter['generateComponentMap'] =>
({ config, i18n }) => {
({ WithServerSideProps, config, i18n }) => {
const componentMap = new Map()
const validRelationships = config.collections.map((c) => c.slug) || []
@@ -73,6 +73,7 @@ export const getGenerateComponentMap =
})
const mappedFields = mapFields({
WithServerSideProps,
config,
fieldSchema: linkFields,
i18n,
@@ -104,6 +105,7 @@ export const getGenerateComponentMap =
})
const mappedFields = mapFields({
WithServerSideProps,
config,
fieldSchema: uploadFields,
i18n,

View File

@@ -1,3 +1,4 @@
import { serverProps } from 'payload/config'
import { deepMerge } from 'payload/utilities'
import React from 'react'
@@ -21,14 +22,23 @@ import React from 'react'
*/
export function withMergedProps<ToMergeIntoProps, CompleteReturnProps>({
Component,
sanitizeServerOnlyProps = true,
toMergeIntoProps,
}: {
Component: React.FC<CompleteReturnProps>
sanitizeServerOnlyProps?: boolean
toMergeIntoProps: ToMergeIntoProps
}): React.FC<CompleteReturnProps> {
// A wrapper around the args.Component to inject the args.toMergeArgs as props, which are merged with the passed props
const MergedPropsComponent: React.FC<CompleteReturnProps> = (passedProps) => {
const mergedProps = deepMerge(passedProps, toMergeIntoProps)
if (sanitizeServerOnlyProps) {
serverProps.forEach((prop) => {
delete (mergedProps)[prop]
})
}
return <Component {...mergedProps} />
}

View File

@@ -10,7 +10,7 @@ import { withCondition } from '../../forms/withCondition/index.js'
export type HiddenInputFieldProps = FormFieldBase & {
disableModifyingForm?: false
forceUsePathFromProps?: boolean
name: string
name?: string
path?: string
value?: unknown
}

View File

@@ -0,0 +1,21 @@
import type { WithServerSideProps as WithServerSidePropsType } from 'payload/types'
import { isReactServerComponent } from 'payload/utilities'
import React from 'react'
export const WithServerSideProps: WithServerSidePropsType = ({ Component, payload, ...rest }) => {
if (Component) {
const WithServerSideProps: React.FC<any> = (passedProps) => {
const propsWithPayload = {
...passedProps,
...(isReactServerComponent(Component) ? { payload } : {}),
}
return <Component {...propsWithPayload} />
}
return <WithServerSideProps {...rest} />
}
return null
}

View File

@@ -1,14 +1,19 @@
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload/types'
import type {
SanitizedCollectionConfig,
SanitizedGlobalConfig,
WithServerSideProps as WithServerSidePropsType,
} from 'payload/types'
import React from 'react'
import type { ActionMap } from './types.js'
export const mapActions = (args: {
WithServerSideProps: WithServerSidePropsType
collectionConfig?: SanitizedCollectionConfig
globalConfig?: SanitizedGlobalConfig
}): ActionMap => {
const { collectionConfig, globalConfig } = args
const { WithServerSideProps, collectionConfig, globalConfig } = args
const editViews = (collectionConfig || globalConfig)?.admin?.components?.views?.Edit
@@ -28,7 +33,7 @@ export const mapActions = (args: {
view.actions.forEach((action) => {
const Action = action
if (typeof Action === 'function') {
result.Edit[key] = [...(result[key] || []), <Action />]
result.Edit[key] = [...(result[key] || []), <WithServerSideProps Component={Action} />]
}
})
}
@@ -40,7 +45,7 @@ export const mapActions = (args: {
const Action = action
if (typeof Action === 'function') {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
result.List = [...result.List, <Action />]
result.List = [...result.List, <WithServerSideProps Component={Action} />]
}
})
}

View File

@@ -7,6 +7,7 @@ import type {
EntityDescriptionFunction,
SanitizedCollectionConfig,
SanitizedConfig,
WithServerSideProps as WithServerSidePropsType,
} from 'payload/types'
import { ViewDescription } from '@payloadcms/ui/elements/ViewDescription'
@@ -21,6 +22,7 @@ import { mapFields } from './fields.js'
export const mapCollections = ({
DefaultEditView,
DefaultListView,
WithServerSideProps,
collections,
config,
i18n,
@@ -28,6 +30,7 @@ export const mapCollections = ({
}: {
DefaultEditView: React.FC<EditViewProps>
DefaultListView: React.FC<AdminViewProps>
WithServerSideProps: WithServerSidePropsType
collections: SanitizedCollectionConfig[]
config: SanitizedConfig
i18n: I18n
@@ -71,44 +74,60 @@ export const mapCollections = ({
const List = CustomListView || DefaultListView
const beforeList = collectionConfig?.admin?.components?.BeforeList
const beforeListTable = collectionConfig?.admin?.components?.BeforeListTable
const afterList = collectionConfig?.admin?.components?.AfterList
const afterListTable = collectionConfig?.admin?.components?.AfterListTable
const SaveButtonComponent = collectionConfig?.admin?.components?.edit?.SaveButton
const SaveButton = SaveButtonComponent ? <SaveButtonComponent /> : undefined
const SaveButton = SaveButtonComponent ? (
<WithServerSideProps Component={SaveButtonComponent} />
) : undefined
const SaveDraftButtonComponent = collectionConfig?.admin?.components?.edit?.SaveDraftButton
const SaveDraftButton = SaveDraftButtonComponent ? <SaveDraftButtonComponent /> : undefined
const SaveDraftButton = SaveDraftButtonComponent ? (
<WithServerSideProps Component={SaveDraftButtonComponent} />
) : undefined
const PreviewButtonComponent = collectionConfig?.admin?.components?.edit?.PreviewButton
const PreviewButton = PreviewButtonComponent ? <PreviewButtonComponent /> : undefined
const PreviewButton = PreviewButtonComponent ? (
<WithServerSideProps Component={PreviewButtonComponent} />
) : undefined
const PublishButtonComponent = collectionConfig?.admin?.components?.edit?.PublishButton
const PublishButton = PublishButtonComponent ? <PublishButtonComponent /> : undefined
const PublishButton = PublishButtonComponent ? (
<WithServerSideProps Component={PublishButtonComponent} />
) : undefined
const beforeList = collectionConfig?.admin?.components?.BeforeList
const BeforeList =
(beforeList && Array.isArray(beforeList) && beforeList?.map((Component) => <Component />)) ||
(beforeList &&
Array.isArray(beforeList) &&
beforeList?.map((Component) => <WithServerSideProps Component={Component} />)) ||
null
const beforeListTable = collectionConfig?.admin?.components?.BeforeListTable
const BeforeListTable =
(beforeListTable &&
Array.isArray(beforeListTable) &&
beforeListTable?.map((Component) => <Component />)) ||
beforeListTable?.map((Component) => <WithServerSideProps Component={Component} />)) ||
null
const afterList = collectionConfig?.admin?.components?.AfterList
const AfterList =
(afterList && Array.isArray(afterList) && afterList?.map((Component) => <Component />)) ||
(afterList &&
Array.isArray(afterList) &&
afterList?.map((Component) => <WithServerSideProps Component={Component} />)) ||
null
const afterListTable = collectionConfig?.admin?.components?.AfterListTable
const AfterListTable =
(afterListTable &&
Array.isArray(afterListTable) &&
afterListTable?.map((Component) => <Component />)) ||
afterListTable?.map((Component) => <WithServerSideProps Component={Component} />)) ||
null
const descriptionProps: ViewDescriptionProps = {
@@ -134,7 +153,7 @@ export const mapCollections = ({
const Description =
DescriptionComponent !== undefined ? (
<DescriptionComponent {...(descriptionProps || {})} />
<WithServerSideProps Component={DescriptionComponent} {...(descriptionProps || {})} />
) : undefined
const componentMap: CollectionComponentMap = {
@@ -150,9 +169,11 @@ export const mapCollections = ({
SaveButton,
SaveDraftButton,
actionsMap: mapActions({
WithServerSideProps,
collectionConfig,
}),
fieldMap: mapFields({
WithServerSideProps,
config,
fieldSchema: fields,
i18n,

View File

@@ -1,4 +1,5 @@
import type { I18n } from '@payloadcms/translations'
import type { CustomComponent } from 'packages/payload/src/config/types.js'
import type {
CellComponentProps,
DescriptionComponent,
@@ -11,11 +12,17 @@ import type {
Option,
RowLabelComponent,
SanitizedConfig,
WithServerSideProps as WithServerSidePropsType,
} from 'payload/types'
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
import { isPlainFunction, isReactComponent } from 'payload/utilities'
import {
isPlainFunction,
isReactClientComponent,
isReactComponent,
isReactServerComponent,
} from 'payload/utilities'
import React, { Fragment } from 'react'
import type { ArrayFieldProps } from '../../../fields/Array/index.js'
@@ -50,6 +57,7 @@ import type {
import { HiddenInput } from '../../../fields/HiddenInput/index.js'
export const mapFields = (args: {
WithServerSideProps: WithServerSidePropsType
config: SanitizedConfig
/**
* If mapFields is used outside of collections, you might not want it to add an id field
@@ -62,6 +70,7 @@ export const mapFields = (args: {
readOnly?: boolean
}): FieldMap => {
const {
WithServerSideProps,
config,
disableAddingID,
fieldSchema,
@@ -74,8 +83,7 @@ export const mapFields = (args: {
const result: FieldMap = fieldSchema.reduce((acc, field): FieldMap => {
const fieldIsPresentational = fieldIsPresentationalOnly(field)
let CustomFieldComponent: React.ComponentType<FieldComponentProps> =
field.admin?.components?.Field
let CustomFieldComponent: CustomComponent<FieldComponentProps> = field.admin?.components?.Field
const CustomCellComponent = field.admin?.components?.Cell
@@ -102,7 +110,7 @@ export const mapFields = (args: {
Array.isArray(field.admin?.components?.afterInput) && (
<Fragment>
{field.admin.components.afterInput.map((Component, i) => (
<Component key={i} />
<WithServerSideProps Component={Component} key={i} />
))}
</Fragment>
)) ||
@@ -115,7 +123,7 @@ export const mapFields = (args: {
Array.isArray(field.admin.components.beforeInput) && (
<Fragment>
{field.admin.components.beforeInput.map((Component, i) => (
<Component key={i} />
<WithServerSideProps Component={Component} key={i} />
))}
</Fragment>
)) ||
@@ -133,12 +141,12 @@ export const mapFields = (args: {
('admin' in field &&
field.admin?.components &&
'Label' in field.admin.components &&
field.admin?.components?.Label) ||
field.admin.components?.Label) ||
undefined
const CustomLabel =
CustomLabelComponent !== undefined ? (
<CustomLabelComponent {...(labelProps || {})} />
<WithServerSideProps Component={CustomLabelComponent} {...(labelProps || {})} />
) : undefined
const descriptionProps: FieldDescriptionProps = {
@@ -164,7 +172,10 @@ export const mapFields = (args: {
const CustomDescription =
CustomDescriptionComponent !== undefined ? (
<CustomDescriptionComponent {...(descriptionProps || {})} />
<WithServerSideProps
Component={CustomDescriptionComponent}
{...(descriptionProps || {})}
/>
) : undefined
const errorProps = {
@@ -180,7 +191,7 @@ export const mapFields = (args: {
const CustomError =
CustomErrorComponent !== undefined ? (
<CustomErrorComponent {...(errorProps || {})} />
<WithServerSideProps Component={CustomErrorComponent} {...(errorProps || {})} />
) : undefined
const baseFieldProps: FormFieldBase = {
@@ -239,7 +250,7 @@ export const mapFields = (args: {
isReactComponent<RowLabelComponent>(field.admin.components.RowLabel)
) {
const CustomRowLabelComponent = field.admin.components.RowLabel
CustomRowLabel = <CustomRowLabelComponent />
CustomRowLabel = <WithServerSideProps Component={CustomRowLabelComponent} />
}
const arrayFieldProps: Omit<ArrayFieldProps, 'indexPath' | 'permissions'> = {
@@ -249,6 +260,7 @@ export const mapFields = (args: {
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: mapFields({
WithServerSideProps,
config,
fieldSchema: field.fields,
filter,
@@ -272,6 +284,7 @@ export const mapFields = (args: {
case 'blocks': {
const blocks = field.blocks.map((block) => {
const blockFieldMap = mapFields({
WithServerSideProps,
config,
fieldSchema: block.fields,
filter,
@@ -355,7 +368,9 @@ export const mapFields = (args: {
if (isReactComponent(field.label) || isPlainFunction(field.label)) {
const CustomCollapsibleLabelComponent = field.label as RowLabelComponent
CustomCollapsibleLabel = <CustomCollapsibleLabelComponent />
CustomCollapsibleLabel = (
<WithServerSideProps Component={CustomCollapsibleLabelComponent} />
)
}
const collapsibleField: Omit<CollapsibleFieldProps, 'indexPath' | 'permissions'> = {
@@ -364,6 +379,7 @@ export const mapFields = (args: {
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: mapFields({
WithServerSideProps,
config,
disableAddingID: true,
fieldSchema: field.fields,
@@ -426,6 +442,7 @@ export const mapFields = (args: {
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: mapFields({
WithServerSideProps,
config,
disableAddingID: true,
fieldSchema: field.fields,
@@ -553,7 +570,12 @@ export const mapFields = (args: {
const RichTextCellComponent = field.editor.CellComponent
if (typeof field.editor.generateComponentMap === 'function') {
const result = field.editor.generateComponentMap({ config, i18n, schemaPath: path })
const result = field.editor.generateComponentMap({
WithServerSideProps,
config,
i18n,
schemaPath: path,
})
richTextField.richTextComponentMap = result
cellComponentProps.richTextComponentMap = result
}
@@ -563,7 +585,9 @@ export const mapFields = (args: {
}
if (RichTextCellComponent) {
cellComponentProps.CellComponentOverride = <RichTextCellComponent />
cellComponentProps.CellComponentOverride = (
<WithServerSideProps Component={RichTextCellComponent} />
)
}
fieldComponentProps = richTextField
@@ -576,6 +600,7 @@ export const mapFields = (args: {
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: mapFields({
WithServerSideProps,
config,
disableAddingID: true,
fieldSchema: field.fields,
@@ -597,6 +622,7 @@ export const mapFields = (args: {
// `tabs` fields require a field map of each of its tab's nested fields
const tabs = field.tabs.map((tab) => {
const tabFieldMap = mapFields({
WithServerSideProps,
config,
disableAddingID: true,
fieldSchema: tab.fields,
@@ -722,10 +748,10 @@ export const mapFields = (args: {
name: 'name' in field ? field.name : undefined,
type: field.type,
CustomCell: CustomCellComponent ? (
<CustomCellComponent {...cellComponentProps} />
<WithServerSideProps Component={CustomCellComponent} {...cellComponentProps} />
) : undefined,
CustomField: CustomFieldComponent ? (
<CustomFieldComponent {...fieldComponentProps} />
<WithServerSideProps Component={CustomFieldComponent} {...fieldComponentProps} />
) : undefined,
cellComponentProps,
disableBulkEdit:

View File

@@ -5,6 +5,7 @@ import type {
EntityDescriptionFunction,
SanitizedConfig,
SanitizedGlobalConfig,
WithServerSideProps as WithServerSidePropsType,
} from 'payload/types'
import { ViewDescription, type ViewDescriptionProps } from '@payloadcms/ui/elements/ViewDescription'
@@ -18,12 +19,14 @@ import { mapFields } from './fields.js'
export const mapGlobals = ({
DefaultEditView,
WithServerSideProps,
config,
globals,
i18n,
readOnly: readOnlyOverride,
}: {
DefaultEditView: React.FC<EditViewProps>
WithServerSideProps: WithServerSidePropsType
config: SanitizedConfig
globals: SanitizedGlobalConfig[]
i18n: I18n
@@ -37,16 +40,28 @@ export const mapGlobals = ({
const editViewFromConfig = globalConfig?.admin?.components?.views?.Edit
const SaveButton = globalConfig?.admin?.components?.elements?.SaveButton
const SaveButtonComponent = SaveButton ? <SaveButton /> : undefined
const SaveButtonComponent = SaveButton ? (
<WithServerSideProps Component={SaveButton} />
) : undefined
const SaveDraftButton = globalConfig?.admin?.components?.elements?.SaveDraftButton
const SaveDraftButtonComponent = SaveDraftButton ? <SaveDraftButton /> : undefined
const SaveDraftButtonComponent = SaveDraftButton ? (
<WithServerSideProps Component={SaveDraftButton} />
) : undefined
const PreviewButton = globalConfig?.admin?.components?.elements?.PreviewButton
const PreviewButtonComponent = PreviewButton ? <PreviewButton /> : undefined
const PreviewButtonComponent = PreviewButton ? (
<WithServerSideProps Component={PreviewButton} />
) : undefined
const PublishButton = globalConfig?.admin?.components?.elements?.PublishButton
const PublishButtonComponent = PublishButton ? <PublishButton /> : undefined
const PublishButtonComponent = PublishButton ? (
<WithServerSideProps Component={PublishButton} />
) : undefined
const CustomEditView =
typeof editViewFromConfig === 'function'
@@ -84,7 +99,7 @@ export const mapGlobals = ({
const Description =
DescriptionComponent !== undefined ? (
<DescriptionComponent {...(descriptionProps || {})} />
<WithServerSideProps Component={DescriptionComponent} {...(descriptionProps || {})} />
) : undefined
const componentMap: GlobalComponentMap = {
@@ -95,9 +110,11 @@ export const mapGlobals = ({
SaveButton: SaveButtonComponent,
SaveDraftButton: SaveDraftButtonComponent,
actionsMap: mapActions({
WithServerSideProps,
globalConfig,
}),
fieldMap: mapFields({
WithServerSideProps,
config,
fieldSchema: fields,
i18n,

View File

@@ -1,10 +1,17 @@
import type { I18n } from '@payloadcms/translations'
import type { AdminViewProps, EditViewProps, SanitizedConfig } from 'payload/types'
import type {
AdminViewProps,
EditViewProps,
Payload,
SanitizedConfig,
WithServerSideProps as WithServerSidePropsType,
} from 'payload/types'
import React from 'react'
import type { ComponentMap } from './types.js'
import { WithServerSideProps as WithServerSidePropsGeneric } from './WithServerSideProps.js'
import { mapCollections } from './collections.js'
import { mapGlobals } from './globals.js'
@@ -14,16 +21,22 @@ export const buildComponentMap = (args: {
children: React.ReactNode
config: SanitizedConfig
i18n: I18n
payload: Payload
readOnly?: boolean
}): {
componentMap: ComponentMap
wrappedChildren: React.ReactNode
} => {
const { DefaultEditView, DefaultListView, children, config, i18n, readOnly } = args
const { DefaultEditView, DefaultListView, children, config, i18n, payload, readOnly } = args
const WithServerSideProps: WithServerSidePropsType = ({ Component, ...rest }) => {
return <WithServerSidePropsGeneric Component={Component} payload={payload} {...rest} />
}
const collections = mapCollections({
DefaultEditView,
DefaultListView,
WithServerSideProps,
collections: config.collections,
config,
i18n,
@@ -32,6 +45,7 @@ export const buildComponentMap = (args: {
const globals = mapGlobals({
DefaultEditView,
WithServerSideProps,
config,
globals: config.globals,
i18n,
@@ -52,17 +66,21 @@ export const buildComponentMap = (args: {
const LogoutButtonComponent = config.admin?.components?.logout?.Button
const LogoutButton = LogoutButtonComponent ? <LogoutButtonComponent /> : null
const LogoutButton = LogoutButtonComponent ? (
<WithServerSideProps Component={LogoutButtonComponent} />
) : null
const IconComponent = config.admin?.components?.graphics?.Icon
const Icon = IconComponent ? <IconComponent /> : null
const Icon = IconComponent ? <WithServerSideProps Component={IconComponent} /> : null
return {
componentMap: {
Icon,
LogoutButton,
actions: config.admin?.components?.actions?.map((Component) => <Component />),
actions: config.admin?.components?.actions?.map((Component) => (
<WithServerSideProps Component={Component} />
)),
collections,
globals,
},

View File

@@ -17,6 +17,8 @@ export type IComponentMapContext = {
}) => MappedField | undefined
}
export { WithServerSideProps } from './buildComponentMap/WithServerSideProps.js'
const ComponentMapContext = createContext<IComponentMapContext>({} as IComponentMapContext)
export const ComponentMapProvider: React.FC<{

View File

@@ -1,8 +1,10 @@
import type { SanitizedConfig } from 'payload/types'
import React from 'react'
const baseClass = 'admin-button'
export const AdminButton: React.FC = () => {
export const AdminButton: SanitizedConfig['admin']['components']['actions'][0] = () => {
return (
<div
className={baseClass}

View File

@@ -1,10 +1,12 @@
import type { SanitizedConfig } from 'payload/types'
import React from 'react'
import './index.scss'
const baseClass = 'after-dashboard'
export const AfterDashboard: React.FC = () => {
export const AfterDashboard: SanitizedConfig['admin']['components']['afterDashboard'][0] = () => {
return (
<div className={baseClass}>
<h4>Test Config</h4>

View File

@@ -1,5 +1,7 @@
'use client'
import type { SanitizedConfig } from 'payload/types'
import { useConfig } from '@payloadcms/ui/providers/Config'
import LinkImport from 'next/link.js'
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
@@ -8,7 +10,7 @@ import React from 'react'
const baseClass = 'after-nav-links'
export const AfterNavLinks: React.FC = () => {
export const AfterNavLinks: SanitizedConfig['admin']['components']['afterNavLinks'][0] = () => {
const {
routes: { admin: adminRoute },
} = useConfig()

View File

@@ -1,10 +1,13 @@
'use client'
import type { SanitizedConfig } from 'payload/types'
import { useTranslation } from '@payloadcms/ui/providers/Translation'
import React from 'react'
export const BeforeLogin: React.FC = () => {
export const BeforeLogin: SanitizedConfig['admin']['components']['beforeLogin'][0] = () => {
const translation = useTranslation()
return (
<div>
<h3>{translation.t('general:welcome')}</h3>

View File

@@ -1,8 +1,10 @@
import type { CustomComponent } from 'payload/config'
import React from 'react'
const baseClass = 'collection-api-button'
export const CollectionAPIButton: React.FC = () => {
export const CollectionAPIButton: CustomComponent = () => {
return (
<div
className={baseClass}

View File

@@ -1,8 +1,10 @@
import type { CustomComponent } from 'payload/config'
import React from 'react'
const baseClass = 'collection-edit-button'
export const CollectionEditButton: React.FC = () => {
export const CollectionEditButton: CustomComponent = () => {
return (
<div
className={baseClass}

View File

@@ -1,8 +1,10 @@
import type { CustomComponent } from 'payload/config'
import React from 'react'
const baseClass = 'collection-list-button'
export const CollectionListButton: React.FC = () => {
export const CollectionListButton: CustomComponent = () => {
return (
<div
className={baseClass}

View File

@@ -161,4 +161,4 @@
".next/types/**/*.ts",
"scripts/**/*.ts"
]
}
}