chore(ui): strictly types fields (#5344)

This commit is contained in:
Jacob Fletcher
2024-03-15 17:41:48 -04:00
committed by GitHub
parent 09f2926bbb
commit fb4651bdad
78 changed files with 933 additions and 554 deletions

View File

@@ -50,10 +50,11 @@ export const SetStepNav: React.FC<{
if (mostRecentDoc) { if (mostRecentDoc) {
if (useAsTitle !== 'id') { if (useAsTitle !== 'id') {
const titleField = fieldMap.find( const titleField = fieldMap.find((f) => {
({ name: fieldName, isFieldAffectingData }) => const { isFieldAffectingData } = f
isFieldAffectingData && fieldName === useAsTitle, const fieldName = 'name' in f ? f.name : undefined
) as FieldAffectingData return isFieldAffectingData && fieldName === useAsTitle
}) as FieldAffectingData
if (titleField && mostRecentDoc[useAsTitle]) { if (titleField && mostRecentDoc[useAsTitle]) {
if (titleField.localized) { if (titleField.localized) {

View File

@@ -28,7 +28,7 @@ const Iterable: React.FC<Props> = ({
return ( return (
<div className={baseClass}> <div className={baseClass}>
{field.label && ( {'label' in field && field.label && (
<Label> <Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>} {locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.label, i18n)} {getTranslation(field.label, i18n)}
@@ -40,12 +40,12 @@ const Iterable: React.FC<Props> = ({
const versionRow = version?.[i] || {} const versionRow = version?.[i] || {}
const comparisonRow = comparison?.[i] || {} const comparisonRow = comparison?.[i] || {}
let subFields: MappedField[] = [] let fieldMap: MappedField[] = []
if (field.type === 'array') subFields = field.subfields if (field.type === 'array' && 'fieldMap' in field) fieldMap = field.fieldMap
if (field.type === 'blocks') { if (field.type === 'blocks') {
subFields = [ fieldMap = [
// { // {
// name: 'blockType', // name: 'blockType',
// label: i18n.t('fields:blockType'), // label: i18n.t('fields:blockType'),
@@ -54,25 +54,28 @@ const Iterable: React.FC<Props> = ({
] ]
if (versionRow?.blockType === comparisonRow?.blockType) { if (versionRow?.blockType === comparisonRow?.blockType) {
const matchedBlock = field.blocks.find( const matchedBlock = ('blocks' in field &&
(block) => block.slug === versionRow?.blockType, field.blocks?.find((block) => block.slug === versionRow?.blockType)) || {
) || { subfields: [] } fieldMap: [],
}
subFields = [...subFields, ...matchedBlock.subfields] fieldMap = [...fieldMap, ...matchedBlock.fieldMap]
} else { } else {
const matchedVersionBlock = field.blocks.find( const matchedVersionBlock = ('blocks' in field &&
(block) => block.slug === versionRow?.blockType, field.blocks?.find((block) => block.slug === versionRow?.blockType)) || {
) || { subfields: [] } fieldMap: [],
}
const matchedComparisonBlock = field.blocks.find( const matchedComparisonBlock = ('blocks' in field &&
(block) => block.slug === comparisonRow?.blockType, field.blocks?.find((block) => block.slug === comparisonRow?.blockType)) || {
) || { subfields: [] } fieldMap: [],
}
subFields = getUniqueListBy<MappedField>( fieldMap = getUniqueListBy<MappedField>(
[ [
...subFields, ...fieldMap,
...matchedVersionBlock.subfields, ...matchedVersionBlock.fieldMap,
...matchedComparisonBlock.subfields, ...matchedComparisonBlock.fieldMap,
], ],
'name', 'name',
) )
@@ -84,7 +87,7 @@ const Iterable: React.FC<Props> = ({
<RenderFieldsToDiff <RenderFieldsToDiff
comparison={comparisonRow} comparison={comparisonRow}
diffComponents={diffComponents} diffComponents={diffComponents}
fieldMap={subFields} fieldMap={fieldMap}
fieldPermissions={permissions} fieldPermissions={permissions}
i18n={i18n} i18n={i18n}
locales={locales} locales={locales}
@@ -98,9 +101,10 @@ const Iterable: React.FC<Props> = ({
{maxRows === 0 && ( {maxRows === 0 && (
<div className={`${baseClass}__no-rows`}> <div className={`${baseClass}__no-rows`}>
{i18n.t('version:noRowsFound', { {i18n.t('version:noRowsFound', {
label: field.labels?.plural label:
? getTranslation(field.labels?.plural, i18n) 'labels' in field && field.labels?.plural
: i18n.t('general:rows'), ? getTranslation(field.labels?.plural, i18n)
: i18n.t('general:rows'),
})} })}
</div> </div>
)} )}

View File

@@ -23,7 +23,7 @@ const Nested: React.FC<Props> = ({
}) => { }) => {
return ( return (
<div className={baseClass}> <div className={baseClass}>
{field.label && ( {'label' in field && field.label && (
<Label> <Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>} {locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.label, i18n)} {getTranslation(field.label, i18n)}

View File

@@ -82,7 +82,7 @@ const Relationship: React.FC<Props> = ({ comparison, field, i18n, locale, versio
let versionToRender = version let versionToRender = version
let comparisonToRender = comparison let comparisonToRender = comparison
if (field.hasMany) { if ('hasMany' in field && field.hasMany) {
if (Array.isArray(version)) if (Array.isArray(version))
versionToRender = version versionToRender = version
.map((val) => generateLabelFromValue(collections, field, locale, val)) .map((val) => generateLabelFromValue(collections, field, locale, val))

View File

@@ -1,4 +1,6 @@
import type { I18n } from '@payloadcms/translations' import type { I18n } from '@payloadcms/translations'
import type { SelectFieldProps } from 'packages/ui/src/forms/fields/Select/types.js'
import type { MappedFieldBase } from 'packages/ui/src/utilities/buildComponentMap/types.js'
import type { OptionObject, SelectField } from 'payload/types' import type { OptionObject, SelectField } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
@@ -44,7 +46,11 @@ const getTranslatedOptions = (
return typeof options === 'string' ? options : getTranslation(options.label, i18n) return typeof options === 'string' ? options : getTranslation(options.label, i18n)
} }
const Select: React.FC<Props> = ({ comparison, diffMethod, field, i18n, locale, version }) => { const Select: React.FC<
Omit<Props, 'field'> & {
field: MappedFieldBase & SelectFieldProps
}
> = ({ comparison, diffMethod, field, i18n, locale, version }) => {
let placeholder = '' let placeholder = ''
if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]` if (version === comparison) placeholder = `[${i18n.t('general:noValue')}]`
@@ -63,7 +69,7 @@ const Select: React.FC<Props> = ({ comparison, diffMethod, field, i18n, locale,
<div className={baseClass}> <div className={baseClass}>
<Label> <Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>} {locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.label || '', i18n)} {'label' in field && getTranslation(field.label || '', i18n)}
</Label> </Label>
<DiffViewer <DiffViewer
comparisonToRender={comparisonToRender} comparisonToRender={comparisonToRender}

View File

@@ -1,3 +1,6 @@
import type { TabsFieldProps } from 'packages/ui/src/forms/fields/Tabs/types.js'
import type { MappedFieldBase } from 'packages/ui/src/utilities/buildComponentMap/types.js'
import React from 'react' import React from 'react'
import type { Props } from '../types.js' import type { Props } from '../types.js'
@@ -7,16 +10,11 @@ import Nested from '../Nested/index.js'
const baseClass = 'tabs-diff' const baseClass = 'tabs-diff'
const Tabs: React.FC<Props> = ({ const Tabs: React.FC<
comparison, Omit<Props, 'field'> & {
diffComponents, field: MappedFieldBase & TabsFieldProps
field, }
i18n, > = ({ comparison, diffComponents, field, i18n, locale, locales, permissions, version }) => {
locale,
locales,
permissions,
version,
}) => {
return ( return (
<div className={baseClass}> <div className={baseClass}>
<div className={`${baseClass}__wrap`}> <div className={`${baseClass}__wrap`}>
@@ -27,7 +25,7 @@ const Tabs: React.FC<Props> = ({
comparison={comparison?.[tab.name]} comparison={comparison?.[tab.name]}
diffComponents={diffComponents} diffComponents={diffComponents}
field={field} field={field}
fieldMap={tab.subfields} fieldMap={tab.fieldMap}
i18n={i18n} i18n={i18n}
key={i} key={i}
locale={locale} locale={locale}
@@ -42,7 +40,7 @@ const Tabs: React.FC<Props> = ({
<RenderFieldsToDiff <RenderFieldsToDiff
comparison={comparison} comparison={comparison}
diffComponents={diffComponents} diffComponents={diffComponents}
fieldMap={tab.subfields} fieldMap={tab.fieldMap}
fieldPermissions={permissions} fieldPermissions={permissions}
i18n={i18n} i18n={i18n}
key={i} key={i}

View File

@@ -35,7 +35,7 @@ const Text: React.FC<Props> = ({
<div className={baseClass}> <div className={baseClass}>
<Label> <Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>} {locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field?.label || '', i18n)} {'label' in field && getTranslation(field?.label || '', i18n)}
</Label> </Label>
<DiffViewer <DiffViewer
comparisonToRender={comparisonToRender} comparisonToRender={comparisonToRender}

View File

@@ -23,7 +23,7 @@ const RenderFieldsToDiff: React.FC<Props> = ({
return ( return (
<div className={baseClass}> <div className={baseClass}>
{fieldMap?.map((field, i) => { {fieldMap?.map((field, i) => {
if (field.name === 'id') return null if ('name' in field && field.name === 'id') return null
const Component = diffComponents[field.type] const Component = diffComponents[field.type]
@@ -31,7 +31,7 @@ const RenderFieldsToDiff: React.FC<Props> = ({
const diffMethod: DiffMethod = diffMethods[field.type] || 'CHARS' const diffMethod: DiffMethod = diffMethods[field.type] || 'CHARS'
if (Component) { if (Component) {
if (field.isFieldAffectingData) { if (field.isFieldAffectingData && 'name' in field) {
const valueIsObject = field.type === 'code' || field.type === 'json' const valueIsObject = field.type === 'code' || field.type === 'json'
const versionValue = valueIsObject const versionValue = valueIsObject
@@ -53,7 +53,7 @@ const RenderFieldsToDiff: React.FC<Props> = ({
diffComponents, diffComponents,
diffMethod, diffMethod,
field, field,
fieldMap: 'subfields' in field ? field.subfields : fieldMap, fieldMap: 'fieldMap' in field ? field.fieldMap : fieldMap,
fieldPermissions: subFieldPermissions, fieldPermissions: subFieldPermissions,
i18n, i18n,
isRichText, isRichText,
@@ -93,7 +93,7 @@ const RenderFieldsToDiff: React.FC<Props> = ({
) )
} }
if (field.type === 'tabs') { if (field.type === 'tabs' && 'fieldMap' in field) {
const Tabs = diffComponents.tabs const Tabs = diffComponents.tabs
return ( return (
@@ -101,7 +101,7 @@ const RenderFieldsToDiff: React.FC<Props> = ({
comparison={comparison} comparison={comparison}
diffComponents={diffComponents} diffComponents={diffComponents}
field={field} field={field}
fieldMap={field.subfields} fieldMap={field.fieldMap}
i18n={i18n} i18n={i18n}
key={i} key={i}
locales={locales} locales={locales}
@@ -111,14 +111,14 @@ const RenderFieldsToDiff: React.FC<Props> = ({
} }
// At this point, we are dealing with a `row`, etc // At this point, we are dealing with a `row`, etc
if (field.subfields) { if ('fieldMap' in field) {
return ( return (
<Nested <Nested
comparison={comparison} comparison={comparison}
diffComponents={diffComponents} diffComponents={diffComponents}
disableGutter disableGutter
field={field} field={field}
fieldMap={field.subfields} fieldMap={field.fieldMap}
i18n={i18n} i18n={i18n}
key={i} key={i}
locales={locales} locales={locales}

View File

@@ -17,6 +17,7 @@ export const RichText: React.FC<
editorConfig: SanitizedClientEditorConfig // With rendered features n stuff editorConfig: SanitizedClientEditorConfig // With rendered features n stuff
name: string name: string
richTextComponentMap: Map<string, React.ReactNode> richTextComponentMap: Map<string, React.ReactNode>
width?: string
} }
> = (props) => { > = (props) => {
const { const {

View File

@@ -1,5 +1,5 @@
import type { FieldMap, FormFieldBase } from '@payloadcms/ui' import type { FieldMap, FormFieldBase } from '@payloadcms/ui'
import type { ReducedBlock } from '@payloadcms/ui/types' import type { ReducedBlock } from '@payloadcms/ui'
import type { FormState } from 'payload/types' import type { FormState } from 'payload/types'
import type { Data } from 'payload/types' import type { Data } from 'payload/types'

View File

@@ -13,7 +13,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { type BlockFields } from '../nodes/BlocksNode.js' import { type BlockFields } from '../nodes/BlocksNode.js'
const baseClass = 'lexical-block' const baseClass = 'lexical-block'
import type { ReducedBlock } from '@payloadcms/ui/types' import type { ReducedBlock } from '@payloadcms/ui'
import type { FormState } from 'payload/types' import type { FormState } from 'payload/types'
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import type { ReducedBlock } from '@payloadcms/ui/types' import type { ReducedBlock } from '@payloadcms/ui'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'

View File

@@ -43,10 +43,10 @@ export const BlocksFeature: FeatureProviderProviderServer<
for (const block of props.blocks) { for (const block of props.blocks) {
clientProps.reducedBlocks.push({ clientProps.reducedBlocks.push({
slug: block.slug, slug: block.slug,
fieldMap: [],
imageAltText: block.imageAltText, imageAltText: block.imageAltText,
imageURL: block.imageURL, imageURL: block.imageURL,
labels: block.labels, labels: block.labels,
subfields: [],
}) })
} }

View File

@@ -2,7 +2,6 @@ import type { FormState } from 'payload/types'
import { import {
Drawer, Drawer,
FieldPathProvider,
Form, Form,
type FormProps, type FormProps,
FormSubmit, FormSubmit,
@@ -79,7 +78,6 @@ export const LinkDrawer: React.FC<Props> = ({ drawerSlug, handleModalSubmit, sta
<Drawer className={baseClass} slug={drawerSlug} title={t('fields:editLink') ?? ''}> <Drawer className={baseClass} slug={drawerSlug} title={t('fields:editLink') ?? ''}>
{initialState !== false && ( {initialState !== false && (
<Form <Form
// @ts-expect-error // TODO: Fix this type. Is this correct?
fields={Array.isArray(fieldMap) ? fieldMap : []} fields={Array.isArray(fieldMap) ? fieldMap : []}
initialState={initialState} initialState={initialState}
onChange={[onChange]} onChange={[onChange]}

View File

@@ -45,8 +45,10 @@ const RichTextField: React.FC<
elements: EnabledFeatures['elements'] elements: EnabledFeatures['elements']
leaves: EnabledFeatures['leaves'] leaves: EnabledFeatures['leaves']
name: string name: string
placeholder?: string
plugins: RichTextPlugin[] plugins: RichTextPlugin[]
richTextComponentMap: Map<string, React.ReactNode> richTextComponentMap: Map<string, React.ReactNode>
width?: string
} }
> = (props) => { > = (props) => {
const { const {

View File

@@ -123,7 +123,7 @@ export const EditMany: React.FC<Props> = (props) => {
const reducedFieldMap = [] const reducedFieldMap = []
fieldMap.map((field) => { fieldMap.map((field) => {
selected.map((selectedField) => { selected.map((selectedField) => {
if (field.name === selectedField.name) { if ('name' in field && field.name === selectedField.name) {
reducedFieldMap.push(field) reducedFieldMap.push(field)
} }
}) })

View File

@@ -27,8 +27,8 @@ export const buildColumns = (args: {
// sort the fields to the order of `defaultColumns` or `columnPreferences` // sort the fields to the order of `defaultColumns` or `columnPreferences`
// TODO: flatten top level field, i.e. `flattenTopLevelField()` from `payload` but that is typed for `Field`, not `fieldMap` // TODO: flatten top level field, i.e. `flattenTopLevelField()` from `payload` but that is typed for `Field`, not `fieldMap`
sortedFieldMap = fieldMap.sort((a, b) => { sortedFieldMap = fieldMap.sort((a, b) => {
const aIndex = sortTo.findIndex((column) => column.accessor === a.name) const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
const bIndex = sortTo.findIndex((column) => column.accessor === b.name) const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
if (aIndex === -1 && bIndex === -1) return 0 if (aIndex === -1 && bIndex === -1) return 0
if (aIndex === -1) return 1 if (aIndex === -1) return 1
if (bIndex === -1) return -1 if (bIndex === -1) return -1
@@ -40,7 +40,7 @@ export const buildColumns = (args: {
const sorted = sortedFieldMap.reduce((acc, field, index) => { const sorted = sortedFieldMap.reduce((acc, field, index) => {
const columnPreference = columnPreferences?.find( const columnPreference = columnPreferences?.find(
(preference) => preference.accessor === field.name, (preference) => 'name' in field && preference.accessor === field.name,
) )
let active = false let active = false
@@ -48,7 +48,7 @@ export const buildColumns = (args: {
if (columnPreference) { if (columnPreference) {
active = columnPreference.active active = columnPreference.active
} else if (defaultColumns && Array.isArray(defaultColumns) && defaultColumns.length > 0) { } else if (defaultColumns && Array.isArray(defaultColumns) && defaultColumns.length > 0) {
active = defaultColumns.includes(field.name) active = 'name' in field && defaultColumns.includes(field.name)
} else if (activeColumnsIndices.length < 4) { } else if (activeColumnsIndices.length < 4) {
active = true active = true
} }
@@ -61,8 +61,8 @@ export const buildColumns = (args: {
if (field) { if (field) {
const column: Column = { const column: Column = {
name: field.name, name: 'name' in field ? field.name : undefined,
accessor: field.name, accessor: 'name' in field ? field.name : undefined,
active, active,
cellProps: { cellProps: {
...cellProps?.[index], ...cellProps?.[index],
@@ -72,7 +72,7 @@ export const buildColumns = (args: {
Cell: field.Cell, Cell: field.Cell,
Heading: field.Heading, Heading: field.Heading,
}, },
label: field.label, label: 'label' in field ? field.label : undefined,
} }
acc.push(column) acc.push(column)

View File

@@ -23,31 +23,41 @@ export { default as FormSubmit } from '../forms/Submit/index.js'
export { default as Submit } from '../forms/Submit/index.js' export { default as Submit } from '../forms/Submit/index.js'
export { buildStateFromSchema } from '../forms/buildStateFromSchema/index.js' export { buildStateFromSchema } from '../forms/buildStateFromSchema/index.js'
export type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js' export type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js'
export type { ArrayFieldProps } from '../forms/fields/Array/types.js'
export { default as SectionTitle } from '../forms/fields/Blocks/SectionTitle/index.js' export { default as SectionTitle } from '../forms/fields/Blocks/SectionTitle/index.js'
export type { BlocksFieldProps } from '../forms/fields/Blocks/types.js'
export { CheckboxInput } from '../forms/fields/Checkbox/Input.js' export { CheckboxInput } from '../forms/fields/Checkbox/Input.js'
export { default as Checkbox } from '../forms/fields/Checkbox/index.js' export { default as Checkbox } from '../forms/fields/Checkbox/index.js'
export type { CheckboxFieldProps } from '../forms/fields/Checkbox/types.js'
export type { CodeFieldProps } from '../forms/fields/Code/types.js'
export { default as ConfirmPassword } from '../forms/fields/ConfirmPassword/index.js' export { default as ConfirmPassword } from '../forms/fields/ConfirmPassword/index.js'
export type { DateFieldProps } from '../forms/fields/DateTime/types.js'
export { default as Email } from '../forms/fields/Email/index.js' export { default as Email } from '../forms/fields/Email/index.js'
export type { EmailFieldProps } from '../forms/fields/Email/types.js'
export type { GroupFieldProps } from '../forms/fields/Group/types.js'
export { default as HiddenInput } from '../forms/fields/HiddenInput/index.js' export { default as HiddenInput } from '../forms/fields/HiddenInput/index.js'
export type { HiddenInputFieldProps } from '../forms/fields/HiddenInput/types.js'
export type { JSONFieldProps } from '../forms/fields/JSON/types.js'
export { default as Number } from '../forms/fields/Number/index.js' export { default as Number } from '../forms/fields/Number/index.js'
export type { NumberFieldProps } from '../forms/fields/Number/types.js'
export { default as Password } from '../forms/fields/Password/index.js' export { default as Password } from '../forms/fields/Password/index.js'
export { default as RadioGroupInput } from '../forms/fields/RadioGroup/index.js' export { default as RadioGroupInput } from '../forms/fields/RadioGroup/index.js'
export type { OnChange } from '../forms/fields/RadioGroup/types.js' export type { OnChange } from '../forms/fields/RadioGroup/types.js'
export { default as Select } from '../forms/fields/Select/index.js' export { default as Select } from '../forms/fields/Select/index.js'
export { default as SelectInput } from '../forms/fields/Select/index.js' export { default as SelectInput } from '../forms/fields/Select/index.js'
export { TextInput, type TextInputProps } from '../forms/fields/Text/Input.js' export { TextInput, type TextInputProps } from '../forms/fields/Text/Input.js'
export { default as Text } from '../forms/fields/Text/index.js' export { default as Text } from '../forms/fields/Text/index.js'
export type { Props as TextFieldProps } from '../forms/fields/Text/types.js'
export { type TextAreaInputProps, TextareaInput } from '../forms/fields/Textarea/Input.js' export { type TextAreaInputProps, TextareaInput } from '../forms/fields/Textarea/Input.js'
export { default as Textarea } from '../forms/fields/Textarea/index.js' export { default as Textarea } from '../forms/fields/Textarea/index.js'
export { UploadInput, type UploadInputProps } from '../forms/fields/Upload/Input.js' export { UploadInput, type UploadInputProps } from '../forms/fields/Upload/Input.js'
export { default as UploadField } from '../forms/fields/Upload/index.js' export { default as UploadField } from '../forms/fields/Upload/index.js'
export { fieldTypes } from '../forms/fields/index.js' export { fieldTypes } from '../forms/fields/index.js'
export { fieldBaseClass } from '../forms/fields/shared.js' export { fieldBaseClass } from '../forms/fields/shared.js'
export { useField } from '../forms/useField/index.js' export { useField } from '../forms/useField/index.js'
export type { FieldType, Options } from '../forms/useField/types.js'
export { withCondition } from '../forms/withCondition/index.js' export { withCondition } from '../forms/withCondition/index.js'
export { buildComponentMap } from '../utilities/buildComponentMap/index.js' export { buildComponentMap } from '../utilities/buildComponentMap/index.js'
export type { ReducedBlock } from '../utilities/buildComponentMap/types.js'

View File

@@ -7,4 +7,3 @@ import type React from 'react'
// import { Link } from 'next/link' // import { Link } from 'next/link'
export type LinkType = React.ElementType export type LinkType = React.ElementType
export type { FormFieldBase } from '../forms/fields/shared.js' export type { FormFieldBase } from '../forms/fields/shared.js'
export type { ReducedBlock } from '../utilities/buildComponentMap/types.js'

View File

@@ -53,7 +53,11 @@ export const RenderFields: React.FC<Props> = (props) => {
ref={intersectionRef} ref={intersectionRef}
> >
{hasRendered && {hasRendered &&
fieldMap?.map(({ name, Field, disabled, readOnly }, fieldIndex) => { fieldMap?.map((f, fieldIndex) => {
const { Field, disabled, readOnly } = f
const name = 'name' in f ? f.name : undefined
return ( return (
<RenderField <RenderField
Field={Field} Field={Field}

View File

@@ -2,24 +2,29 @@ import type { FieldMap } from '../../utilities/buildComponentMap/types.js'
export const buildPathSegments = (parentPath: string, fieldMap: FieldMap): string[] => { export const buildPathSegments = (parentPath: string, fieldMap: FieldMap): string[] => {
const pathNames = fieldMap.reduce((acc, subField) => { const pathNames = fieldMap.reduce((acc, subField) => {
if (subField.subfields && subField.isFieldAffectingData) { if ('fieldMap' in subField) {
// group, block, array if (subField.fieldMap && subField.isFieldAffectingData) {
acc.push(parentPath ? `${parentPath}.${subField.name}.` : `${subField.name}.`) // group, block, array
} else if (subField.subfields) { const name = 'name' in subField ? subField.name : 'unnamed'
// rows, collapsibles, unnamed-tab acc.push(parentPath ? `${parentPath}.${name}.` : `${name}.`)
acc.push(...buildPathSegments(parentPath, subField.subfields)) } else if (subField.fieldMap) {
} else if (subField.type === 'tabs') { // rows, collapsibles, unnamed-tab
// tabs acc.push(...buildPathSegments(parentPath, subField.fieldMap))
subField.tabs.forEach((tab) => { } else if (subField.type === 'tabs') {
let tabPath = parentPath // tabs
if ('name' in tab) { 'tabs' in subField &&
tabPath = parentPath ? `${parentPath}.${tab.name}` : tab.name subField.tabs?.forEach((tab) => {
} let tabPath = parentPath
acc.push(...buildPathSegments(tabPath, tab.subfields)) if ('name' in tab) {
}) tabPath = parentPath ? `${parentPath}.${tab.name}` : tab.name
} else if (subField.isFieldAffectingData) { }
// text, number, date, etc. acc.push(...buildPathSegments(tabPath, tab.fieldMap))
acc.push(parentPath ? `${parentPath}.${subField.name}` : subField.name) })
} else if (subField.isFieldAffectingData) {
// text, number, date, etc.
const name = 'name' in subField ? subField.name : 'unnamed'
acc.push(parentPath ? `${parentPath}.${name}` : name)
}
} }
return acc return acc

View File

@@ -2,7 +2,7 @@
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { ArrayFieldProps } from './types.js'
import Banner from '../../../elements/Banner/index.js' import Banner from '../../../elements/Banner/index.js'
import { Button } from '../../../elements/Button/index.js' import { Button } from '../../../elements/Button/index.js'
@@ -24,7 +24,7 @@ import './index.scss'
const baseClass = 'array-field' const baseClass = 'array-field'
const ArrayFieldType: React.FC<Props> = (props) => { const ArrayFieldType: React.FC<ArrayFieldProps> = (props) => {
const { const {
name, name,
Description, Description,
@@ -37,6 +37,8 @@ const ArrayFieldType: React.FC<Props> = (props) => {
indexPath, indexPath,
label, label,
localized, localized,
maxRows,
minRows,
path: pathFromProps, path: pathFromProps,
permissions, permissions,
readOnly, readOnly,
@@ -46,9 +48,6 @@ const ArrayFieldType: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} /> const Label = LabelFromProps || <LabelComp label={label} required={required} />
const minRows = 'minRows' in props ? props?.minRows : 0
const maxRows = 'maxRows' in props ? props?.maxRows : undefined
const { setDocFieldPreferences } = useDocumentInfo() const { setDocFieldPreferences } = useDocumentInfo()
const { addFieldRow, dispatchFields, setModified } = useForm() const { addFieldRow, dispatchFields, setModified } = useForm()
const submitted = useFormSubmitted() const submitted = useFormSubmitted()
@@ -66,7 +65,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
})() })()
// Handle labeling for Arrays, Global Arrays, and Blocks // Handle labeling for Arrays, Global Arrays, and Blocks
const getLabels = (p: Props) => { const getLabels = (p: ArrayFieldProps) => {
if ('labels' in p && p?.labels) return p.labels if ('labels' in p && p?.labels) return p.labels
if ('label' in p && p?.label) return { plural: undefined, singular: p.label } if ('label' in p && p?.label) return { plural: undefined, singular: p.label }
return { plural: t('general:rows'), singular: t('general:row') } return { plural: t('general:rows'), singular: t('general:row') }

View File

@@ -1,11 +1,19 @@
import type { FieldMap } from '@payloadcms/ui'
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { ArrayField, FieldBase, RowLabel } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type ArrayFieldProps = FormFieldBase & {
RowLabel?: React.ReactNode
fieldMap: FieldMap
forceRender?: boolean forceRender?: boolean
indexPath: string indexPath: string
label: false | string label?: FieldBase['label']
labels?: ArrayField['labels']
maxRows?: ArrayField['maxRows']
minRows?: ArrayField['minRows']
name?: string name?: string
permissions: FieldPermissions permissions: FieldPermissions
width?: string
} }

View File

@@ -96,7 +96,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
blockType={row.blockType} blockType={row.blockType}
blocks={blocks} blocks={blocks}
duplicateRow={duplicateRow} duplicateRow={duplicateRow}
fieldMap={block.subfields} fieldMap={block.fieldMap}
hasMaxRows={hasMaxRows} hasMaxRows={hasMaxRows}
labels={labels} labels={labels}
moveRow={moveRow} moveRow={moveRow}
@@ -135,7 +135,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
<HiddenInput name={`${path}.id`} value={row.id} /> <HiddenInput name={`${path}.id`} value={row.id} />
<RenderFields <RenderFields
className={`${baseClass}__fields`} className={`${baseClass}__fields`}
fieldMap={block.subfields} fieldMap={block.fieldMap}
forceRender={forceRender} forceRender={forceRender}
margins="small" margins="small"
path={path} path={path}

View File

@@ -2,7 +2,7 @@
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { Fragment, useCallback } from 'react' import React, { Fragment, useCallback } from 'react'
import type { Props } from './types.js' import type { BlocksFieldProps } from './types.js'
import Banner from '../../../elements/Banner/index.js' import Banner from '../../../elements/Banner/index.js'
import { Button } from '../../../elements/Button/index.js' import { Button } from '../../../elements/Button/index.js'
@@ -27,7 +27,7 @@ import './index.scss'
const baseClass = 'blocks-field' const baseClass = 'blocks-field'
const BlocksField: React.FC<Props> = (props) => { const BlocksField: React.FC<BlocksFieldProps> = (props) => {
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const { const {
@@ -35,11 +35,15 @@ const BlocksField: React.FC<Props> = (props) => {
Description, Description,
Error, Error,
Label: LabelFromProps, Label: LabelFromProps,
blocks,
className, className,
forceRender = false, forceRender = false,
indexPath, indexPath,
label, label,
labels: labelsFromProps,
localized, localized,
maxRows,
minRows,
path: pathFromProps, path: pathFromProps,
readOnly, readOnly,
required, required,
@@ -48,11 +52,6 @@ const BlocksField: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} /> const Label = LabelFromProps || <LabelComp label={label} required={required} />
const minRows = 'minRows' in props ? props?.minRows : 0
const maxRows = 'maxRows' in props ? props?.maxRows : undefined
const blocks = 'blocks' in props ? props?.blocks : undefined
const labelsFromProps = 'labels' in props ? props?.labels : undefined
const { setDocFieldPreferences } = useDocumentInfo() const { setDocFieldPreferences } = useDocumentInfo()
const { addFieldRow, dispatchFields, setModified } = useForm() const { addFieldRow, dispatchFields, setModified } = useForm()
const { code: locale } = useLocale() const { code: locale } = useLocale()

View File

@@ -1,10 +1,20 @@
import type { FieldMap, ReducedBlock } from '@payloadcms/ui'
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { BlockField, FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type BlocksFieldProps = FormFieldBase & {
blocks?: ReducedBlock[]
fieldMap: FieldMap
forceRender?: boolean forceRender?: boolean
indexPath: string indexPath: string
label?: FieldBase['label']
labels?: BlockField['labels']
maxRows?: number
minRows?: number
name?: string name?: string
permissions: FieldPermissions permissions: FieldPermissions
slug?: string
width?: string
} }

View File

@@ -3,7 +3,7 @@ import type { ClientValidate } from 'payload/types'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { CheckboxFieldProps } from './types.js'
import { generateFieldID } from '../../../utilities/generateFieldID.js' import { generateFieldID } from '../../../utilities/generateFieldID.js'
import { useForm } from '../../Form/context.js' import { useForm } from '../../Form/context.js'
@@ -16,7 +16,7 @@ import './index.scss'
const baseClass = 'checkbox' const baseClass = 'checkbox'
const Checkbox: React.FC<Props> = (props) => { const Checkbox: React.FC<CheckboxFieldProps> = (props) => {
const { const {
id, id,
name, name,

View File

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

View File

@@ -2,7 +2,7 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { CodeFieldProps } from './types.js'
import { CodeEditor } from '../../../elements/CodeEditor/index.js' import { CodeEditor } from '../../../elements/CodeEditor/index.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
@@ -18,7 +18,7 @@ const prismToMonacoLanguageMap = {
const baseClass = 'code-field' const baseClass = 'code-field'
const Code: React.FC<Props> = (props) => { const Code: React.FC<CodeFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,

View File

@@ -1,6 +1,12 @@
import type { CodeField, FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type CodeFieldProps = FormFieldBase & {
editorOptions?: CodeField['admin']['editorOptions']
label?: FieldBase['label']
language?: CodeField['admin']['language']
name?: string name?: string
path?: string path?: string
width: string
} }

View File

@@ -4,7 +4,7 @@ import type { DocumentPreferences } from 'payload/types'
import React, { Fragment, useCallback, useEffect, useState } from 'react' import React, { Fragment, useCallback, useEffect, useState } from 'react'
import type { Props } from './types.js' import type { CollapsibleFieldProps } from './types.js'
import { Collapsible } from '../../../elements/Collapsible/index.js' import { Collapsible } from '../../../elements/Collapsible/index.js'
import { ErrorPill } from '../../../elements/ErrorPill/index.js' import { ErrorPill } from '../../../elements/ErrorPill/index.js'
@@ -21,7 +21,7 @@ import './index.scss'
const baseClass = 'collapsible-field' const baseClass = 'collapsible-field'
const CollapsibleField: React.FC<Props> = (props) => { const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
const { const {
Description, Description,
Label: LabelFromProps, Label: LabelFromProps,

View File

@@ -1,10 +1,16 @@
import type { FieldMap } from '@payloadcms/ui'
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { FieldTypes } from 'payload/config' import type { FieldTypes } from 'payload/config'
import type { FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type CollapsibleFieldProps = FormFieldBase & {
fieldMap: FieldMap
fieldTypes: FieldTypes fieldTypes: FieldTypes
indexPath: string indexPath: string
initCollapsed?: boolean
label?: FieldBase['label']
permissions: FieldPermissions permissions: FieldPermissions
width?: string
} }

View File

@@ -3,7 +3,7 @@ import type { FormField } from 'payload/types'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { ConfirmPasswordFieldProps } from './types.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
import Error from '../../Error/index.js' import Error from '../../Error/index.js'
@@ -13,7 +13,7 @@ import { useField } from '../../useField/index.js'
import { fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
const ConfirmPassword: React.FC<Props> = (props) => { const ConfirmPassword: React.FC<ConfirmPasswordFieldProps> = (props) => {
const { disabled } = props const { disabled } = props
const password = useFormFields<FormField>(([fields]) => fields.password) const password = useFormFields<FormField>(([fields]) => fields.password)

View File

@@ -1,3 +1,3 @@
export type Props = { export type ConfirmPasswordFieldProps = {
disabled?: boolean disabled?: boolean
} }

View File

@@ -5,25 +5,27 @@ import type { ClientValidate } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { DateFieldProps } from './types.js'
import { DatePickerField } from '../../../elements/DatePicker/index.js' import { DatePickerField } from '../../../elements/DatePicker/index.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
import LabelComp from '../../Label/index.js'
import { useField } from '../../useField/index.js' import { useField } from '../../useField/index.js'
import { fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
const baseClass = 'date-time-field' const baseClass = 'date-time-field'
const DateTime: React.FC<Props> = (props) => { const DateTime: React.FC<DateFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,
BeforeInput, BeforeInput,
Description, Description,
Error, Error,
Label: LabelComp, Label: LabelFromProps,
className, className,
date: datePickerProps,
label, label,
path: pathFromProps, path: pathFromProps,
placeholder, placeholder,
@@ -34,9 +36,7 @@ const DateTime: React.FC<Props> = (props) => {
width, width,
} = props } = props
const Label = LabelComp || label const Label = LabelFromProps || <LabelComp label={label} required={required} />
const datePickerProps = 'date' in props ? props.date : {}
const { i18n } = useTranslation() const { i18n } = useTranslation()

View File

@@ -1,6 +1,12 @@
import type { DateField, FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type DateFieldProps = FormFieldBase & {
date?: DateField['admin']['date']
label?: FieldBase['label']
name?: string name?: string
path: string path?: string
placeholder?: DateField['admin']['placeholder'] | string
width?: string
} }

View File

@@ -4,7 +4,7 @@ import type { ClientValidate } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { EmailFieldProps } from './types.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
@@ -13,7 +13,7 @@ import { withCondition } from '../../withCondition/index.js'
import { fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
export const Email: React.FC<Props> = (props) => { export const Email: React.FC<EmailFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,

View File

@@ -1,7 +1,12 @@
import type { EmailField, FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type EmailFieldProps = FormFieldBase & {
autoComplete?: string autoComplete?: string
label?: FieldBase['label']
name?: string name?: string
path?: string path?: string
placeholder?: EmailField['admin']['placeholder']
width?: string
} }

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import type { Props } from './types.js' import type { GroupFieldProps } from './types.js'
import { useCollapsible } from '../../../elements/Collapsible/provider.js' import { useCollapsible } from '../../../elements/Collapsible/provider.js'
import { ErrorPill } from '../../../elements/ErrorPill/index.js' import { ErrorPill } from '../../../elements/ErrorPill/index.js'
@@ -19,7 +19,7 @@ import { GroupProvider, useGroup } from './provider.js'
const baseClass = 'group-field' const baseClass = 'group-field'
const Group: React.FC<Props> = (props) => { const Group: React.FC<GroupFieldProps> = (props) => {
const { const {
Description, Description,
Label: LabelFromProps, Label: LabelFromProps,

View File

@@ -1,11 +1,16 @@
import type { FieldMap } from '@payloadcms/ui'
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type GroupFieldProps = FormFieldBase & {
fieldMap: FieldMap
forceRender?: boolean forceRender?: boolean
hideGutter?: boolean hideGutter?: boolean
indexPath: string indexPath: string
label?: FieldBase['label']
name?: string name?: string
permissions: FieldPermissions permissions: FieldPermissions
width?: string
} }

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import type { Props } from './types.js' import type { HiddenInputFieldProps } from './types.js'
import { useField } from '../../useField/index.js' import { useField } from '../../useField/index.js'
import { withCondition } from '../../withCondition/index.js' import { withCondition } from '../../withCondition/index.js'
@@ -10,7 +10,7 @@ import { withCondition } from '../../withCondition/index.js'
* This is mainly used to save a value on the form that is not visible to the user. * This is mainly used to save a value on the form that is not visible to the user.
* For example, this sets the `ìd` property of a block in the Blocks field. * For example, this sets the `ìd` property of a block in the Blocks field.
*/ */
const HiddenInput: React.FC<Props> = (props) => { const HiddenInput: React.FC<HiddenInputFieldProps> = (props) => {
const { name, disableModifyingForm = true, path: pathFromProps, value: valueFromProps } = props const { name, disableModifyingForm = true, path: pathFromProps, value: valueFromProps } = props
const { path, setValue, value } = useField({ const { path, setValue, value } = useField({

View File

@@ -1,4 +1,4 @@
export type Props = { export type HiddenInputFieldProps = {
disableModifyingForm?: false disableModifyingForm?: false
name: string name: string
path?: string path?: string

View File

@@ -3,7 +3,7 @@ import type { ClientValidate } from 'payload/types'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import type { Props } from './types.js' import type { JSONFieldProps } from './types.js'
import { CodeEditor } from '../../../elements/CodeEditor/index.js' import { CodeEditor } from '../../../elements/CodeEditor/index.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
@@ -14,7 +14,7 @@ import './index.scss'
const baseClass = 'json-field' const baseClass = 'json-field'
const JSONField: React.FC<Props> = (props) => { const JSONField: React.FC<JSONFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,
@@ -23,6 +23,7 @@ const JSONField: React.FC<Props> = (props) => {
Error, Error,
Label: LabelFromProps, Label: LabelFromProps,
className, className,
editorOptions,
label, label,
path: pathFromProps, path: pathFromProps,
readOnly, readOnly,
@@ -34,9 +35,6 @@ const JSONField: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} /> const Label = LabelFromProps || <LabelComp label={label} required={required} />
// eslint-disable-next-line react/destructuring-assignment
const editorOptions = 'editorOptions' in props ? props.editorOptions : {}
const [stringValue, setStringValue] = useState<string>() const [stringValue, setStringValue] = useState<string>()
const [jsonError, setJsonError] = useState<string>() const [jsonError, setJsonError] = useState<string>()
const [hasLoadedValue, setHasLoadedValue] = useState(false) const [hasLoadedValue, setHasLoadedValue] = useState(false)

View File

@@ -1,6 +1,11 @@
import type { FieldBase, JSONField } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type JSONFieldProps = FormFieldBase & {
editorOptions?: JSONField['admin']['editorOptions']
label?: FieldBase['label']
name?: string name?: string
path?: string path?: string
width?: string
} }

View File

@@ -5,7 +5,7 @@ import { isNumber } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import type { Option } from '../../../elements/ReactSelect/types.js' import type { Option } from '../../../elements/ReactSelect/types.js'
import type { Props } from './types.js' import type { NumberFieldProps } from './types.js'
import ReactSelect from '../../../elements/ReactSelect/index.js' import ReactSelect from '../../../elements/ReactSelect/index.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
@@ -15,7 +15,7 @@ import { withCondition } from '../../withCondition/index.js'
import { fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
const NumberField: React.FC<Props> = (props) => { const NumberField: React.FC<NumberFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,
@@ -24,12 +24,17 @@ const NumberField: React.FC<Props> = (props) => {
Error, Error,
Label: LabelFromProps, Label: LabelFromProps,
className, className,
hasMany = false,
label, label,
max = Infinity,
maxRows = Infinity,
min = -Infinity,
onChange: onChangeFromProps, onChange: onChangeFromProps,
path: pathFromProps, path: pathFromProps,
placeholder, placeholder,
readOnly, readOnly,
required, required,
step = 1,
style, style,
validate, validate,
width, width,
@@ -37,12 +42,6 @@ const NumberField: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} /> const Label = LabelFromProps || <LabelComp label={label} required={required} />
const max = 'max' in props ? props.max : Infinity
const min = 'min' in props ? props.min : -Infinity
const step = 'step' in props ? props.step : 1
const hasMany = 'hasMany' in props ? props.hasMany : false
const maxRows = 'maxRows' in props ? props.maxRows : Infinity
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
const memoizedValidate = useCallback( const memoizedValidate = useCallback(

View File

@@ -1,7 +1,17 @@
import type { FieldBase, NumberField } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type NumberFieldProps = FormFieldBase & {
hasMany?: boolean
label?: FieldBase['label']
max?: number
maxRows?: number
min?: number
name?: string name?: string
onChange?: (e: number) => void onChange?: (e: number) => void
path?: string path?: string
placeholder?: NumberField['admin']['placeholder']
step?: number
width?: string
} }

View File

@@ -3,7 +3,7 @@ import type { ClientValidate } from 'payload/types'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { PasswordFieldProps } from './types.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
import { useField } from '../../useField/index.js' import { useField } from '../../useField/index.js'
@@ -11,7 +11,7 @@ import { withCondition } from '../../withCondition/index.js'
import { fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
export const Password: React.FC<Props> = (props) => { export const Password: React.FC<PasswordFieldProps> = (props) => {
const { const {
name, name,
Error, Error,

View File

@@ -4,7 +4,7 @@ import type React from 'react'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type PasswordFieldProps = FormFieldBase & {
autoComplete?: string autoComplete?: string
className?: string className?: string
description?: Description description?: Description

View File

@@ -5,7 +5,7 @@ import type { ClientValidate } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { PointFieldProps } from './types.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
@@ -16,7 +16,7 @@ import './index.scss'
const baseClass = 'point' const baseClass = 'point'
const PointField: React.FC<Props> = (props) => { const PointField: React.FC<PointFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,
@@ -30,6 +30,7 @@ const PointField: React.FC<Props> = (props) => {
placeholder, placeholder,
readOnly, readOnly,
required, required,
step,
style, style,
validate, validate,
width, width,
@@ -39,8 +40,6 @@ const PointField: React.FC<Props> = (props) => {
const { i18n } = useTranslation() const { i18n } = useTranslation()
const step = 'step' in props ? props.step : 1
const memoizedValidate: ClientValidate = useCallback( const memoizedValidate: ClientValidate = useCallback(
(value, options) => { (value, options) => {
if (typeof validate === 'function') { if (typeof validate === 'function') {

View File

@@ -1,6 +1,12 @@
import type { FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type PointFieldProps = FormFieldBase & {
label?: FieldBase['label']
name?: string name?: string
path?: string path?: string
placeholder?: string
step?: number
width?: string
} }

View File

@@ -4,7 +4,7 @@
import { optionIsObject } from 'payload/types' import { optionIsObject } from 'payload/types'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { RadioFieldProps } from './types.js'
import { useForm } from '../../Form/context.js' import { useForm } from '../../Form/context.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
@@ -16,7 +16,7 @@ import './index.scss'
const baseClass = 'radio-group' const baseClass = 'radio-group'
const RadioGroup: React.FC<Props> = (props) => { const RadioGroup: React.FC<RadioFieldProps> = (props) => {
const { const {
name, name,
Description, Description,
@@ -24,7 +24,9 @@ const RadioGroup: React.FC<Props> = (props) => {
Label: LabelFromProps, Label: LabelFromProps,
className, className,
label, label,
layout = 'horizontal',
onChange: onChangeFromProps, onChange: onChangeFromProps,
options = [],
path: pathFromProps, path: pathFromProps,
readOnly, readOnly,
required, required,
@@ -38,10 +40,6 @@ const RadioGroup: React.FC<Props> = (props) => {
const Label = LabelFromProps || <LabelComp label={label} required={required} /> const Label = LabelFromProps || <LabelComp label={label} required={required} />
const options = 'options' in props ? props.options : []
const layout = 'layout' in props ? props.layout : 'horizontal'
const memoizedValidate = useCallback( const memoizedValidate = useCallback(
(value, validationOptions) => { (value, validationOptions) => {
if (typeof validate === 'function') if (typeof validate === 'function')

View File

@@ -1,10 +1,16 @@
import type { FieldBase, Option } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type RadioFieldProps = FormFieldBase & {
label?: FieldBase['label']
layout?: 'horizontal' | 'vertical'
name?: string name?: string
onChange?: OnChange onChange?: OnChange
options?: Option[]
path?: string path?: string
value?: string value?: string
width?: string
} }
export type OnChange<T = string> = (value: T) => void export type OnChange<T = string> = (value: T) => void

View File

@@ -7,7 +7,7 @@ import qs from 'qs'
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react' import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js' import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
import type { GetResults, Option, Props, Value } from './types.js' import type { GetResults, Option, RelationshipFieldProps, Value } from './types.js'
import ReactSelect from '../../../elements/ReactSelect/index.js' import ReactSelect from '../../../elements/ReactSelect/index.js'
import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback.js' import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback.js'
@@ -31,7 +31,7 @@ const maxResultsPerRequest = 10
const baseClass = 'relationship' const baseClass = 'relationship'
const Relationship: React.FC<Props> = (props) => { const Relationship: React.FC<RelationshipFieldProps> = (props) => {
const { const {
name, name,
Description, Description,

View File

@@ -1,11 +1,17 @@
import type { I18n } from '@payloadcms/translations' import type { I18n } from '@payloadcms/translations'
import type { SanitizedCollectionConfig } from 'payload/types' import type { RelationshipField, SanitizedCollectionConfig } from 'payload/types'
import type { SanitizedConfig } from 'payload/types' import type { SanitizedConfig } from 'payload/types'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type RelationshipFieldProps = FormFieldBase & {
allowCreate?: RelationshipField['admin']['allowCreate']
hasMany?: boolean
isSortable?: boolean
name: string name: string
relationTo?: RelationshipField['relationTo']
sortOptions?: RelationshipField['admin']['sortOptions']
width?: string
} }
export type Option = { export type Option = {

View File

@@ -1,8 +1,8 @@
import type React from 'react' import type React from 'react'
import type { Props } from './types.js' import type { RichTextFieldProps } from './types.js'
const RichText: React.FC<Props> = (props) => { const RichText: React.FC<RichTextFieldProps> = (props) => {
return null return null
} }

View File

@@ -1,3 +1,7 @@
import type { MappedField } from '@payloadcms/ui'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase export type RichTextFieldProps = FormFieldBase & {
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
}

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import React from 'react' import React from 'react'
import type { Props } from './types.js' import type { RowFieldProps } from './types.js'
import { useFieldProps } from '../../FieldPropsProvider/index.js' import { useFieldProps } from '../../FieldPropsProvider/index.js'
import { RenderFields } from '../../RenderFields/index.js' import { RenderFields } from '../../RenderFields/index.js'
@@ -12,7 +12,7 @@ import { RowProvider } from './provider.js'
const baseClass = 'row' const baseClass = 'row'
const Row: React.FC<Props> = (props) => { const Row: React.FC<RowFieldProps> = (props) => {
const { className, fieldMap, forceRender = false } = props const { className, fieldMap, forceRender = false } = props
const { path, readOnly, schemaPath, siblingPermissions } = useFieldProps() const { path, readOnly, schemaPath, siblingPermissions } = useFieldProps()

View File

@@ -1,12 +1,15 @@
import type { FieldMap } from '@payloadcms/ui'
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { FieldTypes } from 'payload/config' import type { FieldTypes } from 'payload/config'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type RowFieldProps = FormFieldBase & {
fieldMap: FieldMap
fieldTypes: FieldTypes fieldTypes: FieldTypes
forceRender?: boolean forceRender?: boolean
indexPath: string indexPath: string
path?: string path?: string
permissions?: FieldPermissions permissions?: FieldPermissions
width?: string
} }

View File

@@ -5,7 +5,7 @@ import type { ClientValidate, Option, OptionObject } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import type { Props } from './types.js' import type { SelectFieldProps } from './types.js'
import ReactSelect from '../../../elements/ReactSelect/index.js' import ReactSelect from '../../../elements/ReactSelect/index.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
@@ -27,7 +27,7 @@ const formatOptions = (options: Option[]): OptionObject[] =>
} as OptionObject } as OptionObject
}) })
export const Select: React.FC<Props> = (props) => { export const Select: React.FC<SelectFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,

View File

@@ -1,8 +1,16 @@
import type { FieldBase, Option } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type SelectFieldProps = FormFieldBase & {
hasMany?: boolean
isClearable?: boolean
isSortable?: boolean
label?: FieldBase['label']
name?: string name?: string
onChange?: (e: string) => void onChange?: (e: string) => void
options?: Option[]
path?: string path?: string
value?: string value?: string
width?: string
} }

View File

@@ -30,7 +30,7 @@ export const TabComponent: React.FC<TabProps> = ({ isActive, parentPath, setIsAc
return ( return (
<React.Fragment> <React.Fragment>
<WatchChildErrors fieldMap={tab.subfields} path={path} setErrorCount={setErrorCount} /> <WatchChildErrors fieldMap={tab.fieldMap} path={path} setErrorCount={setErrorCount} />
<button <button
className={[ className={[
baseClass, baseClass,

View File

@@ -5,7 +5,7 @@ import { getTranslation } from '@payloadcms/translations'
import { toKebabCase } from 'payload/utilities' import { toKebabCase } from 'payload/utilities'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import type { Props } from './types.js' import type { TabsFieldProps } from './types.js'
import { useCollapsible } from '../../../elements/Collapsible/provider.js' import { useCollapsible } from '../../../elements/Collapsible/provider.js'
import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js' import { useDocumentInfo } from '../../../providers/DocumentInfo/index.js'
@@ -21,7 +21,7 @@ import { TabsProvider } from './provider.js'
const baseClass = 'tabs-field' const baseClass = 'tabs-field'
const TabsField: React.FC<Props> = (props) => { const TabsField: React.FC<TabsFieldProps> = (props) => {
const { const {
name, name,
Description, Description,
@@ -130,7 +130,7 @@ const TabsField: React.FC<Props> = (props) => {
> >
{Description} {Description}
<RenderFields <RenderFields
fieldMap={activeTabConfig.subfields} fieldMap={activeTabConfig.fieldMap}
forceRender={forceRender} forceRender={forceRender}
key={ key={
activeTabConfig.label activeTabConfig.label

View File

@@ -1,13 +1,15 @@
import type { FieldPermissions } from 'payload/auth' import type { FieldPermissions } from 'payload/auth'
import type { MappedTab } from '../../../utilities/buildComponentMap/types.js' import type { FieldMap, MappedTab } from '../../../utilities/buildComponentMap/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type TabsFieldProps = FormFieldBase & {
fieldMap: FieldMap
forceRender?: boolean forceRender?: boolean
indexPath: string indexPath: string
name?: string name?: string
path?: string path?: string
permissions: FieldPermissions permissions: FieldPermissions
tabs?: MappedTab[] tabs?: MappedTab[]
width?: string
} }

View File

@@ -2,16 +2,18 @@
import type { ChangeEvent } from 'react' import type { ChangeEvent } from 'react'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { TextField } from 'payload/types.js'
import React from 'react' import React from 'react'
import type { Option } from '../../../elements/ReactSelect/types.js' import type { Option } from '../../../elements/ReactSelect/types.js'
import type { TextareaFieldProps } from '../Textarea/types.js'
import ReactSelect from '../../../elements/ReactSelect/index.js' import ReactSelect from '../../../elements/ReactSelect/index.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
import { type FormFieldBase, fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
export type TextInputProps = Omit<FormFieldBase, 'type'> & { export type TextInputProps = Omit<TextareaFieldProps, 'type'> & {
hasMany?: boolean hasMany?: boolean
inputRef?: React.MutableRefObject<HTMLInputElement> inputRef?: React.MutableRefObject<HTMLInputElement>
maxRows?: number maxRows?: number

View File

@@ -4,7 +4,7 @@ import type { ClientValidate } from 'payload/types'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import type { Option } from '../../../elements/ReactSelect/types.js' import type { Option } from '../../../elements/ReactSelect/types.js'
import type { Props } from './types.js' import type { TextFieldProps } from './types.js'
import { useConfig } from '../../../providers/Config/index.js' import { useConfig } from '../../../providers/Config/index.js'
import { useLocale } from '../../../providers/Locale/index.js' import { useLocale } from '../../../providers/Locale/index.js'
@@ -15,7 +15,7 @@ import { isFieldRTL } from '../shared.js'
import { TextInput } from './Input.js' import { TextInput } from './Input.js'
import './index.scss' import './index.scss'
const Text: React.FC<Props> = (props) => { const Text: React.FC<TextFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,

View File

@@ -1,11 +1,18 @@
import type { FieldBase, TextField } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type TextFieldProps = FormFieldBase & {
hasMany?: boolean hasMany?: boolean
inputRef?: React.MutableRefObject<HTMLInputElement> inputRef?: React.MutableRefObject<HTMLInputElement>
label?: FieldBase['label']
maxLength?: number
maxRows?: number maxRows?: number
minLength?: number
minRows?: number minRows?: number
name?: string name?: string
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement> onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
path?: string path?: string
placeholder?: TextField['admin']['placeholder']
width?: string
} }

View File

@@ -2,11 +2,13 @@
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { type ChangeEvent } from 'react' import React, { type ChangeEvent } from 'react'
import type { TextareaFieldProps } from './types.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
import { type FormFieldBase, fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
export type TextAreaInputProps = FormFieldBase & { export type TextAreaInputProps = TextareaFieldProps & {
onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
rows?: number rows?: number
showError?: boolean showError?: boolean

View File

@@ -5,7 +5,7 @@ import type { ClientValidate } from 'payload/types'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { TextareaFieldProps } from './types.js'
import { useConfig } from '../../../providers/Config/index.js' import { useConfig } from '../../../providers/Config/index.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
@@ -16,7 +16,7 @@ import { isFieldRTL } from '../shared.js'
import { TextareaInput } from './Input.js' import { TextareaInput } from './Input.js'
import './index.scss' import './index.scss'
const Textarea: React.FC<Props> = (props) => { const Textarea: React.FC<TextareaFieldProps> = (props) => {
const { const {
name, name,
AfterInput, AfterInput,

View File

@@ -1,6 +1,14 @@
import type { FieldBase, TextareaField } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type TextareaFieldProps = FormFieldBase & {
label?: FieldBase['label']
maxLength?: number
minLength?: number
name?: string name?: string
path?: string path?: string
placeholder?: TextareaField['admin']['placeholder']
rows?: number
width?: string
} }

View File

@@ -7,6 +7,7 @@ import React, { useCallback, useEffect, useState } from 'react'
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js' import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
import type { ListDrawerProps } from '../../../elements/ListDrawer/types.js' import type { ListDrawerProps } from '../../../elements/ListDrawer/types.js'
import type { UploadFieldProps } from './types.js'
import { Button } from '../../../elements/Button/index.js' import { Button } from '../../../elements/Button/index.js'
import { useDocumentDrawer } from '../../../elements/DocumentDrawer/index.js' import { useDocumentDrawer } from '../../../elements/DocumentDrawer/index.js'
@@ -14,12 +15,12 @@ import FileDetails from '../../../elements/FileDetails/index.js'
import { useListDrawer } from '../../../elements/ListDrawer/index.js' import { useListDrawer } from '../../../elements/ListDrawer/index.js'
import { useTranslation } from '../../../providers/Translation/index.js' import { useTranslation } from '../../../providers/Translation/index.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
import { type FormFieldBase, fieldBaseClass } from '../shared.js' import { fieldBaseClass } from '../shared.js'
import './index.scss' import './index.scss'
const baseClass = 'upload' const baseClass = 'upload'
export type UploadInputProps = FormFieldBase & { export type UploadInputProps = Omit<UploadFieldProps, 'filterOptions'> & {
api?: string api?: string
collection?: SanitizedCollectionConfig collection?: SanitizedCollectionConfig
filterOptions?: FilterOptionsResult filterOptions?: FilterOptionsResult

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import type { Props } from './types.js' import type { UploadFieldProps } from './types.js'
import { useConfig } from '../../../providers/Config/index.js' import { useConfig } from '../../../providers/Config/index.js'
import LabelComp from '../../Label/index.js' import LabelComp from '../../Label/index.js'
@@ -9,7 +9,7 @@ import { useField } from '../../useField/index.js'
import { UploadInput } from './Input.js' import { UploadInput } from './Input.js'
import './index.scss' import './index.scss'
const Upload: React.FC<Props> = (props) => { const Upload: React.FC<UploadFieldProps> = (props) => {
const { const {
Description, Description,
Error, Error,

View File

@@ -2,9 +2,11 @@ import type { UploadField } from 'payload/types'
import type { FormFieldBase } from '../shared.js' import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & { export type UploadFieldProps = FormFieldBase & {
filterOptions?: UploadField['filterOptions'] filterOptions?: UploadField['filterOptions']
label?: UploadField['label']
name?: string name?: string
path?: string path?: string
relationTo?: UploadField['relationTo'] relationTo?: UploadField['relationTo']
width?: string
} }

View File

@@ -1,26 +1,6 @@
import type { User } from 'payload/auth' import type { User } from 'payload/auth'
import type { Locale, SanitizedLocalizationConfig } from 'payload/config' import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
import type { import type { DocumentPreferences, FormState, Validate } from 'payload/types'
ArrayField,
BlockField,
CodeField,
DateField,
DocumentPreferences,
FormState,
JSONField,
RelationshipField,
RowLabel,
UploadField,
Validate,
} from 'payload/types'
import type { Option } from 'payload/types'
import type {
FieldMap,
MappedField,
MappedTab,
ReducedBlock,
} from '../../utilities/buildComponentMap/types.js'
export const fieldBaseClass = 'field-type' export const fieldBaseClass = 'field-type'
@@ -30,99 +10,19 @@ export type FormFieldBase = {
Description?: React.ReactNode Description?: React.ReactNode
Error?: React.ReactNode Error?: React.ReactNode
Label?: React.ReactNode Label?: React.ReactNode
RowLabel?: React.ReactNode
className?: string className?: string
disabled?: boolean disabled?: boolean
docPreferences?: DocumentPreferences docPreferences?: DocumentPreferences
fieldMap?: FieldMap
initialSubfieldState?: FormState
label?: string
locale?: Locale locale?: Locale
localized?: boolean localized?: boolean
maxLength?: number
minLength?: number
path?: string path?: string
placeholder?: Record<string, string> | string
readOnly?: boolean readOnly?: boolean
required?: boolean required?: boolean
rtl?: boolean rtl?: boolean
style?: React.CSSProperties style?: React.CSSProperties
user?: User user?: User
validate?: Validate validate?: Validate
width?: string }
} & (
| {
// For `array` fields
label?: RowLabel
labels?: ArrayField['labels']
maxRows?: ArrayField['maxRows']
minRows?: ArrayField['minRows']
}
| {
// For `blocks` fields
blocks?: ReducedBlock[]
labels?: BlockField['labels']
maxRows?: BlockField['maxRows']
minRows?: BlockField['minRows']
slug?: string
}
| {
// For `code` fields
editorOptions?: CodeField['admin']['editorOptions']
language?: CodeField['admin']['language']
}
| {
// For `collapsible` fields
initCollapsed?: boolean
}
| {
// For `date` fields
date?: DateField['admin']['date']
}
| {
// For `json` fields
editorOptions?: JSONField['admin']['editorOptions']
}
| {
// For `number` fields
hasMany?: boolean
max?: number
maxRows?: number
min?: number
step?: number
}
| {
// For `radio` fields
layout?: 'horizontal' | 'vertical'
options?: Option[]
}
| {
// For `relationship` fields
allowCreate?: RelationshipField['admin']['allowCreate']
relationTo?: RelationshipField['relationTo']
sortOptions?: RelationshipField['admin']['sortOptions']
}
| {
// For `richText` fields
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
}
| {
// For `select` fields
isClearable?: boolean
isSortable?: boolean
}
| {
// For `textarea` fields
rows?: number
}
| {
// For `upload` fields
relationTo?: UploadField['relationTo']
}
| {
tabs?: MappedTab[]
}
)
/** /**
* Determines whether a field should be displayed as right-to-left (RTL) based on its configuration, payload's localization configuration and the adming user's currently enabled locale. * Determines whether a field should be displayed as right-to-left (RTL) based on its configuration, payload's localization configuration and the adming user's currently enabled locale.

View File

@@ -40,7 +40,7 @@ export const ComponentMapProvider: React.FC<{
} }
// TODO: better lookup for nested fields, etc. // TODO: better lookup for nested fields, etc.
return fieldMap.find((field) => field.name === path) return fieldMap.find((field) => 'name' in field && field.name === path)
}, },
[componentMap], [componentMap],
) )

View File

@@ -5,8 +5,32 @@ import { isPlainObject } from 'payload/utilities'
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import type { Props as FieldDescription } from '../../forms/FieldDescription/types.js' import type { Props as FieldDescription } from '../../forms/FieldDescription/types.js'
import type { ArrayFieldProps } from '../../forms/fields/Array/types.js'
import type { BlocksFieldProps } from '../../forms/fields/Blocks/types.js'
import type { CheckboxFieldProps } from '../../forms/fields/Checkbox/types.js'
import type { CodeFieldProps } from '../../forms/fields/Code/types.js'
import type { CollapsibleFieldProps } from '../../forms/fields/Collapsible/types.js'
import type { DateFieldProps } from '../../forms/fields/DateTime/types.js'
import type { EmailFieldProps } from '../../forms/fields/Email/types.js'
import type { GroupFieldProps } from '../../forms/fields/Group/types.js'
import type { JSONFieldProps } from '../../forms/fields/JSON/types.js'
import type { NumberFieldProps } from '../../forms/fields/Number/types.js'
import type { PointFieldProps } from '../../forms/fields/Point/types.js'
import type { RelationshipFieldProps } from '../../forms/fields/Relationship/types.js'
import type { RowFieldProps } from '../../forms/fields/Row/types.js'
import type { SelectFieldProps } from '../../forms/fields/Select/types.js'
import type { TabsFieldProps } from '../../forms/fields/Tabs/types.js'
import type { TextFieldProps } from '../../forms/fields/Text/types.js'
import type { TextareaFieldProps } from '../../forms/fields/Textarea/types.js'
import type { UploadFieldProps } from '../../forms/fields/Upload/types.js'
import type { FormFieldBase } from '../../forms/fields/shared.js' import type { FormFieldBase } from '../../forms/fields/shared.js'
import type { FieldMap, MappedField, MappedTab, ReducedBlock } from './types.js' import type {
FieldComponentProps,
FieldMap,
MappedField,
MappedTab,
ReducedBlock,
} from './types.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { SortColumn } from '../../elements/SortColumn/index.js' import { SortColumn } from '../../elements/SortColumn/index.js'
@@ -83,178 +107,83 @@ export const mapFields = (args: {
readOnly: readOnlyOverride, readOnly: readOnlyOverride,
}) })
// `tabs` fields require a field map of each of its tab's nested fields const AfterInput = 'admin' in field &&
const tabs = 'components' in field.admin &&
'tabs' in field && 'afterInput' in field.admin.components &&
field.tabs && Array.isArray(field.admin?.components?.afterInput) && (
Array.isArray(field.tabs) && <Fragment>
field.tabs.map((tab) => { {field.admin.components.afterInput.map((Component, i) => (
const tabFieldMap = mapFields({ <Component key={i} />
DefaultCell, ))}
config, </Fragment>
fieldSchema: tab.fields, )
filter,
parentPath: path,
readOnly: readOnlyOverride,
})
const reducedTab: MappedTab = { const BeforeInput = 'admin' in field &&
name: 'name' in tab ? tab.name : undefined, field.admin?.components &&
label: tab.label, 'beforeInput' in field.admin.components &&
subfields: tabFieldMap, Array.isArray(field.admin.components.beforeInput) && (
<Fragment>
{field.admin.components.beforeInput.map((Component, i) => (
<Component key={i} />
))}
</Fragment>
)
const Description = (
<RenderCustomComponent
CustomComponent={
field.admin &&
'description' in field.admin &&
field.admin.description &&
typeof field.admin.description === 'function' &&
(field.admin.description as React.FC<any>)
} }
DefaultComponent={DefaultDescription}
componentProps={descriptionProps}
/>
)
return reducedTab const Error = (
}) <RenderCustomComponent
CustomComponent={
// `blocks` fields require a field map of each of its block's nested fields 'admin' in field &&
const blocks = field.admin.components &&
'blocks' in field && 'Error' in field.admin.components &&
field.blocks && field.admin?.components?.Error
Array.isArray(field.blocks) &&
field.blocks.map((block) => {
const blockFieldMap = mapFields({
DefaultCell,
config,
fieldSchema: block.fields,
filter,
parentPath: `${path}.${block.slug}`,
readOnly: readOnlyOverride,
})
const reducedBlock: ReducedBlock = {
slug: block.slug,
imageAltText: block.imageAltText,
imageURL: block.imageURL,
labels: block.labels,
subfields: blockFieldMap,
} }
DefaultComponent={DefaultError}
componentProps={{ path }}
/>
)
return reducedBlock const Label = (
}) <RenderCustomComponent
CustomComponent={
'admin' in field &&
field.admin?.components &&
'Label' in field.admin.components &&
field.admin?.components?.Label
}
DefaultComponent={DefaultLabel}
componentProps={labelProps}
/>
)
let RowLabel: React.ReactNode const baseFieldProps: FormFieldBase = {
AfterInput,
if ( BeforeInput,
'admin' in field && Description,
field.admin.components && Error,
'RowLabel' in field.admin.components && Label,
field.admin.components.RowLabel && disabled: 'admin' in field && 'disabled' in field.admin ? field.admin?.disabled : false,
!isPlainObject(field.admin.components.RowLabel) path,
) { required: 'required' in field ? field.required : undefined,
const CustomRowLabel = field.admin.components.RowLabel as React.ComponentType
RowLabel = <CustomRowLabel />
} }
// TODO: these types can get cleaned up let fieldComponentProps: FieldComponentProps
// i.e. not all fields have `maxRows` or `min` or `max`
// but this is labor intensive and requires consuming components to be updated
const fieldComponentProps: FormFieldBase = {
AfterInput: 'admin' in field &&
'components' in field.admin &&
'afterInput' in field.admin.components &&
Array.isArray(field.admin?.components?.afterInput) && (
<Fragment>
{field.admin.components.afterInput.map((Component, i) => (
<Component key={i} />
))}
</Fragment>
),
BeforeInput: 'admin' in field &&
field.admin?.components &&
'beforeInput' in field.admin.components &&
Array.isArray(field.admin.components.beforeInput) && (
<Fragment>
{field.admin.components.beforeInput.map((Component, i) => (
<Component key={i} />
))}
</Fragment>
),
Description: (
<RenderCustomComponent
CustomComponent={
field.admin &&
'description' in field.admin &&
field.admin.description &&
typeof field.admin.description === 'function' &&
(field.admin.description as React.FC<any>)
}
DefaultComponent={DefaultDescription}
componentProps={descriptionProps}
/>
),
Error: (
<RenderCustomComponent
CustomComponent={
'admin' in field &&
field.admin.components &&
'Error' in field.admin.components &&
field.admin?.components?.Error
}
DefaultComponent={DefaultError}
componentProps={{ path }}
/>
),
Label: (
<RenderCustomComponent
CustomComponent={
'admin' in field &&
field.admin?.components &&
'Label' in field.admin.components &&
field.admin?.components?.Label
}
DefaultComponent={DefaultLabel}
componentProps={labelProps}
/>
),
RowLabel,
blocks,
className:
'admin' in field && 'className' in field.admin ? field?.admin?.className : undefined,
date: 'admin' in field && 'date' in field.admin ? field.admin.date : undefined,
disabled: field?.admin && 'disabled' in field.admin ? field.admin?.disabled : false,
fieldMap: nestedFieldMap,
hasMany: 'hasMany' in field ? field.hasMany : undefined,
label: 'label' in field && typeof field.label === 'string' ? field.label : undefined,
max: 'max' in field ? field.max : undefined,
maxRows: 'maxRows' in field ? field.maxRows : undefined,
min: 'min' in field ? field.min : undefined,
options: 'options' in field ? field.options : undefined,
placeholder:
'admin' in field && 'placeholder' in field.admin
? field?.admin?.placeholder
: undefined,
readOnly:
'admin' in field && 'readOnly' in field.admin ? field.admin.readOnly : undefined,
relationTo: 'relationTo' in field ? field.relationTo : undefined,
richTextComponentMap: undefined,
step: 'admin' in field && 'step' in field.admin ? field.admin.step : undefined,
style: 'admin' in field && 'style' in field.admin ? field?.admin?.style : undefined,
tabs,
width: 'admin' in field && 'width' in field.admin ? field?.admin?.width : undefined,
}
if (
field.type === 'collapsible' &&
typeof field.label === 'object' &&
!isPlainObject(field.label)
) {
const CollapsibleLabel = field.label as unknown as React.ComponentType
fieldComponentProps.Label = <CollapsibleLabel />
}
let Field = <FieldComponent {...fieldComponentProps} />
const cellComponentProps: CellProps = { const cellComponentProps: CellProps = {
name: 'name' in field ? field.name : undefined, name: 'name' in field ? field.name : undefined,
blocks:
'blocks' in field &&
field.blocks.map((b) => ({
slug: b.slug,
labels: b.labels,
})),
dateDisplayFormat:
'admin' in field && 'date' in field.admin ? field.admin.date.displayFormat : undefined,
fieldType: field.type, fieldType: field.type,
isFieldAffectingData, isFieldAffectingData,
label: label:
@@ -265,6 +194,418 @@ export const mapFields = (args: {
options: 'options' in field ? field.options : undefined, options: 'options' in field ? field.options : undefined,
} }
switch (field.type) {
case 'array': {
let RowLabel: React.ReactNode
if (
'admin' in field &&
field.admin.components &&
'RowLabel' in field.admin.components &&
field.admin.components.RowLabel &&
!isPlainObject(field.admin.components.RowLabel)
) {
const CustomRowLabel = field.admin.components.RowLabel as React.ComponentType
RowLabel = <CustomRowLabel />
}
const arrayFieldProps: Omit<ArrayFieldProps, 'indexPath' | 'permissions'> = {
...baseFieldProps,
name: field.name,
RowLabel,
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: nestedFieldMap,
label: field?.label || undefined,
labels: field.labels,
maxRows: field.maxRows,
minRows: field.minRows,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = arrayFieldProps
break
}
case 'blocks': {
const blocks = field.blocks.map((block) => {
const blockFieldMap = mapFields({
DefaultCell,
config,
fieldSchema: block.fields,
filter,
parentPath: `${path}.${block.slug}`,
readOnly: readOnlyOverride,
})
const reducedBlock: ReducedBlock = {
slug: block.slug,
fieldMap: blockFieldMap,
imageAltText: block.imageAltText,
imageURL: block.imageURL,
labels: block.labels,
}
return reducedBlock
})
const blocksField: Omit<BlocksFieldProps, 'indexPath' | 'permissions'> = {
...baseFieldProps,
name: field.name,
blocks,
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: nestedFieldMap,
label: field?.label || undefined,
labels: field.labels,
maxRows: field.maxRows,
minRows: field.minRows,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = blocksField
cellComponentProps.blocks = field.blocks.map((b) => ({
slug: b.slug,
labels: b.labels,
}))
break
}
case 'checkbox': {
const checkboxField: CheckboxFieldProps = {
...baseFieldProps,
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,
width: field.admin?.width,
}
fieldComponentProps = checkboxField
break
}
case 'code': {
const codeField: CodeFieldProps = {
...baseFieldProps,
name: field.name,
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,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = codeField
break
}
case 'collapsible': {
let CollapsibleLabel: React.ReactNode
if (typeof field.label === 'object' && !isPlainObject(field.label)) {
const LabelToRender = field.label as unknown as React.ComponentType
CollapsibleLabel = <LabelToRender />
}
const collapsibleField: Omit<CollapsibleFieldProps, 'indexPath' | 'permissions'> = {
...baseFieldProps,
Label: CollapsibleLabel,
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: nestedFieldMap,
fieldTypes,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = collapsibleField
break
}
case 'date': {
const dateField: DateFieldProps = {
...baseFieldProps,
name: field.name,
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,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = dateField
cellComponentProps.dateDisplayFormat = field.admin?.date?.displayFormat
break
}
case 'email': {
const emailField: EmailFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
placeholder: field.admin?.placeholder,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = emailField
break
}
case 'group': {
const groupField: Omit<GroupFieldProps, 'indexPath' | 'permissions'> = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: nestedFieldMap,
readOnly: field.admin?.readOnly,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = groupField
break
}
case 'json': {
const jsonField: JSONFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
editorOptions: field.admin?.editorOptions,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = jsonField
break
}
case 'number': {
const numberField: NumberFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
hasMany: field.hasMany,
max: field.max,
maxRows: field.maxRows,
min: field.min,
readOnly: field.admin?.readOnly,
required: field.required,
step: field.admin?.step,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = numberField
break
}
case 'point': {
const pointField: PointFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = pointField
break
}
case 'relationship': {
const relationshipField: RelationshipFieldProps = {
...baseFieldProps,
name: field.name,
allowCreate: field.admin.allowCreate,
className: field.admin?.className,
disabled: field.admin?.disabled,
hasMany: field.hasMany,
readOnly: field.admin?.readOnly,
relationTo: field.relationTo,
required: field.required,
sortOptions: field.admin.sortOptions,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = relationshipField
break
}
case 'richText': {
const richTextField = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = richTextField
break
}
case 'row': {
const rowField: Omit<RowFieldProps, 'indexPath' | 'permissions'> = {
...baseFieldProps,
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: nestedFieldMap,
fieldTypes,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = rowField
break
}
case 'tabs': {
// `tabs` fields require a field map of each of its tab's nested fields
const tabs = field.tabs.map((tab) => {
const tabFieldMap = mapFields({
DefaultCell,
config,
fieldSchema: tab.fields,
filter,
parentPath: path,
readOnly: readOnlyOverride,
})
const reducedTab: MappedTab = {
name: 'name' in tab ? tab.name : undefined,
fieldMap: tabFieldMap,
label: tab.label,
}
return reducedTab
})
const tabsField: Omit<TabsFieldProps, 'indexPath' | 'permissions'> = {
...baseFieldProps,
name: 'name' in field ? (field.name as string) : undefined,
className: field.admin?.className,
disabled: field.admin?.disabled,
fieldMap: nestedFieldMap,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
tabs,
width: field.admin?.width,
}
fieldComponentProps = tabsField
break
}
case 'text': {
const textField: TextFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
maxLength: field.maxLength,
minLength: field.minLength,
placeholder: field.admin?.placeholder,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = textField
break
}
case 'textarea': {
const textareaField: TextareaFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
maxLength: field.maxLength,
minLength: field.minLength,
placeholder: field.admin?.placeholder,
readOnly: field.admin?.readOnly,
required: field.required,
rows: field.admin?.rows,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = textareaField
break
}
case 'ui': {
fieldComponentProps = baseFieldProps
break
}
case 'upload': {
const uploadField: UploadFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
filterOptions: field.filterOptions,
readOnly: field.admin?.readOnly,
relationTo: field.relationTo,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = uploadField
break
}
case 'select': {
const selectField: SelectFieldProps = {
...baseFieldProps,
name: field.name,
className: field.admin?.className,
disabled: field.admin?.disabled,
hasMany: field.hasMany,
isClearable: field.admin?.isClearable,
options: field.options,
readOnly: field.admin?.readOnly,
required: field.required,
style: field.admin?.style,
width: field.admin?.width,
}
fieldComponentProps = selectField
break
}
default: {
break
}
}
let Field = <FieldComponent {...fieldComponentProps} />
/** /**
* Handle RichText Field Components, Cell Components, and component maps * Handle RichText Field Components, Cell Components, and component maps
*/ */
@@ -288,49 +629,43 @@ export const mapFields = (args: {
} }
} }
const Cell = (
<RenderCustomComponent
CustomComponent={field.admin?.components?.Cell}
DefaultComponent={DefaultCell}
componentProps={cellComponentProps}
/>
)
const Heading = (
<SortColumn
disable={
('disableSort' in field && Boolean(field.disableSort)) ||
fieldIsPresentationalOnly(field) ||
undefined
}
label={
'label' in field && field.label && typeof field.label !== 'function'
? field.label
: 'name' in field
? field.name
: undefined
}
name={'name' in field ? field.name : undefined}
/>
)
const reducedField: MappedField = { const reducedField: MappedField = {
name: 'name' in field ? field.name : '', ...fieldComponentProps,
type: field.type, type: field.type,
Cell: ( Cell,
<RenderCustomComponent
CustomComponent={field.admin?.components?.Cell}
DefaultComponent={DefaultCell}
componentProps={cellComponentProps}
/>
),
Field, Field,
Heading: ( Heading,
<SortColumn
disable={
('disableSort' in field && Boolean(field.disableSort)) ||
fieldIsPresentationalOnly(field) ||
undefined
}
label={
'label' in field && field.label && typeof field.label !== 'function'
? field.label
: 'name' in field
? field.name
: undefined
}
name={'name' in field ? field.name : undefined}
/>
),
blocks,
disabled: field?.admin && 'disabled' in field.admin ? field.admin?.disabled : false,
fieldIsPresentational, fieldIsPresentational,
hasMany: 'hasMany' in field ? field.hasMany : undefined,
isFieldAffectingData, isFieldAffectingData,
isSidebar: 'admin' in field && field.admin?.position === 'sidebar', isSidebar:
label: 'label' in field && typeof field.label !== 'function' ? field.label : undefined, 'admin' in field && 'position' in field.admin && field.admin.position === 'sidebar',
labels: 'labels' in field ? field.labels : undefined,
localized: 'localized' in field ? field.localized : false, localized: 'localized' in field ? field.localized : false,
options: 'options' in field ? field.options : undefined,
readOnly:
'admin' in field && 'readOnly' in field.admin ? field.admin.readOnly : undefined,
relationTo: 'relationTo' in field ? field.relationTo : undefined,
subfields: nestedFieldMap,
tabs,
} }
if (FieldComponent) { if (FieldComponent) {
@@ -343,7 +678,7 @@ export const mapFields = (args: {
}, []) }, [])
const hasID = const hasID =
result.findIndex(({ name, isFieldAffectingData }) => isFieldAffectingData && name === 'id') > -1 result.findIndex((f) => 'name' in f && f.isFieldAffectingData && f.name === 'id') > -1
if (!disableAddingID && !hasID) { if (!disableAddingID && !hasID) {
result.push({ result.push({
@@ -354,13 +689,8 @@ export const mapFields = (args: {
Heading: <SortColumn label="ID" name="id" />, Heading: <SortColumn label="ID" name="id" />,
fieldIsPresentational: false, fieldIsPresentational: false,
isFieldAffectingData: true, isFieldAffectingData: true,
isSidebar: false, // label: 'ID',
label: 'ID',
labels: undefined,
localized: undefined, localized: undefined,
readOnly: false,
subfields: [],
tabs: [],
}) })
} }

View File

@@ -10,67 +10,74 @@ import type {
TabsField, TabsField,
} from 'payload/types' } from 'payload/types'
import type { ArrayFieldProps } from '../../forms/fields/Array/types.js'
import type { BlocksFieldProps } from '../../forms/fields/Blocks/types.js'
import type { CheckboxFieldProps } from '../../forms/fields/Checkbox/types.js'
import type { CodeFieldProps } from '../../forms/fields/Code/types.js'
import type { CollapsibleFieldProps } from '../../forms/fields/Collapsible/types.js'
import type { DateFieldProps } from '../../forms/fields/DateTime/types.js'
import type { EmailFieldProps } from '../../forms/fields/Email/types.js'
import type { GroupFieldProps } from '../../forms/fields/Group/types.js'
import type { JSONFieldProps } from '../../forms/fields/JSON/types.js'
import type { NumberFieldProps } from '../../forms/fields/Number/types.js'
import type { PointFieldProps } from '../../forms/fields/Point/types.js'
import type { RelationshipFieldProps } from '../../forms/fields/Relationship/types.js'
import type { RowFieldProps } from '../../forms/fields/Row/types.js'
import type { SelectFieldProps } from '../../forms/fields/Select/types.js'
import type { TabsFieldProps } from '../../forms/fields/Tabs/types.js'
import type { TextFieldProps } from '../../forms/fields/Text/types.js'
import type { TextareaFieldProps } from '../../forms/fields/Textarea/types.js'
import type { UploadFieldProps } from '../../forms/fields/Upload/types.js'
import type { fieldTypes } from '../../forms/fields/index.js' import type { fieldTypes } from '../../forms/fields/index.js'
export type MappedTab = { export type MappedTab = {
fieldMap?: FieldMap
label: TabsField['tabs'][0]['label'] label: TabsField['tabs'][0]['label']
name?: string name?: string
subfields?: FieldMap
} }
export type ReducedBlock = { export type ReducedBlock = {
fieldMap: FieldMap
imageAltText?: string imageAltText?: string
imageURL?: string imageURL?: string
labels: BlockField['labels'] labels: BlockField['labels']
slug: string slug: string
subfields: FieldMap
} }
export type MappedField = { export type FieldComponentProps =
| ArrayFieldProps
| BlocksFieldProps
| CheckboxFieldProps
| CodeFieldProps
| CollapsibleFieldProps
| DateFieldProps
| EmailFieldProps
| GroupFieldProps
| JSONFieldProps
| NumberFieldProps
| PointFieldProps
| RelationshipFieldProps
| RowFieldProps
| SelectFieldProps
| TabsFieldProps
| TextFieldProps
| TextareaFieldProps
| UploadFieldProps
export type MappedFieldBase = {
Cell: React.ReactNode Cell: React.ReactNode
Field: React.ReactNode Field: React.ReactNode
Heading: React.ReactNode Heading: React.ReactNode
/**
* On `block` fields only
*/
blocks?: ReducedBlock[]
disabled?: boolean disabled?: boolean
/**
* On `richText` fields only
*/
editor?: RichTextField['editor']
fieldIsPresentational: boolean fieldIsPresentational: boolean
fieldMap?: FieldMap
hasMany?: boolean
isFieldAffectingData: boolean isFieldAffectingData: boolean
isSidebar: boolean isSidebar?: boolean
label: FieldBase['label']
labels: Labels
localized: boolean localized: boolean
name: string
/**
* On `select` fields only
*/
options?: Option[]
/**
* This is the `admin.readOnly` value from the field's config
*/
readOnly: boolean
/**
* On `relationship` fields only
*/
relationTo?: RelationshipField['relationTo']
/**
* On `array`, `group`, `collapsible`, and `tabs` fields only
*/
subfields?: FieldMap
/**
* On `tabs` fields only
*/
tabs?: MappedTab[]
type: keyof typeof fieldTypes type: keyof typeof fieldTypes
} }
export type MappedField = FieldComponentProps & MappedFieldBase
export type FieldMap = MappedField[] export type FieldMap = MappedField[]
export type ActionMap = { export type ActionMap = {

View File

@@ -14,10 +14,10 @@ export const PostsCollection: CollectionConfig = {
name: 'text', name: 'text',
type: 'text', type: 'text',
}, },
{ // {
name: 'richText', // name: 'richText',
type: 'richText', // type: 'richText',
}, // },
{ {
name: 'relationship', name: 'relationship',
type: 'relationship', type: 'relationship',

View File

@@ -304,7 +304,7 @@ describe('versions', () => {
await expect( await expect(
page.locator('.autosave:has-text("Last saved less than a minute ago")'), page.locator('.autosave:has-text("Last saved less than a minute ago")'),
).toBeVisible() ).toBeVisible()
expect(await titleField.inputValue()).toBe('global title') await expect(titleField).toHaveValue('global title')
// refresh the page and ensure value autosaved // refresh the page and ensure value autosaved
await page.goto(url.global(autoSaveGlobalSlug)) await page.goto(url.global(autoSaveGlobalSlug))
@@ -339,7 +339,7 @@ describe('versions', () => {
// change locale back to en // change locale back to en
await changeLocale(page, en) await changeLocale(page, en)
// verify en loads its own title // verify en loads its own title
expect(await titleField.inputValue()).toEqual(title) await expect(titleField).toHaveValue(title)
// change non-localized description field // change non-localized description field
await descriptionField.fill(newDescription) await descriptionField.fill(newDescription)
await wait(500) await wait(500)
@@ -352,8 +352,8 @@ describe('versions', () => {
// title should not be english title // title should not be english title
// description should be new description // description should be new description
await page.reload() await page.reload()
expect(await titleField.inputValue()).toEqual(spanishTitle) await expect(titleField).toHaveValue(spanishTitle)
expect(await descriptionField.inputValue()).toEqual(newDescription) await expect(descriptionField).toHaveValue(newDescription)
}) })
test('should restore localized docs correctly', async () => { test('should restore localized docs correctly', async () => {