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

View File

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

View File

@@ -1,10 +1,10 @@
import type { LabelFunction } from '../../config/types.js'
export type LabelProps = {
CustomLabel?: React.ReactNode
as?: 'label' | 'span'
htmlFor?: string
label?: LabelFunction | Record<string, string> | false | string
label?: Record<string, string> | string
required?: 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 RowLabel = Record<string, string> | RowLabelComponent | string
export type RowLabel = Record<string, string> | string

View File

@@ -22,7 +22,7 @@ export type {
FieldDescriptionProps,
} from './forms/FieldDescription.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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ export const baseAdminComponentFields = joi
.object()
.keys({
Cell: componentSchema,
Description: componentSchema,
Field: componentSchema,
Filter: componentSchema,
})
@@ -18,7 +19,7 @@ export const baseAdminFields = joi.object().keys({
custom: joi.object().pattern(joi.string(), joi.any()),
description: joi
.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),
disabled: joi.boolean().default(false),
hidden: joi.boolean().default(false),
@@ -269,9 +270,27 @@ export const row = baseField.keys({
export const collapsible = baseField.keys({
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')),
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({

View File

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

View File

@@ -19,6 +19,7 @@ const globalSchema = joi
admin: joi.object({
components: joi.object({
elements: joi.object({
Description: componentSchema,
PreviewButton: componentSchema,
PublishButton: componentSchema,
SaveButton: componentSchema,
@@ -40,7 +41,9 @@ const globalSchema = joi
}),
}),
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
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()])),

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
'use client'
import type { TextField } from 'payload/types'
import type { TextFieldProps } from '@payloadcms/ui/fields/Text'
import { Select } from '@payloadcms/ui/fields/Select'
import { useForm } from '@payloadcms/ui/forms/Form'
@@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'
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 [options, setOptions] = useState<SelectFieldOption[]>([])

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ type MetaImageProps = UploadInputProps & {
}
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)
@@ -77,7 +77,7 @@ export const MetaImage: React.FC<MetaImageProps> = (props) => {
}}
>
<div className="plugin-seo__field">
<FieldLabel CustomLabel={CustomLabel} {...(labelProps || {})} />
<FieldLabel CustomLabel={CustomLabel} label={label} {...(labelProps || {})} />
{hasGenerateImageFn && (
<React.Fragment>
&nbsp; &mdash; &nbsp;

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
'use client'
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 { HistoryEditor } from 'slate-history'
import type { ReactEditor } from 'slate-react'
@@ -50,7 +50,6 @@ declare module 'slate' {
const RichTextField: React.FC<
FormFieldBase & {
elements: EnabledFeatures['elements']
label?: FieldBase['label']
leaves: EnabledFeatures['leaves']
name: string
placeholder?: string

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
'use client'
import type { FieldPermissions } from 'payload/auth'
import type { FieldBase } from 'payload/types'
import type { ArrayField as ArrayFieldType } from 'payload/types'
import { getTranslation } from '@payloadcms/translations'
@@ -38,7 +37,6 @@ export type ArrayFieldProps = FormFieldBase & {
fieldMap: FieldMap
forceRender?: boolean
isSortable?: boolean
label?: FieldBase['label']
labels?: ArrayFieldType['labels']
maxRows?: ArrayFieldType['maxRows']
minRows?: ArrayFieldType['minRows']
@@ -93,9 +91,9 @@ export const _ArrayField: React.FC<ArrayFieldProps> = (props) => {
})()
// 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 ('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') }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ import './index.scss'
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 { FieldError } from '@payloadcms/ui/forms/FieldError'
@@ -26,7 +26,6 @@ import { withCondition } from '../../forms/withCondition/index.js'
export type DateFieldProps = FormFieldBase & {
date?: DateField['admin']['date']
label?: FieldBase['label']
name?: string
path?: string
placeholder?: DateField['admin']['placeholder'] | string

View File

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

View File

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

View File

@@ -12,7 +12,7 @@ import './index.scss'
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 { FieldError } from '@payloadcms/ui/forms/FieldError'
@@ -23,7 +23,6 @@ import type { FormFieldBase } from '../shared/index.js'
export type JSONFieldProps = FormFieldBase & {
editorOptions?: JSONFieldType['admin']['editorOptions']
jsonSchema?: Record<string, unknown>
label?: FieldBase['label']
name?: string
path?: string
width?: string

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/destructuring-assignment */
'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 { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
@@ -22,7 +22,6 @@ export type SelectFieldProps = FormFieldBase & {
hasMany?: boolean
isClearable?: boolean
isSortable?: boolean
label?: FieldBase['label']
name?: string
onChange?: (e: string) => void
options?: Option[]

View File

@@ -31,6 +31,7 @@ const TextField: React.FC<TextFieldProps> = (props) => {
errorProps,
hasMany,
inputRef,
label,
labelProps,
localized,
maxLength,
@@ -127,6 +128,7 @@ const TextField: React.FC<TextFieldProps> = (props) => {
errorProps={errorProps}
hasMany={hasMany}
inputRef={inputRef}
label={label}
labelProps={labelProps}
maxRows={maxRows}
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 { Option } from '../../elements/ReactSelect/types.js'
@@ -7,7 +7,6 @@ import type { FormFieldBase } from '../shared/index.js'
export type TextFieldProps = FormFieldBase & {
hasMany?: boolean
inputRef?: React.MutableRefObject<HTMLInputElement>
label?: FieldBase['label']
maxLength?: number
maxRows?: number
minLength?: number

View File

@@ -29,6 +29,7 @@ const TextareaField: React.FC<TextareaFieldProps> = (props) => {
className,
descriptionProps,
errorProps,
label,
labelProps,
locale,
localized,
@@ -82,6 +83,7 @@ const TextareaField: React.FC<TextareaFieldProps> = (props) => {
className={className}
descriptionProps={descriptionProps}
errorProps={errorProps}
label={label}
labelProps={labelProps}
onChange={(e) => {
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 { FormFieldBase } from '../shared/index.js'
export type TextareaFieldProps = FormFieldBase & {
label?: FieldBase['label']
maxLength?: number
minLength?: number
name?: string

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import type { I18n } from '@payloadcms/translations'
import type { LabelProps, RowLabel, RowLabelComponent } from 'payload/types'
import React from 'react'
import type { LabelProps } from 'payload/types'
import type React from 'react'
export type Props = {
RowLabelComponent?: React.ReactNode
@@ -11,7 +10,3 @@ export type Props = {
rowLabel?: LabelProps['label']
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} />
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 {
AdminViewProps,
EditViewProps,
EntityDescriptionComponent,
EntityDescriptionFunction,
SanitizedCollectionConfig,
SanitizedConfig,
WithServerSideProps as WithServerSidePropsType,
} from 'payload/types'
import { ViewDescription } from '@payloadcms/ui/elements/ViewDescription'
import { isPlainFunction, isReactComponent } from 'payload/utilities'
import React from 'react'
import type { CollectionComponentMap } from './types.js'
@@ -19,15 +16,7 @@ import type { CollectionComponentMap } from './types.js'
import { mapActions } from './actions.js'
import { mapFields } from './fields.js'
export const mapCollections = ({
DefaultEditView,
DefaultListView,
WithServerSideProps,
collections,
config,
i18n,
readOnly: readOnlyOverride,
}: {
export const mapCollections = (args: {
DefaultEditView: React.FC<EditViewProps>
DefaultListView: React.FC<AdminViewProps>
WithServerSideProps: WithServerSidePropsType
@@ -37,8 +26,19 @@ export const mapCollections = ({
readOnly?: boolean
}): {
[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 internalCollections = ['payload-preferences', 'payload-migrations']
@@ -130,26 +130,25 @@ export const mapCollections = ({
afterListTable?.map((Component) => <WithServerSideProps Component={Component} />)) ||
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 = {
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,
description,
}
const DescriptionComponent =
(collectionConfig.admin &&
'description' in collectionConfig.admin &&
((isReactComponent<EntityDescriptionComponent>(collectionConfig.admin.description) &&
collectionConfig.admin.description) ||
(collectionConfig.admin.description && ViewDescription))) ||
undefined
collectionConfig.admin?.components?.edit?.Description ||
(description ? ViewDescription : undefined)
const Description =
DescriptionComponent !== undefined ? (
@@ -187,3 +186,4 @@ export const mapCollections = ({
[slug]: componentMap,
}
}, {})
}

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ import React from 'react'
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 { mapGlobals } from './globals.js'

View File

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

View File

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