From cfeac79b997de23e570c97375083f42799de22fe Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Thu, 9 May 2024 17:12:01 -0400 Subject: [PATCH] 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 --- packages/next/src/views/Login/index.tsx | 18 ++- .../src/admin/forms/FieldDescription.ts | 6 +- packages/payload/src/admin/forms/Label.ts | 6 +- packages/payload/src/admin/forms/RowLabel.ts | 2 +- packages/payload/src/admin/types.ts | 2 +- .../payload/src/collections/config/schema.ts | 5 +- .../payload/src/collections/config/types.ts | 3 + packages/payload/src/config/types.ts | 8 +- packages/payload/src/exports/utilities.ts | 5 +- .../payload/src/fields/config/sanitize.ts | 1 + packages/payload/src/fields/config/schema.ts | 25 +++- packages/payload/src/fields/config/types.ts | 27 +++- packages/payload/src/globals/config/schema.ts | 5 +- packages/payload/src/globals/config/types.ts | 3 +- .../payload/src/utilities/isReactComponent.ts | 22 ++-- .../Forms/DynamicFieldSelector.tsx | 4 +- .../Forms/DynamicPriceSelector.tsx | 10 +- .../plugin-seo/src/fields/MetaDescription.tsx | 4 +- packages/plugin-seo/src/fields/MetaImage.tsx | 4 +- packages/plugin-seo/src/fields/MetaTitle.tsx | 4 +- packages/plugin-seo/src/index.tsx | 8 +- packages/richtext-lexical/src/field/Field.tsx | 2 - .../richtext-slate/src/field/RichText.tsx | 3 +- packages/translations/src/clientKeys.ts | 8 ++ .../ui/src/elements/FieldSelect/index.tsx | 7 +- .../TableColumns/buildColumnState.tsx | 3 +- .../WithServerSideProps/index.tsx} | 6 +- packages/ui/src/fields/Array/index.tsx | 6 +- packages/ui/src/fields/Blocks/index.tsx | 1 - packages/ui/src/fields/Checkbox/Input.tsx | 6 +- packages/ui/src/fields/Checkbox/types.ts | 3 - packages/ui/src/fields/Code/index.tsx | 1 - packages/ui/src/fields/Collapsible/index.tsx | 12 +- packages/ui/src/fields/DateTime/index.tsx | 3 +- packages/ui/src/fields/Email/index.tsx | 1 - packages/ui/src/fields/Group/index.tsx | 12 +- packages/ui/src/fields/JSON/index.tsx | 3 +- packages/ui/src/fields/Number/index.tsx | 1 - packages/ui/src/fields/Password/index.tsx | 1 - packages/ui/src/fields/Point/index.tsx | 6 +- packages/ui/src/fields/RadioGroup/index.tsx | 3 +- packages/ui/src/fields/Relationship/types.ts | 1 - packages/ui/src/fields/RichText/index.tsx | 2 - packages/ui/src/fields/Select/index.tsx | 3 +- packages/ui/src/fields/Text/index.tsx | 2 + packages/ui/src/fields/Text/types.ts | 3 +- packages/ui/src/fields/Textarea/index.tsx | 2 + packages/ui/src/fields/Textarea/types.ts | 3 +- packages/ui/src/fields/Upload/types.ts | 1 - packages/ui/src/fields/shared/index.tsx | 4 +- packages/ui/src/forms/RowLabel/types.ts | 9 +- packages/ui/src/graphics/Account/index.tsx | 12 -- .../buildComponentMap/collections.tsx | 60 ++++----- .../ComponentMap/buildComponentMap/fields.tsx | 119 ++++++++---------- .../buildComponentMap/globals.tsx | 59 +++++---- .../ComponentMap/buildComponentMap/index.tsx | 2 +- .../ui/src/providers/ComponentMap/index.tsx | 2 - test/admin/collections/Posts.ts | 4 +- test/fields/collections/Collapsible/index.ts | 32 +++-- 59 files changed, 296 insertions(+), 284 deletions(-) rename packages/ui/src/{providers/ComponentMap/buildComponentMap/WithServerSideProps.tsx => elements/WithServerSideProps/index.tsx} (66%) diff --git a/packages/next/src/views/Login/index.tsx b/packages/next/src/views/Login/index.tsx index 1919fbc65..ae0b9ccd3 100644 --- a/packages/next/src/views/Login/index.tsx +++ b/packages/next/src/views/Login/index.tsx @@ -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 = ({ initPageResult, searchPara const { payload: { config }, + payload, user, } = req @@ -25,6 +27,18 @@ export const LoginView: React.FC = ({ initPageResult, searchPara routes: { admin }, } = config + const BeforeLogins = Array.isArray(beforeLogin) + ? beforeLogin.map((Component, i) => ( + + )) + : null + + const AfterLogins = Array.isArray(afterLogin) + ? afterLogin.map((Component, i) => ( + + )) + : null + if (user) { redirect(admin) } @@ -36,9 +50,9 @@ export const LoginView: React.FC = ({ initPageResult, searchPara
- {Array.isArray(beforeLogin) && beforeLogin.map((Component, i) => )} + {Array.isArray(BeforeLogins) && BeforeLogins.map((Component) => Component)} {!collectionConfig?.auth?.disableLocalStrategy && } - {Array.isArray(afterLogin) && afterLogin.map((Component, i) => )} + {Array.isArray(AfterLogins) && AfterLogins.map((Component) => Component)} ) } diff --git a/packages/payload/src/admin/forms/FieldDescription.ts b/packages/payload/src/admin/forms/FieldDescription.ts index 7974559df..23883aa19 100644 --- a/packages/payload/src/admin/forms/FieldDescription.ts +++ b/packages/payload/src/admin/forms/FieldDescription.ts @@ -7,11 +7,7 @@ export type DescriptionFunction = LabelFunction export type DescriptionComponent = CustomComponent -export type Description = - | DescriptionComponent - | DescriptionFunction - | Record - | string +export type Description = DescriptionFunction | Record | string export type FieldDescriptionProps = { CustomDescription?: React.ReactNode diff --git a/packages/payload/src/admin/forms/Label.ts b/packages/payload/src/admin/forms/Label.ts index 0a725d7c5..61ed75eb6 100644 --- a/packages/payload/src/admin/forms/Label.ts +++ b/packages/payload/src/admin/forms/Label.ts @@ -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 | false | string + label?: Record | string required?: boolean unstyled?: boolean } + +export type SanitizedLabelProps = Omit diff --git a/packages/payload/src/admin/forms/RowLabel.ts b/packages/payload/src/admin/forms/RowLabel.ts index d75d7b0a7..75ad28945 100644 --- a/packages/payload/src/admin/forms/RowLabel.ts +++ b/packages/payload/src/admin/forms/RowLabel.ts @@ -2,4 +2,4 @@ import type { CustomComponent } from '../../config/types.js' export type RowLabelComponent = CustomComponent -export type RowLabel = Record | RowLabelComponent | string +export type RowLabel = Record | string diff --git a/packages/payload/src/admin/types.ts b/packages/payload/src/admin/types.ts index af64aecee..38ffbe0e0 100644 --- a/packages/payload/src/admin/types.ts +++ b/packages/payload/src/admin/types.ts @@ -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 { diff --git a/packages/payload/src/collections/config/schema.ts b/packages/payload/src/collections/config/schema.ts index acb264619..0e71b8a50 100644 --- a/packages/payload/src/collections/config/schema.ts +++ b/packages/payload/src/collections/config/schema.ts @@ -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()])), diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 4964bd784..c839991b0 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -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 */ diff --git a/packages/payload/src/config/types.ts b/packages/payload/src/config/types.ts index ebfcc99a2..6a94f9557 100644 --- a/packages/payload/src/config/types.ts +++ b/packages/payload/src/config/types.ts @@ -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 +export type EntityDescription = EntityDescriptionFunction | Record | string export type { EmailAdapter, SendEmailOptions } diff --git a/packages/payload/src/exports/utilities.ts b/packages/payload/src/exports/utilities.ts index 1d1e28e93..aa5955ec2 100644 --- a/packages/payload/src/exports/utilities.ts +++ b/packages/payload/src/exports/utilities.ts @@ -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' diff --git a/packages/payload/src/fields/config/sanitize.ts b/packages/payload/src/fields/config/sanitize.ts index d252ea7ee..df024734f 100644 --- a/packages/payload/src/fields/config/sanitize.ts +++ b/packages/payload/src/fields/config/sanitize.ts @@ -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) diff --git a/packages/payload/src/fields/config/schema.ts b/packages/payload/src/fields/config/schema.ts index d5562ab7d..e7f08a0e3 100644 --- a/packages/payload/src/fields/config/schema.ts +++ b/packages/payload/src/fields/config/schema.ts @@ -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({ diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index c7051f704..be0f6f913 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -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 } @@ -361,13 +364,25 @@ export type RowField = Omit & { } export type CollapsibleField = Omit & { - admin?: Admin & { - initCollapsed?: boolean - } fields: Field[] - label: RowLabel type: 'collapsible' -} +} & ( + | { + admin: Admin & { + components: { + RowLabel: RowLabelComponent + } & Admin['components'] + initCollapsed?: boolean + } + label?: Required + } + | { + admin?: Admin & { + initCollapsed?: boolean + } + label: Required + } + ) export type TabsAdmin = Omit @@ -610,7 +625,7 @@ export type RichTextField< export type ArrayField = FieldBase & { admin?: Admin & { components?: { - RowLabel?: RowLabel + RowLabel?: RowLabelComponent } & Admin['components'] initCollapsed?: boolean /** diff --git a/packages/payload/src/globals/config/schema.ts b/packages/payload/src/globals/config/schema.ts index 7a40ebca1..fcf158e86 100644 --- a/packages/payload/src/globals/config/schema.ts +++ b/packages/payload/src/globals/config/schema.ts @@ -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()])), diff --git a/packages/payload/src/globals/config/types.ts b/packages/payload/src/globals/config/types.ts index b501694f7..06e9c7f74 100644 --- a/packages/payload/src/globals/config/types.ts +++ b/packages/payload/src/globals/config/types.ts @@ -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 */ diff --git a/packages/payload/src/utilities/isReactComponent.ts b/packages/payload/src/utilities/isReactComponent.ts index 3910f1c4a..d554e48a4 100644 --- a/packages/payload/src/utilities/isReactComponent.ts +++ b/packages/payload/src/utilities/isReactComponent.ts @@ -1,13 +1,19 @@ import type React from 'react' -import { isValidElement } from 'react' - import { isPlainObject } from './isPlainObject.js' -export function isReactServerComponent( +export function isReactServerComponentOrFunction( 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( @@ -17,12 +23,8 @@ export function isReactClientComponent( return typeof component === 'object' && !isPlainObject(component) } -export function isReactComponent( +export function isReactComponentOrFunction( component: React.ComponentType | any, ): component is T { - return isReactServerComponent(component) || isReactClientComponent(component) -} - -export function isPlainFunction(fn: any): fn is T { - return typeof fn === 'function' && !isReactComponent(fn) + return isReactServerComponentOrFunction(component) || isReactClientComponent(component) } diff --git a/packages/plugin-form-builder/src/collections/Forms/DynamicFieldSelector.tsx b/packages/plugin-form-builder/src/collections/Forms/DynamicFieldSelector.tsx index 3877df090..db03aa9f1 100644 --- a/packages/plugin-form-builder/src/collections/Forms/DynamicFieldSelector.tsx +++ b/packages/plugin-form-builder/src/collections/Forms/DynamicFieldSelector.tsx @@ -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 = (props) => { +export const DynamicFieldSelector: React.FC = (props) => { const { fields, getDataByPath } = useForm() const [options, setOptions] = useState([]) diff --git a/packages/plugin-form-builder/src/collections/Forms/DynamicPriceSelector.tsx b/packages/plugin-form-builder/src/collections/Forms/DynamicPriceSelector.tsx index 7825b42fd..76e9d9125 100644 --- a/packages/plugin-form-builder/src/collections/Forms/DynamicPriceSelector.tsx +++ b/packages/plugin-form-builder/src/collections/Forms/DynamicPriceSelector.tsx @@ -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 = (props) => { const { fields, getData, getDataByPath } = useWatchForm() const locale = useLocale() - const { t } = useTranslation() const [isNumberField, setIsNumberField] = useState() const [valueType, setValueType] = useState<'static' | 'valueOfField'>() @@ -54,12 +52,8 @@ export const DynamicPriceSelector: React.FC = (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) { diff --git a/packages/plugin-seo/src/fields/MetaDescription.tsx b/packages/plugin-seo/src/fields/MetaDescription.tsx index 54ba05e33..f15e50b85 100644 --- a/packages/plugin-seo/src/fields/MetaDescription.tsx +++ b/packages/plugin-seo/src/fields/MetaDescription.tsx @@ -27,7 +27,7 @@ type MetaDescriptionProps = FormFieldBase & { } export const MetaDescription: React.FC = (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 = (props) => { }} >
- + {hasGenerateDescriptionFn && (   —   diff --git a/packages/plugin-seo/src/fields/MetaImage.tsx b/packages/plugin-seo/src/fields/MetaImage.tsx index 9be4a6c4d..bbb32ea9f 100644 --- a/packages/plugin-seo/src/fields/MetaImage.tsx +++ b/packages/plugin-seo/src/fields/MetaImage.tsx @@ -23,7 +23,7 @@ type MetaImageProps = UploadInputProps & { } export const MetaImage: React.FC = (props) => { - const { CustomLabel, hasGenerateImageFn, labelProps, relationTo, required } = props || {} + const { CustomLabel, hasGenerateImageFn, label, labelProps, relationTo, required } = props || {} const field: FieldType = useField(props as Options) @@ -77,7 +77,7 @@ export const MetaImage: React.FC = (props) => { }} >
- + {hasGenerateImageFn && (   —   diff --git a/packages/plugin-seo/src/fields/MetaTitle.tsx b/packages/plugin-seo/src/fields/MetaTitle.tsx index 4d8e1b36b..35767a4c6 100644 --- a/packages/plugin-seo/src/fields/MetaTitle.tsx +++ b/packages/plugin-seo/src/fields/MetaTitle.tsx @@ -28,7 +28,7 @@ type MetaTitleProps = FormFieldBase & { } export const MetaTitle: React.FC = (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 = (props) => { }} >
- + {hasGenerateTitleFn && (   —   diff --git a/packages/plugin-seo/src/index.tsx b/packages/plugin-seo/src/index.tsx index f28947a93..ae6b3fe3a 100644 --- a/packages/plugin-seo/src/index.tsx +++ b/packages/plugin-seo/src/index.tsx @@ -43,7 +43,7 @@ export const seoPlugin = type: 'text', admin: { components: { - Field: (props) => ( + Field: ({ payload: _payload, ...props }) => ( ( + Field: ({ payload: _payload, ...props }) => ( ( + Field: ({ payload: _payload, ...props }) => ( ( + Field: ({ payload: _payload, ...props }) => ( width?: string diff --git a/packages/richtext-slate/src/field/RichText.tsx b/packages/richtext-slate/src/field/RichText.tsx index 96cdd9eb2..428726ad9 100644 --- a/packages/richtext-slate/src/field/RichText.tsx +++ b/packages/richtext-slate/src/field/RichText.tsx @@ -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 diff --git a/packages/translations/src/clientKeys.ts b/packages/translations/src/clientKeys.ts index 0f5e8cbf6..806e2b5da 100644 --- a/packages/translations/src/clientKeys.ts +++ b/packages/translations/src/clientKeys.ts @@ -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', diff --git a/packages/ui/src/elements/FieldSelect/index.tsx b/packages/ui/src/elements/FieldSelect/index.tsx index 6f6b76a19..5538f0217 100644 --- a/packages/ui/src/elements/FieldSelect/index.tsx +++ b/packages/ui/src/elements/FieldSelect/index.tsx @@ -34,8 +34,11 @@ const combineLabel = ({ ? field.fieldComponentProps.CustomLabel : null const DefaultLabelToRender = - field && 'labelProps' in field.fieldComponentProps && field.fieldComponentProps.labelProps ? ( - + field && 'label' in field.fieldComponentProps && field.fieldComponentProps.label ? ( + ) : null const LabelToRender = CustomLabelToRender || DefaultLabelToRender || customLabel diff --git a/packages/ui/src/elements/TableColumns/buildColumnState.tsx b/packages/ui/src/elements/TableColumns/buildColumnState.tsx index 9bfca3283..7b070ba07 100644 --- a/packages/ui/src/elements/TableColumns/buildColumnState.tsx +++ b/packages/ui/src/elements/TableColumns/buildColumnState.tsx @@ -103,7 +103,8 @@ export const buildColumnState = (args: Args): Column[] => { const Label = ( ) diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/WithServerSideProps.tsx b/packages/ui/src/elements/WithServerSideProps/index.tsx similarity index 66% rename from packages/ui/src/providers/ComponentMap/buildComponentMap/WithServerSideProps.tsx rename to packages/ui/src/elements/WithServerSideProps/index.tsx index 44d2ab773..d0b0e90e2 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/WithServerSideProps.tsx +++ b/packages/ui/src/elements/WithServerSideProps/index.tsx @@ -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 = (passedProps) => { + const WithServerSideProps: React.FC = (passedProps) => { const propsWithPayload = { ...passedProps, - ...(isReactServerComponent(Component) ? { payload } : {}), + ...(isReactServerComponentOrFunction(Component) ? { payload } : {}), } return diff --git a/packages/ui/src/fields/Array/index.tsx b/packages/ui/src/fields/Array/index.tsx index bab05037f..f1955178d 100644 --- a/packages/ui/src/fields/Array/index.tsx +++ b/packages/ui/src/fields/Array/index.tsx @@ -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 = (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') } } diff --git a/packages/ui/src/fields/Blocks/index.tsx b/packages/ui/src/fields/Blocks/index.tsx index 63dfc0871..c7ce8096c 100644 --- a/packages/ui/src/fields/Blocks/index.tsx +++ b/packages/ui/src/fields/Blocks/index.tsx @@ -40,7 +40,6 @@ export type BlocksFieldProps = FormFieldBase & { blocks?: ReducedBlock[] forceRender?: boolean isSortable?: boolean - label?: FieldBase['label'] labels?: BlockField['labels'] maxRows?: number minRows?: number diff --git a/packages/ui/src/fields/Checkbox/Input.tsx b/packages/ui/src/fields/Checkbox/Input.tsx index 4f54fdd4b..9c8d2a7ce 100644 --- a/packages/ui/src/fields/Checkbox/Input.tsx +++ b/packages/ui/src/fields/Checkbox/Input.tsx @@ -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 - label?: FieldBase['label'] - labelProps?: LabelProps + label?: LabelProps['label'] + labelProps?: SanitizedLabelProps name?: string onToggle: (event: React.ChangeEvent) => void partialChecked?: boolean diff --git a/packages/ui/src/fields/Checkbox/types.ts b/packages/ui/src/fields/Checkbox/types.ts index 6ee087d61..814fa5ba0 100644 --- a/packages/ui/src/fields/Checkbox/types.ts +++ b/packages/ui/src/fields/Checkbox/types.ts @@ -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 diff --git a/packages/ui/src/fields/Code/index.tsx b/packages/ui/src/fields/Code/index.tsx index 5d73a0b06..016dd4153 100644 --- a/packages/ui/src/fields/Code/index.tsx +++ b/packages/ui/src/fields/Code/index.tsx @@ -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 diff --git a/packages/ui/src/fields/Collapsible/index.tsx b/packages/ui/src/fields/Collapsible/index.tsx index f9e66ce55..e9b902d92 100644 --- a/packages/ui/src/fields/Collapsible/index.tsx +++ b/packages/ui/src/fields/Collapsible/index.tsx @@ -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 = (props) => { descriptionProps, fieldMap, initCollapsed = false, - labelProps, + label, path: pathFromProps, readOnly: readOnlyFromProps, } = props @@ -139,12 +138,7 @@ const CollapsibleField: React.FC = (props) => { collapsibleStyle={fieldHasErrors ? 'error' : 'default'} header={
- + {fieldHasErrors && }
} diff --git a/packages/ui/src/fields/DateTime/index.tsx b/packages/ui/src/fields/DateTime/index.tsx index 64d266e90..58e1bdf3e 100644 --- a/packages/ui/src/fields/DateTime/index.tsx +++ b/packages/ui/src/fields/DateTime/index.tsx @@ -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 diff --git a/packages/ui/src/fields/Email/index.tsx b/packages/ui/src/fields/Email/index.tsx index e980f1ea8..415e5bcaa 100644 --- a/packages/ui/src/fields/Email/index.tsx +++ b/packages/ui/src/fields/Email/index.tsx @@ -19,7 +19,6 @@ import './index.scss' export type EmailFieldProps = FormFieldBase & { autoComplete?: string - label?: FieldBase['label'] name?: string path?: string placeholder?: EmailFieldType['admin']['placeholder'] diff --git a/packages/ui/src/fields/Group/index.tsx b/packages/ui/src/fields/Group/index.tsx index 52ed72d80..4ace28cee 100644 --- a/packages/ui/src/fields/Group/index.tsx +++ b/packages/ui/src/fields/Group/index.tsx @@ -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 = (props) => { descriptionProps, fieldMap, hideGutter, - labelProps, + label, readOnly: readOnlyFromProps, style, width, @@ -89,14 +87,12 @@ const GroupField: React.FC = (props) => {
- {(CustomLabel || CustomDescription || labelProps?.label) && ( + {(CustomLabel || CustomDescription || label) && (
{CustomLabel !== undefined ? ( CustomLabel - ) : labelProps?.label ? ( -

- {getTranslation(labelProps.label, i18n)} -

+ ) : label ? ( +

{getTranslation(label, i18n)}

) : null} {CustomDescription !== undefined ? ( CustomDescription diff --git a/packages/ui/src/fields/JSON/index.tsx b/packages/ui/src/fields/JSON/index.tsx index 6d177376d..e8210e8b4 100644 --- a/packages/ui/src/fields/JSON/index.tsx +++ b/packages/ui/src/fields/JSON/index.tsx @@ -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 - label?: FieldBase['label'] name?: string path?: string width?: string diff --git a/packages/ui/src/fields/Number/index.tsx b/packages/ui/src/fields/Number/index.tsx index b437c1faf..e898d0d4d 100644 --- a/packages/ui/src/fields/Number/index.tsx +++ b/packages/ui/src/fields/Number/index.tsx @@ -22,7 +22,6 @@ import './index.scss' export type NumberFieldProps = FormFieldBase & { hasMany?: boolean - label?: FieldBase['label'] max?: number maxRows?: number min?: number diff --git a/packages/ui/src/fields/Password/index.tsx b/packages/ui/src/fields/Password/index.tsx index 7391a55db..a2804b5df 100644 --- a/packages/ui/src/fields/Password/index.tsx +++ b/packages/ui/src/fields/Password/index.tsx @@ -17,7 +17,6 @@ export type PasswordFieldProps = FormFieldBase & { className?: string description?: Description disabled?: boolean - label?: string name: string path?: string required?: boolean diff --git a/packages/ui/src/fields/Point/index.tsx b/packages/ui/src/fields/Point/index.tsx index 406ad9698..d508438e8 100644 --- a/packages/ui/src/fields/Point/index.tsx +++ b/packages/ui/src/fields/Point/index.tsx @@ -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 = (props) => { className, descriptionProps, errorProps, + label, labelProps, path: pathFromProps, placeholder, @@ -90,7 +90,7 @@ const PointField: React.FC = (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, diff --git a/packages/ui/src/fields/RadioGroup/index.tsx b/packages/ui/src/fields/RadioGroup/index.tsx index e5a71a776..7a45fe93c 100644 --- a/packages/ui/src/fields/RadioGroup/index.tsx +++ b/packages/ui/src/fields/RadioGroup/index.tsx @@ -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 diff --git a/packages/ui/src/fields/Relationship/types.ts b/packages/ui/src/fields/Relationship/types.ts index 8ff7a7c29..da22fb5fb 100644 --- a/packages/ui/src/fields/Relationship/types.ts +++ b/packages/ui/src/fields/Relationship/types.ts @@ -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'] diff --git a/packages/ui/src/fields/RichText/index.tsx b/packages/ui/src/fields/RichText/index.tsx index 73066b60c..c85fd56e6 100644 --- a/packages/ui/src/fields/RichText/index.tsx +++ b/packages/ui/src/fields/RichText/index.tsx @@ -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 width?: string diff --git a/packages/ui/src/fields/Select/index.tsx b/packages/ui/src/fields/Select/index.tsx index 81bc09377..513491b03 100644 --- a/packages/ui/src/fields/Select/index.tsx +++ b/packages/ui/src/fields/Select/index.tsx @@ -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[] diff --git a/packages/ui/src/fields/Text/index.tsx b/packages/ui/src/fields/Text/index.tsx index 9bb161f34..c07b85895 100644 --- a/packages/ui/src/fields/Text/index.tsx +++ b/packages/ui/src/fields/Text/index.tsx @@ -31,6 +31,7 @@ const TextField: React.FC = (props) => { errorProps, hasMany, inputRef, + label, labelProps, localized, maxLength, @@ -127,6 +128,7 @@ const TextField: React.FC = (props) => { errorProps={errorProps} hasMany={hasMany} inputRef={inputRef} + label={label} labelProps={labelProps} maxRows={maxRows} minRows={minRows} diff --git a/packages/ui/src/fields/Text/types.ts b/packages/ui/src/fields/Text/types.ts index 208e7773d..1d14d27ab 100644 --- a/packages/ui/src/fields/Text/types.ts +++ b/packages/ui/src/fields/Text/types.ts @@ -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 - label?: FieldBase['label'] maxLength?: number maxRows?: number minLength?: number diff --git a/packages/ui/src/fields/Textarea/index.tsx b/packages/ui/src/fields/Textarea/index.tsx index 57e4d6bd3..d9f5afb65 100644 --- a/packages/ui/src/fields/Textarea/index.tsx +++ b/packages/ui/src/fields/Textarea/index.tsx @@ -29,6 +29,7 @@ const TextareaField: React.FC = (props) => { className, descriptionProps, errorProps, + label, labelProps, locale, localized, @@ -82,6 +83,7 @@ const TextareaField: React.FC = (props) => { className={className} descriptionProps={descriptionProps} errorProps={errorProps} + label={label} labelProps={labelProps} onChange={(e) => { setValue(e.target.value) diff --git a/packages/ui/src/fields/Textarea/types.ts b/packages/ui/src/fields/Textarea/types.ts index 2a4ad1f95..bbd393075 100644 --- a/packages/ui/src/fields/Textarea/types.ts +++ b/packages/ui/src/fields/Textarea/types.ts @@ -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 diff --git a/packages/ui/src/fields/Upload/types.ts b/packages/ui/src/fields/Upload/types.ts index 961652f44..9968757fa 100644 --- a/packages/ui/src/fields/Upload/types.ts +++ b/packages/ui/src/fields/Upload/types.ts @@ -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'] diff --git a/packages/ui/src/fields/shared/index.tsx b/packages/ui/src/fields/shared/index.tsx index 6df1efb22..7d05d79eb 100644 --- a/packages/ui/src/fields/shared/index.tsx +++ b/packages/ui/src/fields/shared/index.tsx @@ -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 diff --git a/packages/ui/src/forms/RowLabel/types.ts b/packages/ui/src/forms/RowLabel/types.ts index d0b71fa5d..9d8070eb8 100644 --- a/packages/ui/src/forms/RowLabel/types.ts +++ b/packages/ui/src/forms/RowLabel/types.ts @@ -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) -} diff --git a/packages/ui/src/graphics/Account/index.tsx b/packages/ui/src/graphics/Account/index.tsx index 256559491..5e61931e3 100644 --- a/packages/ui/src/graphics/Account/index.tsx +++ b/packages/ui/src/graphics/Account/index.tsx @@ -24,15 +24,3 @@ export const Account = () => { if (Avatar) return return } - -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) -} diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx index ccfa0023f..deca0f1ee 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/collections.tsx @@ -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 DefaultListView: React.FC 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) => )) || 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(collectionConfig.admin?.description) && - collectionConfig.admin?.description()))) || - undefined, + description, } const DescriptionComponent = - (collectionConfig.admin && - 'description' in collectionConfig.admin && - ((isReactComponent(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, } }, {}) +} diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx index e16dace55..4c12861f9 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx @@ -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 ? ( ) : 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(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(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: { ) : 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(field.admin.components.RowLabel) + field.admin.components.RowLabel ) { const CustomRowLabelComponent = field.admin.components.RowLabel - CustomRowLabel = + CustomRowLabel = ( + + ) } const arrayFieldProps: Omit = { @@ -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 = ( - + ) } @@ -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, diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx index 10d44da0f..2a50eff92 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/globals.tsx @@ -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 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) || 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(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(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, } }, {}) +} diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx index 8abade7e5..3abb65cc0 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/index.tsx @@ -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' diff --git a/packages/ui/src/providers/ComponentMap/index.tsx b/packages/ui/src/providers/ComponentMap/index.tsx index e0b378532..79e0b4efb 100644 --- a/packages/ui/src/providers/ComponentMap/index.tsx +++ b/packages/ui/src/providers/ComponentMap/index.tsx @@ -17,8 +17,6 @@ export type IComponentMapContext = { }) => MappedField | undefined } -export { WithServerSideProps } from './buildComponentMap/WithServerSideProps.js' - const ComponentMapContext = createContext({} as IComponentMapContext) export const ComponentMapProvider: React.FC<{ diff --git a/test/admin/collections/Posts.ts b/test/admin/collections/Posts.ts index 70c58f493..9fa3f8cd1 100644 --- a/test/admin/collections/Posts.ts +++ b/test/admin/collections/Posts.ts @@ -119,7 +119,9 @@ export const Posts: CollectionConfig = { name: 'descriptionAsComponent', type: 'text', admin: { - description: FieldDescriptionComponent, + components: { + Description: FieldDescriptionComponent, + }, }, }, ], diff --git a/test/fields/collections/Collapsible/index.ts b/test/fields/collections/Collapsible/index.ts index 2286392e8..b31899586 100644 --- a/test/fields/collections/Collapsible/index.ts +++ b/test/fields/collections/Collapsible/index.ts @@ -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: [ {