feat!: fix non-functional custom RSC component handling, separate label and description props, fix non-functional label function handling (#6264)

Breaking Changes:

- Globals config: `admin.description` no longer accepts a custom component. You will have to move it to `admin.components.elements.Description`
- Collections config: `admin.description` no longer accepts a custom component. You will have to move it to `admin.components.edit.Description`
- All Fields: `field.admin.description` no longer accepts a custom component. You will have to move it to `field.admin.components.Description`
- Collapsible Field: `field.label` no longer accepts a custom component. You will have to move it to `field.admin.components.RowLabel`
- Array Field: `field.admin.components.RowLabel` no longer accepts strings or records
- If you are using our exported field components in your own app, their `labelProps` property has been stripped down and no longer contains the `label` and `required` prop. Those can now only be configured at the top-level
This commit is contained in:
Alessio Gravili
2024-05-09 17:12:01 -04:00
committed by GitHub
parent 821bed0ea6
commit cfeac79b99
59 changed files with 296 additions and 284 deletions

View File

@@ -1,5 +1,6 @@
import type { AdminViewProps } from 'payload/types' import type { AdminViewProps } from 'payload/types'
import { WithServerSideProps } from '@payloadcms/ui/elements/WithServerSideProps'
import { Logo } from '@payloadcms/ui/graphics/Logo' import { Logo } from '@payloadcms/ui/graphics/Logo'
import { redirect } from 'next/navigation.js' import { redirect } from 'next/navigation.js'
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
@@ -16,6 +17,7 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, searchPara
const { const {
payload: { config }, payload: { config },
payload,
user, user,
} = req } = req
@@ -25,6 +27,18 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, searchPara
routes: { admin }, routes: { admin },
} = config } = config
const BeforeLogins = Array.isArray(beforeLogin)
? beforeLogin.map((Component, i) => (
<WithServerSideProps Component={Component} key={i} payload={payload} />
))
: null
const AfterLogins = Array.isArray(afterLogin)
? afterLogin.map((Component, i) => (
<WithServerSideProps Component={Component} key={i} payload={payload} />
))
: null
if (user) { if (user) {
redirect(admin) redirect(admin)
} }
@@ -36,9 +50,9 @@ export const LoginView: React.FC<AdminViewProps> = ({ initPageResult, searchPara
<div className={`${loginBaseClass}__brand`}> <div className={`${loginBaseClass}__brand`}>
<Logo config={config} /> <Logo config={config} />
</div> </div>
{Array.isArray(beforeLogin) && beforeLogin.map((Component, i) => <Component key={i} />)} {Array.isArray(BeforeLogins) && BeforeLogins.map((Component) => Component)}
{!collectionConfig?.auth?.disableLocalStrategy && <LoginForm searchParams={searchParams} />} {!collectionConfig?.auth?.disableLocalStrategy && <LoginForm searchParams={searchParams} />}
{Array.isArray(afterLogin) && afterLogin.map((Component, i) => <Component key={i} />)} {Array.isArray(AfterLogins) && AfterLogins.map((Component) => Component)}
</Fragment> </Fragment>
) )
} }

View File

@@ -7,11 +7,7 @@ export type DescriptionFunction = LabelFunction
export type DescriptionComponent = CustomComponent<FieldDescriptionProps> export type DescriptionComponent = CustomComponent<FieldDescriptionProps>
export type Description = export type Description = DescriptionFunction | Record<string, string> | string
| DescriptionComponent
| DescriptionFunction
| Record<string, string>
| string
export type FieldDescriptionProps = { export type FieldDescriptionProps = {
CustomDescription?: React.ReactNode CustomDescription?: React.ReactNode

View File

@@ -1,10 +1,10 @@
import type { LabelFunction } from '../../config/types.js'
export type LabelProps = { export type LabelProps = {
CustomLabel?: React.ReactNode CustomLabel?: React.ReactNode
as?: 'label' | 'span' as?: 'label' | 'span'
htmlFor?: string htmlFor?: string
label?: LabelFunction | Record<string, string> | false | string label?: Record<string, string> | string
required?: boolean required?: boolean
unstyled?: boolean unstyled?: boolean
} }
export type SanitizedLabelProps = Omit<LabelProps, 'label' | 'required'>

View File

@@ -2,4 +2,4 @@ import type { CustomComponent } from '../../config/types.js'
export type RowLabelComponent = CustomComponent export type RowLabelComponent = CustomComponent
export type RowLabel = Record<string, string> | RowLabelComponent | string export type RowLabel = Record<string, string> | string

View File

@@ -22,7 +22,7 @@ export type {
FieldDescriptionProps, FieldDescriptionProps,
} from './forms/FieldDescription.js' } from './forms/FieldDescription.js'
export type { Data, FilterOptionsResult, FormField, FormState, Row } from './forms/Form.js' export type { Data, FilterOptionsResult, FormField, FormState, Row } from './forms/Form.js'
export type { LabelProps } from './forms/Label.js' export type { LabelProps, SanitizedLabelProps } from './forms/Label.js'
export type { RowLabel, RowLabelComponent } from './forms/RowLabel.js' export type { RowLabel, RowLabelComponent } from './forms/RowLabel.js'
export type { export type {

View File

@@ -30,6 +30,7 @@ const collectionSchema = joi.object().keys({
BeforeList: joi.array().items(componentSchema), BeforeList: joi.array().items(componentSchema),
BeforeListTable: joi.array().items(componentSchema), BeforeListTable: joi.array().items(componentSchema),
edit: joi.object({ edit: joi.object({
Description: componentSchema,
PreviewButton: componentSchema, PreviewButton: componentSchema,
PublishButton: componentSchema, PublishButton: componentSchema,
SaveButton: componentSchema, SaveButton: componentSchema,
@@ -59,7 +60,9 @@ const collectionSchema = joi.object().keys({
}), }),
custom: joi.object().pattern(joi.string(), joi.any()), custom: joi.object().pattern(joi.string(), joi.any()),
defaultColumns: joi.array().items(joi.string()), defaultColumns: joi.array().items(joi.string()),
description: joi.alternatives().try(joi.string(), componentSchema), description: joi
.alternatives()
.try(joi.func(), joi.object().pattern(joi.string(), [joi.string()]), joi.string()),
enableRichTextLink: joi.boolean(), enableRichTextLink: joi.boolean(),
enableRichTextRelationship: joi.boolean(), enableRichTextRelationship: joi.boolean(),
group: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])), group: joi.alternatives().try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),

View File

@@ -14,6 +14,7 @@ import type {
EditConfig, EditConfig,
Endpoint, Endpoint,
EntityDescription, EntityDescription,
EntityDescriptionComponent,
GeneratePreviewURL, GeneratePreviewURL,
LabelFunction, LabelFunction,
LivePreviewConfig, LivePreviewConfig,
@@ -212,6 +213,8 @@ export type CollectionAdminOptions = {
* Components within the edit view * Components within the edit view
*/ */
edit?: { edit?: {
Description?: EntityDescriptionComponent
/** /**
* Replaces the "Preview" button * Replaces the "Preview" button
*/ */

View File

@@ -694,12 +694,8 @@ export type EditConfig =
export type EntityDescriptionComponent = CustomComponent export type EntityDescriptionComponent = CustomComponent
export type EntityDescriptionFunction = () => string export type EntityDescriptionFunction = ({ t }: { t: TFunction }) => string
export type EntityDescription = export type EntityDescription = EntityDescriptionFunction | Record<string, string> | string
| EntityDescriptionComponent
| EntityDescriptionFunction
| Record<string, string>
| string
export type { EmailAdapter, SendEmailOptions } export type { EmailAdapter, SendEmailOptions }

View File

@@ -42,10 +42,9 @@ export { isNumber } from '../utilities/isNumber.js'
export { isPlainObject } from '../utilities/isPlainObject.js' export { isPlainObject } from '../utilities/isPlainObject.js'
export { export {
isPlainFunction,
isReactClientComponent, isReactClientComponent,
isReactComponent, isReactComponentOrFunction,
isReactServerComponent, isReactServerComponentOrFunction,
} from '../utilities/isReactComponent.js' } from '../utilities/isReactComponent.js'
export { isValidID } from '../utilities/isValidID.js' export { isValidID } from '../utilities/isValidID.js'

View File

@@ -64,6 +64,7 @@ export const sanitizeFields = async ({
field.name && field.name &&
typeof field.label !== 'object' && typeof field.label !== 'object' &&
typeof field.label !== 'string' && typeof field.label !== 'string' &&
typeof field.label !== 'function' &&
field.label !== false field.label !== false
) { ) {
field.label = toWords(field.name) field.label = toWords(field.name)

View File

@@ -6,6 +6,7 @@ export const baseAdminComponentFields = joi
.object() .object()
.keys({ .keys({
Cell: componentSchema, Cell: componentSchema,
Description: componentSchema,
Field: componentSchema, Field: componentSchema,
Filter: componentSchema, Filter: componentSchema,
}) })
@@ -18,7 +19,7 @@ export const baseAdminFields = joi.object().keys({
custom: joi.object().pattern(joi.string(), joi.any()), custom: joi.object().pattern(joi.string(), joi.any()),
description: joi description: joi
.alternatives() .alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), componentSchema), .try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), joi.function()),
disableBulkEdit: joi.boolean().default(false), disableBulkEdit: joi.boolean().default(false),
disabled: joi.boolean().default(false), disabled: joi.boolean().default(false),
hidden: joi.boolean().default(false), hidden: joi.boolean().default(false),
@@ -269,9 +270,27 @@ export const row = baseField.keys({
export const collapsible = baseField.keys({ export const collapsible = baseField.keys({
type: joi.string().valid('collapsible').required(), type: joi.string().valid('collapsible').required(),
admin: baseAdminFields.default(), admin: baseAdminFields
.keys({
components: baseAdminComponentFields
.keys({
RowLabel: componentSchema.optional(),
})
.default({}),
})
.default({}),
fields: joi.array().items(joi.link('#field')), fields: joi.array().items(joi.link('#field')),
label: joi.alternatives().try(joi.string(), componentSchema), label: joi.alternatives().conditional('admin.components.RowLabel', {
is: joi.exist(),
otherwise: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), joi.function())
.required(),
then: joi
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), joi.function())
.optional(),
}),
}) })
const tab = baseField.keys({ const tab = baseField.keys({

View File

@@ -11,9 +11,11 @@ import type { RichTextAdapter, RichTextAdapterProvider } from '../../admin/RichT
import type { import type {
ConditionalDateProps, ConditionalDateProps,
Description, Description,
DescriptionComponent,
ErrorProps, ErrorProps,
LabelProps, LabelProps,
RowLabel, RowLabel,
RowLabelComponent,
} from '../../admin/types.js' } from '../../admin/types.js'
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js' import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js'
import type { CustomComponent, LabelFunction } from '../../config/types.js' import type { CustomComponent, LabelFunction } from '../../config/types.js'
@@ -117,6 +119,7 @@ type Admin = {
className?: string className?: string
components?: { components?: {
Cell?: CustomComponent Cell?: CustomComponent
Description?: DescriptionComponent
Field?: CustomComponent Field?: CustomComponent
Filter?: React.ComponentType<any> Filter?: React.ComponentType<any>
} }
@@ -361,13 +364,25 @@ export type RowField = Omit<FieldBase, 'admin' | 'label' | 'name'> & {
} }
export type CollapsibleField = Omit<FieldBase, 'label' | 'name'> & { export type CollapsibleField = Omit<FieldBase, 'label' | 'name'> & {
admin?: Admin & {
initCollapsed?: boolean
}
fields: Field[] fields: Field[]
label: RowLabel
type: 'collapsible' type: 'collapsible'
} } & (
| {
admin: Admin & {
components: {
RowLabel: RowLabelComponent
} & Admin['components']
initCollapsed?: boolean
}
label?: Required<FieldBase['label']>
}
| {
admin?: Admin & {
initCollapsed?: boolean
}
label: Required<FieldBase['label']>
}
)
export type TabsAdmin = Omit<Admin, 'description'> export type TabsAdmin = Omit<Admin, 'description'>
@@ -610,7 +625,7 @@ export type RichTextField<
export type ArrayField = FieldBase & { export type ArrayField = FieldBase & {
admin?: Admin & { admin?: Admin & {
components?: { components?: {
RowLabel?: RowLabel RowLabel?: RowLabelComponent
} & Admin['components'] } & Admin['components']
initCollapsed?: boolean initCollapsed?: boolean
/** /**

View File

@@ -19,6 +19,7 @@ const globalSchema = joi
admin: joi.object({ admin: joi.object({
components: joi.object({ components: joi.object({
elements: joi.object({ elements: joi.object({
Description: componentSchema,
PreviewButton: componentSchema, PreviewButton: componentSchema,
PublishButton: componentSchema, PublishButton: componentSchema,
SaveButton: componentSchema, SaveButton: componentSchema,
@@ -40,7 +41,9 @@ const globalSchema = joi
}), }),
}), }),
custom: joi.object().pattern(joi.string(), joi.any()), custom: joi.object().pattern(joi.string(), joi.any()),
description: joi.alternatives().try(joi.string(), componentSchema), description: joi
.alternatives()
.try(joi.func(), joi.object().pattern(joi.string(), [joi.string()]), joi.string()),
group: joi group: joi
.alternatives() .alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])), .try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),

View File

@@ -7,12 +7,12 @@ import type {
CustomSaveButton, CustomSaveButton,
CustomSaveDraftButton, CustomSaveDraftButton,
} from '../../admin/types.js' } from '../../admin/types.js'
import type { User } from '../../auth/types.js'
import type { import type {
Access, Access,
EditConfig, EditConfig,
Endpoint, Endpoint,
EntityDescription, EntityDescription,
EntityDescriptionComponent,
GeneratePreviewURL, GeneratePreviewURL,
LivePreviewConfig, LivePreviewConfig,
} from '../../config/types.js' } from '../../config/types.js'
@@ -77,6 +77,7 @@ export type GlobalAdminOptions = {
*/ */
components?: { components?: {
elements?: { elements?: {
Description?: EntityDescriptionComponent
/** /**
* Replaces the "Preview" button * Replaces the "Preview" button
*/ */

View File

@@ -1,13 +1,19 @@
import type React from 'react' import type React from 'react'
import { isValidElement } from 'react'
import { isPlainObject } from './isPlainObject.js' import { isPlainObject } from './isPlainObject.js'
export function isReactServerComponent<T extends any>( export function isReactServerComponentOrFunction<T extends any>(
component: React.ComponentType | any, component: React.ComponentType | any,
): component is T { ): component is T {
return typeof component === 'function' && isValidElement(component) const isClassComponent =
typeof component === 'function' &&
component.prototype &&
typeof component.prototype.render === 'function'
const isFunctionalComponent =
typeof component === 'function' && (!component.prototype || !component.prototype.render)
return isClassComponent || isFunctionalComponent
} }
export function isReactClientComponent<T extends any>( export function isReactClientComponent<T extends any>(
@@ -17,12 +23,8 @@ export function isReactClientComponent<T extends any>(
return typeof component === 'object' && !isPlainObject(component) return typeof component === 'object' && !isPlainObject(component)
} }
export function isReactComponent<T extends any>( export function isReactComponentOrFunction<T extends any>(
component: React.ComponentType | any, component: React.ComponentType | any,
): component is T { ): component is T {
return isReactServerComponent(component) || isReactClientComponent(component) return isReactServerComponentOrFunction(component) || isReactClientComponent(component)
}
export function isPlainFunction<T extends Function>(fn: any): fn is T {
return typeof fn === 'function' && !isReactComponent(fn)
} }

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import type { TextField } from 'payload/types' import type { TextFieldProps } from '@payloadcms/ui/fields/Text'
import { Select } from '@payloadcms/ui/fields/Select' import { Select } from '@payloadcms/ui/fields/Select'
import { useForm } from '@payloadcms/ui/forms/Form' import { useForm } from '@payloadcms/ui/forms/Form'
@@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'
import type { SelectFieldOption } from '../../types.js' import type { SelectFieldOption } from '../../types.js'
export const DynamicFieldSelector: React.FC<TextField> = (props) => { export const DynamicFieldSelector: React.FC<TextFieldProps> = (props) => {
const { fields, getDataByPath } = useForm() const { fields, getDataByPath } = useForm()
const [options, setOptions] = useState<SelectFieldOption[]>([]) const [options, setOptions] = useState<SelectFieldOption[]>([])

View File

@@ -6,7 +6,6 @@ import type { Data } from 'payload/types'
import { Text } from '@payloadcms/ui/fields/Text' import { Text } from '@payloadcms/ui/fields/Text'
import { useWatchForm } from '@payloadcms/ui/forms/Form' import { useWatchForm } from '@payloadcms/ui/forms/Form'
import { useLocale } from '@payloadcms/ui/providers/Locale' import { useLocale } from '@payloadcms/ui/providers/Locale'
import { useTranslation } from '@payloadcms/ui/providers/Translation'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
type FieldWithID = { type FieldWithID = {
@@ -20,7 +19,6 @@ export const DynamicPriceSelector: React.FC<TextFieldProps> = (props) => {
const { fields, getData, getDataByPath } = useWatchForm() const { fields, getData, getDataByPath } = useWatchForm()
const locale = useLocale() const locale = useLocale()
const { t } = useTranslation()
const [isNumberField, setIsNumberField] = useState<boolean>() const [isNumberField, setIsNumberField] = useState<boolean>()
const [valueType, setValueType] = useState<'static' | 'valueOfField'>() const [valueType, setValueType] = useState<'static' | 'valueOfField'>()
@@ -54,12 +52,8 @@ export const DynamicPriceSelector: React.FC<TextFieldProps> = (props) => {
const localeCode = typeof locale === 'object' && 'code' in locale ? locale.code : locale const localeCode = typeof locale === 'object' && 'code' in locale ? locale.code : locale
const localLabels = const localLabels = typeof label === 'object' ? label : { [localeCode]: label }
typeof label === 'function'
? label({ t })
: typeof label === 'object'
? label
: { [localeCode]: label }
const labelValue = localLabels[localeCode] || localLabels['en'] || '' const labelValue = localLabels[localeCode] || localLabels['en'] || ''
if (valueType === 'valueOfField' && !isNumberField) { if (valueType === 'valueOfField' && !isNumberField) {

View File

@@ -27,7 +27,7 @@ type MetaDescriptionProps = FormFieldBase & {
} }
export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => { export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
const { CustomLabel, hasGenerateDescriptionFn, labelProps, path, required } = props const { CustomLabel, hasGenerateDescriptionFn, label, labelProps, path, required } = props
const { path: pathFromContext } = useFieldProps() const { path: pathFromContext } = useFieldProps()
const { t } = useTranslation() const { t } = useTranslation()
@@ -76,7 +76,7 @@ export const MetaDescription: React.FC<MetaDescriptionProps> = (props) => {
}} }}
> >
<div className="plugin-seo__field"> <div className="plugin-seo__field">
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} /> <FieldLabel CustomLabel={CustomLabel} label={label} {...(labelProps || {})} />
{hasGenerateDescriptionFn && ( {hasGenerateDescriptionFn && (
<React.Fragment> <React.Fragment>
&nbsp; &mdash; &nbsp; &nbsp; &mdash; &nbsp;

View File

@@ -23,7 +23,7 @@ type MetaImageProps = UploadInputProps & {
} }
export const MetaImage: React.FC<MetaImageProps> = (props) => { export const MetaImage: React.FC<MetaImageProps> = (props) => {
const { CustomLabel, hasGenerateImageFn, labelProps, relationTo, required } = props || {} const { CustomLabel, hasGenerateImageFn, label, labelProps, relationTo, required } = props || {}
const field: FieldType<string> = useField(props as Options) const field: FieldType<string> = useField(props as Options)
@@ -77,7 +77,7 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
}} }}
> >
<div className="plugin-seo__field"> <div className="plugin-seo__field">
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} /> <FieldLabel CustomLabel={CustomLabel} label={label} {...(labelProps || {})} />
{hasGenerateImageFn && ( {hasGenerateImageFn && (
<React.Fragment> <React.Fragment>
&nbsp; &mdash; &nbsp; &nbsp; &mdash; &nbsp;

View File

@@ -28,7 +28,7 @@ type MetaTitleProps = FormFieldBase & {
} }
export const MetaTitle: React.FC<MetaTitleProps> = (props) => { export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
const { CustomLabel, hasGenerateTitleFn, labelProps, path, required } = props || {} const { CustomLabel, hasGenerateTitleFn, label, labelProps, path, required } = props || {}
const { path: pathFromContext } = useFieldProps() const { path: pathFromContext } = useFieldProps()
const { t } = useTranslation() const { t } = useTranslation()
@@ -77,7 +77,7 @@ export const MetaTitle: React.FC<MetaTitleProps> = (props) => {
}} }}
> >
<div className="plugin-seo__field"> <div className="plugin-seo__field">
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} /> <FieldLabel CustomLabel={CustomLabel} label={label} {...(labelProps || {})} />
{hasGenerateTitleFn && ( {hasGenerateTitleFn && (
<React.Fragment> <React.Fragment>
&nbsp; &mdash; &nbsp; &nbsp; &mdash; &nbsp;

View File

@@ -43,7 +43,7 @@ export const seoPlugin =
type: 'text', type: 'text',
admin: { admin: {
components: { components: {
Field: (props) => ( Field: ({ payload: _payload, ...props }) => (
<MetaTitle <MetaTitle
{...props} {...props}
hasGenerateTitleFn={typeof pluginConfig?.generateTitle === 'function'} hasGenerateTitleFn={typeof pluginConfig?.generateTitle === 'function'}
@@ -59,7 +59,7 @@ export const seoPlugin =
type: 'textarea', type: 'textarea',
admin: { admin: {
components: { components: {
Field: (props) => ( Field: ({ payload: _payload, ...props }) => (
<MetaDescription <MetaDescription
{...props} {...props}
hasGenerateDescriptionFn={ hasGenerateDescriptionFn={
@@ -80,7 +80,7 @@ export const seoPlugin =
type: 'upload', type: 'upload',
admin: { admin: {
components: { components: {
Field: (props) => ( Field: ({ payload: _payload, ...props }) => (
<MetaImage <MetaImage
{...props} {...props}
hasGenerateImageFn={typeof pluginConfig?.generateImage === 'function'} hasGenerateImageFn={typeof pluginConfig?.generateImage === 'function'}
@@ -103,7 +103,7 @@ export const seoPlugin =
type: 'ui', type: 'ui',
admin: { admin: {
components: { components: {
Field: (props) => ( Field: ({ payload: _payload, ...props }) => (
<Preview <Preview
{...props} {...props}
hasGenerateURLFn={typeof pluginConfig?.generateURL === 'function'} hasGenerateURLFn={typeof pluginConfig?.generateURL === 'function'}

View File

@@ -1,7 +1,6 @@
'use client' 'use client'
import type { FormFieldBase } from '@payloadcms/ui/fields/shared' import type { FormFieldBase } from '@payloadcms/ui/fields/shared'
import type { SerializedEditorState } from 'lexical' import type { SerializedEditorState } from 'lexical'
import type { FieldBase } from 'payload/types'
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription' import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
import { FieldError } from '@payloadcms/ui/forms/FieldError' import { FieldError } from '@payloadcms/ui/forms/FieldError'
@@ -22,7 +21,6 @@ const baseClass = 'rich-text-lexical'
const _RichText: React.FC< const _RichText: React.FC<
FormFieldBase & { FormFieldBase & {
editorConfig: SanitizedClientEditorConfig // With rendered features n stuff editorConfig: SanitizedClientEditorConfig // With rendered features n stuff
label?: FieldBase['label']
name: string name: string
richTextComponentMap: Map<string, React.ReactNode> richTextComponentMap: Map<string, React.ReactNode>
width?: string width?: string

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import type { FormFieldBase } from '@payloadcms/ui/fields/shared' import type { FormFieldBase } from '@payloadcms/ui/fields/shared'
import type { ClientValidate, FieldBase } from 'payload/types' import type { ClientValidate } from 'payload/types'
import type { BaseEditor, BaseOperation } from 'slate' import type { BaseEditor, BaseOperation } from 'slate'
import type { HistoryEditor } from 'slate-history' import type { HistoryEditor } from 'slate-history'
import type { ReactEditor } from 'slate-react' import type { ReactEditor } from 'slate-react'
@@ -50,7 +50,6 @@ declare module 'slate' {
const RichTextField: React.FC< const RichTextField: React.FC<
FormFieldBase & { FormFieldBase & {
elements: EnabledFeatures['elements'] elements: EnabledFeatures['elements']
label?: FieldBase['label']
leaves: EnabledFeatures['leaves'] leaves: EnabledFeatures['leaves']
name: string name: string
placeholder?: string placeholder?: string

View File

@@ -74,8 +74,13 @@ export const clientTranslationKeys = [
'fields:blockType', 'fields:blockType',
'fields:chooseBetweenCustomTextOrDocument', 'fields:chooseBetweenCustomTextOrDocument',
'fields:customURL', 'fields:customURL',
'fields:chooseDocumentToLink',
'fields:openInNewTab',
'fields:enterURL',
'fields:internalLink', 'fields:internalLink',
'fields:chooseFromExisting', 'fields:chooseFromExisting',
'fields:linkType',
'fields:textToDisplay',
'fields:collapseAll', 'fields:collapseAll',
'fields:editLink', 'fields:editLink',
'fields:editRelationship', 'fields:editRelationship',
@@ -120,6 +125,7 @@ export const clientTranslationKeys = [
'general:copy', 'general:copy',
'general:create', 'general:create',
'general:created', 'general:created',
'general:createdAt',
'general:createNew', 'general:createNew',
'general:createNewLabel', 'general:createNewLabel',
'general:creating', 'general:creating',
@@ -239,6 +245,8 @@ export const clientTranslationKeys = [
'upload:setFocalPoint', 'upload:setFocalPoint',
'upload:sizesFor', 'upload:sizesFor',
'upload:width', 'upload:width',
'upload:fileName',
'upload:fileSize',
'validation:emailAddress', 'validation:emailAddress',
'validation:fieldHasNo', 'validation:fieldHasNo',

View File

@@ -34,8 +34,11 @@ const combineLabel = ({
? field.fieldComponentProps.CustomLabel ? field.fieldComponentProps.CustomLabel
: null : null
const DefaultLabelToRender = const DefaultLabelToRender =
field && 'labelProps' in field.fieldComponentProps && field.fieldComponentProps.labelProps ? ( field && 'label' in field.fieldComponentProps && field.fieldComponentProps.label ? (
<FieldLabel {...field.fieldComponentProps.labelProps} /> <FieldLabel
label={field.fieldComponentProps.label}
{...(field.fieldComponentProps.labelProps || {})}
/>
) : null ) : null
const LabelToRender = CustomLabelToRender || DefaultLabelToRender || customLabel const LabelToRender = CustomLabelToRender || DefaultLabelToRender || customLabel

View File

@@ -103,7 +103,8 @@ export const buildColumnState = (args: Args): Column[] => {
const Label = ( const Label = (
<FieldLabel <FieldLabel
CustomLabel={CustomLabelToRender} CustomLabel={CustomLabelToRender}
{...field.fieldComponentProps?.labelProps} label={field.fieldComponentProps?.label}
{...(field.fieldComponentProps?.labelProps || {})}
unstyled unstyled
/> />
) )

View File

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

View File

@@ -1,6 +1,5 @@
'use client' 'use client'
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { FieldBase } from 'payload/types'
import type { ArrayField as ArrayFieldType } from 'payload/types' import type { ArrayField as ArrayFieldType } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
@@ -38,7 +37,6 @@ export type ArrayFieldProps = FormFieldBase & {
fieldMap: FieldMap fieldMap: FieldMap
forceRender?: boolean forceRender?: boolean
isSortable?: boolean isSortable?: boolean
label?: FieldBase['label']
labels?: ArrayFieldType['labels'] labels?: ArrayFieldType['labels']
maxRows?: ArrayFieldType['maxRows'] maxRows?: ArrayFieldType['maxRows']
minRows?: ArrayFieldType['minRows'] minRows?: ArrayFieldType['minRows']
@@ -93,9 +91,9 @@ export const _ArrayField: React.FC<ArrayFieldProps> = (props) => {
})() })()
// Handle labeling for Arrays, Global Arrays, and Blocks // Handle labeling for Arrays, Global Arrays, and Blocks
const getLabels = (p: ArrayFieldProps) => { const getLabels = (p: ArrayFieldProps): ArrayFieldType['labels'] => {
if ('labels' in p && p?.labels) return p.labels if ('labels' in p && p?.labels) return p.labels
if ('label' in p && p?.label) return { plural: undefined, singular: p.label } if ('label' in p && p?.label) return { plural: undefined, singular: p?.label }
return { plural: t('general:rows'), singular: t('general:row') } return { plural: t('general:rows'), singular: t('general:row') }
} }

View File

@@ -40,7 +40,6 @@ export type BlocksFieldProps = FormFieldBase & {
blocks?: ReducedBlock[] blocks?: ReducedBlock[]
forceRender?: boolean forceRender?: boolean
isSortable?: boolean isSortable?: boolean
label?: FieldBase['label']
labels?: BlockField['labels'] labels?: BlockField['labels']
maxRows?: number maxRows?: number
minRows?: number minRows?: number

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { FieldBase, LabelProps } from 'payload/types' import type { LabelProps, SanitizedLabelProps } from 'payload/types'
import { FieldLabel } from '@payloadcms/ui/forms/FieldLabel' import { FieldLabel } from '@payloadcms/ui/forms/FieldLabel'
import React from 'react' import React from 'react'
@@ -15,8 +15,8 @@ type Props = {
className?: string className?: string
id?: string id?: string
inputRef?: React.RefObject<HTMLInputElement> inputRef?: React.RefObject<HTMLInputElement>
label?: FieldBase['label'] label?: LabelProps['label']
labelProps?: LabelProps labelProps?: SanitizedLabelProps
name?: string name?: string
onToggle: (event: React.ChangeEvent<HTMLInputElement>) => void onToggle: (event: React.ChangeEvent<HTMLInputElement>) => void
partialChecked?: boolean partialChecked?: boolean

View File

@@ -1,12 +1,9 @@
import type { FieldBase } from 'payload/types'
import type { FormFieldBase } from '../shared/index.js' import type { FormFieldBase } from '../shared/index.js'
export type CheckboxFieldProps = FormFieldBase & { export type CheckboxFieldProps = FormFieldBase & {
checked?: boolean checked?: boolean
disableFormData?: boolean disableFormData?: boolean
id?: string id?: string
label?: FieldBase['label']
name?: string name?: string
onChange?: (val: boolean) => void onChange?: (val: boolean) => void
partialChecked?: boolean partialChecked?: boolean

View File

@@ -18,7 +18,6 @@ import './index.scss'
export type CodeFieldProps = FormFieldBase & { export type CodeFieldProps = FormFieldBase & {
editorOptions?: CodeFieldType['admin']['editorOptions'] editorOptions?: CodeFieldType['admin']['editorOptions']
label?: FieldBase['label']
language?: CodeFieldType['admin']['language'] language?: CodeFieldType['admin']['language']
name?: string name?: string
path?: string path?: string

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import type { DocumentPreferences, FieldBase } from 'payload/types' import type { DocumentPreferences } from 'payload/types'
import React, { Fragment, useCallback, useEffect, useState } from 'react' import React, { Fragment, useCallback, useEffect, useState } from 'react'
@@ -28,7 +28,6 @@ import type { FormFieldBase } from '../shared/index.js'
export type CollapsibleFieldProps = FormFieldBase & { export type CollapsibleFieldProps = FormFieldBase & {
fieldMap: FieldMap fieldMap: FieldMap
initCollapsed?: boolean initCollapsed?: boolean
label?: FieldBase['label']
permissions: FieldPermissions permissions: FieldPermissions
width?: string width?: string
} }
@@ -41,7 +40,7 @@ const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
descriptionProps, descriptionProps,
fieldMap, fieldMap,
initCollapsed = false, initCollapsed = false,
labelProps, label,
path: pathFromProps, path: pathFromProps,
readOnly: readOnlyFromProps, readOnly: readOnlyFromProps,
} = props } = props
@@ -139,12 +138,7 @@ const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
collapsibleStyle={fieldHasErrors ? 'error' : 'default'} collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
header={ header={
<div className={`${baseClass}__row-label-wrap`}> <div className={`${baseClass}__row-label-wrap`}>
<RowLabel <RowLabel RowLabelComponent={CustomLabel} i18n={i18n} path={path} rowLabel={label} />
RowLabelComponent={CustomLabel}
i18n={i18n}
path={path}
rowLabel={labelProps.label}
/>
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />} {fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
</div> </div>
} }

View File

@@ -14,7 +14,7 @@ import './index.scss'
const baseClass = 'date-time-field' const baseClass = 'date-time-field'
import type { DateField, FieldBase } from 'payload/types' import type { DateField } from 'payload/types'
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription' import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
import { FieldError } from '@payloadcms/ui/forms/FieldError' import { FieldError } from '@payloadcms/ui/forms/FieldError'
@@ -26,7 +26,6 @@ import { withCondition } from '../../forms/withCondition/index.js'
export type DateFieldProps = FormFieldBase & { export type DateFieldProps = FormFieldBase & {
date?: DateField['admin']['date'] date?: DateField['admin']['date']
label?: FieldBase['label']
name?: string name?: string
path?: string path?: string
placeholder?: DateField['admin']['placeholder'] | string placeholder?: DateField['admin']['placeholder'] | string

View File

@@ -19,7 +19,6 @@ import './index.scss'
export type EmailFieldProps = FormFieldBase & { export type EmailFieldProps = FormFieldBase & {
autoComplete?: string autoComplete?: string
label?: FieldBase['label']
name?: string name?: string
path?: string path?: string
placeholder?: EmailFieldType['admin']['placeholder'] placeholder?: EmailFieldType['admin']['placeholder']

View File

@@ -1,6 +1,5 @@
'use client' 'use client'
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { FieldBase } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription' import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
@@ -29,7 +28,6 @@ export type GroupFieldProps = FormFieldBase & {
fieldMap: FieldMap fieldMap: FieldMap
forceRender?: boolean forceRender?: boolean
hideGutter?: boolean hideGutter?: boolean
label?: FieldBase['label']
name?: string name?: string
permissions: FieldPermissions permissions: FieldPermissions
width?: string width?: string
@@ -43,7 +41,7 @@ const GroupField: React.FC<GroupFieldProps> = (props) => {
descriptionProps, descriptionProps,
fieldMap, fieldMap,
hideGutter, hideGutter,
labelProps, label,
readOnly: readOnlyFromProps, readOnly: readOnlyFromProps,
style, style,
width, width,
@@ -89,14 +87,12 @@ const GroupField: React.FC<GroupFieldProps> = (props) => {
<GroupProvider> <GroupProvider>
<div className={`${baseClass}__wrap`}> <div className={`${baseClass}__wrap`}>
<div className={`${baseClass}__header`}> <div className={`${baseClass}__header`}>
{(CustomLabel || CustomDescription || labelProps?.label) && ( {(CustomLabel || CustomDescription || label) && (
<header> <header>
{CustomLabel !== undefined ? ( {CustomLabel !== undefined ? (
CustomLabel CustomLabel
) : labelProps?.label ? ( ) : label ? (
<h3 className={`${baseClass}__title`}> <h3 className={`${baseClass}__title`}>{getTranslation(label, i18n)}</h3>
{getTranslation(labelProps.label, i18n)}
</h3>
) : null} ) : null}
{CustomDescription !== undefined ? ( {CustomDescription !== undefined ? (
CustomDescription CustomDescription

View File

@@ -12,7 +12,7 @@ import './index.scss'
const baseClass = 'json-field' const baseClass = 'json-field'
import type { FieldBase, JSONField as JSONFieldType } from 'payload/types' import type { JSONField as JSONFieldType } from 'payload/types'
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription' import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
import { FieldError } from '@payloadcms/ui/forms/FieldError' import { FieldError } from '@payloadcms/ui/forms/FieldError'
@@ -23,7 +23,6 @@ import type { FormFieldBase } from '../shared/index.js'
export type JSONFieldProps = FormFieldBase & { export type JSONFieldProps = FormFieldBase & {
editorOptions?: JSONFieldType['admin']['editorOptions'] editorOptions?: JSONFieldType['admin']['editorOptions']
jsonSchema?: Record<string, unknown> jsonSchema?: Record<string, unknown>
label?: FieldBase['label']
name?: string name?: string
path?: string path?: string
width?: string width?: string

View File

@@ -22,7 +22,6 @@ import './index.scss'
export type NumberFieldProps = FormFieldBase & { export type NumberFieldProps = FormFieldBase & {
hasMany?: boolean hasMany?: boolean
label?: FieldBase['label']
max?: number max?: number
maxRows?: number maxRows?: number
min?: number min?: number

View File

@@ -17,7 +17,6 @@ export type PasswordFieldProps = FormFieldBase & {
className?: string className?: string
description?: Description description?: Description
disabled?: boolean disabled?: boolean
label?: string
name: string name: string
path?: string path?: string
required?: boolean required?: boolean

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/destructuring-assignment */ /* eslint-disable react/destructuring-assignment */
'use client' 'use client'
import type { ClientValidate, FieldBase } from 'payload/types' import type { ClientValidate } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
@@ -21,7 +21,6 @@ import { useFieldProps } from '@payloadcms/ui/forms/FieldPropsProvider'
import type { FormFieldBase } from '../shared/index.js' import type { FormFieldBase } from '../shared/index.js'
export type PointFieldProps = FormFieldBase & { export type PointFieldProps = FormFieldBase & {
label?: FieldBase['label']
name?: string name?: string
path?: string path?: string
placeholder?: string placeholder?: string
@@ -40,6 +39,7 @@ const PointField: React.FC<PointFieldProps> = (props) => {
className, className,
descriptionProps, descriptionProps,
errorProps, errorProps,
label,
labelProps, labelProps,
path: pathFromProps, path: pathFromProps,
placeholder, placeholder,
@@ -90,7 +90,7 @@ const PointField: React.FC<PointFieldProps> = (props) => {
const getCoordinateFieldLabel = (type: 'latitude' | 'longitude') => { const getCoordinateFieldLabel = (type: 'latitude' | 'longitude') => {
const suffix = type === 'longitude' ? t('fields:longitude') : t('fields:latitude') const suffix = type === 'longitude' ? t('fields:longitude') : t('fields:latitude')
const fieldLabel = labelProps && labelProps.label ? getTranslation(labelProps.label, i18n) : '' const fieldLabel = label ? getTranslation(label, i18n) : ''
return { return {
...labelProps, ...labelProps,

View File

@@ -1,7 +1,7 @@
/* eslint-disable react/destructuring-assignment */ /* eslint-disable react/destructuring-assignment */
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
'use client' 'use client'
import type { FieldBase, Option } from 'payload/types' import type { Option } from 'payload/types'
import { optionIsObject } from 'payload/types' import { optionIsObject } from 'payload/types'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
@@ -23,7 +23,6 @@ import { useFieldProps } from '@payloadcms/ui/forms/FieldPropsProvider'
import type { FormFieldBase } from '../shared/index.js' import type { FormFieldBase } from '../shared/index.js'
export type RadioFieldProps = FormFieldBase & { export type RadioFieldProps = FormFieldBase & {
label?: FieldBase['label']
layout?: 'horizontal' | 'vertical' layout?: 'horizontal' | 'vertical'
name?: string name?: string
onChange?: OnChange onChange?: OnChange

View File

@@ -8,7 +8,6 @@ export type RelationshipFieldProps = FormFieldBase & {
allowCreate?: RelationshipField['admin']['allowCreate'] allowCreate?: RelationshipField['admin']['allowCreate']
hasMany?: boolean hasMany?: boolean
isSortable?: boolean isSortable?: boolean
label?: FieldBase['label']
name: string name: string
relationTo?: RelationshipField['relationTo'] relationTo?: RelationshipField['relationTo']
sortOptions?: RelationshipField['admin']['sortOptions'] sortOptions?: RelationshipField['admin']['sortOptions']

View File

@@ -1,11 +1,9 @@
import type { FieldBase } from 'payload/types'
import type React from 'react' import type React from 'react'
import type { MappedField } from '../../providers/ComponentMap/buildComponentMap/types.js' import type { MappedField } from '../../providers/ComponentMap/buildComponentMap/types.js'
import type { FormFieldBase } from '../shared/index.js' import type { FormFieldBase } from '../shared/index.js'
export type RichTextFieldProps = FormFieldBase & { export type RichTextFieldProps = FormFieldBase & {
label?: FieldBase['label']
name: string name: string
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode> richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
width?: string width?: string

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/destructuring-assignment */ /* eslint-disable react/destructuring-assignment */
'use client' 'use client'
import type { ClientValidate, FieldBase, Option, OptionObject } from 'payload/types' import type { ClientValidate, Option, OptionObject } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription' import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
@@ -22,7 +22,6 @@ export type SelectFieldProps = FormFieldBase & {
hasMany?: boolean hasMany?: boolean
isClearable?: boolean isClearable?: boolean
isSortable?: boolean isSortable?: boolean
label?: FieldBase['label']
name?: string name?: string
onChange?: (e: string) => void onChange?: (e: string) => void
options?: Option[] options?: Option[]

View File

@@ -31,6 +31,7 @@ const TextField: React.FC<TextFieldProps> = (props) => {
errorProps, errorProps,
hasMany, hasMany,
inputRef, inputRef,
label,
labelProps, labelProps,
localized, localized,
maxLength, maxLength,
@@ -127,6 +128,7 @@ const TextField: React.FC<TextFieldProps> = (props) => {
errorProps={errorProps} errorProps={errorProps}
hasMany={hasMany} hasMany={hasMany}
inputRef={inputRef} inputRef={inputRef}
label={label}
labelProps={labelProps} labelProps={labelProps}
maxRows={maxRows} maxRows={maxRows}
minRows={minRows} minRows={minRows}

View File

@@ -1,4 +1,4 @@
import type { FieldBase, TextField } from 'payload/types' import type { TextField } from 'payload/types'
import type { ChangeEvent } from 'react' import type { ChangeEvent } from 'react'
import type { Option } from '../../elements/ReactSelect/types.js' import type { Option } from '../../elements/ReactSelect/types.js'
@@ -7,7 +7,6 @@ import type { FormFieldBase } from '../shared/index.js'
export type TextFieldProps = FormFieldBase & { export type TextFieldProps = FormFieldBase & {
hasMany?: boolean hasMany?: boolean
inputRef?: React.MutableRefObject<HTMLInputElement> inputRef?: React.MutableRefObject<HTMLInputElement>
label?: FieldBase['label']
maxLength?: number maxLength?: number
maxRows?: number maxRows?: number
minLength?: number minLength?: number

View File

@@ -29,6 +29,7 @@ const TextareaField: React.FC<TextareaFieldProps> = (props) => {
className, className,
descriptionProps, descriptionProps,
errorProps, errorProps,
label,
labelProps, labelProps,
locale, locale,
localized, localized,
@@ -82,6 +83,7 @@ const TextareaField: React.FC<TextareaFieldProps> = (props) => {
className={className} className={className}
descriptionProps={descriptionProps} descriptionProps={descriptionProps}
errorProps={errorProps} errorProps={errorProps}
label={label}
labelProps={labelProps} labelProps={labelProps}
onChange={(e) => { onChange={(e) => {
setValue(e.target.value) setValue(e.target.value)

View File

@@ -1,11 +1,10 @@
import type { FieldBase, TextareaField as TextareaFieldType } from 'payload/types' import type { TextareaField as TextareaFieldType } from 'payload/types'
import { type ChangeEvent } from 'react' import { type ChangeEvent } from 'react'
import type { FormFieldBase } from '../shared/index.js' import type { FormFieldBase } from '../shared/index.js'
export type TextareaFieldProps = FormFieldBase & { export type TextareaFieldProps = FormFieldBase & {
label?: FieldBase['label']
maxLength?: number maxLength?: number
minLength?: number minLength?: number
name?: string name?: string

View File

@@ -4,7 +4,6 @@ import type { FormFieldBase } from '../shared/index.js'
export type UploadFieldProps = FormFieldBase & { export type UploadFieldProps = FormFieldBase & {
filterOptions?: UploadField['filterOptions'] filterOptions?: UploadField['filterOptions']
label?: UploadField['label']
name?: string name?: string
path?: string path?: string
relationTo?: UploadField['relationTo'] relationTo?: UploadField['relationTo']

View File

@@ -5,6 +5,7 @@ import type {
ErrorProps, ErrorProps,
FieldDescriptionProps, FieldDescriptionProps,
LabelProps, LabelProps,
SanitizedLabelProps,
Validate, Validate,
} from 'payload/types' } from 'payload/types'
@@ -22,7 +23,8 @@ export type FormFieldBase = {
disabled?: boolean disabled?: boolean
docPreferences?: DocumentPreferences docPreferences?: DocumentPreferences
errorProps?: ErrorProps errorProps?: ErrorProps
labelProps?: LabelProps label?: LabelProps['label']
labelProps?: SanitizedLabelProps
locale?: Locale locale?: Locale
localized?: boolean localized?: boolean
path?: string path?: string

View File

@@ -1,7 +1,6 @@
import type { I18n } from '@payloadcms/translations' import type { I18n } from '@payloadcms/translations'
import type { LabelProps, RowLabel, RowLabelComponent } from 'payload/types' import type { LabelProps } from 'payload/types'
import type React from 'react'
import React from 'react'
export type Props = { export type Props = {
RowLabelComponent?: React.ReactNode RowLabelComponent?: React.ReactNode
@@ -11,7 +10,3 @@ export type Props = {
rowLabel?: LabelProps['label'] rowLabel?: LabelProps['label']
rowNumber?: number rowNumber?: number
} }
export function isComponent(label: RowLabel): label is RowLabelComponent {
return React.isValidElement(label)
}

View File

@@ -24,15 +24,3 @@ export const Account = () => {
if (Avatar) return <Avatar active={isOnAccountPage} /> if (Avatar) return <Avatar active={isOnAccountPage} />
return <DefaultAccountIcon active={isOnAccountPage} /> return <DefaultAccountIcon active={isOnAccountPage} />
} }
function isClassComponent(component) {
return typeof component === 'function' && !!component.prototype.isReactComponent
}
function isFunctionComponent(component) {
return typeof component === 'function' && String(component).includes('return React.createElement')
}
function isReactComponent(component) {
return isClassComponent(component) || isFunctionComponent(component)
}

View File

@@ -3,15 +3,12 @@ import type { ViewDescriptionProps } from '@payloadcms/ui/elements/ViewDescripti
import type { import type {
AdminViewProps, AdminViewProps,
EditViewProps, EditViewProps,
EntityDescriptionComponent,
EntityDescriptionFunction,
SanitizedCollectionConfig, SanitizedCollectionConfig,
SanitizedConfig, SanitizedConfig,
WithServerSideProps as WithServerSidePropsType, WithServerSideProps as WithServerSidePropsType,
} from 'payload/types' } from 'payload/types'
import { ViewDescription } from '@payloadcms/ui/elements/ViewDescription' import { ViewDescription } from '@payloadcms/ui/elements/ViewDescription'
import { isPlainFunction, isReactComponent } from 'payload/utilities'
import React from 'react' import React from 'react'
import type { CollectionComponentMap } from './types.js' import type { CollectionComponentMap } from './types.js'
@@ -19,15 +16,7 @@ import type { CollectionComponentMap } from './types.js'
import { mapActions } from './actions.js' import { mapActions } from './actions.js'
import { mapFields } from './fields.js' import { mapFields } from './fields.js'
export const mapCollections = ({ export const mapCollections = (args: {
DefaultEditView,
DefaultListView,
WithServerSideProps,
collections,
config,
i18n,
readOnly: readOnlyOverride,
}: {
DefaultEditView: React.FC<EditViewProps> DefaultEditView: React.FC<EditViewProps>
DefaultListView: React.FC<AdminViewProps> DefaultListView: React.FC<AdminViewProps>
WithServerSideProps: WithServerSidePropsType WithServerSideProps: WithServerSidePropsType
@@ -37,8 +26,19 @@ export const mapCollections = ({
readOnly?: boolean readOnly?: boolean
}): { }): {
[key: SanitizedCollectionConfig['slug']]: CollectionComponentMap [key: SanitizedCollectionConfig['slug']]: CollectionComponentMap
} => } => {
collections.reduce((acc, collectionConfig) => { const {
DefaultEditView,
DefaultListView,
WithServerSideProps,
collections,
config,
i18n,
i18n: { t },
readOnly: readOnlyOverride,
} = args
return collections.reduce((acc, collectionConfig) => {
const { slug, fields } = collectionConfig const { slug, fields } = collectionConfig
const internalCollections = ['payload-preferences', 'payload-migrations'] const internalCollections = ['payload-preferences', 'payload-migrations']
@@ -130,26 +130,25 @@ export const mapCollections = ({
afterListTable?.map((Component) => <WithServerSideProps Component={Component} />)) || afterListTable?.map((Component) => <WithServerSideProps Component={Component} />)) ||
null null
let description = undefined
if (collectionConfig.admin && 'description' in collectionConfig.admin) {
if (
typeof collectionConfig.admin?.description === 'string' ||
typeof collectionConfig.admin?.description === 'object'
) {
description = collectionConfig.admin.description
} else if (typeof collectionConfig.admin?.description === 'function') {
description = collectionConfig.admin?.description({ t })
}
}
const descriptionProps: ViewDescriptionProps = { const descriptionProps: ViewDescriptionProps = {
description: description,
(collectionConfig.admin &&
'description' in collectionConfig.admin &&
(((typeof collectionConfig.admin?.description === 'string' ||
typeof collectionConfig.admin?.description === 'object') &&
collectionConfig.admin.description) ||
(typeof collectionConfig.admin?.description === 'function' &&
isPlainFunction<EntityDescriptionFunction>(collectionConfig.admin?.description) &&
collectionConfig.admin?.description()))) ||
undefined,
} }
const DescriptionComponent = const DescriptionComponent =
(collectionConfig.admin && collectionConfig.admin?.components?.edit?.Description ||
'description' in collectionConfig.admin && (description ? ViewDescription : undefined)
((isReactComponent<EntityDescriptionComponent>(collectionConfig.admin.description) &&
collectionConfig.admin.description) ||
(collectionConfig.admin.description && ViewDescription))) ||
undefined
const Description = const Description =
DescriptionComponent !== undefined ? ( DescriptionComponent !== undefined ? (
@@ -187,3 +186,4 @@ export const mapCollections = ({
[slug]: componentMap, [slug]: componentMap,
} }
}, {}) }, {})
}

View File

@@ -2,27 +2,17 @@ import type { I18n } from '@payloadcms/translations'
import type { CustomComponent } from 'payload/config' import type { CustomComponent } from 'payload/config'
import type { import type {
CellComponentProps, CellComponentProps,
DescriptionComponent,
DescriptionFunction,
Field, Field,
FieldBase,
FieldDescriptionProps, FieldDescriptionProps,
FieldWithPath, FieldWithPath,
LabelProps, LabelProps,
Option, Option,
RowLabelComponent,
SanitizedConfig, SanitizedConfig,
WithServerSideProps as WithServerSidePropsType, WithServerSideProps as WithServerSidePropsType,
} from 'payload/types' } from 'payload/types'
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription' import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types' import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
import {
isPlainFunction,
isReactClientComponent,
isReactComponent,
isReactServerComponent,
} from 'payload/utilities'
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import type { ArrayFieldProps } from '../../../fields/Array/index.js' import type { ArrayFieldProps } from '../../../fields/Array/index.js'
@@ -129,11 +119,17 @@ export const mapFields = (args: {
)) || )) ||
null null
let label = undefined
if ('label' in field) {
if (typeof field.label === 'string' || typeof field.label === 'object') {
label = field.label
} else if (typeof field.label === 'function') {
label = field.label({ t })
}
}
const labelProps: LabelProps = { const labelProps: LabelProps = {
label: label,
'label' in field && !isPlainFunction(field.label) && !isReactComponent(field.label)
? field.label
: undefined,
required: 'required' in field ? field.required : undefined, required: 'required' in field ? field.required : undefined,
} }
@@ -144,31 +140,38 @@ export const mapFields = (args: {
field.admin.components?.Label) || field.admin.components?.Label) ||
undefined undefined
// If we return undefined here (so if no CUSTOM label component is set), the field client component is responsible for falling back to the default label
const CustomLabel = const CustomLabel =
CustomLabelComponent !== undefined ? ( CustomLabelComponent !== undefined ? (
<WithServerSideProps Component={CustomLabelComponent} {...(labelProps || {})} /> <WithServerSideProps Component={CustomLabelComponent} {...(labelProps || {})} />
) : undefined ) : undefined
const descriptionProps: FieldDescriptionProps = { let description = undefined
description: if (field.admin && 'description' in field.admin) {
(field.admin && if (
'description' in field.admin && typeof field.admin?.description === 'string' ||
(((typeof field.admin?.description === 'string' || typeof field.admin?.description === 'object'
typeof field.admin?.description === 'object') && ) {
field.admin.description) || description = field.admin.description
(typeof field.admin?.description === 'function' && } else if (typeof field.admin?.description === 'function') {
isPlainFunction<DescriptionFunction>(field.admin?.description) && description = field.admin?.description({ t })
field.admin?.description({ t })))) || }
undefined,
} }
const CustomDescriptionComponent = const descriptionProps: FieldDescriptionProps = {
(field.admin && description,
'description' in field.admin && }
((isReactComponent<DescriptionComponent>(field.admin.description) &&
field.admin.description) || let CustomDescriptionComponent = undefined
(field.admin.description && FieldDescription))) || if (
undefined field.admin?.components &&
'Description' in field.admin.components &&
field.admin.components?.Description
) {
CustomDescriptionComponent = field.admin.components.Description
} else if (description) {
CustomDescriptionComponent = FieldDescription
}
const CustomDescription = const CustomDescription =
CustomDescriptionComponent !== undefined ? ( CustomDescriptionComponent !== undefined ? (
@@ -194,6 +197,7 @@ export const mapFields = (args: {
<WithServerSideProps Component={CustomErrorComponent} {...(errorProps || {})} /> <WithServerSideProps Component={CustomErrorComponent} {...(errorProps || {})} />
) : undefined ) : undefined
// These fields are shared across all field types even if they are not used in the default field, as the custom field component can use them
const baseFieldProps: FormFieldBase = { const baseFieldProps: FormFieldBase = {
AfterInput, AfterInput,
BeforeInput, BeforeInput,
@@ -204,7 +208,7 @@ export const mapFields = (args: {
descriptionProps, descriptionProps,
disabled: 'admin' in field && 'disabled' in field.admin ? field.admin?.disabled : false, disabled: 'admin' in field && 'disabled' in field.admin ? field.admin?.disabled : false,
errorProps, errorProps,
labelProps, label: labelProps?.label,
path, path,
required: 'required' in field ? field.required : undefined, required: 'required' in field ? field.required : undefined,
} }
@@ -217,7 +221,7 @@ export const mapFields = (args: {
fieldOptions = field.options.map((option) => { fieldOptions = field.options.map((option) => {
if (typeof option === 'object' && typeof option.label === 'function') { if (typeof option === 'object' && typeof option.label === 'function') {
return { return {
label: option.label({ t: i18n.t }), label: option.label({ t }),
value: option.value, value: option.value,
} }
} }
@@ -230,10 +234,7 @@ export const mapFields = (args: {
name: 'name' in field ? field.name : undefined, name: 'name' in field ? field.name : undefined,
fieldType: field.type, fieldType: field.type,
isFieldAffectingData, isFieldAffectingData,
label: label: labelProps?.label || undefined,
'label' in field && field.label && typeof field.label !== 'function'
? field.label
: undefined,
labels: 'labels' in field ? field.labels : undefined, labels: 'labels' in field ? field.labels : undefined,
options: 'options' in field ? fieldOptions : undefined, options: 'options' in field ? fieldOptions : undefined,
relationTo: 'relationTo' in field ? field.relationTo : undefined, relationTo: 'relationTo' in field ? field.relationTo : undefined,
@@ -247,11 +248,12 @@ export const mapFields = (args: {
'admin' in field && 'admin' in field &&
field.admin.components && field.admin.components &&
'RowLabel' in field.admin.components && 'RowLabel' in field.admin.components &&
field.admin.components.RowLabel && field.admin.components.RowLabel
isReactComponent<RowLabelComponent>(field.admin.components.RowLabel)
) { ) {
const CustomRowLabelComponent = field.admin.components.RowLabel const CustomRowLabelComponent = field.admin.components.RowLabel
CustomRowLabel = <WithServerSideProps Component={CustomRowLabelComponent} /> CustomRowLabel = (
<WithServerSideProps Component={CustomRowLabelComponent} {...(labelProps || {})} />
)
} }
const arrayFieldProps: Omit<ArrayFieldProps, 'indexPath' | 'permissions'> = { const arrayFieldProps: Omit<ArrayFieldProps, 'indexPath' | 'permissions'> = {
@@ -270,7 +272,6 @@ export const mapFields = (args: {
readOnly: readOnlyOverride, readOnly: readOnlyOverride,
}), }),
isSortable: field.admin?.isSortable, isSortable: field.admin?.isSortable,
label: field?.label,
labels: field.labels, labels: field.labels,
maxRows: field.maxRows, maxRows: field.maxRows,
minRows: field.minRows, minRows: field.minRows,
@@ -314,7 +315,6 @@ export const mapFields = (args: {
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
isSortable: field.admin?.isSortable, isSortable: field.admin?.isSortable,
label: field?.label,
labels: field.labels, labels: field.labels,
maxRows: field.maxRows, maxRows: field.maxRows,
minRows: field.minRows, minRows: field.minRows,
@@ -339,7 +339,6 @@ export const mapFields = (args: {
name: field.name, name: field.name,
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
label: field.label,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
style: field.admin?.style, style: field.admin?.style,
@@ -356,7 +355,6 @@ export const mapFields = (args: {
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
editorOptions: field.admin?.editorOptions, editorOptions: field.admin?.editorOptions,
label: field.label,
language: field.admin?.language, language: field.admin?.language,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
@@ -369,11 +367,17 @@ export const mapFields = (args: {
} }
case 'collapsible': { case 'collapsible': {
let CustomCollapsibleLabel: React.ReactNode let CustomCollapsibleLabel: React.ReactNode
if (
if (isReactComponent(field.label) || isPlainFunction(field.label)) { field?.admin?.components &&
const CustomCollapsibleLabelComponent = field.label as RowLabelComponent 'RowLabel' in field.admin.components &&
field?.admin?.components?.RowLabel
) {
const CustomCollapsibleLabelComponent = field.admin.components.RowLabel
CustomCollapsibleLabel = ( CustomCollapsibleLabel = (
<WithServerSideProps Component={CustomCollapsibleLabelComponent} /> <WithServerSideProps
Component={CustomCollapsibleLabelComponent}
{...(labelProps || {})}
/>
) )
} }
@@ -393,7 +397,6 @@ export const mapFields = (args: {
readOnly: readOnlyOverride, readOnly: readOnlyOverride,
}), }),
initCollapsed: field.admin?.initCollapsed, initCollapsed: field.admin?.initCollapsed,
label: !CustomCollapsibleLabel ? (field.label as FieldBase['label']) : undefined,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
style: field.admin?.style, style: field.admin?.style,
@@ -410,7 +413,6 @@ export const mapFields = (args: {
className: field.admin?.className, className: field.admin?.className,
date: field.admin?.date, date: field.admin?.date,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
label: field.label,
placeholder: field.admin?.placeholder, placeholder: field.admin?.placeholder,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
@@ -428,7 +430,6 @@ export const mapFields = (args: {
name: field.name, name: field.name,
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
label: field.label,
placeholder: field.admin?.placeholder, placeholder: field.admin?.placeholder,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
@@ -455,7 +456,6 @@ export const mapFields = (args: {
parentPath: path, parentPath: path,
readOnly: readOnlyOverride, readOnly: readOnlyOverride,
}), }),
label: field.label,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
style: field.admin?.style, style: field.admin?.style,
width: field.admin?.width, width: field.admin?.width,
@@ -472,7 +472,6 @@ export const mapFields = (args: {
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
editorOptions: field.admin?.editorOptions, editorOptions: field.admin?.editorOptions,
jsonSchema: field.jsonSchema, jsonSchema: field.jsonSchema,
label: field.label,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
style: field.admin?.style, style: field.admin?.style,
@@ -489,7 +488,6 @@ export const mapFields = (args: {
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
hasMany: field.hasMany, hasMany: field.hasMany,
label: field.label,
max: field.max, max: field.max,
maxRows: field.maxRows, maxRows: field.maxRows,
min: field.min, min: field.min,
@@ -509,7 +507,6 @@ export const mapFields = (args: {
name: field.name, name: field.name,
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
label: field.label,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
style: field.admin?.style, style: field.admin?.style,
@@ -527,7 +524,6 @@ export const mapFields = (args: {
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
hasMany: field.hasMany, hasMany: field.hasMany,
label: field.label,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
relationTo: field.relationTo, relationTo: field.relationTo,
required: field.required, required: field.required,
@@ -546,7 +542,6 @@ export const mapFields = (args: {
name: field.name, name: field.name,
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
label: field.label,
options: fieldOptions, options: fieldOptions,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
@@ -564,7 +559,6 @@ export const mapFields = (args: {
name: field.name, name: field.name,
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
label: field.label,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
style: field.admin?.style, style: field.admin?.style,
@@ -671,7 +665,6 @@ export const mapFields = (args: {
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
hasMany: field.hasMany, hasMany: field.hasMany,
label: field.label,
maxLength: field.maxLength, maxLength: field.maxLength,
minLength: field.minLength, minLength: field.minLength,
placeholder: field.admin?.placeholder, placeholder: field.admin?.placeholder,
@@ -690,7 +683,6 @@ export const mapFields = (args: {
name: field.name, name: field.name,
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
label: field.label,
maxLength: field.maxLength, maxLength: field.maxLength,
minLength: field.minLength, minLength: field.minLength,
placeholder: field.admin?.placeholder, placeholder: field.admin?.placeholder,
@@ -715,7 +707,6 @@ export const mapFields = (args: {
className: field.admin?.className, className: field.admin?.className,
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
filterOptions: field.filterOptions, filterOptions: field.filterOptions,
label: field.label,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
relationTo: field.relationTo, relationTo: field.relationTo,
required: field.required, required: field.required,
@@ -735,7 +726,6 @@ export const mapFields = (args: {
disabled: field.admin?.disabled, disabled: field.admin?.disabled,
hasMany: field.hasMany, hasMany: field.hasMany,
isClearable: field.admin?.isClearable, isClearable: field.admin?.isClearable,
label: field.label,
options: fieldOptions, options: fieldOptions,
readOnly: field.admin?.readOnly, readOnly: field.admin?.readOnly,
required: field.required, required: field.required,
@@ -798,9 +788,6 @@ export const mapFields = (args: {
fieldComponentProps: { fieldComponentProps: {
name: 'id', name: 'id',
label: 'ID', label: 'ID',
labelProps: {
label: 'ID',
},
}, },
fieldIsPresentational: false, fieldIsPresentational: false,
isFieldAffectingData: true, isFieldAffectingData: true,

View File

@@ -1,15 +1,12 @@
import type { I18n } from '@payloadcms/translations' import type { I18n } from '@payloadcms/translations'
import type { import type {
EditViewProps, EditViewProps,
EntityDescriptionComponent,
EntityDescriptionFunction,
SanitizedConfig, SanitizedConfig,
SanitizedGlobalConfig, SanitizedGlobalConfig,
WithServerSideProps as WithServerSidePropsType, WithServerSideProps as WithServerSidePropsType,
} from 'payload/types' } from 'payload/types'
import { ViewDescription, type ViewDescriptionProps } from '@payloadcms/ui/elements/ViewDescription' import { ViewDescription, type ViewDescriptionProps } from '@payloadcms/ui/elements/ViewDescription'
import { isPlainFunction, isReactComponent } from 'payload/utilities'
import React from 'react' import React from 'react'
import type { GlobalComponentMap } from './types.js' import type { GlobalComponentMap } from './types.js'
@@ -17,14 +14,7 @@ import type { GlobalComponentMap } from './types.js'
import { mapActions } from './actions.js' import { mapActions } from './actions.js'
import { mapFields } from './fields.js' import { mapFields } from './fields.js'
export const mapGlobals = ({ export const mapGlobals = (args: {
DefaultEditView,
WithServerSideProps,
config,
globals,
i18n,
readOnly: readOnlyOverride,
}: {
DefaultEditView: React.FC<EditViewProps> DefaultEditView: React.FC<EditViewProps>
WithServerSideProps: WithServerSidePropsType WithServerSideProps: WithServerSidePropsType
config: SanitizedConfig config: SanitizedConfig
@@ -33,8 +23,18 @@ export const mapGlobals = ({
readOnly?: boolean readOnly?: boolean
}): { }): {
[key: SanitizedGlobalConfig['slug']]: GlobalComponentMap [key: SanitizedGlobalConfig['slug']]: GlobalComponentMap
} => } => {
globals.reduce((acc, globalConfig) => { const {
DefaultEditView,
WithServerSideProps,
config,
globals,
i18n,
i18n: { t },
readOnly: readOnlyOverride,
} = args
return globals.reduce((acc, globalConfig) => {
const { slug, fields } = globalConfig const { slug, fields } = globalConfig
const editViewFromConfig = globalConfig?.admin?.components?.views?.Edit const editViewFromConfig = globalConfig?.admin?.components?.views?.Edit
@@ -76,26 +76,24 @@ export const mapGlobals = ({
const Edit = (CustomEditView as React.FC<EditViewProps>) || DefaultEditView const Edit = (CustomEditView as React.FC<EditViewProps>) || DefaultEditView
const descriptionProps: ViewDescriptionProps = { let description = undefined
description: if (globalConfig.admin && 'description' in globalConfig.admin) {
(globalConfig.admin && if (
'description' in globalConfig.admin && typeof globalConfig.admin?.description === 'string' ||
(((typeof globalConfig.admin?.description === 'string' || typeof globalConfig.admin?.description === 'object'
typeof globalConfig.admin?.description === 'object') && ) {
globalConfig.admin.description) || description = globalConfig.admin.description
(typeof globalConfig.admin?.description === 'function' && } else if (typeof globalConfig.admin?.description === 'function') {
isPlainFunction<EntityDescriptionFunction>(globalConfig.admin?.description) && description = globalConfig.admin?.description({ t })
globalConfig.admin?.description()))) || }
undefined,
} }
const descriptionProps: ViewDescriptionProps = {
description,
}
const DescriptionComponent = const DescriptionComponent =
(globalConfig.admin && globalConfig.admin?.components?.elements?.Description ||
'description' in globalConfig.admin && (description ? ViewDescription : undefined)
((isReactComponent<EntityDescriptionComponent>(globalConfig.admin.description) &&
globalConfig.admin.description) ||
(globalConfig.admin.description && ViewDescription))) ||
undefined
const Description = const Description =
DescriptionComponent !== undefined ? ( DescriptionComponent !== undefined ? (
@@ -128,3 +126,4 @@ export const mapGlobals = ({
[slug]: componentMap, [slug]: componentMap,
} }
}, {}) }, {})
}

View File

@@ -11,7 +11,7 @@ import React from 'react'
import type { ComponentMap } from './types.js' import type { ComponentMap } from './types.js'
import { WithServerSideProps as WithServerSidePropsGeneric } from './WithServerSideProps.js' import { WithServerSideProps as WithServerSidePropsGeneric } from '../../../elements/WithServerSideProps/index.js'
import { mapCollections } from './collections.js' import { mapCollections } from './collections.js'
import { mapGlobals } from './globals.js' import { mapGlobals } from './globals.js'

View File

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

View File

@@ -119,7 +119,9 @@ export const Posts: CollectionConfig = {
name: 'descriptionAsComponent', name: 'descriptionAsComponent',
type: 'text', type: 'text',
admin: { admin: {
description: FieldDescriptionComponent, components: {
Description: FieldDescriptionComponent,
},
}, },
}, },
], ],

View File

@@ -78,16 +78,18 @@ const CollapsibleFields: CollectionConfig = {
], ],
}, },
{ {
label: () =>
getCustomLabel({
path: 'functionTitleField',
fallback: 'Custom Collapsible Label',
style: {},
}),
type: 'collapsible', type: 'collapsible',
admin: { admin: {
description: 'Collapsible label rendered from a function.', description: 'Collapsible label rendered from a function.',
initCollapsed: true, initCollapsed: true,
components: {
RowLabel: () =>
getCustomLabel({
path: 'functionTitleField',
fallback: 'Custom Collapsible Label',
style: {},
}),
},
}, },
fields: [ fields: [
{ {
@@ -97,10 +99,12 @@ const CollapsibleFields: CollectionConfig = {
], ],
}, },
{ {
label: () => getCustomLabel({ path: 'componentTitleField', style: {} }),
type: 'collapsible', type: 'collapsible',
admin: { admin: {
description: 'Collapsible label rendered as a react component.', description: 'Collapsible label rendered as a react component.',
components: {
RowLabel: () => getCustomLabel({ path: 'componentTitleField', style: {} }),
},
}, },
fields: [ fields: [
{ {
@@ -109,8 +113,12 @@ const CollapsibleFields: CollectionConfig = {
}, },
{ {
type: 'collapsible', type: 'collapsible',
label: () => admin: {
getCustomLabel({ path: 'nestedTitle', fallback: 'Nested Collapsible', style: {} }), components: {
RowLabel: () =>
getCustomLabel({ path: 'nestedTitle', fallback: 'Nested Collapsible', style: {} }),
},
},
fields: [ fields: [
{ {
type: 'text', type: 'text',
@@ -125,7 +133,11 @@ const CollapsibleFields: CollectionConfig = {
type: 'array', type: 'array',
fields: [ fields: [
{ {
label: NestedCustomLabel, admin: {
components: {
RowLabel: NestedCustomLabel,
},
},
type: 'collapsible', type: 'collapsible',
fields: [ fields: [
{ {