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 (useAsTitle !== 'id') {
const titleField = fieldMap.find(
({ name: fieldName, isFieldAffectingData }) =>
isFieldAffectingData && fieldName === useAsTitle,
) as FieldAffectingData
const titleField = fieldMap.find((f) => {
const { isFieldAffectingData } = f
const fieldName = 'name' in f ? f.name : undefined
return isFieldAffectingData && fieldName === useAsTitle
}) as FieldAffectingData
if (titleField && mostRecentDoc[useAsTitle]) {
if (titleField.localized) {

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
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 { getTranslation } from '@payloadcms/translations'
@@ -44,7 +46,11 @@ const getTranslatedOptions = (
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 = ''
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}>
<Label>
{locale && <span className={`${baseClass}__locale-label`}>{locale}</span>}
{getTranslation(field.label || '', i18n)}
{'label' in field && getTranslation(field.label || '', i18n)}
</Label>
<DiffViewer
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 type { Props } from '../types.js'
@@ -7,16 +10,11 @@ import Nested from '../Nested/index.js'
const baseClass = 'tabs-diff'
const Tabs: React.FC<Props> = ({
comparison,
diffComponents,
field,
i18n,
locale,
locales,
permissions,
version,
}) => {
const Tabs: React.FC<
Omit<Props, 'field'> & {
field: MappedFieldBase & TabsFieldProps
}
> = ({ comparison, diffComponents, field, i18n, locale, locales, permissions, version }) => {
return (
<div className={baseClass}>
<div className={`${baseClass}__wrap`}>
@@ -27,7 +25,7 @@ const Tabs: React.FC<Props> = ({
comparison={comparison?.[tab.name]}
diffComponents={diffComponents}
field={field}
fieldMap={tab.subfields}
fieldMap={tab.fieldMap}
i18n={i18n}
key={i}
locale={locale}
@@ -42,7 +40,7 @@ const Tabs: React.FC<Props> = ({
<RenderFieldsToDiff
comparison={comparison}
diffComponents={diffComponents}
fieldMap={tab.subfields}
fieldMap={tab.fieldMap}
fieldPermissions={permissions}
i18n={i18n}
key={i}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
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 { 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'
const baseClass = 'lexical-block'
import type { ReducedBlock } from '@payloadcms/ui/types'
import type { ReducedBlock } from '@payloadcms/ui'
import type { FormState } from 'payload/types'
import { v4 as uuid } from 'uuid'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,8 +27,8 @@ export const buildColumns = (args: {
// 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`
sortedFieldMap = fieldMap.sort((a, b) => {
const aIndex = sortTo.findIndex((column) => column.accessor === a.name)
const bIndex = sortTo.findIndex((column) => column.accessor === b.name)
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
if (aIndex === -1 && bIndex === -1) return 0
if (aIndex === -1) return 1
if (bIndex === -1) return -1
@@ -40,7 +40,7 @@ export const buildColumns = (args: {
const sorted = sortedFieldMap.reduce((acc, field, index) => {
const columnPreference = columnPreferences?.find(
(preference) => preference.accessor === field.name,
(preference) => 'name' in field && preference.accessor === field.name,
)
let active = false
@@ -48,7 +48,7 @@ export const buildColumns = (args: {
if (columnPreference) {
active = columnPreference.active
} 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) {
active = true
}
@@ -61,8 +61,8 @@ export const buildColumns = (args: {
if (field) {
const column: Column = {
name: field.name,
accessor: field.name,
name: 'name' in field ? field.name : undefined,
accessor: 'name' in field ? field.name : undefined,
active,
cellProps: {
...cellProps?.[index],
@@ -72,7 +72,7 @@ export const buildColumns = (args: {
Cell: field.Cell,
Heading: field.Heading,
},
label: field.label,
label: 'label' in field ? field.label : undefined,
}
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 { buildStateFromSchema } 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 type { BlocksFieldProps } from '../forms/fields/Blocks/types.js'
export { CheckboxInput } from '../forms/fields/Checkbox/Input.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 type { DateFieldProps } from '../forms/fields/DateTime/types.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 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 type { NumberFieldProps } from '../forms/fields/Number/types.js'
export { default as Password } from '../forms/fields/Password/index.js'
export { default as RadioGroupInput } from '../forms/fields/RadioGroup/index.js'
export type { OnChange } from '../forms/fields/RadioGroup/types.js'
export { default as Select } 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 { 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 { default as Textarea } from '../forms/fields/Textarea/index.js'
export { UploadInput, type UploadInputProps } from '../forms/fields/Upload/Input.js'
export { default as UploadField } from '../forms/fields/Upload/index.js'
export { fieldTypes } from '../forms/fields/index.js'
export { fieldBaseClass } from '../forms/fields/shared.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 { 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'
export type LinkType = React.ElementType
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}
>
{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 (
<RenderField
Field={Field}

View File

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

View File

@@ -2,7 +2,7 @@
import { getTranslation } from '@payloadcms/translations'
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 { Button } from '../../../elements/Button/index.js'
@@ -24,7 +24,7 @@ import './index.scss'
const baseClass = 'array-field'
const ArrayFieldType: React.FC<Props> = (props) => {
const ArrayFieldType: React.FC<ArrayFieldProps> = (props) => {
const {
name,
Description,
@@ -37,6 +37,8 @@ const ArrayFieldType: React.FC<Props> = (props) => {
indexPath,
label,
localized,
maxRows,
minRows,
path: pathFromProps,
permissions,
readOnly,
@@ -46,9 +48,6 @@ const ArrayFieldType: React.FC<Props> = (props) => {
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 { addFieldRow, dispatchFields, setModified } = useForm()
const submitted = useFormSubmitted()
@@ -66,7 +65,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
})()
// 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 ('label' in p && p?.label) return { plural: undefined, singular: p.label }
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 { ArrayField, FieldBase, RowLabel } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & {
export type ArrayFieldProps = FormFieldBase & {
RowLabel?: React.ReactNode
fieldMap: FieldMap
forceRender?: boolean
indexPath: string
label: false | string
label?: FieldBase['label']
labels?: ArrayField['labels']
maxRows?: ArrayField['maxRows']
minRows?: ArrayField['minRows']
name?: string
permissions: FieldPermissions
width?: string
}

View File

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

View File

@@ -2,7 +2,7 @@
import { getTranslation } from '@payloadcms/translations'
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 { Button } from '../../../elements/Button/index.js'
@@ -27,7 +27,7 @@ import './index.scss'
const baseClass = 'blocks-field'
const BlocksField: React.FC<Props> = (props) => {
const BlocksField: React.FC<BlocksFieldProps> = (props) => {
const { i18n, t } = useTranslation()
const {
@@ -35,11 +35,15 @@ const BlocksField: React.FC<Props> = (props) => {
Description,
Error,
Label: LabelFromProps,
blocks,
className,
forceRender = false,
indexPath,
label,
labels: labelsFromProps,
localized,
maxRows,
minRows,
path: pathFromProps,
readOnly,
required,
@@ -48,11 +52,6 @@ const BlocksField: React.FC<Props> = (props) => {
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 { addFieldRow, dispatchFields, setModified } = useForm()
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 { BlockField, FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & {
export type BlocksFieldProps = FormFieldBase & {
blocks?: ReducedBlock[]
fieldMap: FieldMap
forceRender?: boolean
indexPath: string
label?: FieldBase['label']
labels?: BlockField['labels']
maxRows?: number
minRows?: number
name?: string
permissions: FieldPermissions
slug?: string
width?: string
}

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
'use client'
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 LabelComp from '../../Label/index.js'
@@ -18,7 +18,7 @@ const prismToMonacoLanguageMap = {
const baseClass = 'code-field'
const Code: React.FC<Props> = (props) => {
const Code: React.FC<CodeFieldProps> = (props) => {
const {
name,
AfterInput,

View File

@@ -1,6 +1,12 @@
import type { CodeField, FieldBase } from 'payload/types.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
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 type { Props } from './types.js'
import type { CollapsibleFieldProps } from './types.js'
import { Collapsible } from '../../../elements/Collapsible/index.js'
import { ErrorPill } from '../../../elements/ErrorPill/index.js'
@@ -21,7 +21,7 @@ import './index.scss'
const baseClass = 'collapsible-field'
const CollapsibleField: React.FC<Props> = (props) => {
const CollapsibleField: React.FC<CollapsibleFieldProps> = (props) => {
const {
Description,
Label: LabelFromProps,

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,12 @@
import type { DateField, FieldBase } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & {
export type DateFieldProps = FormFieldBase & {
date?: DateField['admin']['date']
label?: FieldBase['label']
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 React, { useCallback } from 'react'
import type { Props } from './types.js'
import type { EmailFieldProps } from './types.js'
import { useTranslation } from '../../../providers/Translation/index.js'
import LabelComp from '../../Label/index.js'
@@ -13,7 +13,7 @@ import { withCondition } from '../../withCondition/index.js'
import { fieldBaseClass } from '../shared.js'
import './index.scss'
export const Email: React.FC<Props> = (props) => {
export const Email: React.FC<EmailFieldProps> = (props) => {
const {
name,
AfterInput,

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
'use client'
import React, { useEffect } from 'react'
import type { Props } from './types.js'
import type { HiddenInputFieldProps } from './types.js'
import { useField } from '../../useField/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.
* 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 { path, setValue, value } = useField({

View File

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

View File

@@ -3,7 +3,7 @@ import type { ClientValidate } from 'payload/types'
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 LabelComp from '../../Label/index.js'
@@ -14,7 +14,7 @@ import './index.scss'
const baseClass = 'json-field'
const JSONField: React.FC<Props> = (props) => {
const JSONField: React.FC<JSONFieldProps> = (props) => {
const {
name,
AfterInput,
@@ -23,6 +23,7 @@ const JSONField: React.FC<Props> = (props) => {
Error,
Label: LabelFromProps,
className,
editorOptions,
label,
path: pathFromProps,
readOnly,
@@ -34,9 +35,6 @@ const JSONField: React.FC<Props> = (props) => {
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 [jsonError, setJsonError] = useState<string>()
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'
export type Props = FormFieldBase & {
export type JSONFieldProps = FormFieldBase & {
editorOptions?: JSONField['admin']['editorOptions']
label?: FieldBase['label']
name?: string
path?: string
width?: string
}

View File

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

View File

@@ -1,7 +1,17 @@
import type { FieldBase, NumberField } from 'payload/types.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
onChange?: (e: number) => void
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 type { Props } from './types.js'
import type { PasswordFieldProps } from './types.js'
import LabelComp from '../../Label/index.js'
import { useField } from '../../useField/index.js'
@@ -11,7 +11,7 @@ import { withCondition } from '../../withCondition/index.js'
import { fieldBaseClass } from '../shared.js'
import './index.scss'
export const Password: React.FC<Props> = (props) => {
export const Password: React.FC<PasswordFieldProps> = (props) => {
const {
name,
Error,

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,16 @@
import type { FieldBase, Option } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & {
export type RadioFieldProps = FormFieldBase & {
label?: FieldBase['label']
layout?: 'horizontal' | 'vertical'
name?: string
onChange?: OnChange
options?: Option[]
path?: string
value?: string
width?: string
}
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 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 { useDebouncedCallback } from '../../../hooks/useDebouncedCallback.js'
@@ -31,7 +31,7 @@ const maxResultsPerRequest = 10
const baseClass = 'relationship'
const Relationship: React.FC<Props> = (props) => {
const Relationship: React.FC<RelationshipFieldProps> = (props) => {
const {
name,
Description,

View File

@@ -1,11 +1,17 @@
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 { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & {
export type RelationshipFieldProps = FormFieldBase & {
allowCreate?: RelationshipField['admin']['allowCreate']
hasMany?: boolean
isSortable?: boolean
name: string
relationTo?: RelationshipField['relationTo']
sortOptions?: RelationshipField['admin']['sortOptions']
width?: string
}
export type Option = {

View File

@@ -1,8 +1,8 @@
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
}

View File

@@ -1,3 +1,7 @@
import type { MappedField } from '@payloadcms/ui'
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'
import React from 'react'
import type { Props } from './types.js'
import type { RowFieldProps } from './types.js'
import { useFieldProps } from '../../FieldPropsProvider/index.js'
import { RenderFields } from '../../RenderFields/index.js'
@@ -12,7 +12,7 @@ import { RowProvider } from './provider.js'
const baseClass = 'row'
const Row: React.FC<Props> = (props) => {
const Row: React.FC<RowFieldProps> = (props) => {
const { className, fieldMap, forceRender = false } = props
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 { FieldTypes } from 'payload/config'
import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & {
export type RowFieldProps = FormFieldBase & {
fieldMap: FieldMap
fieldTypes: FieldTypes
forceRender?: boolean
indexPath: string
path?: string
permissions?: FieldPermissions
width?: string
}

View File

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

View File

@@ -1,8 +1,16 @@
import type { FieldBase, Option } from 'payload/types.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
onChange?: (e: string) => void
options?: Option[]
path?: string
value?: string
width?: string
}

View File

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

View File

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

View File

@@ -1,13 +1,15 @@
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'
export type Props = FormFieldBase & {
export type TabsFieldProps = FormFieldBase & {
fieldMap: FieldMap
forceRender?: boolean
indexPath: string
name?: string
path?: string
permissions: FieldPermissions
tabs?: MappedTab[]
width?: string
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,14 @@
import type { FieldBase, TextareaField } from 'payload/types.js'
import type { FormFieldBase } from '../shared.js'
export type Props = FormFieldBase & {
export type TextareaFieldProps = FormFieldBase & {
label?: FieldBase['label']
maxLength?: number
minLength?: number
name?: 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 { ListDrawerProps } from '../../../elements/ListDrawer/types.js'
import type { UploadFieldProps } from './types.js'
import { Button } from '../../../elements/Button/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 { useTranslation } from '../../../providers/Translation/index.js'
import LabelComp from '../../Label/index.js'
import { type FormFieldBase, fieldBaseClass } from '../shared.js'
import { fieldBaseClass } from '../shared.js'
import './index.scss'
const baseClass = 'upload'
export type UploadInputProps = FormFieldBase & {
export type UploadInputProps = Omit<UploadFieldProps, 'filterOptions'> & {
api?: string
collection?: SanitizedCollectionConfig
filterOptions?: FilterOptionsResult

View File

@@ -1,7 +1,7 @@
'use client'
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 LabelComp from '../../Label/index.js'
@@ -9,7 +9,7 @@ import { useField } from '../../useField/index.js'
import { UploadInput } from './Input.js'
import './index.scss'
const Upload: React.FC<Props> = (props) => {
const Upload: React.FC<UploadFieldProps> = (props) => {
const {
Description,
Error,

View File

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

View File

@@ -1,26 +1,6 @@
import type { User } from 'payload/auth'
import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
import type {
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'
import type { DocumentPreferences, FormState, Validate } from 'payload/types'
export const fieldBaseClass = 'field-type'
@@ -30,99 +10,19 @@ export type FormFieldBase = {
Description?: React.ReactNode
Error?: React.ReactNode
Label?: React.ReactNode
RowLabel?: React.ReactNode
className?: string
disabled?: boolean
docPreferences?: DocumentPreferences
fieldMap?: FieldMap
initialSubfieldState?: FormState
label?: string
locale?: Locale
localized?: boolean
maxLength?: number
minLength?: number
path?: string
placeholder?: Record<string, string> | string
readOnly?: boolean
required?: boolean
rtl?: boolean
style?: React.CSSProperties
user?: User
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.

View File

@@ -40,7 +40,7 @@ export const ComponentMapProvider: React.FC<{
}
// 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],
)

View File

@@ -5,8 +5,32 @@ import { isPlainObject } from 'payload/utilities'
import React, { Fragment } from 'react'
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 { 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 { SortColumn } from '../../elements/SortColumn/index.js'
@@ -83,56 +107,95 @@ export const mapFields = (args: {
readOnly: readOnlyOverride,
})
// `tabs` fields require a field map of each of its tab's nested fields
const tabs =
'tabs' in field &&
field.tabs &&
Array.isArray(field.tabs) &&
field.tabs.map((tab) => {
const tabFieldMap = mapFields({
DefaultCell,
config,
fieldSchema: tab.fields,
filter,
parentPath: path,
readOnly: readOnlyOverride,
})
const 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>
)
const reducedTab: MappedTab = {
name: 'name' in tab ? tab.name : undefined,
label: tab.label,
subfields: tabFieldMap,
const 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>
)
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}
/>
)
const Error = (
<RenderCustomComponent
CustomComponent={
'admin' in field &&
field.admin.components &&
'Error' in field.admin.components &&
field.admin?.components?.Error
}
DefaultComponent={DefaultError}
componentProps={{ path }}
/>
)
const Label = (
<RenderCustomComponent
CustomComponent={
'admin' in field &&
field.admin?.components &&
'Label' in field.admin.components &&
field.admin?.components?.Label
}
DefaultComponent={DefaultLabel}
componentProps={labelProps}
/>
)
const baseFieldProps: FormFieldBase = {
AfterInput,
BeforeInput,
Description,
Error,
Label,
disabled: 'admin' in field && 'disabled' in field.admin ? field.admin?.disabled : false,
path,
required: 'required' in field ? field.required : undefined,
}
return reducedTab
})
let fieldComponentProps: FieldComponentProps
// `blocks` fields require a field map of each of its block's nested fields
const blocks =
'blocks' in field &&
field.blocks &&
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,
const cellComponentProps: CellProps = {
name: 'name' in field ? field.name : undefined,
fieldType: field.type,
isFieldAffectingData,
label:
'label' in field && field.label && typeof field.label !== 'function'
? field.label
: undefined,
labels: 'labels' in field ? field.labels : undefined,
options: 'options' in field ? field.options : undefined,
}
return reducedBlock
})
switch (field.type) {
case 'array': {
let RowLabel: React.ReactNode
if (
@@ -146,125 +209,403 @@ export const mapFields = (args: {
RowLabel = <CustomRowLabel />
}
// TODO: these types can get cleaned up
// 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}
/>
),
const arrayFieldProps: Omit<ArrayFieldProps, 'indexPath' | 'permissions'> = {
...baseFieldProps,
name: field.name,
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,
className: field.admin?.className,
disabled: field.admin?.disabled,
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,
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,
}
if (
field.type === 'collapsible' &&
typeof field.label === 'object' &&
!isPlainObject(field.label)
) {
const CollapsibleLabel = field.label as unknown as React.ComponentType
fieldComponentProps.Label = <CollapsibleLabel />
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} />
const cellComponentProps: CellProps = {
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,
isFieldAffectingData,
label:
'label' in field && field.label && typeof field.label !== 'function'
? field.label
: undefined,
labels: 'labels' in field ? field.labels : undefined,
options: 'options' in field ? field.options : undefined,
}
/**
* Handle RichText Field Components, Cell Components, and component maps
*/
@@ -288,18 +629,15 @@ export const mapFields = (args: {
}
}
const reducedField: MappedField = {
name: 'name' in field ? field.name : '',
type: field.type,
Cell: (
const Cell = (
<RenderCustomComponent
CustomComponent={field.admin?.components?.Cell}
DefaultComponent={DefaultCell}
componentProps={cellComponentProps}
/>
),
Field,
Heading: (
)
const Heading = (
<SortColumn
disable={
('disableSort' in field && Boolean(field.disableSort)) ||
@@ -315,22 +653,19 @@ export const mapFields = (args: {
}
name={'name' in field ? field.name : undefined}
/>
),
blocks,
disabled: field?.admin && 'disabled' in field.admin ? field.admin?.disabled : false,
)
const reducedField: MappedField = {
...fieldComponentProps,
type: field.type,
Cell,
Field,
Heading,
fieldIsPresentational,
hasMany: 'hasMany' in field ? field.hasMany : undefined,
isFieldAffectingData,
isSidebar: 'admin' in field && field.admin?.position === 'sidebar',
label: 'label' in field && typeof field.label !== 'function' ? field.label : undefined,
labels: 'labels' in field ? field.labels : undefined,
isSidebar:
'admin' in field && 'position' in field.admin && field.admin.position === 'sidebar',
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) {
@@ -343,7 +678,7 @@ export const mapFields = (args: {
}, [])
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) {
result.push({
@@ -354,13 +689,8 @@ export const mapFields = (args: {
Heading: <SortColumn label="ID" name="id" />,
fieldIsPresentational: false,
isFieldAffectingData: true,
isSidebar: false,
label: 'ID',
labels: undefined,
// label: 'ID',
localized: undefined,
readOnly: false,
subfields: [],
tabs: [],
})
}

View File

@@ -10,67 +10,74 @@ import type {
TabsField,
} 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'
export type MappedTab = {
fieldMap?: FieldMap
label: TabsField['tabs'][0]['label']
name?: string
subfields?: FieldMap
}
export type ReducedBlock = {
fieldMap: FieldMap
imageAltText?: string
imageURL?: string
labels: BlockField['labels']
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
Field: React.ReactNode
Heading: React.ReactNode
/**
* On `block` fields only
*/
blocks?: ReducedBlock[]
disabled?: boolean
/**
* On `richText` fields only
*/
editor?: RichTextField['editor']
fieldIsPresentational: boolean
fieldMap?: FieldMap
hasMany?: boolean
isFieldAffectingData: boolean
isSidebar: boolean
label: FieldBase['label']
labels: Labels
isSidebar?: 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
}
export type MappedField = FieldComponentProps & MappedFieldBase
export type FieldMap = MappedField[]
export type ActionMap = {

View File

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

View File

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