feat: threads field config through components and strictly types props (#7754)

## Description

Threads the field config to all "field subcomponents" through props,
i.e. field label, description, error, etc. This way, the field config
that controls any particular component is easily accessible and strongly
typed, i.e. `props.field.maxLength`. This is true for both server and
client components, whose server-side props are now also contextually
typed. This behavior was temporarily removed in #7474 due to bloating
HTML, but has since been resolved in #7620. This PR also makes
significant improvements to component types by exporting explicit types
for _every component of every field_, each with its own client/server
variation. Now, a custom component can look something like this:

```tsx
import type { TextFieldLabelServerComponent } from 'payload'

import React from 'react'

export const CustomLabel: TextFieldLabelServerComponent = (props) => {
  return (
    <div>{`The max length of this field is: ${props?.field?.maxLength}`}</div>
  )
}
```

The following types are now available:

```ts
import type {
  TextFieldClientComponent,
  TextFieldServerComponent,
  TextFieldLabelClientComponent,
  TextFieldLabelServerComponent,
  TextFieldDescriptionClientComponent,
  TextFieldDescriptionServerComponent,
  TextFieldErrorClientComponent,
  TextFieldErrorServerComponent,
  // ...and so one for each field
} from 'payload'
```

BREAKING CHANGES:

In order to strictly type these components, a few breaking changes have
been made _solely to type definitions_. This only effects you if you are
heavily using custom components.

Old
```ts
import type { ErrorComponent, LabelComponent, DescriptionComponent } from 'payload'
```

New:
```ts
import type {
  FieldErrorClientComponent,
  FieldErrorServerComponent,
  FieldLabelClientComponent,
  FieldLabelServerComponent,
  FieldDescriptionClientComponent,
  FieldDescriptionServerComponent,
  // Note: these are the generic, underlying types of the more stricter types described above ^
  // For example, you should use the type that is explicit for your particular field and environment
  // i.e. `TextFieldLabelClientComponent` and not simply `FieldLabelClientComponent`
} from 'payload'
```

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
This commit is contained in:
Jacob Fletcher
2024-08-20 00:25:10 -04:00
committed by GitHub
parent 9e6e8357b8
commit 3a91deb0a4
99 changed files with 1380 additions and 621 deletions

View File

@@ -347,31 +347,13 @@ Custom Label Components receive all [Field Component](#the-field-component) prop
#### TypeScript
When building Custom Label Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview). The convention is to append `LabelComponent` to the type of field, i.e. `TextFieldLabelComponent`.
When building Custom Label Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
ArrayFieldLabelComponent,
BlocksFieldLabelComponent,
CheckboxFieldLabelComponent,
CodeFieldLabelComponent,
CollapsibleFieldLabelComponent,
DateFieldLabelComponent,
EmailFieldLabelComponent,
GroupFieldLabelComponent,
HiddenFieldLabelComponent,
JSONFieldLabelComponent,
NumberFieldLabelComponent,
PointFieldLabelComponent,
RadioFieldLabelComponent,
RelationshipFieldLabelComponent,
RichTextFieldLabelComponent,
RowFieldLabelComponent,
SelectFieldLabelComponent,
TabsFieldLabelComponent,
TextFieldLabelComponent,
TextareaFieldLabelComponent,
UploadFieldLabelComponent
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// And so on for each Field Type
} from 'payload'
```
@@ -410,31 +392,13 @@ Custom Error Components receive all [Field Component](#the-field-component) prop
#### TypeScript
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
ArrayFieldErrorComponent,
BlocksFieldErrorComponent,
CheckboxFieldErrorComponent,
CodeFieldErrorComponent,
CollapsibleFieldErrorComponent,
DateFieldErrorComponent,
EmailFieldErrorComponent,
GroupFieldErrorComponent,
HiddenFieldErrorComponent,
JSONFieldErrorComponent,
NumberFieldErrorComponent,
PointFieldErrorComponent,
RadioFieldErrorComponent,
RelationshipFieldErrorComponent,
RichTextFieldErrorComponent,
RowFieldErrorComponent,
SelectFieldErrorComponent,
TabsFieldErrorComponent,
TextFieldErrorComponent,
TextareaFieldErrorComponent,
UploadFieldErrorComponent
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
@@ -544,31 +508,13 @@ Custom Description Components receive all [Field Component](#the-field-component
#### TypeScript
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview). The convention is to append `DescriptionComponent` to the type of field, i.e. `TextFieldDescriptionComponent`.
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
ArrayFieldDescriptionComponent,
BlocksFieldDescriptionComponent,
CheckboxFieldDescriptionComponent,
CodeFieldDescriptionComponent,
CollapsibleFieldDescriptionComponent,
DateFieldDescriptionComponent,
EmailFieldDescriptionComponent,
GroupFieldDescriptionComponent,
HiddenFieldDescriptionComponent,
JSONFieldDescriptionComponent,
NumberFieldDescriptionComponent,
PointFieldDescriptionComponent,
RadioFieldDescriptionComponent,
RelationshipFieldDescriptionComponent,
RichTextFieldDescriptionComponent,
RowFieldDescriptionComponent,
SelectFieldDescriptionComponent,
TabsFieldDescriptionComponent,
TextFieldDescriptionComponent,
TextareaFieldDescriptionComponent,
UploadFieldDescriptionComponent
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```

View File

@@ -467,10 +467,10 @@ export const ServerRenderedDescription = () => <ClientRenderedDescription />
// file: components/ClientRenderedDescription.tsx
'use client'
import React from 'react'
import type { DescriptionComponent } from 'payload'
import type { TextFieldDescriptionClientComponent } from 'payload'
import { useFieldProps, useFormFields } from '@payloadcms/ui'
export const ClientRenderedDescription: DescriptionComponent = () ={
export const ClientRenderedDescription: TextFieldDescriptionClientComponent = () ={
const { path } = useFieldProps()
const { value } = useFormFields(([fields]) => fields[path])
const customDescription = `Component description: ${path} - ${value}`

View File

@@ -7,7 +7,6 @@ import type {
} from 'payload'
import { RenderComponent, getCreateMappedComponent } from '@payloadcms/ui/shared'
import { isPlainObject } from 'payload'
import React from 'react'
import { ShouldRenderTabs } from './ShouldRenderTabs.js'

View File

@@ -11,9 +11,9 @@ import './index.scss'
const baseClass = 'payload-settings'
export const Settings: React.FC<{
className?: string
i18n: I18n
languageOptions: LanguageOptions
readonly className?: string
readonly i18n: I18n
readonly languageOptions: LanguageOptions
}> = (props) => {
const { className, i18n, languageOptions } = props
@@ -21,7 +21,7 @@ export const Settings: React.FC<{
<div className={[baseClass, className].filter(Boolean).join(' ')}>
<h3>{i18n.t('general:payloadSettings')}</h3>
<div className={`${baseClass}__language`}>
<FieldLabel htmlFor="language-select" label={i18n.t('general:language')} />
<FieldLabel field={null} htmlFor="language-select" label={i18n.t('general:language')} />
<LanguageSelector languageOptions={languageOptions} />
</div>
<ToggleTheme />

View File

@@ -128,8 +128,6 @@ export const getViewsFromConfig = ({
views,
})
console.log('CustomViewComponent', customViewKey)
if (customViewKey) {
viewKey = customViewKey

View File

@@ -129,6 +129,7 @@ export const APIKey: React.FC<{ readonly enabled: boolean; readonly readOnly?: b
Component: null,
RenderedComponent: APIKeyLabel,
}}
field={null}
htmlFor={path}
/>
<input

View File

@@ -1,23 +1,33 @@
import type { MarkOptional } from 'ts-essentials'
import type { ArrayFieldClient } from '../../fields/config/types.js'
import type { ArrayField, ArrayFieldClient } from '../../fields/config/types.js'
import type { ArrayFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
DescriptionComponent,
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
LabelComponent,
MappedComponent,
} from '../types.js'
type ArrayFieldClientWithoutType = MarkOptional<ArrayFieldClient, 'type'>
export type ArrayFieldProps = {
readonly CustomRowLabel?: MappedComponent
readonly field: MarkOptional<ArrayFieldClient, 'type'>
readonly validate?: ArrayFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<ArrayFieldClientWithoutType>, 'validate'>
export type ArrayFieldLabelComponent = LabelComponent<'array'>
export type ArrayFieldLabelServerComponent = FieldLabelServerComponent<ArrayField>
export type ArrayFieldDescriptionComponent = DescriptionComponent<'array'>
export type ArrayFieldLabelClientComponent = FieldLabelClientComponent<ArrayFieldClientWithoutType>
export type ArrayFieldErrorComponent = ErrorComponent<'array'>
export type ArrayFieldDescriptionServerComponent = FieldDescriptionServerComponent<ArrayField>
export type ArrayFieldDescriptionClientComponent =
FieldDescriptionClientComponent<ArrayFieldClientWithoutType>
export type ArrayFieldErrorServerComponent = FieldErrorServerComponent<ArrayField>
export type ArrayFieldErrorClientComponent = FieldErrorClientComponent<ArrayFieldClientWithoutType>

View File

@@ -1,17 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { BlockFieldClient } from '../../fields/config/types.js'
import type { BlockField, BlockFieldClient } from '../../fields/config/types.js'
import type { BlockFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type BlocksFieldClientWithoutType = MarkOptional<BlockFieldClient, 'type'>
export type BlockFieldProps = {
readonly field: MarkOptional<BlockFieldClient, 'type'>
readonly validate?: BlockFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<BlocksFieldClientWithoutType>, 'validate'>
export type BlockFieldLabelComponent = LabelComponent<'blocks'>
export type BlockFieldLabelServerComponent = FieldLabelServerComponent<BlockField>
export type BlockFieldDescriptionComponent = DescriptionComponent<'blocks'>
export type BlockFieldLabelClientComponent = FieldLabelClientComponent<BlocksFieldClientWithoutType>
export type BlockFieldErrorComponent = ErrorComponent<'blocks'>
export type BlockFieldDescriptionServerComponent = FieldDescriptionServerComponent<BlockField>
export type BlockFieldDescriptionClientComponent =
FieldDescriptionClientComponent<BlocksFieldClientWithoutType>
export type BlockFieldErrorServerComponent = FieldErrorServerComponent<BlockField>
export type BlockFieldErrorClientComponent = FieldErrorClientComponent<BlocksFieldClientWithoutType>

View File

@@ -1,22 +1,38 @@
import type { MarkOptional } from 'ts-essentials'
import type { CheckboxFieldClient } from '../../fields/config/types.js'
import type { CheckboxField, CheckboxFieldClient } from '../../fields/config/types.js'
import type { CheckboxFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type CheckboxFieldClientWithoutType = MarkOptional<CheckboxFieldClient, 'type'>
export type CheckboxFieldProps = {
readonly checked?: boolean
readonly disableFormData?: boolean
readonly field: MarkOptional<CheckboxFieldClient, 'type'>
readonly id?: string
readonly onChange?: (value: boolean) => void
readonly partialChecked?: boolean
readonly validate?: CheckboxFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<CheckboxFieldClientWithoutType>, 'validate'>
export type CheckboxFieldLabelComponent = LabelComponent<'checkbox'>
export type CheckboxFieldLabelServerComponent = FieldLabelServerComponent<CheckboxField>
export type CheckboxFieldDescriptionComponent = DescriptionComponent<'checkbox'>
export type CheckboxFieldLabelClientComponent =
FieldLabelClientComponent<CheckboxFieldClientWithoutType>
export type CheckboxFieldErrorComponent = ErrorComponent<'checkbox'>
export type CheckboxFieldDescriptionServerComponent = FieldDescriptionServerComponent<CheckboxField>
export type CheckboxFieldDescriptionClientComponent =
FieldDescriptionClientComponent<CheckboxFieldClientWithoutType>
export type CheckboxFieldErrorServerComponent = FieldErrorServerComponent<CheckboxField>
export type CheckboxFieldErrorClientComponent =
FieldErrorClientComponent<CheckboxFieldClientWithoutType>

View File

@@ -1,18 +1,32 @@
import type { MarkOptional } from 'ts-essentials'
import type { CodeFieldClient } from '../../fields/config/types.js'
import type { CodeField, CodeFieldClient } from '../../fields/config/types.js'
import type { CodeFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type CodeFieldClientWithoutType = MarkOptional<CodeFieldClient, 'type'>
export type CodeFieldProps = {
readonly autoComplete?: string
readonly field: MarkOptional<CodeFieldClient, 'type'>
readonly validate?: CodeFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<CodeFieldClientWithoutType>, 'validate'>
export type CodeFieldLabelComponent = LabelComponent<'code'>
export type CodeFieldLabelServerComponent = FieldLabelServerComponent<CodeField>
export type CodeFieldDescriptionComponent = DescriptionComponent<'code'>
export type CodeFieldLabelClientComponent = FieldLabelClientComponent<CodeFieldClientWithoutType>
export type CodeFieldErrorComponent = ErrorComponent<'code'>
export type CodeFieldDescriptionServerComponent = FieldDescriptionServerComponent<CodeField>
export type CodeFieldDescriptionClientComponent =
FieldDescriptionClientComponent<CodeFieldClientWithoutType>
export type CodeFieldErrorServerComponent = FieldErrorServerComponent<CodeField>
export type CodeFieldErrorClientComponent = FieldErrorClientComponent<CodeFieldClientWithoutType>

View File

@@ -1,15 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { CollapsibleFieldClient } from '../../fields/config/types.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { CollapsibleField, CollapsibleFieldClient } from '../../fields/config/types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
export type CollapsibleFieldProps = {
readonly field: MarkOptional<CollapsibleFieldClient, 'type'>
} & FormFieldBase
type CollapsibleFieldClientWithoutType = MarkOptional<CollapsibleFieldClient, 'type'>
export type CollapsibleFieldLabelComponent = LabelComponent<'collapsible'>
export type CollapsibleFieldProps = FormFieldBase<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldDescriptionComponent = DescriptionComponent<'collapsible'>
export type CollapsibleFieldLabelServerComponent = FieldLabelServerComponent<CollapsibleField>
export type CollapsibleFieldErrorComponent = ErrorComponent<'collapsible'>
export type CollapsibleFieldLabelClientComponent =
FieldLabelClientComponent<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldDescriptionServerComponent =
FieldDescriptionServerComponent<CollapsibleField>
export type CollapsibleFieldDescriptionClientComponent =
FieldDescriptionClientComponent<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldErrorServerComponent = FieldErrorServerComponent<CollapsibleField>
export type CollapsibleFieldErrorClientComponent =
FieldErrorClientComponent<CollapsibleFieldClientWithoutType>

View File

@@ -1,17 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { DateFieldClient } from '../../fields/config/types.js'
import type { DateField, DateFieldClient } from '../../fields/config/types.js'
import type { DateFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type DateFieldClientWithoutType = MarkOptional<DateFieldClient, 'type'>
export type DateFieldProps = {
readonly field: MarkOptional<DateFieldClient, 'type'>
readonly validate?: DateFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<DateFieldClientWithoutType>, 'validate'>
export type DateFieldLabelComponent = LabelComponent<'date'>
export type DateFieldLabelServerComponent = FieldLabelServerComponent<DateField>
export type DateFieldDescriptionComponent = DescriptionComponent<'date'>
export type DateFieldLabelClientComponent = FieldLabelClientComponent<DateFieldClientWithoutType>
export type DateFieldErrorComponent = ErrorComponent<'date'>
export type DateFieldDescriptionServerComponent = FieldDescriptionServerComponent<DateField>
export type DateFieldDescriptionClientComponent =
FieldDescriptionClientComponent<DateFieldClientWithoutType>
export type DateFieldErrorServerComponent = FieldErrorServerComponent<DateField>
export type DateFieldErrorClientComponent = FieldErrorClientComponent<DateFieldClientWithoutType>

View File

@@ -1,18 +1,32 @@
import type { MarkOptional } from 'ts-essentials'
import type { EmailFieldClient } from '../../fields/config/types.js'
import type { EmailField, EmailFieldClient } from '../../fields/config/types.js'
import type { EmailFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type EmailFieldClientWithoutType = MarkOptional<EmailFieldClient, 'type'>
export type EmailFieldProps = {
readonly autoComplete?: string
readonly field: MarkOptional<EmailFieldClient, 'type'>
readonly validate?: EmailFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<EmailFieldClientWithoutType>, 'validate'>
export type EmailFieldLabelComponent = LabelComponent<'email'>
export type EmailFieldLabelServerComponent = FieldLabelServerComponent<EmailField>
export type EmailFieldDescriptionComponent = DescriptionComponent<'email'>
export type EmailFieldLabelClientComponent = FieldLabelClientComponent<EmailFieldClientWithoutType>
export type EmailFieldErrorComponent = ErrorComponent<'email'>
export type EmailFieldDescriptionServerComponent = FieldDescriptionServerComponent<EmailField>
export type EmailFieldDescriptionClientComponent =
FieldDescriptionClientComponent<EmailFieldClientWithoutType>
export type EmailFieldErrorServerComponent = FieldErrorServerComponent<EmailField>
export type EmailFieldErrorClientComponent = FieldErrorClientComponent<EmailFieldClientWithoutType>

View File

@@ -1,15 +1,28 @@
import type { MarkOptional } from 'ts-essentials'
import type { GroupFieldClient } from '../../fields/config/types.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { GroupField, GroupFieldClient } from '../../fields/config/types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
export type GroupFieldProps = {
readonly field: MarkOptional<GroupFieldClient, 'type'>
} & FormFieldBase
type GroupFieldClientWithoutType = MarkOptional<GroupFieldClient, 'type'>
export type GroupFieldLabelComponent = LabelComponent<'group'>
export type GroupFieldProps = FormFieldBase<GroupFieldClientWithoutType>
export type GroupFieldDescriptionComponent = DescriptionComponent<'group'>
export type GroupFieldLabelServerComponent = FieldLabelServerComponent<GroupField>
export type GroupFieldErrorComponent = ErrorComponent<'group'>
export type GroupFieldLabelClientComponent = FieldLabelClientComponent<GroupFieldClientWithoutType>
export type GroupFieldDescriptionServerComponent = FieldDescriptionServerComponent<GroupField>
export type GroupFieldDescriptionClientComponent =
FieldDescriptionClientComponent<GroupFieldClientWithoutType>
export type GroupFieldErrorServerComponent = FieldErrorServerComponent<GroupField>
export type GroupFieldErrorClientComponent = FieldErrorClientComponent<GroupFieldClientWithoutType>

View File

@@ -1,6 +1,5 @@
import type { ClientField } from '../../fields/config/client.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FormFieldBase } from '../types.js'
export type HiddenFieldProps = {
readonly disableModifyingForm?: false
@@ -10,9 +9,3 @@ export type HiddenFieldProps = {
readonly forceUsePathFromProps?: boolean
readonly value?: unknown
} & FormFieldBase
export type HiddenFieldLabelComponent = LabelComponent<'hidden'>
export type HiddenFieldDescriptionComponent = DescriptionComponent<'hidden'>
export type HiddenFieldErrorComponent = ErrorComponent<'hidden'>

View File

@@ -1,17 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { JSONFieldClient } from '../../fields/config/types.js'
import type { JSONField, JSONFieldClient } from '../../fields/config/types.js'
import type { JSONFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type JSONFieldClientWithoutType = MarkOptional<JSONFieldClient, 'type'>
export type JSONFieldProps = {
readonly field: MarkOptional<JSONFieldClient, 'type'>
readonly validate?: JSONFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<JSONFieldClientWithoutType>, 'validate'>
export type JSONFieldLabelComponent = LabelComponent<'json'>
export type JSONFieldLabelServerComponent = FieldLabelServerComponent<JSONField>
export type JSONFieldDescriptionComponent = DescriptionComponent<'json'>
export type JSONFieldLabelClientComponent = FieldLabelClientComponent<JSONFieldClientWithoutType>
export type JSONFieldErrorComponent = ErrorComponent<'json'>
export type JSONFieldDescriptionServerComponent = FieldDescriptionServerComponent<JSONField>
export type JSONFieldDescriptionClientComponent =
FieldDescriptionClientComponent<JSONFieldClientWithoutType>
export type JSONFieldErrorServerComponent = FieldErrorServerComponent<JSONField>
export type JSONFieldErrorClientComponent = FieldErrorClientComponent<JSONFieldClientWithoutType>

View File

@@ -1,18 +1,34 @@
import type { MarkOptional } from 'ts-essentials'
import type { NumberFieldClient } from '../../fields/config/types.js'
import type { NumberField, NumberFieldClient } from '../../fields/config/types.js'
import type { NumberFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type NumberFieldClientWithoutType = MarkOptional<NumberFieldClient, 'type'>
export type NumberFieldProps = {
readonly field: MarkOptional<NumberFieldClient, 'type'>
readonly onChange?: (e: number) => void
readonly validate?: NumberFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<NumberFieldClientWithoutType>, 'validate'>
export type NumberFieldLabelComponent = LabelComponent<'number'>
export type NumberFieldLabelServerComponent = FieldLabelServerComponent<NumberField>
export type NumberFieldDescriptionComponent = DescriptionComponent<'number'>
export type NumberFieldLabelClientComponent =
FieldLabelClientComponent<NumberFieldClientWithoutType>
export type NumberFieldErrorComponent = ErrorComponent<'number'>
export type NumberFieldDescriptionServerComponent = FieldDescriptionServerComponent<NumberField>
export type NumberFieldDescriptionClientComponent =
FieldDescriptionClientComponent<NumberFieldClientWithoutType>
export type NumberFieldErrorServerComponent = FieldErrorServerComponent<NumberField>
export type NumberFieldErrorClientComponent =
FieldErrorClientComponent<NumberFieldClientWithoutType>

View File

@@ -1,17 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { PointFieldClient } from '../../fields/config/types.js'
import type { PointField, PointFieldClient } from '../../fields/config/types.js'
import type { PointFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type PointFieldClientWithoutType = MarkOptional<PointFieldClient, 'type'>
export type PointFieldProps = {
readonly field: MarkOptional<PointFieldClient, 'type'>
readonly validate?: PointFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<PointFieldClientWithoutType>, 'validate'>
export type PointFieldLabelComponent = LabelComponent<'point'>
export type PointFieldLabelServerComponent = FieldLabelServerComponent<PointField>
export type PointFieldDescriptionComponent = DescriptionComponent<'point'>
export type PointFieldLabelClientComponent = FieldLabelClientComponent<PointFieldClientWithoutType>
export type PointFieldErrorComponent = ErrorComponent<'point'>
export type PointFieldDescriptionServerComponent = FieldDescriptionServerComponent<PointField>
export type PointFieldDescriptionClientComponent =
FieldDescriptionClientComponent<PointFieldClientWithoutType>
export type PointFieldErrorServerComponent = FieldErrorServerComponent<PointField>
export type PointFieldErrorClientComponent = FieldErrorClientComponent<PointFieldClientWithoutType>

View File

@@ -1,21 +1,35 @@
import type { MarkOptional } from 'ts-essentials'
import type { RadioFieldClient } from '../../fields/config/types.js'
import type { RadioField, RadioFieldClient } from '../../fields/config/types.js'
import type { RadioFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type RadioFieldClientWithoutType = MarkOptional<RadioFieldClient, 'type'>
export type RadioFieldProps = {
readonly field: MarkOptional<RadioFieldClient, 'type'>
readonly onChange?: OnChange
readonly validate?: RadioFieldValidation
readonly value?: string
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<RadioFieldClientWithoutType>, 'validate'>
export type OnChange<T = string> = (value: T) => void
export type RadioFieldLabelComponent = LabelComponent<'radio'>
export type RadioFieldLabelServerComponent = FieldLabelServerComponent<RadioField>
export type RadioFieldDescriptionComponent = DescriptionComponent<'radio'>
export type RadioFieldLabelClientComponent = FieldLabelClientComponent<RadioFieldClientWithoutType>
export type RadioFieldErrorComponent = ErrorComponent<'radio'>
export type RadioFieldDescriptionServerComponent = FieldDescriptionServerComponent<RadioField>
export type RadioFieldDescriptionClientComponent =
FieldDescriptionClientComponent<RadioFieldClientWithoutType>
export type RadioFieldErrorServerComponent = FieldErrorServerComponent<RadioField>
export type RadioFieldErrorClientComponent = FieldErrorClientComponent<RadioFieldClientWithoutType>

View File

@@ -1,17 +1,34 @@
import type { MarkOptional } from 'ts-essentials'
import type { RelationshipFieldClient } from '../../fields/config/types.js'
import type { RelationshipField, RelationshipFieldClient } from '../../fields/config/types.js'
import type { RelationshipFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type RelationshipFieldClientWithoutType = MarkOptional<RelationshipFieldClient, 'type'>
export type RelationshipFieldProps = {
readonly field: MarkOptional<RelationshipFieldClient, 'type'>
readonly validate?: RelationshipFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<RelationshipFieldClientWithoutType>, 'validate'>
export type RelationshipFieldLabelComponent = LabelComponent<'relationship'>
export type RelationshipFieldLabelServerComponent = FieldLabelServerComponent<RelationshipField>
export type RelationshipFieldDescriptionComponent = DescriptionComponent<'relationship'>
export type RelationshipFieldLabelClientComponent =
FieldLabelClientComponent<RelationshipFieldClientWithoutType>
export type RelationshipFieldErrorComponent = ErrorComponent<'relationship'>
export type RelationshipFieldDescriptionServerComponent =
FieldDescriptionServerComponent<RelationshipField>
export type RelationshipFieldDescriptionClientComponent =
FieldDescriptionClientComponent<RelationshipFieldClientWithoutType>
export type RelationshipFieldErrorServerComponent = FieldErrorServerComponent<RelationshipField>
export type RelationshipFieldErrorClientComponent =
FieldErrorClientComponent<RelationshipFieldClientWithoutType>

View File

@@ -1,21 +1,37 @@
import type { MarkOptional } from 'ts-essentials'
import type { RichTextFieldClient } from '../../fields/config/types.js'
import type { RichTextField, RichTextFieldClient } from '../../fields/config/types.js'
import type { RichTextFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type RichTextFieldClientWithoutType = MarkOptional<RichTextFieldClient, 'type'>
export type RichTextFieldProps<
TValue extends object = any,
TAdapterProps = any,
TExtraProperties = object,
> = {
readonly field: MarkOptional<RichTextFieldClient<TValue, TAdapterProps, TExtraProperties>, 'type'>
readonly validate?: RichTextFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<RichTextFieldClientWithoutType>, 'validate'>
export type RichTextFieldLabelComponent = LabelComponent<'richText'>
export type RichTextFieldLabelServerComponent = FieldLabelServerComponent<RichTextField>
export type RichTextFieldDescriptionComponent = DescriptionComponent<'richText'>
export type RichTextFieldLabelClientComponent =
FieldLabelClientComponent<RichTextFieldClientWithoutType>
export type RichTextFieldErrorComponent = ErrorComponent<'richText'>
export type RichTextFieldDescriptionServerComponent = FieldDescriptionServerComponent<RichTextField>
export type RichTextFieldDescriptionClientComponent =
FieldDescriptionClientComponent<RichTextFieldClientWithoutType>
export type RichTextFieldErrorServerComponent = FieldErrorServerComponent<RichTextField>
export type RichTextFieldErrorClientComponent =
FieldErrorClientComponent<RichTextFieldClientWithoutType>

View File

@@ -1,17 +1,32 @@
import type { DescriptionComponent, FormFieldBase, LabelComponent } from 'payload'
import type { MarkOptional } from 'ts-essentials'
import type { RowFieldClient } from '../../fields/config/types.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { RowField, RowFieldClient } from '../../fields/config/types.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldErrorClientComponent,
FieldErrorServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type RowFieldClientWithoutType = MarkOptional<RowFieldClient, 'type'>
export type RowFieldProps = {
field: MarkOptional<RowFieldClient, 'type'>
forceRender?: boolean
indexPath: string
} & FormFieldBase
readonly forceRender?: boolean
readonly indexPath: string
} & FormFieldBase<RowFieldClientWithoutType>
export type RowFieldLabelComponent = LabelComponent<'row'>
export type RowFieldLabelServerComponent = FieldLabelServerComponent<RowField>
export type RowFieldDescriptionComponent = DescriptionComponent<'row'>
export type RowFieldLabelClientComponent = FieldLabelClientComponent<RowFieldClientWithoutType>
export type RowFieldErrorComponent = ErrorComponent<'row'>
export type RowFieldDescriptionServerComponent = FieldDescriptionServerComponent<RowField>
export type RowFieldDescriptionClientComponent =
FieldDescriptionClientComponent<RowFieldClientWithoutType>
export type RowFieldErrorServerComponent = FieldErrorServerComponent<RowField>
export type RowFieldErrorClientComponent = FieldErrorClientComponent<RowFieldClientWithoutType>

View File

@@ -1,19 +1,35 @@
import type { MarkOptional } from 'ts-essentials'
import type { SelectFieldClient } from '../../fields/config/types.js'
import type { SelectField, SelectFieldClient } from '../../fields/config/types.js'
import type { SelectFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type SelectFieldClientWithoutType = MarkOptional<SelectFieldClient, 'type'>
export type SelectFieldProps = {
readonly field: MarkOptional<SelectFieldClient, 'type'>
readonly onChange?: (e: string | string[]) => void
readonly validate?: SelectFieldValidation
readonly value?: string
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<SelectFieldClientWithoutType>, 'validate'>
export type SelectFieldLabelComponent = LabelComponent<'select'>
export type SelectFieldLabelServerComponent = FieldLabelServerComponent<SelectField>
export type SelectFieldDescriptionComponent = DescriptionComponent<'select'>
export type SelectFieldLabelClientComponent =
FieldLabelClientComponent<SelectFieldClientWithoutType>
export type SelectFieldErrorComponent = ErrorComponent<'select'>
export type SelectFieldDescriptionServerComponent = FieldDescriptionServerComponent<SelectField>
export type SelectFieldDescriptionClientComponent =
FieldDescriptionClientComponent<SelectFieldClientWithoutType>
export type SelectFieldErrorServerComponent = FieldErrorServerComponent<SelectField>
export type SelectFieldErrorClientComponent =
FieldErrorClientComponent<SelectFieldClientWithoutType>

View File

@@ -3,22 +3,36 @@ import type { MarkOptional } from 'ts-essentials'
import type {
ClientField,
NamedTab,
TabsField,
TabsFieldClient,
UnnamedTab,
} from '../../fields/config/types.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
export type ClientTab =
| ({ fields: ClientField[] } & Omit<NamedTab, 'fields'>)
| ({ fields: ClientField[] } & Omit<UnnamedTab, 'fields'>)
export type TabsFieldProps = {
readonly field: MarkOptional<TabsFieldClient, 'type'>
} & FormFieldBase
export type TabsFieldClientWithoutType = MarkOptional<TabsFieldClient, 'type'>
export type TabsFieldLabelComponent = LabelComponent<'tabs'>
export type TabsFieldProps = FormFieldBase<TabsFieldClientWithoutType>
export type TabsFieldDescriptionComponent = DescriptionComponent<'tabs'>
export type TabsFieldLabelServerComponent = FieldLabelServerComponent<TabsField>
export type TabsFieldErrorComponent = ErrorComponent<'tabs'>
export type TabsFieldLabelClientComponent = FieldLabelClientComponent<TabsFieldClientWithoutType>
export type TabsFieldDescriptionServerComponent = FieldDescriptionServerComponent<TabsField>
export type TabsFieldDescriptionClientComponent =
FieldDescriptionClientComponent<TabsFieldClientWithoutType>
export type TabsFieldErrorServerComponent = FieldErrorServerComponent<TabsField>
export type TabsFieldErrorClientComponent = FieldErrorClientComponent<TabsFieldClientWithoutType>

View File

@@ -1,20 +1,34 @@
import type React from 'react'
import type { MarkOptional } from 'ts-essentials'
import type { TextFieldClient } from '../../fields/config/types.js'
import type { TextField, TextFieldClient } from '../../fields/config/types.js'
import type { TextFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type TextFieldClientWithoutType = MarkOptional<TextFieldClient, 'type'>
export type TextFieldProps = {
readonly field: MarkOptional<TextFieldClient, 'type'>
readonly inputRef?: React.RefObject<HTMLInputElement>
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly validate?: TextFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<TextFieldClientWithoutType>, 'validate'>
export type TextFieldLabelComponent = LabelComponent<'text'>
export type TextFieldLabelServerComponent = FieldLabelServerComponent<TextField>
export type TextFieldDescriptionComponent = DescriptionComponent<'text'>
export type TextFieldLabelClientComponent = FieldLabelClientComponent<TextFieldClientWithoutType>
export type TextFieldErrorComponent = ErrorComponent<'text'>
export type TextFieldDescriptionServerComponent = FieldDescriptionServerComponent<TextField>
export type TextFieldDescriptionClientComponent =
FieldDescriptionClientComponent<TextFieldClientWithoutType>
export type TextFieldErrorServerComponent = FieldErrorServerComponent<TextField>
export type TextFieldErrorClientComponent = FieldErrorClientComponent<TextFieldClientWithoutType>

View File

@@ -1,20 +1,36 @@
import type React from 'react'
import type { MarkOptional } from 'ts-essentials'
import type { TextareaFieldClient } from '../../fields/config/types.js'
import type { TextareaField, TextareaFieldClient } from '../../fields/config/types.js'
import type { TextareaFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type TextareaFieldClientWithoutType = MarkOptional<TextareaFieldClient, 'type'>
export type TextareaFieldProps = {
readonly field: MarkOptional<TextareaFieldClient, 'type'>
readonly inputRef?: React.Ref<HTMLInputElement>
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly validate?: TextareaFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<TextareaFieldClientWithoutType>, 'validate'>
export type TextareaFieldLabelComponent = LabelComponent<'textarea'>
export type TextareaFieldLabelServerComponent = FieldLabelServerComponent<TextareaField>
export type TextareaFieldDescriptionComponent = DescriptionComponent<'textarea'>
export type TextareaFieldLabelClientComponent =
FieldLabelClientComponent<TextareaFieldClientWithoutType>
export type TextareaFieldErrorComponent = ErrorComponent<'textarea'>
export type TextareaFieldDescriptionServerComponent = FieldDescriptionServerComponent<TextareaField>
export type TextareaFieldDescriptionClientComponent =
FieldDescriptionClientComponent<TextareaFieldClientWithoutType>
export type TextareaFieldErrorServerComponent = FieldErrorServerComponent<TextareaField>
export type TextareaFieldErrorClientComponent =
FieldErrorClientComponent<TextareaFieldClientWithoutType>

View File

@@ -1,17 +1,33 @@
import type { MarkOptional } from 'ts-essentials'
import type { UploadFieldClient } from '../../fields/config/types.js'
import type { UploadField, UploadFieldClient } from '../../fields/config/types.js'
import type { UploadFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type UploadFieldClientWithoutType = MarkOptional<UploadFieldClient, 'type'>
export type UploadFieldProps = {
readonly field: MarkOptional<UploadFieldClient, 'type'>
readonly validate?: UploadFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<UploadFieldClientWithoutType>, 'validate'>
export type UploadFieldLabelComponent = LabelComponent<'upload'>
export type UploadFieldLabelServerComponent = FieldLabelServerComponent<UploadField>
export type UploadFieldDescriptionComponent = DescriptionComponent<'upload'>
export type UploadFieldLabelClientComponent =
FieldLabelClientComponent<UploadFieldClientWithoutType>
export type UploadFieldErrorComponent = ErrorComponent<'upload'>
export type UploadFieldDescriptionServerComponent = FieldDescriptionServerComponent<UploadField>
export type UploadFieldDescriptionClientComponent =
FieldDescriptionClientComponent<UploadFieldClientWithoutType>
export type UploadFieldErrorServerComponent = FieldErrorServerComponent<UploadField>
export type UploadFieldErrorClientComponent =
FieldErrorClientComponent<UploadFieldClientWithoutType>

View File

@@ -0,0 +1,38 @@
import type { MarkOptional } from 'ts-essentials'
import type { LabelFunction, ServerProps } from '../../config/types.js'
import type { ClientField, Field } from '../../fields/config/types.js'
import type { MappedComponent } from '../types.js'
export type DescriptionFunction = LabelFunction
type ClientFieldWithOptionalType = MarkOptional<ClientField, 'type'>
export type FieldDescriptionClientComponent<
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
> = React.ComponentType<FieldDescriptionClientProps<TFieldClient>>
export type FieldDescriptionServerComponent<TFieldServer extends Field = Field> =
React.ComponentType<FieldDescriptionServerProps<TFieldServer>>
export type StaticDescription = Record<string, string> | string
export type Description = DescriptionFunction | StaticDescription
export type GenericDescriptionProps = {
readonly Description?: MappedComponent
readonly className?: string
readonly description?: StaticDescription
readonly marginPlacement?: 'bottom' | 'top'
}
export type FieldDescriptionServerProps<TFieldServer extends Field = Field> = {
field: TFieldServer
} & GenericDescriptionProps &
Partial<ServerProps>
export type FieldDescriptionClientProps<
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
> = {
field: TFieldClient
} & GenericDescriptionProps

View File

@@ -1,5 +1,7 @@
import type { CustomComponent, ServerProps } from '../../config/types.js'
import type { FieldTypes } from '../../fields/config/types.js'
import type { MarkOptional } from 'ts-essentials'
import type { ServerProps } from '../../config/types.js'
import type { ClientField, Field } from '../../fields/config/types.js'
import type { MappedComponent } from '../types.js'
export type GenericErrorProps = {
@@ -10,9 +12,23 @@ export type GenericErrorProps = {
readonly showError?: boolean
}
export type ErrorProps<T extends 'hidden' | FieldTypes = any> = {
type: T
type ClientFieldWithOptionalType = MarkOptional<ClientField, 'type'>
export type FieldErrorClientProps<
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
> = {
field: TFieldClient
} & GenericErrorProps
export type FieldErrorServerProps<TFieldServer extends Field> = {
field: TFieldServer
} & GenericErrorProps &
Partial<ServerProps>
export type ErrorComponent<T extends 'hidden' | FieldTypes = any> = CustomComponent<ErrorProps<T>>
export type FieldErrorClientComponent<
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
> = React.ComponentType<FieldErrorClientProps<TFieldClient>>
export type FieldErrorServerComponent<TFieldServer extends Field = Field> = React.ComponentType<
FieldErrorServerProps<TFieldServer>
>

View File

@@ -1,24 +1,28 @@
import type { MarkOptional } from 'ts-essentials'
import type { User } from '../../auth/types.js'
import type { Locale } from '../../config/types.js'
import type { Validate } from '../../fields/config/types.js'
import type { ClientField, Validate } from '../../fields/config/types.js'
import type { DocumentPreferences } from '../../preferences/types.js'
import type { ErrorProps } from './Error.js'
import type { FieldDescriptionProps } from './FieldDescription.js'
import type { LabelProps } from './Label.js'
import type { FieldDescriptionClientProps } from './Description.js'
import type { FieldErrorClientProps } from './Error.js'
import type { FieldLabelClientProps } from './Label.js'
// TODO: Check if we still need this. Shouldnt most of it be present in the field type?
export type FormFieldBase = {
readonly descriptionProps?: FieldDescriptionProps
export type FormFieldBase<
TFieldClient extends MarkOptional<ClientField, 'type'> = MarkOptional<ClientField, 'type'>,
> = {
readonly descriptionProps?: FieldDescriptionClientProps<TFieldClient>
readonly docPreferences?: DocumentPreferences
readonly errorProps?: ErrorProps
readonly errorProps?: FieldErrorClientProps<TFieldClient>
readonly field: TFieldClient
/**
* forceRender is added by RenderField automatically
* `forceRender` is added by RenderField automatically.
*/
readonly forceRender?: boolean
readonly labelProps?: LabelProps
readonly labelProps?: FieldLabelClientProps<TFieldClient>
readonly locale?: Locale
/**
* forceRender is added by RenderField automatically. This should be used instead of field.admin.readOnly
* `readOnly` is added by RenderField automatically. This should be used instead of `field.admin.readOnly`.
*/
readonly readOnly?: boolean
readonly user?: User

View File

@@ -1,23 +0,0 @@
import type { CustomComponent, LabelFunction, ServerProps } from '../../config/types.js'
import type { FieldTypes } from '../../fields/config/types.js'
import type { MappedComponent } from '../types.js'
export type DescriptionFunction = LabelFunction
export type DescriptionComponent<T extends 'hidden' | FieldTypes = any> = CustomComponent<
FieldDescriptionProps<T>
>
export type StaticDescription = Record<string, string> | string
export type Description = DescriptionFunction | StaticDescription
export type GenericDescriptionProps = {
readonly Description?: MappedComponent
readonly className?: string
readonly description?: StaticDescription
readonly marginPlacement?: 'bottom' | 'top'
}
export type FieldDescriptionProps<T extends 'hidden' | FieldTypes = any> = {
type: T
} & GenericDescriptionProps &
Partial<ServerProps>

View File

@@ -1,5 +1,7 @@
import type { CustomComponent, ServerProps, StaticLabel } from '../../config/types.js'
import type { FieldTypes } from '../../fields/config/types.js'
import type { MarkOptional } from 'ts-essentials'
import type { ServerProps, StaticLabel } from '../../config/types.js'
import type { ClientField, Field } from '../../fields/config/types.js'
import type { MappedComponent } from '../types.js'
export type GenericLabelProps = {
@@ -11,14 +13,28 @@ export type GenericLabelProps = {
readonly unstyled?: boolean
}
export type LabelProps<T extends 'hidden' | FieldTypes = any> = {
type: T
type ClientFieldWithOptionalType = MarkOptional<ClientField, 'type'>
export type FieldLabelClientProps<
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
> = {
field: TFieldClient
} & GenericLabelProps
export type FieldLabelServerProps<TFieldServer extends Field> = {
field: TFieldServer
} & GenericLabelProps &
Partial<ServerProps>
export type SanitizedLabelProps<T extends 'hidden' | FieldTypes = any> = Omit<
LabelProps<T>,
export type SanitizedLabelProps<TFieldClient extends ClientField> = Omit<
FieldLabelClientProps<TFieldClient>,
'label' | 'required'
>
export type LabelComponent<T extends 'hidden' | FieldTypes = any> = CustomComponent<LabelProps<T>>
export type FieldLabelClientComponent<
TFieldClient extends ClientFieldWithOptionalType = ClientFieldWithOptionalType,
> = React.ComponentType<FieldLabelClientProps<TFieldClient>>
export type FieldLabelServerComponent<TFieldServer extends Field = Field> = React.ComponentType<
FieldLabelServerProps<TFieldServer>
>

View File

@@ -33,172 +33,237 @@ export type {
} from './elements/WithServerSideProps.js'
export type {
ArrayFieldDescriptionComponent,
ArrayFieldErrorComponent,
ArrayFieldLabelComponent,
ArrayFieldDescriptionClientComponent,
ArrayFieldDescriptionServerComponent,
ArrayFieldErrorClientComponent,
ArrayFieldErrorServerComponent,
ArrayFieldLabelClientComponent,
ArrayFieldLabelServerComponent,
ArrayFieldProps,
} from './fields/Array.js'
export type {
BlockFieldDescriptionComponent,
BlockFieldErrorComponent,
BlockFieldLabelComponent,
BlockFieldDescriptionClientComponent,
BlockFieldDescriptionServerComponent,
BlockFieldErrorClientComponent,
BlockFieldErrorServerComponent,
BlockFieldLabelClientComponent,
BlockFieldLabelServerComponent,
BlockFieldProps,
} from './fields/Blocks.js'
export type {
CheckboxFieldDescriptionComponent,
CheckboxFieldErrorComponent,
CheckboxFieldLabelComponent,
CheckboxFieldDescriptionClientComponent,
CheckboxFieldDescriptionServerComponent,
CheckboxFieldErrorClientComponent,
CheckboxFieldErrorServerComponent,
CheckboxFieldLabelClientComponent,
CheckboxFieldLabelServerComponent,
CheckboxFieldProps,
} from './fields/Checkbox.js'
export type {
CodeFieldDescriptionComponent,
CodeFieldErrorComponent,
CodeFieldLabelComponent,
CodeFieldDescriptionClientComponent,
CodeFieldDescriptionServerComponent,
CodeFieldErrorClientComponent,
CodeFieldErrorServerComponent,
CodeFieldLabelClientComponent,
CodeFieldLabelServerComponent,
CodeFieldProps,
} from './fields/Code.js'
export type {
CollapsibleFieldDescriptionComponent,
CollapsibleFieldErrorComponent,
CollapsibleFieldLabelComponent,
CollapsibleFieldDescriptionClientComponent,
CollapsibleFieldDescriptionServerComponent,
CollapsibleFieldErrorClientComponent,
CollapsibleFieldErrorServerComponent,
CollapsibleFieldLabelClientComponent,
CollapsibleFieldLabelServerComponent,
CollapsibleFieldProps,
} from './fields/Collapsible.js'
export type {
DateFieldDescriptionComponent,
DateFieldErrorComponent,
DateFieldLabelComponent,
DateFieldDescriptionClientComponent,
DateFieldDescriptionServerComponent,
DateFieldErrorClientComponent,
DateFieldErrorServerComponent,
DateFieldLabelClientComponent,
DateFieldLabelServerComponent,
DateFieldProps,
} from './fields/Date.js'
export type {
EmailFieldDescriptionComponent,
EmailFieldErrorComponent,
EmailFieldLabelComponent,
EmailFieldDescriptionClientComponent,
EmailFieldDescriptionServerComponent,
EmailFieldErrorClientComponent,
EmailFieldErrorServerComponent,
EmailFieldLabelClientComponent,
EmailFieldLabelServerComponent,
EmailFieldProps,
} from './fields/Email.js'
export type {
GroupFieldDescriptionComponent,
GroupFieldErrorComponent,
GroupFieldLabelComponent,
GroupFieldDescriptionClientComponent,
GroupFieldDescriptionServerComponent,
GroupFieldErrorClientComponent,
GroupFieldErrorServerComponent,
GroupFieldLabelClientComponent,
GroupFieldLabelServerComponent,
GroupFieldProps,
} from './fields/Group.js'
export type {
HiddenFieldDescriptionComponent,
HiddenFieldErrorComponent,
HiddenFieldLabelComponent,
HiddenFieldProps,
} from './fields/Hidden.js'
export type { HiddenFieldProps } from './fields/Hidden.js'
export type {
JSONFieldDescriptionComponent,
JSONFieldErrorComponent,
JSONFieldLabelComponent,
JSONFieldDescriptionClientComponent,
JSONFieldDescriptionServerComponent,
JSONFieldErrorClientComponent,
JSONFieldErrorServerComponent,
JSONFieldLabelClientComponent,
JSONFieldLabelServerComponent,
JSONFieldProps,
} from './fields/JSON.js'
export type {
NumberFieldDescriptionComponent,
NumberFieldErrorComponent,
NumberFieldLabelComponent,
NumberFieldDescriptionClientComponent,
NumberFieldDescriptionServerComponent,
NumberFieldErrorClientComponent,
NumberFieldErrorServerComponent,
NumberFieldLabelClientComponent,
NumberFieldLabelServerComponent,
NumberFieldProps,
} from './fields/Number.js'
export type {
PointFieldDescriptionComponent,
PointFieldErrorComponent,
PointFieldLabelComponent,
PointFieldDescriptionClientComponent,
PointFieldDescriptionServerComponent,
PointFieldErrorClientComponent,
PointFieldErrorServerComponent,
PointFieldLabelClientComponent,
PointFieldLabelServerComponent,
PointFieldProps,
} from './fields/Point.js'
export type {
RadioFieldDescriptionComponent,
RadioFieldErrorComponent,
RadioFieldLabelComponent,
RadioFieldDescriptionClientComponent,
RadioFieldDescriptionServerComponent,
RadioFieldErrorClientComponent,
RadioFieldErrorServerComponent,
RadioFieldLabelClientComponent,
RadioFieldLabelServerComponent,
RadioFieldProps,
} from './fields/Radio.js'
export type {
RelationshipFieldDescriptionComponent,
RelationshipFieldErrorComponent,
RelationshipFieldLabelComponent,
RelationshipFieldDescriptionClientComponent,
RelationshipFieldDescriptionServerComponent,
RelationshipFieldErrorClientComponent,
RelationshipFieldErrorServerComponent,
RelationshipFieldLabelClientComponent,
RelationshipFieldLabelServerComponent,
RelationshipFieldProps,
} from './fields/Relationship.js'
export type {
RichTextFieldDescriptionComponent,
RichTextFieldErrorComponent,
RichTextFieldLabelComponent,
RichTextFieldDescriptionClientComponent,
RichTextFieldDescriptionServerComponent,
RichTextFieldErrorClientComponent,
RichTextFieldErrorServerComponent,
RichTextFieldLabelClientComponent,
RichTextFieldLabelServerComponent,
RichTextFieldProps,
} from './fields/RichText.js'
export type {
RowFieldDescriptionComponent,
RowFieldErrorComponent,
RowFieldLabelComponent,
RowFieldDescriptionClientComponent,
RowFieldDescriptionServerComponent,
RowFieldErrorClientComponent,
RowFieldErrorServerComponent,
RowFieldLabelClientComponent,
RowFieldLabelServerComponent,
RowFieldProps,
} from './fields/Row.js'
export type {
SelectFieldDescriptionComponent,
SelectFieldErrorComponent,
SelectFieldLabelComponent,
SelectFieldDescriptionClientComponent,
SelectFieldDescriptionServerComponent,
SelectFieldErrorClientComponent,
SelectFieldErrorServerComponent,
SelectFieldLabelClientComponent,
SelectFieldLabelServerComponent,
SelectFieldProps,
} from './fields/Select.js'
export type {
ClientTab,
TabsFieldDescriptionComponent,
TabsFieldErrorComponent,
TabsFieldLabelComponent,
TabsFieldDescriptionClientComponent,
TabsFieldDescriptionServerComponent,
TabsFieldErrorClientComponent,
TabsFieldErrorServerComponent,
TabsFieldLabelClientComponent,
TabsFieldLabelServerComponent,
TabsFieldProps,
} from './fields/Tabs.js'
export type {
TextFieldDescriptionComponent,
TextFieldErrorComponent,
TextFieldLabelComponent,
TextFieldDescriptionClientComponent,
TextFieldDescriptionServerComponent,
TextFieldErrorClientComponent,
TextFieldErrorServerComponent,
TextFieldLabelClientComponent,
TextFieldLabelServerComponent,
TextFieldProps,
} from './fields/Text.js'
export type {
TextareaFieldDescriptionComponent,
TextareaFieldErrorComponent,
TextareaFieldLabelComponent,
TextareaFieldDescriptionClientComponent,
TextareaFieldDescriptionServerComponent,
TextareaFieldErrorClientComponent,
TextareaFieldErrorServerComponent,
TextareaFieldLabelClientComponent,
TextareaFieldLabelServerComponent,
TextareaFieldProps,
} from './fields/Textarea.js'
export type {
UploadFieldDescriptionComponent,
UploadFieldErrorComponent,
UploadFieldLabelComponent,
UploadFieldDescriptionClientComponent,
UploadFieldDescriptionServerComponent,
UploadFieldErrorClientComponent,
UploadFieldErrorServerComponent,
UploadFieldLabelClientComponent,
UploadFieldLabelServerComponent,
UploadFieldProps,
} from './fields/Upload.js'
export type { ErrorComponent, ErrorProps, GenericErrorProps } from './forms/Error.js'
export type { FormFieldBase } from './forms/Field.js'
export type {
Description,
DescriptionComponent,
DescriptionFunction,
FieldDescriptionProps,
FieldDescriptionClientComponent,
FieldDescriptionClientProps,
FieldDescriptionServerComponent,
FieldDescriptionServerProps,
GenericDescriptionProps,
StaticDescription,
} from './forms/FieldDescription.js'
} from './forms/Description.js'
export type {
FieldErrorClientComponent,
FieldErrorClientProps,
FieldErrorServerComponent,
FieldErrorServerProps,
GenericErrorProps,
} from './forms/Error.js'
export type { FormFieldBase } from './forms/Field.js'
export type { Data, FilterOptionsResult, FormField, FormState, Row } from './forms/Form.js'
export type {
FieldLabelClientComponent,
FieldLabelClientProps,
FieldLabelServerComponent,
FieldLabelServerProps,
GenericLabelProps,
LabelComponent,
LabelProps,
SanitizedLabelProps,
} from './forms/Label.js'
@@ -241,14 +306,20 @@ export type MappedComponent<TComponentClientProps extends JsonObject = JsonObjec
export type CreateMappedComponent = {
<T extends JsonObject>(
component: { Component: React.FC<T> } | PayloadComponent<T> | null,
props: object,
props: {
clientProps?: JsonObject
serverProps?: object
},
fallback: React.FC,
identifier: string,
): MappedComponent<T>
<T extends JsonObject>(
components: ({ Component: React.FC<T> } | PayloadComponent<T>)[],
props: object,
props: {
clientProps?: JsonObject
serverProps?: object
},
fallback: React.FC,
identifier: string,
): MappedComponent<T>[]

View File

@@ -1,4 +1,4 @@
import type { MappedComponent } from '../../admin/types.js'
import type { MappedComponent, StaticDescription } from '../../admin/types.js'
import type { MappedView } from '../../admin/views/types.js'
import type { LivePreviewConfig, ServerOnlyLivePreviewProperties } from '../../config/types.js'
import type { ClientField } from '../../fields/config/client.js'
@@ -46,7 +46,7 @@ export type ClientCollectionConfig = {
}
}
}
description?: Record<string, string> | string
description?: StaticDescription
livePreview?: Omit<LivePreviewConfig, ServerOnlyLivePreviewProperties>
} & Omit<
SanitizedCollectionConfig['admin'],

View File

@@ -398,14 +398,14 @@ export type EditViewConfig = {
)
export type ServerProps = {
[key: string]: unknown
i18n: I18nClient
locale?: Locale
params?: { [key: string]: string | string[] | undefined }
payload: Payload
permissions?: Permissions
searchParams?: { [key: string]: string | string[] | undefined }
user?: TypedUser
readonly i18n: I18nClient
readonly locale?: Locale
readonly params?: { [key: string]: string | string[] | undefined }
readonly payload: Payload
readonly permissions?: Permissions
readonly [key: string]: unknown
readonly searchParams?: { [key: string]: string | string[] | undefined }
readonly user?: TypedUser
}
export const serverProps: (keyof ServerProps)[] = [

View File

@@ -6,16 +6,97 @@ import type { CSSProperties } from 'react'
import type { DeepUndefinable } from 'ts-essentials'
import type { RichTextAdapter, RichTextAdapterProvider } from '../../admin/RichText.js'
import type { ErrorComponent } from '../../admin/forms/Error.js'
import type {
ArrayFieldErrorClientComponent,
ArrayFieldErrorServerComponent,
ArrayFieldLabelClientComponent,
ArrayFieldLabelServerComponent,
ArrayFieldProps,
BlockFieldErrorClientComponent,
BlockFieldErrorServerComponent,
BlockFieldProps,
CheckboxFieldErrorClientComponent,
CheckboxFieldErrorServerComponent,
CheckboxFieldLabelClientComponent,
CheckboxFieldLabelServerComponent,
CheckboxFieldProps,
ClientTab,
CodeFieldErrorClientComponent,
CodeFieldErrorServerComponent,
CodeFieldLabelClientComponent,
CodeFieldLabelServerComponent,
CodeFieldProps,
CollapsibleFieldLabelClientComponent,
CollapsibleFieldLabelServerComponent,
CollapsibleFieldProps,
ConditionalDateProps,
DateFieldErrorClientComponent,
DateFieldErrorServerComponent,
DateFieldLabelClientComponent,
DateFieldLabelServerComponent,
DateFieldProps,
Description,
DescriptionComponent,
LabelComponent,
EmailFieldErrorClientComponent,
EmailFieldErrorServerComponent,
EmailFieldLabelClientComponent,
EmailFieldLabelServerComponent,
EmailFieldProps,
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
GroupFieldLabelClientComponent,
GroupFieldLabelServerComponent,
GroupFieldProps,
HiddenFieldProps,
JSONFieldErrorClientComponent,
JSONFieldErrorServerComponent,
JSONFieldLabelClientComponent,
JSONFieldLabelServerComponent,
JSONFieldProps,
MappedComponent,
NumberFieldErrorClientComponent,
NumberFieldErrorServerComponent,
NumberFieldLabelClientComponent,
NumberFieldLabelServerComponent,
NumberFieldProps,
PointFieldErrorClientComponent,
PointFieldErrorServerComponent,
PointFieldLabelClientComponent,
PointFieldLabelServerComponent,
PointFieldProps,
RadioFieldErrorClientComponent,
RadioFieldErrorServerComponent,
RadioFieldLabelClientComponent,
RadioFieldLabelServerComponent,
RadioFieldProps,
RelationshipFieldErrorClientComponent,
RelationshipFieldErrorServerComponent,
RelationshipFieldLabelClientComponent,
RelationshipFieldLabelServerComponent,
RelationshipFieldProps,
RichTextFieldProps,
RowFieldProps,
RowLabelComponent,
SelectFieldErrorClientComponent,
SelectFieldErrorServerComponent,
SelectFieldLabelClientComponent,
SelectFieldLabelServerComponent,
SelectFieldProps,
StaticDescription,
TabsFieldProps,
TextFieldErrorClientComponent,
TextFieldErrorServerComponent,
TextFieldLabelClientComponent,
TextFieldLabelServerComponent,
TextareaFieldErrorClientComponent,
TextareaFieldErrorServerComponent,
TextareaFieldLabelClientComponent,
TextareaFieldLabelServerComponent,
TextareaFieldProps,
UploadFieldErrorClientComponent,
UploadFieldErrorServerComponent,
UploadFieldLabelClientComponent,
UploadFieldLabelServerComponent,
UploadFieldProps,
} from '../../admin/types.js'
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types.js'
import type {
@@ -148,7 +229,7 @@ type Admin = {
className?: string
components?: {
Cell?: CustomComponent
Description?: DescriptionComponent
Description?: CustomComponent<FieldDescriptionClientComponent | FieldDescriptionServerComponent>
Field?: CustomComponent
/**
* The Filter component has to be a client component
@@ -339,8 +420,8 @@ export type NumberField = {
/** Set this property to a string that will be used for browser autocomplete. */
autoComplete?: string
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<NumberFieldErrorClientComponent | NumberFieldErrorServerComponent>
Label?: CustomComponent<NumberFieldLabelClientComponent | NumberFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -392,8 +473,8 @@ export type TextField = {
admin?: {
autoComplete?: string
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<TextFieldErrorClientComponent | TextFieldErrorServerComponent>
Label?: CustomComponent<TextFieldLabelClientComponent | TextFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -441,8 +522,8 @@ export type EmailField = {
admin?: {
autoComplete?: string
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<EmailFieldErrorClientComponent | EmailFieldErrorServerComponent>
Label?: CustomComponent<EmailFieldLabelClientComponent | EmailFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -468,8 +549,8 @@ export type EmailFieldClient = {
export type TextareaField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<TextareaFieldErrorClientComponent | TextareaFieldErrorServerComponent>
Label?: CustomComponent<TextareaFieldLabelClientComponent | TextareaFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -499,8 +580,8 @@ export type TextareaFieldClient = {
export type CheckboxField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<CheckboxFieldErrorClientComponent | CheckboxFieldErrorServerComponent>
Label?: CustomComponent<CheckboxFieldLabelClientComponent | CheckboxFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -524,8 +605,8 @@ export type CheckboxFieldClient = {
export type DateField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<DateFieldErrorClientComponent | DateFieldErrorServerComponent>
Label?: CustomComponent<DateFieldLabelClientComponent | DateFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -552,7 +633,7 @@ export type DateFieldClient = {
export type GroupField = {
admin?: {
components?: {
Label?: LabelComponent
Label?: CustomComponent<GroupFieldLabelClientComponent | GroupFieldLabelServerComponent>
} & Admin['components']
hideGutter?: boolean
} & Admin
@@ -598,7 +679,9 @@ export type CollapsibleField = {
| {
admin: {
components: {
Label?: LabelComponent
Label?: CustomComponent<
CollapsibleFieldLabelClientComponent | CollapsibleFieldLabelServerComponent
>
RowLabel: RowLabelComponent
} & Admin['components']
initCollapsed?: boolean
@@ -608,7 +691,9 @@ export type CollapsibleField = {
| {
admin?: {
components?: {
Label?: LabelComponent
Label?: CustomComponent<
CollapsibleFieldLabelClientComponent | CollapsibleFieldLabelServerComponent
>
} & Admin['components']
initCollapsed?: boolean
} & Admin
@@ -742,8 +827,8 @@ export type UIFieldClient = {
export type UploadField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<UploadFieldErrorClientComponent | UploadFieldErrorServerComponent>
Label?: CustomComponent<UploadFieldLabelClientComponent | UploadFieldLabelServerComponent>
} & Admin['components']
}
displayPreview?: boolean
@@ -772,8 +857,8 @@ export type UploadFieldClient = {
export type CodeField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<CodeFieldErrorClientComponent | CodeFieldErrorServerComponent>
Label?: CustomComponent<CodeFieldLabelClientComponent | CodeFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -802,8 +887,8 @@ export type CodeFieldClient = {
export type JSONField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<JSONFieldErrorClientComponent | JSONFieldErrorServerComponent>
Label?: CustomComponent<JSONFieldLabelClientComponent | JSONFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -835,8 +920,8 @@ export type JSONFieldClient = {
export type SelectField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<SelectFieldErrorClientComponent | SelectFieldErrorServerComponent>
Label?: CustomComponent<SelectFieldLabelClientComponent | SelectFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -920,8 +1005,12 @@ type SharedRelationshipPropertiesClient = FieldBaseClient &
type RelationshipAdmin = {
allowCreate?: boolean
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<
RelationshipFieldErrorClientComponent | RelationshipFieldErrorServerComponent
>
Label?: CustomComponent<
RelationshipFieldLabelClientComponent | RelationshipFieldLabelServerComponent
>
} & Admin['components']
isSortable?: boolean
} & Admin
@@ -988,8 +1077,8 @@ export type RichTextField<
> = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent
Label?: CustomComponent
} & Admin['components']
} & Admin
editor?:
@@ -1015,6 +1104,7 @@ export type RichTextFieldClient<
Error?: MappedComponent
Label?: MappedComponent
} & AdminClient['components']
placeholder?: Record<string, string> | string
} & AdminClient
richTextComponentMap?: Map<string, any>
} & FieldBaseClient &
@@ -1024,8 +1114,8 @@ export type RichTextFieldClient<
export type ArrayField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<ArrayFieldErrorClientComponent | ArrayFieldErrorServerComponent>
Label?: CustomComponent<ArrayFieldLabelClientComponent | ArrayFieldLabelServerComponent>
RowLabel?: RowLabelComponent
} & Admin['components']
initCollapsed?: boolean
@@ -1070,8 +1160,8 @@ export type ArrayFieldClient = {
export type RadioField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<RadioFieldErrorClientComponent | RadioFieldErrorServerComponent>
Label?: CustomComponent<RadioFieldLabelClientComponent | RadioFieldLabelServerComponent>
} & Admin['components']
layout?: 'horizontal' | 'vertical'
} & Admin
@@ -1157,7 +1247,7 @@ export type ClientBlock = {
export type BlockField = {
admin?: {
components?: {
Error?: ErrorComponent
Error?: CustomComponent<BlockFieldErrorClientComponent | BlockFieldErrorServerComponent>
} & Admin['components']
initCollapsed?: boolean
/**
@@ -1189,8 +1279,8 @@ export type BlockFieldClient = {
export type PointField = {
admin?: {
components?: {
Error?: ErrorComponent
Label?: LabelComponent
Error?: CustomComponent<PointFieldErrorClientComponent | PointFieldErrorServerComponent>
Label?: CustomComponent<PointFieldLabelClientComponent | PointFieldLabelServerComponent>
afterInput?: CustomComponent[]
beforeInput?: CustomComponent[]
} & Admin['components']
@@ -1260,6 +1350,28 @@ export type ClientField =
| UIFieldClient
| UploadFieldClient
export type ClientFieldProps =
| ArrayFieldProps
| BlockFieldProps
| CheckboxFieldProps
| CodeFieldProps
| CollapsibleFieldProps
| DateFieldProps
| EmailFieldProps
| GroupFieldProps
| HiddenFieldProps
| JSONFieldProps
| NumberFieldProps
| PointFieldProps
| RadioFieldProps
| RelationshipFieldProps
| RichTextFieldProps
| RowFieldProps
| SelectFieldProps
| TabsFieldProps
| TextareaFieldProps
| UploadFieldProps
type ExtractFieldTypes<T> = T extends { type: infer U } ? U : never
export type FieldTypes = ExtractFieldTypes<Field>
@@ -1373,9 +1485,9 @@ export type FieldWithManyClient = RelationshipFieldClient | SelectFieldClient
export type FieldWithMaxDepth = RelationshipField | UploadField
export type FieldWithMaxDepthClient = RelationshipFieldClient | UploadFieldClient
export function fieldHasSubFields<T extends ClientField | Field>(
field: T,
): field is T & (T extends ClientField ? FieldWithSubFieldsClient : FieldWithSubFields) {
export function fieldHasSubFields<TField extends ClientField | Field>(
field: TField,
): field is TField & (TField extends ClientField ? FieldWithSubFieldsClient : FieldWithSubFields) {
return (
field.type === 'group' ||
field.type === 'array' ||
@@ -1384,21 +1496,21 @@ export function fieldHasSubFields<T extends ClientField | Field>(
)
}
export function fieldIsArrayType<T extends ClientField | Field>(
field: T,
): field is T & (T extends ClientField ? ArrayFieldClient : ArrayField) {
export function fieldIsArrayType<TField extends ClientField | Field>(
field: TField,
): field is TField & (TField extends ClientField ? ArrayFieldClient : ArrayField) {
return field.type === 'array'
}
export function fieldIsBlockType<T extends ClientField | Field>(
field: T,
): field is T & (T extends ClientField ? BlockFieldClient : BlockField) {
export function fieldIsBlockType<TField extends ClientField | Field>(
field: TField,
): field is TField & (TField extends ClientField ? BlockFieldClient : BlockField) {
return field.type === 'blocks'
}
export function fieldIsGroupType<T extends ClientField | Field>(
field: T,
): field is T & (T extends ClientField ? GroupFieldClient : GroupField) {
export function fieldIsGroupType<TField extends ClientField | Field>(
field: TField,
): field is TField & (TField extends ClientField ? GroupFieldClient : GroupField) {
return field.type === 'group'
}
@@ -1414,40 +1526,44 @@ export function optionIsValue(option: Option): option is string {
return typeof option === 'string'
}
export function fieldSupportsMany<T extends ClientField | Field>(
field: T,
): field is T & (T extends ClientField ? FieldWithManyClient : FieldWithMany) {
export function fieldSupportsMany<TField extends ClientField | Field>(
field: TField,
): field is TField & (TField extends ClientField ? FieldWithManyClient : FieldWithMany) {
return field.type === 'select' || field.type === 'relationship'
}
export function fieldHasMaxDepth<T extends ClientField | Field>(
field: T,
): field is T & (T extends ClientField ? FieldWithMaxDepthClient : FieldWithMaxDepth) {
export function fieldHasMaxDepth<TField extends ClientField | Field>(
field: TField,
): field is TField & (TField extends ClientField ? FieldWithMaxDepthClient : FieldWithMaxDepth) {
return (
(field.type === 'upload' || field.type === 'relationship') && typeof field.maxDepth === 'number'
)
}
export function fieldIsPresentationalOnly<
T extends ClientField | Field | TabAsField | TabAsFieldClient,
>(field: T): field is T & (T extends ClientField | TabAsFieldClient ? UIFieldClient : UIField) {
TField extends ClientField | Field | TabAsField | TabAsFieldClient,
>(
field: TField,
): field is TField & (TField extends ClientField | TabAsFieldClient ? UIFieldClient : UIField) {
return field.type === 'ui'
}
export function fieldIsSidebar<T extends ClientField | Field | TabAsField | TabAsFieldClient>(
field: T,
): field is { admin: { position: 'sidebar' } } & T {
export function fieldIsSidebar<TField extends ClientField | Field | TabAsField | TabAsFieldClient>(
field: TField,
): field is { admin: { position: 'sidebar' } } & TField {
return 'admin' in field && 'position' in field.admin && field.admin.position === 'sidebar'
}
export function fieldAffectsData<T extends ClientField | Field | TabAsField | TabAsFieldClient>(
field: T,
): field is T &
(T extends ClientField | TabAsFieldClient ? FieldAffectingDataClient : FieldAffectingData) {
export function fieldAffectsData<
TField extends ClientField | Field | TabAsField | TabAsFieldClient,
>(
field: TField,
): field is TField &
(TField extends ClientField | TabAsFieldClient ? FieldAffectingDataClient : FieldAffectingData) {
return 'name' in field && !fieldIsPresentationalOnly(field)
}
export function tabHasName<T extends ClientTab | Tab>(tab: T): tab is NamedTab & T {
export function tabHasName<TField extends ClientTab | Tab>(tab: TField): tab is NamedTab & TField {
return 'name' in tab
}

View File

@@ -1,3 +1,4 @@
import type { ClientTab } from '../admin/fields/Tabs.js'
import type { ClientField } from '../fields/config/client.js'
import type {
Field,
@@ -14,13 +15,12 @@ import {
fieldIsPresentationalOnly,
tabHasName,
} from '../fields/config/types.js'
import { ClientTab } from '../admin/fields/Tabs.js'
type FlattenedField<T> = T extends ClientField
type FlattenedField<TField> = TField extends ClientField
? FieldAffectingDataClient | FieldPresentationalOnlyClient
: FieldAffectingData | FieldPresentationalOnly
type TabType<T> = T extends ClientField ? ClientTab : Tab
type TabType<TField> = TField extends ClientField ? ClientTab : Tab
/**
* Flattens a collection's fields into a single array of fields, as long
@@ -29,27 +29,30 @@ type TabType<T> = T extends ClientField ? ClientTab : Tab
* @param fields
* @param keepPresentationalFields if true, will skip flattening fields that are presentational only
*/
function flattenFields<T extends ClientField | Field>(
fields: T[],
function flattenFields<TField extends ClientField | Field>(
fields: TField[],
keepPresentationalFields?: boolean,
): FlattenedField<T>[] {
return fields.reduce<FlattenedField<T>[]>((fieldsToUse, field) => {
): FlattenedField<TField>[] {
return fields.reduce<FlattenedField<TField>[]>((fieldsToUse, field) => {
if (fieldAffectsData(field) || (keepPresentationalFields && fieldIsPresentationalOnly(field))) {
return [...fieldsToUse, field as FlattenedField<T>]
return [...fieldsToUse, field as FlattenedField<TField>]
}
if (fieldHasSubFields(field)) {
return [...fieldsToUse, ...flattenFields(field.fields as T[], keepPresentationalFields)]
return [...fieldsToUse, ...flattenFields(field.fields as TField[], keepPresentationalFields)]
}
if (field.type === 'tabs' && 'tabs' in field) {
return [
...fieldsToUse,
...field.tabs.reduce<FlattenedField<T>[]>((tabFields, tab: TabType<T>) => {
...field.tabs.reduce<FlattenedField<TField>[]>((tabFields, tab: TabType<TField>) => {
if (tabHasName(tab)) {
return [...tabFields, { ...tab, type: 'tab' } as unknown as FlattenedField<T>]
return [...tabFields, { ...tab, type: 'tab' } as unknown as FlattenedField<TField>]
} else {
return [...tabFields, ...flattenFields(tab.fields as T[], keepPresentationalFields)]
return [
...tabFields,
...flattenFields(tab.fields as TField[], keepPresentationalFields),
]
}
}, []),
]

View File

@@ -88,7 +88,7 @@ export const getFields = ({ collection, prefix }: Args): Field[] => {
type: 'group',
fields: [
{
...(existingSizeURLField || {}),
...(existingSizeURLField || ({} as any)),
...baseURLField,
},
],

View File

@@ -108,7 +108,7 @@ export const getFields = ({
fields: [
...(adapter.fields || []),
{
...(existingSizeURLField || {}),
...(existingSizeURLField || ({} as any)),
...baseURLField,
hooks: {
afterRead: [

View File

@@ -24,7 +24,7 @@ import { LengthIndicator } from '../../ui/LengthIndicator.js'
const { maxLength, minLength } = defaults.description
type MetaDescriptionProps = {
hasGenerateDescriptionFn: boolean
readonly hasGenerateDescriptionFn: boolean
} & TextareaFieldProps
export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props) => {
@@ -58,9 +58,16 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
const genDescriptionResponse = await fetch('/api/plugin-seo/generate-description', {
body: JSON.stringify({
...docInfo,
doc: { ...getData() },
id: docInfo.id,
slug: docInfo.slug,
doc: getData(),
docPermissions: docInfo.docPermissions,
hasPublishPermission: docInfo.hasPublishPermission,
hasSavePermission: docInfo.hasSavePermission,
initialData: docInfo.initialData,
initialState: docInfo.initialState,
locale: typeof locale === 'object' ? locale?.code : locale,
title: docInfo.title,
} satisfies Omit<Parameters<GenerateDescription>[0], 'req'>),
credentials: 'include',
headers: {
@@ -87,7 +94,7 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
}}
>
<div className="plugin-seo__field">
<FieldLabel Label={Label} label={label} {...(labelProps || {})} />
<FieldLabel Label={Label} field={null} label={label} {...(labelProps || {})} />
{hasGenerateDescriptionFn && (
<React.Fragment>
&nbsp; &mdash; &nbsp;

View File

@@ -21,7 +21,7 @@ import type { GenerateImage } from '../../types.js'
import { Pill } from '../../ui/Pill.js'
type MetaImageProps = {
hasGenerateImageFn: boolean
readonly hasGenerateImageFn: boolean
} & UploadFieldProps
export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
@@ -54,9 +54,16 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
const genImageResponse = await fetch('/api/plugin-seo/generate-image', {
body: JSON.stringify({
...docInfo,
doc: { ...getData() },
id: docInfo.id,
slug: docInfo.slug,
doc: getData(),
docPermissions: docInfo.docPermissions,
hasPublishPermission: docInfo.hasPublishPermission,
hasSavePermission: docInfo.hasSavePermission,
initialData: docInfo.initialData,
initialState: docInfo.initialState,
locale: typeof locale === 'object' ? locale?.code : locale,
title: docInfo.title,
} satisfies Omit<Parameters<GenerateImage>[0], 'req'>),
credentials: 'include',
headers: {
@@ -91,7 +98,7 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
}}
>
<div className="plugin-seo__field">
<FieldLabel Label={Label} label={label} {...(labelProps || {})} />
<FieldLabel Label={Label} field={null} label={label} {...(labelProps || {})} />
{hasGenerateImageFn && (
<React.Fragment>
&nbsp; &mdash; &nbsp;

View File

@@ -25,7 +25,7 @@ import '../index.scss'
const { maxLength, minLength } = defaults.title
type MetaTitleProps = {
hasGenerateTitleFn: boolean
readonly hasGenerateTitleFn: boolean
} & TextFieldProps
export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
@@ -59,9 +59,16 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
const genTitleResponse = await fetch('/api/plugin-seo/generate-title', {
body: JSON.stringify({
...docInfo,
doc: { ...getData() },
id: docInfo.id,
slug: docInfo.slug,
doc: getData(),
docPermissions: docInfo.docPermissions,
hasPublishPermission: docInfo.hasPublishPermission,
hasSavePermission: docInfo.hasSavePermission,
initialData: docInfo.initialData,
initialState: docInfo.initialState,
locale: typeof locale === 'object' ? locale?.code : locale,
title: docInfo.title,
} satisfies Omit<Parameters<GenerateTitle>[0], 'req'>),
credentials: 'include',
headers: {
@@ -88,7 +95,7 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
}}
>
<div className="plugin-seo__field">
<FieldLabel Label={Label} label={label} {...(labelProps || {})} />
<FieldLabel Label={Label} field={null} label={label} {...(labelProps || {})} />
{hasGenerateTitleFn && (
<React.Fragment>
&nbsp; &mdash; &nbsp;

View File

@@ -15,16 +15,18 @@ import type { PluginSEOTranslationKeys, PluginSEOTranslations } from '../../tran
import type { GenerateURL } from '../../types.js'
type PreviewProps = {
descriptionPath?: string
hasGenerateURLFn: boolean
titlePath?: string
readonly descriptionPath?: string
readonly hasGenerateURLFn: boolean
readonly titlePath?: string
} & UIField
export const PreviewComponent: React.FC<PreviewProps> = ({
descriptionPath: descriptionPathFromContext,
hasGenerateURLFn,
titlePath: titlePathFromContext,
}) => {
export const PreviewComponent: React.FC<PreviewProps> = (props) => {
const {
descriptionPath: descriptionPathFromContext,
hasGenerateURLFn,
titlePath: titlePathFromContext,
} = props
const { t } = useTranslation<PluginSEOTranslations, PluginSEOTranslationKeys>()
const locale = useLocale()
@@ -46,9 +48,15 @@ export const PreviewComponent: React.FC<PreviewProps> = ({
const getHref = async () => {
const genURLResponse = await fetch('/api/plugin-seo/generate-url', {
body: JSON.stringify({
...docInfo,
doc: { ...getData() },
id: docInfo.id,
doc: getData(),
docPermissions: docInfo.docPermissions,
hasPublishPermission: docInfo.hasPublishPermission,
hasSavePermission: docInfo.hasSavePermission,
initialData: docInfo.initialData,
initialState: docInfo.initialState,
locale: typeof locale === 'object' ? locale?.code : locale,
title: docInfo.title,
} satisfies Omit<Parameters<GenerateURL>[0], 'req'>),
credentials: 'include',
headers: {

View File

@@ -1,8 +1,23 @@
import type { DocumentInfoContext } from '@payloadcms/ui'
import type { Field, PayloadRequest, TextField, TextareaField, UploadField } from 'payload'
export type PartialDocumentInfoContext = Pick<
DocumentInfoContext,
| 'docPermissions'
| 'hasPublishPermission'
| 'hasSavePermission'
| 'id'
| 'initialData'
| 'initialState'
| 'preferencesKey'
| 'publishedDoc'
| 'slug'
| 'title'
| 'versionsCount'
>
export type GenerateTitle<T = any> = (
args: { doc: T; locale?: string; req: PayloadRequest } & DocumentInfoContext,
args: { doc: T; locale?: string; req: PayloadRequest } & PartialDocumentInfoContext,
) => Promise<string> | string
export type GenerateDescription<T = any> = (
@@ -10,15 +25,15 @@ export type GenerateDescription<T = any> = (
doc: T
locale?: string
req: PayloadRequest
} & DocumentInfoContext,
} & PartialDocumentInfoContext,
) => Promise<string> | string
export type GenerateImage<T = any> = (
args: { doc: T; locale?: string; req: PayloadRequest } & DocumentInfoContext,
args: { doc: T; locale?: string; req: PayloadRequest } & PartialDocumentInfoContext,
) => Promise<string> | string
export type GenerateURL<T = any> = (
args: { doc: T; locale?: string; req: PayloadRequest } & DocumentInfoContext,
args: { doc: T; locale?: string; req: PayloadRequest } & PartialDocumentInfoContext,
) => Promise<string> | string
export type SEOPluginConfig = {

View File

@@ -18,8 +18,8 @@ import { getEnabledNodes } from '../lexical/nodes/index.js'
export const RichTextCell: React.FC<
{
admin?: LexicalFieldAdminProps
lexicalEditorConfig: LexicalEditorConfig
readonly admin?: LexicalFieldAdminProps
readonly lexicalEditorConfig: LexicalEditorConfig
} & CellComponentProps<RichTextFieldClient>
> = (props) => {
const {

View File

@@ -94,8 +94,20 @@ const RichTextComponent: React.FC<
width,
}}
>
<FieldError CustomError={Error} path={path} {...(errorProps || {})} alignCaret="left" />
<FieldLabel Label={Label} label={label} required={required} {...(labelProps || {})} />
<FieldError
CustomError={Error}
field={field}
path={path}
{...(errorProps || {})}
alignCaret="left"
/>
<FieldLabel
Label={Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${baseClass}__wrap`}>
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
<LexicalProvider
@@ -119,7 +131,7 @@ const RichTextComponent: React.FC<
value={value}
/>
</ErrorBoundary>
<FieldDescription Description={Description} {...(descriptionProps || {})} />
<FieldDescription Description={Description} field={field} {...(descriptionProps || {})} />
</div>
</div>
)

View File

@@ -44,9 +44,11 @@ export const getGenerateComponentMap =
const mappedComponent: MappedComponent = createMappedComponent(
payloadComponent,
{
componentKey,
featureKey: resolvedFeature.key,
key: `${resolvedFeature.key}-${componentKey}`,
clientProps: {
componentKey,
featureKey: resolvedFeature.key,
key: `${resolvedFeature.key}-${componentKey}`,
},
},
undefined,
'lexical-from-resolvedFeature',

View File

@@ -54,6 +54,7 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
descriptionProps,
elements,
errorProps,
field,
field: {
name,
_path: pathFromProps,
@@ -316,9 +317,15 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
width,
}}
>
<FieldLabel Label={Label} label={label} required={required} {...(labelProps || {})} />
<FieldLabel
Label={Label}
label={label}
required={required}
{...(labelProps || {})}
field={field}
/>
<div className={`${baseClass}__wrap`}>
<FieldError CustomError={Error} path={path} {...(errorProps || {})} />
<FieldError CustomError={Error} field={field} path={path} {...(errorProps || {})} />
<Slate
editor={editor}
key={JSON.stringify({ initialValue, path })} // makes sure slate is completely re-rendered when initialValue changes, bypassing the slate-internal value memoization. That way, external changes to the form will update the editor
@@ -449,7 +456,7 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
</div>
</div>
</Slate>
<FieldDescription Description={Description} {...(descriptionProps || {})} />
<FieldDescription Description={Description} field={field} {...(descriptionProps || {})} />
</div>
</div>
)

View File

@@ -175,7 +175,7 @@ export const FieldSelect: React.FC<FieldSelectProps> = ({ fields, setSelected })
return (
<div className={baseClass}>
<FieldLabel label={t('fields:selectFieldsToEdit')} />
<FieldLabel field={null} label={t('fields:selectFieldsToEdit')} />
<ReactSelect
getOptionValue={(option) => {
if (typeof option.value === 'object' && 'path' in option.value) {

View File

@@ -258,7 +258,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
)}
{moreThanOneAvailableCollection && (
<div className={`${baseClass}__select-collection-wrap`}>
<FieldLabel label={t('upload:selectCollectionToBrowse')} />
<FieldLabel field={null} label={t('upload:selectCollectionToBrowse')} />
<ReactSelect
className={`${baseClass}__select-collection`}
onChange={setSelectedOption} // this is only changing the options which is not rerunning my effect

View File

@@ -100,6 +100,7 @@ export const buildColumnState = (args: Args): Column[] => {
const Label = (
<FieldLabel
Label={CustomLabelToRender}
field={field}
label={'label' in field ? (field.label as StaticLabel) : undefined}
unstyled
/>

View File

@@ -222,7 +222,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
return (
<div className={[fieldBaseClass, baseClass].filter(Boolean).join(' ')}>
<FieldError message={errorMessage} showError={showError} />
<FieldError field={null} message={errorMessage} showError={showError} />
{doc.filename && !replacingFile && (
<FileDetails
collectionSlug={collectionSlug}

View File

@@ -1,21 +1,21 @@
'use client'
import type { DescriptionFunction, StaticDescription } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
import { useTranslation } from '../../providers/Translation/index.js'
import './index.scss'
export type DescriptionFunction = () => string
export type ViewDescriptionComponent = React.ComponentType<any>
export type DescriptionComponent = React.ComponentType<any>
type Description = DescriptionComponent | DescriptionFunction | Record<string, string> | string
type Description = DescriptionFunction | StaticDescription | ViewDescriptionComponent | string
export type ViewDescriptionProps = {
description?: Description
readonly description?: Description
}
export function isComponent(description: Description): description is DescriptionComponent {
export function isComponent(description: Description): description is ViewDescriptionComponent {
return React.isValidElement(description)
}
@@ -29,11 +29,7 @@ export const ViewDescription: React.FC<ViewDescriptionProps> = (props) => {
}
if (description) {
return (
<div className="view-description">
{typeof description === 'function' ? description() : getTranslation(description, i18n)}
</div>
)
return <div className="view-description">{getTranslation(description, i18n)}</div>
}
return null

View File

@@ -215,6 +215,7 @@ export const ArrayFieldComponent: React.FC<ArrayFieldProps> = (props) => {
{showError && (
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -226,6 +227,7 @@ export const ArrayFieldComponent: React.FC<ArrayFieldProps> = (props) => {
<FieldLabel
Label={field?.admin?.components?.Label}
as="span"
field={field}
label={label}
required={required}
unstyled
@@ -262,6 +264,7 @@ export const ArrayFieldComponent: React.FC<ArrayFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</header>

View File

@@ -56,6 +56,7 @@ const BlocksFieldComponent: React.FC<BlockFieldProps> = (props) => {
readOnly: readOnlyFromTopLevelProps,
validate,
} = props
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
const { indexPath, readOnly: readOnlyFromContext } = useFieldProps()
@@ -217,6 +218,7 @@ const BlocksFieldComponent: React.FC<BlockFieldProps> = (props) => {
{showError && (
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -228,6 +230,7 @@ const BlocksFieldComponent: React.FC<BlockFieldProps> = (props) => {
<FieldLabel
Label={field?.admin?.components?.Description}
as="span"
field={field}
label={label}
required={required}
unstyled
@@ -264,6 +267,7 @@ const BlocksFieldComponent: React.FC<BlockFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</header>

View File

@@ -1,5 +1,11 @@
'use client'
import type { LabelProps, MappedComponent, SanitizedLabelProps } from 'payload'
import type {
CheckboxFieldClient,
FieldLabelClientProps,
MappedComponent,
StaticLabel,
} from 'payload'
import type { MarkOptional } from 'ts-essentials'
import React from 'react'
@@ -14,10 +20,11 @@ export type CheckboxInputProps = {
readonly beforeInput?: MappedComponent[]
readonly checked?: boolean
readonly className?: string
readonly field?: MarkOptional<CheckboxFieldClient, 'type'>
readonly id?: string
readonly inputRef?: React.RefObject<HTMLInputElement | null>
readonly label?: LabelProps<'checkbox'>['label']
readonly labelProps?: SanitizedLabelProps
readonly label?: StaticLabel
readonly labelProps?: FieldLabelClientProps<MarkOptional<CheckboxFieldClient, 'type'>>
readonly name?: string
readonly onToggle: (event: React.ChangeEvent<HTMLInputElement>) => void
readonly partialChecked?: boolean
@@ -35,6 +42,7 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = ({
beforeInput,
checked,
className,
field,
inputRef,
label,
labelProps,
@@ -79,6 +87,7 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = ({
</div>
<FieldLabel
Label={Label}
field={field}
htmlFor={id}
label={label}
required={required}

View File

@@ -103,6 +103,7 @@ const CheckboxFieldComponent: React.FC<CheckboxFieldProps> = (props) => {
>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
alignCaret="left"
@@ -125,6 +126,7 @@ const CheckboxFieldComponent: React.FC<CheckboxFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -85,6 +85,7 @@ const CodeFieldComponent: React.FC<CodeFieldProps> = (props) => {
>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
@@ -92,6 +93,7 @@ const CodeFieldComponent: React.FC<CodeFieldProps> = (props) => {
<div className={`${fieldBaseClass}__wrap`}>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -108,6 +110,7 @@ const CodeFieldComponent: React.FC<CodeFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -163,6 +163,7 @@ const CollapsibleFieldComponent: React.FC<CollapsibleFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -38,12 +38,13 @@ export const ConfirmPasswordField: React.FC<ConfirmPasswordFieldProps> = (props)
.join(' ')}
>
<FieldLabel
field={null}
htmlFor="field-confirm-password"
label={t('authentication:confirmPassword')}
required
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError path={path} />
<FieldError field={null} path={path} />
{/* disable eslint here because the label is dynamic */}
{}
<input

View File

@@ -83,6 +83,7 @@ const DateTimeFieldComponent: React.FC<DateFieldProps> = (props) => {
>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
@@ -90,6 +91,7 @@ const DateTimeFieldComponent: React.FC<DateFieldProps> = (props) => {
<div className={`${fieldBaseClass}__wrap`} id={`field-${path.replace(/\./g, '__')}`}>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -108,6 +110,7 @@ const DateTimeFieldComponent: React.FC<DateFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -20,7 +20,6 @@ const EmailFieldComponent: React.FC<EmailFieldProps> = (props) => {
autoComplete,
descriptionProps,
errorProps,
field,
field: {
name,
_path: pathFromProps,
@@ -36,10 +35,12 @@ const EmailFieldComponent: React.FC<EmailFieldProps> = (props) => {
label,
required,
} = {} as EmailFieldProps['field'],
field,
labelProps,
readOnly: readOnlyFromTopLevelProps,
validate,
} = props
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
const { i18n } = useTranslation()
@@ -74,6 +75,7 @@ const EmailFieldComponent: React.FC<EmailFieldProps> = (props) => {
>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
@@ -81,6 +83,7 @@ const EmailFieldComponent: React.FC<EmailFieldProps> = (props) => {
<div className={`${fieldBaseClass}__wrap`}>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -103,6 +106,7 @@ const EmailFieldComponent: React.FC<EmailFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -1,5 +1,5 @@
'use client'
import type { GenericDescriptionProps } from 'payload'
import type { FieldDescriptionClientComponent, GenericDescriptionProps } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
@@ -38,7 +38,7 @@ const DefaultFieldDescription: React.FC<GenericDescriptionProps> = (props) => {
return null
}
export const FieldDescription: React.FC<GenericDescriptionProps> = (props) => {
export const FieldDescription: FieldDescriptionClientComponent = (props) => {
const { Description, ...rest } = props
if (Description) {

View File

@@ -1,6 +1,6 @@
'use client'
import type { GenericErrorProps } from 'payload'
import type { FieldErrorClientComponent, GenericErrorProps } from 'payload'
import React from 'react'
@@ -42,7 +42,7 @@ const DefaultFieldError: React.FC<GenericErrorProps> = (props) => {
return null
}
export const FieldError: React.FC<GenericErrorProps> = (props) => {
export const FieldError: FieldErrorClientComponent = (props) => {
const { CustomError, ...rest } = props
if (CustomError) {

View File

@@ -1,6 +1,6 @@
'use client'
import type { GenericLabelProps } from 'payload'
import type { FieldLabelClientComponent, GenericLabelProps } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
@@ -41,7 +41,7 @@ const DefaultFieldLabel: React.FC<GenericLabelProps> = (props) => {
return null
}
export const FieldLabel: React.FC<GenericLabelProps> = (props) => {
export const FieldLabel: FieldLabelClientComponent = (props) => {
const { Label, ...rest } = props
if (Label) {

View File

@@ -96,6 +96,7 @@ export const GroupFieldComponent: React.FC<GroupFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</header>

View File

@@ -128,6 +128,7 @@ const JSONFieldComponent: React.FC<JSONFieldProps> = (props) => {
>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
@@ -135,6 +136,7 @@ const JSONFieldComponent: React.FC<JSONFieldProps> = (props) => {
<div className={`${fieldBaseClass}__wrap`}>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -152,6 +154,7 @@ const JSONFieldComponent: React.FC<JSONFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -149,6 +149,7 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
@@ -156,6 +157,7 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
<div className={`${fieldBaseClass}__wrap`}>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -210,6 +212,7 @@ const NumberFieldComponent: React.FC<NumberFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -23,6 +23,7 @@ export const PasswordInput: React.FC<PasswordInputProps> = (props) => {
beforeInput,
className,
errorProps,
field,
inputRef,
label,
labelProps,
@@ -59,13 +60,14 @@ export const PasswordInput: React.FC<PasswordInputProps> = (props) => {
>
<FieldLabel
Label={Label}
field={field}
htmlFor={`field-${path.replace(/\./g, '__')}`}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={Error} path={path} {...(errorProps || {})} />
<FieldError CustomError={Error} field={field} path={path} {...(errorProps || {})} />
<div>
<RenderComponent mappedComponent={beforeInput} />
<input

View File

@@ -1,8 +1,9 @@
import type {
ErrorProps,
FieldBaseClient,
FieldDescriptionClientProps,
FieldErrorClientProps,
FieldLabelClientProps,
FormFieldBase,
LabelProps,
MappedComponent,
PasswordFieldValidation,
StaticDescription,
@@ -14,8 +15,11 @@ import type { MarkOptional } from 'ts-essentials'
export type PasswordFieldProps = {
readonly autoComplete?: string
readonly descriptionProps?: FieldDescriptionClientProps<MarkOptional<TextFieldClient, 'type'>>
readonly errorProps?: FieldErrorClientProps<MarkOptional<TextFieldClient, 'type'>>
readonly field: MarkOptional<TextFieldClient, 'type'>
readonly inputRef?: React.RefObject<HTMLInputElement>
readonly labelProps?: FieldLabelClientProps<MarkOptional<TextFieldClient, 'type'>>
readonly validate?: PasswordFieldValidation
} & FormFieldBase
@@ -28,10 +32,11 @@ export type PasswordInputProps = {
readonly beforeInput?: MappedComponent[]
readonly className?: string
readonly description?: StaticDescription
readonly errorProps: ErrorProps
readonly errorProps: FieldErrorClientProps<MarkOptional<TextFieldClient, 'type'>>
readonly field?: MarkOptional<TextFieldClient, 'type'>
readonly inputRef?: React.RefObject<HTMLInputElement>
readonly label: FieldBaseClient['label']
readonly labelProps: LabelProps
readonly labelProps: FieldLabelClientProps<MarkOptional<TextFieldClient, 'type'>>
readonly onChange?: (e: ChangeEvent<HTMLInputElement>) => void
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly path: string

View File

@@ -133,11 +133,13 @@ export const PointFieldComponent: React.FC<PointFieldProps> = (props) => {
<li>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
{...getCoordinateFieldLabel('latitude')}
/>
<div className="input-wrapper">
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -161,6 +163,7 @@ export const PointFieldComponent: React.FC<PointFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -93,12 +93,14 @@ const RadioGroupFieldComponent: React.FC<RadioFieldProps> = (props) => {
>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
alignCaret="left"
/>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
@@ -144,6 +146,7 @@ const RadioGroupFieldComponent: React.FC<RadioFieldProps> = (props) => {
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -489,6 +489,7 @@ const RelationshipFieldComponent: React.FC<RelationshipFieldProps> = (props) =>
>
<FieldLabel
Label={field?.admin?.components?.Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
@@ -496,6 +497,7 @@ const RelationshipFieldComponent: React.FC<RelationshipFieldProps> = (props) =>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError
CustomError={field?.admin?.components?.Error}
field={field}
path={path}
{...(errorProps || {})}
/>
@@ -606,6 +608,7 @@ const RelationshipFieldComponent: React.FC<RelationshipFieldProps> = (props) =>
<FieldDescription
Description={field?.admin?.components?.Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -1,5 +1,12 @@
'use client'
import type { MappedComponent, OptionObject, StaticDescription, StaticLabel } from 'payload'
import type {
MappedComponent,
OptionObject,
SelectFieldClient,
StaticDescription,
StaticLabel,
} from 'payload'
import type { MarkOptional } from 'ts-essentials'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
@@ -25,6 +32,7 @@ export type SelectInputProps = {
readonly description?: StaticDescription
readonly descriptionProps?: Record<string, unknown>
readonly errorProps?: Record<string, unknown>
readonly field?: MarkOptional<SelectFieldClient, 'type'>
readonly hasMany?: boolean
readonly isClearable?: boolean
readonly isSortable?: boolean
@@ -53,6 +61,7 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
description,
descriptionProps,
errorProps,
field,
hasMany = false,
isClearable = true,
isSortable = true,
@@ -106,9 +115,15 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
width,
}}
>
<FieldLabel Label={Label} label={label} required={required} {...(labelProps || {})} />
<FieldLabel
Label={Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={Error} path={path} {...(errorProps || {})} />
<FieldError CustomError={Error} field={field} path={path} {...(errorProps || {})} />
<RenderComponent mappedComponent={beforeInput} />
<ReactSelect
disabled={readOnly}
@@ -128,6 +143,7 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
<FieldDescription
Description={Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -101,6 +101,7 @@ const SelectFieldComponent: React.FC<SelectFieldProps> = (props) => {
beforeInput={field?.admin?.components?.beforeInput}
className={className}
description={description}
field={field}
hasMany={hasMany}
isClearable={isClearable}
isSortable={isSortable}

View File

@@ -154,7 +154,7 @@ const TabsFieldComponent: React.FC<TabsFieldProps> = (props) => {
.filter(Boolean)
.join(' ')}
>
<FieldDescription Description={field?.admin?.components?.Description} />
<FieldDescription Description={field?.admin?.components?.Description} field={field} />
<RenderFields
fields={activeTabConfig.fields}
forceRender={forceRender}

View File

@@ -26,6 +26,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
description,
descriptionProps,
errorProps,
field,
hasMany,
inputRef,
label,
@@ -64,9 +65,15 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
width,
}}
>
<FieldLabel Label={Label} label={label} required={required} {...(labelProps || {})} />
<FieldLabel
Label={Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={Error} path={path} {...(errorProps || {})} />
<FieldError CustomError={Error} field={field} path={path} {...(errorProps || {})} />
<RenderComponent mappedComponent={beforeInput} />
{hasMany ? (
<ReactSelect
@@ -111,6 +118,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
<FieldDescription
Description={Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -127,6 +127,7 @@ const TextFieldComponent: React.FC<TextFieldProps> = (props) => {
beforeInput={field?.admin?.components?.beforeInput}
className={className}
description={description}
field={field}
hasMany={hasMany}
inputRef={inputRef}
label={label}

View File

@@ -1,6 +1,7 @@
import type { MappedComponent, StaticDescription, StaticLabel } from 'payload'
import type { MappedComponent, StaticDescription, StaticLabel, TextFieldClient } from 'payload'
import type { ChangeEvent } from 'react'
import type React from 'react'
import type { MarkOptional } from 'ts-essentials'
import type { Option, ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js'
@@ -24,6 +25,7 @@ export type TextInputProps = {
readonly description?: StaticDescription
readonly descriptionProps?: Record<string, unknown>
readonly errorProps?: Record<string, unknown>
readonly field?: MarkOptional<TextFieldClient, 'type'>
readonly inputRef?: React.RefObject<HTMLInputElement>
readonly label: StaticLabel
readonly labelProps?: Record<string, unknown>

View File

@@ -23,6 +23,7 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
description,
descriptionProps,
errorProps,
field,
label,
labelProps,
onChange,
@@ -56,9 +57,15 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
width,
}}
>
<FieldLabel Label={Label} label={label} required={required} {...(labelProps || {})} />
<FieldLabel
Label={Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={Error} path={path} {...(errorProps || {})} />
<FieldError CustomError={Error} field={field} path={path} {...(errorProps || {})} />
<RenderComponent mappedComponent={beforeInput} />
<label className="textarea-outer" htmlFor={`field-${path.replace(/\./g, '__')}`}>
<div className="textarea-inner">
@@ -80,6 +87,7 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
<FieldDescription
Description={Description}
description={description}
field={field}
{...(descriptionProps || {})}
/>
</div>

View File

@@ -19,6 +19,8 @@ export { TextAreaInputProps, TextareaInput }
const TextareaFieldComponent: React.FC<TextareaFieldProps> = (props) => {
const {
descriptionProps,
errorProps,
field,
field: {
name,
@@ -39,10 +41,12 @@ const TextareaFieldComponent: React.FC<TextareaFieldProps> = (props) => {
minLength,
required,
},
labelProps,
locale,
readOnly: readOnlyFromTopLevelProps,
validate,
} = props
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
const { i18n } = useTranslation()
@@ -84,7 +88,10 @@ const TextareaFieldComponent: React.FC<TextareaFieldProps> = (props) => {
beforeInput={field?.admin?.components?.beforeInput}
className={className}
description={description}
descriptionProps={descriptionProps}
errorProps={errorProps}
label={label}
labelProps={labelProps}
onChange={(e) => {
setValue(e.target.value)
}}

View File

@@ -1,5 +1,14 @@
import type { MappedComponent, StaticDescription, StaticLabel } from 'payload'
import type {
FieldDescriptionClientProps,
FieldErrorClientProps,
FieldLabelClientProps,
MappedComponent,
StaticDescription,
StaticLabel,
TextareaFieldClient,
} from 'payload'
import type React from 'react'
import type { MarkOptional } from 'ts-essentials'
import { type ChangeEvent } from 'react'
@@ -11,11 +20,12 @@ export type TextAreaInputProps = {
readonly beforeInput?: MappedComponent[]
readonly className?: string
readonly description?: StaticDescription
readonly descriptionProps?: Record<string, unknown>
readonly errorProps?: Record<string, unknown>
readonly descriptionProps?: FieldDescriptionClientProps<MarkOptional<TextareaFieldClient, 'type'>>
readonly errorProps?: FieldErrorClientProps<MarkOptional<TextareaFieldClient, 'type'>>
readonly field?: MarkOptional<TextareaFieldClient, 'type'>
readonly inputRef?: React.RefObject<HTMLInputElement>
readonly label: StaticLabel
readonly labelProps?: Record<string, unknown>
readonly labelProps?: FieldLabelClientProps<MarkOptional<TextareaFieldClient, 'type'>>
readonly onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
readonly path: string

View File

@@ -2,12 +2,17 @@
import type {
ClientCollectionConfig,
FieldDescriptionClientProps,
FieldErrorClientProps,
FieldLabelClientProps,
FilterOptionsResult,
MappedComponent,
StaticDescription,
StaticLabel,
UploadField,
UploadFieldClient,
} from 'payload'
import type { MarkOptional } from 'ts-essentials'
import { getTranslation } from '@payloadcms/translations'
import React, { useCallback, useEffect, useState } from 'react'
@@ -41,11 +46,12 @@ export type UploadInputProps = {
readonly collection?: ClientCollectionConfig
readonly customUploadActions?: React.ReactNode[]
readonly description?: StaticDescription
readonly descriptionProps?: Record<string, unknown>
readonly errorProps?: Record<string, unknown>
readonly descriptionProps?: FieldDescriptionClientProps<MarkOptional<UploadFieldClient, 'type'>>
readonly errorProps?: FieldErrorClientProps<MarkOptional<UploadFieldClient, 'type'>>
readonly field?: MarkOptional<UploadFieldClient, 'type'>
readonly filterOptions?: FilterOptionsResult
readonly label: StaticLabel
readonly labelProps?: Record<string, unknown>
readonly labelProps?: FieldLabelClientProps<MarkOptional<UploadFieldClient, 'type'>>
readonly onChange?: (e) => void
readonly readOnly?: boolean
readonly relationTo?: UploadField['relationTo']
@@ -69,6 +75,7 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
customUploadActions,
descriptionProps,
errorProps,
field,
filterOptions,
label,
labelProps,
@@ -159,10 +166,15 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
width,
}}
>
<FieldLabel Label={Label} label={label} required={required} {...(labelProps || {})} />
<FieldLabel
Label={Label}
field={field}
label={label}
required={required}
{...(labelProps || {})}
/>
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={Error} {...(errorProps || {})} />
<FieldError CustomError={Error} field={field} {...(errorProps || {})} />
{collection?.upload && (
<React.Fragment>
{fileDoc && !missingFile && (
@@ -203,7 +215,11 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
</div>
</div>
)}
<FieldDescription Description={Description} {...(descriptionProps || {})} />
<FieldDescription
Description={Description}
field={field}
{...(descriptionProps || {})}
/>
</React.Fragment>
)}
{!readOnly && <DocumentDrawer onSave={onSave} />}

View File

@@ -19,6 +19,8 @@ export type { UploadInputProps }
const UploadComponent: React.FC<UploadFieldProps> = (props) => {
const {
descriptionProps,
errorProps,
field,
field: {
_path: pathFromProps,
@@ -27,9 +29,11 @@ const UploadComponent: React.FC<UploadFieldProps> = (props) => {
relationTo,
required,
},
labelProps,
readOnly: readOnlyFromTopLevelProps,
validate,
} = props
const readOnlyFromProps = readOnlyFromTopLevelProps || readOnlyFromAdmin
const {
@@ -92,8 +96,11 @@ const UploadComponent: React.FC<UploadFieldProps> = (props) => {
api={apiRoute}
className={className}
collection={collection}
descriptionProps={descriptionProps}
errorProps={errorProps}
filterOptions={filterOptions}
label={label}
labelProps={labelProps}
onChange={onChange}
readOnly={disabled}
relationTo={relationTo}

View File

@@ -1,11 +1,11 @@
import type { I18nClient } from '@payloadcms/translations'
import type { LabelProps, MappedComponent } from 'payload'
import type { MappedComponent, StaticLabel } from 'payload'
export type RowLabelProps = {
readonly RowLabel?: MappedComponent
readonly className?: string
readonly i18n: I18nClient
readonly path: string
readonly rowLabel?: LabelProps['label']
readonly rowLabel?: StaticLabel
readonly rowNumber?: number
}

View File

@@ -210,6 +210,7 @@ export const createClientCollectionConfig = ({
}
let description = undefined
if (collection.admin?.description) {
if (
typeof collection.admin?.description === 'string' ||
@@ -220,13 +221,16 @@ export const createClientCollectionConfig = ({
description = collection.admin?.description({ t: i18n.t })
}
}
clientCollection.admin.description = description
if (collection.admin.components.edit?.Description) {
clientCollection.admin.components.edit.Description = createMappedComponent(
collection.admin.components.edit.Description,
{
description,
clientProps: {
description,
},
},
undefined,
'collection.admin.components.edit.Description',
@@ -261,7 +265,9 @@ export const createClientCollectionConfig = ({
? collection.admin.components.views.edit.default.Component
: null,
{
collectionSlug: collection.slug,
clientProps: {
collectionSlug: collection.slug,
},
},
DefaultEditView,
'collection.admin.components.views.edit.default',
@@ -280,7 +286,9 @@ export const createClientCollectionConfig = ({
clientCollection.admin.components.views.edit[key].Component = createMappedComponent(
view.Component,
{
collectionSlug: collection.slug,
clientProps: {
collectionSlug: collection.slug,
},
},
undefined,
'collection.admin.components.views.edit.key.Component',
@@ -318,7 +326,9 @@ export const createClientCollectionConfig = ({
? collection.admin.components.views.list.Component
: null,
{
collectionSlug: collection.slug,
clientProps: {
collectionSlug: collection.slug,
},
},
DefaultListView,
'collection.admin.components.views.list',

View File

@@ -7,8 +7,9 @@ import type {
ClientField,
CreateMappedComponent,
Field,
FieldLabelClientComponent,
FieldLabelServerComponent,
ImportMap,
LabelComponent,
LabelsClient,
MappedComponent,
Payload,
@@ -106,12 +107,14 @@ export const createClientField = ({
clientField.label = incomingField.label({ t: i18n.t })
}
const CustomLabel: LabelComponent | RowLabelComponent =
const CustomLabel: FieldLabelClientComponent | FieldLabelServerComponent | RowLabelComponent =
'admin' in incomingField &&
'components' in incomingField.admin &&
'Label' in incomingField.admin.components &&
incomingField.admin.components.Label
const serverProps = { serverProps: { field: incomingField } }
switch (incomingField.type) {
case 'array':
case 'group':
@@ -133,7 +136,7 @@ export const createClientField = ({
if (incomingField?.admin?.components && 'RowLabel' in incomingField.admin.components) {
;(field as unknown as ArrayFieldClient).admin.components.RowLabel = createMappedComponent(
incomingField.admin.components.RowLabel,
undefined,
serverProps,
undefined,
'incomingField.admin.components.RowLabel',
)
@@ -178,7 +181,7 @@ export const createClientField = ({
if (block.admin?.components?.Label) {
clientBlock.admin.components.Label = createMappedComponent(
block.admin.components.Label,
undefined,
serverProps,
undefined,
'block.admin.components.Label',
)
@@ -202,36 +205,40 @@ export const createClientField = ({
}
case 'richText': {
const field = clientField as RichTextFieldClient
const field = clientField
if (!incomingField?.editor) {
throw new MissingEditorProp(incomingField) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
}
if (typeof incomingField?.editor === 'function') {
throw new Error('Attempted to access unsanitized rich text editor.')
}
if (!field.admin) {
field.admin = {}
}
if (!field.admin.components) {
field.admin.components = {}
}
field.admin.components.Field = createMappedComponent(
incomingField.editor.FieldComponent,
undefined,
serverProps,
undefined,
'incomingField.editor.FieldComponent',
)
field.admin.components.Cell = createMappedComponent(
incomingField.editor.CellComponent,
undefined,
serverProps,
undefined,
'incomingField.editor.CellComponent',
)
if (incomingField.editor.generateComponentMap) {
const { Component: generateComponentMap, serverProps } = getComponent({
const { Component: generateComponentMap, serverProps: richTextServerProps } = getComponent({
identifier: 'richText-generateComponentMap',
importMap,
payloadComponent: incomingField.editor.generateComponentMap,
@@ -241,10 +248,10 @@ export const createClientField = ({
if (generateComponentMap) {
const actualGenerateComponentMap: RichTextGenerateComponentMap = (
generateComponentMap as any
)(serverProps)
)(richTextServerProps)
const result = actualGenerateComponentMap({
clientField: field,
clientField: field as RichTextFieldClient,
createMappedComponent,
field: incomingField,
i18n,
@@ -253,7 +260,7 @@ export const createClientField = ({
schemaPath: field._schemaPath,
})
field.richTextComponentMap = result
;(field as RichTextFieldClient).richTextComponentMap = result
}
}
break
@@ -349,7 +356,7 @@ export const createClientField = ({
if (incomingField?.admin?.components?.Cell !== undefined) {
clientField.admin.components.Cell = createMappedComponent(
incomingField.admin.components.Cell,
undefined,
serverProps,
undefined,
'incomingField.admin.components.Cell',
)
@@ -367,7 +374,7 @@ export const createClientField = ({
;(clientField as FieldWithDescriptionComponent).admin.components.Description =
createMappedComponent(
incomingField.admin.components.Description,
undefined,
serverProps,
undefined,
'incomingField.admin.components.Description',
)
@@ -387,7 +394,7 @@ export const createClientField = ({
) {
;(clientField as FieldWithErrorComponent).admin.components.Error = createMappedComponent(
incomingField.admin.components.Error,
undefined,
serverProps,
undefined,
'incomingField.admin.components.Error',
)
@@ -396,7 +403,7 @@ export const createClientField = ({
if (incomingField?.admin?.components?.Field !== undefined) {
clientField.admin.components.Field = createMappedComponent(
incomingField.admin.components.Field,
undefined,
serverProps,
undefined,
'incomingField.admin.components.Field',
)
@@ -409,7 +416,7 @@ export const createClientField = ({
) {
clientField.admin.components.Filter = createMappedComponent(
incomingField.admin.components.Filter,
undefined,
serverProps,
undefined,
'incomingField.admin.components.Filter',
)
@@ -429,7 +436,7 @@ export const createClientField = ({
) {
;(clientField as FieldWithLabelComponent).admin.components.Label = createMappedComponent(
CustomLabel,
undefined,
serverProps,
undefined,
'incomingField.admin.components.Label',
)
@@ -450,7 +457,7 @@ export const createClientField = ({
;(clientField as FieldWithBeforeInputComponent).admin.components.beforeInput =
createMappedComponent(
incomingField.admin?.components?.beforeInput,
undefined,
serverProps,
undefined,
'incomingField.admin.components.beforeInput',
)
@@ -471,7 +478,7 @@ export const createClientField = ({
;(clientField as FieldWithAfterInputComponent).admin.components.afterInput =
createMappedComponent(
incomingField.admin?.components?.afterInput,
undefined,
serverProps,
undefined,
'incomingField.admin.components.afterInput',
)
@@ -512,10 +519,12 @@ export const createClientFields = ({
parentPath,
payload,
})
if (newField) {
newClientFields.push({ ...newField })
}
}
const hasID = newClientFields.findIndex((f) => fieldAffectsData(f) && f.name === 'id') > -1
if (!disableAddingID && !hasID) {

View File

@@ -27,10 +27,15 @@ export function getCreateMappedComponent({
const createSingleMappedComponent = (
payloadComponent: { ReactComponent: React.FC<any> } | PayloadComponent,
key: number | string,
props: JsonObject,
props: {
clientProps: JsonObject
serverProps: object
},
Fallback: React.FC<any>,
identifier: string,
): MappedComponent => {
const { clientProps, serverProps: componentServerProps } = props || {}
if (payloadComponent === undefined || payloadComponent === null) {
if (!Fallback) {
return undefined
@@ -40,7 +45,9 @@ export function getCreateMappedComponent({
return {
type: 'server',
Component: null,
RenderedComponent: <Fallback key={key} {...serverProps} {...props} />,
RenderedComponent: (
<Fallback key={key} {...serverProps} {...componentServerProps} {...clientProps} />
),
}
} else {
const toReturn: MappedComponent = {
@@ -49,7 +56,7 @@ export function getCreateMappedComponent({
}
// conditionally set props here to avoid bloating the HTML with `$undefined` props
if (props) toReturn.props = props
if (clientProps) toReturn.props = clientProps
return toReturn
}
@@ -88,7 +95,13 @@ export function getCreateMappedComponent({
type: 'server',
Component: null,
RenderedComponent: (
<Component key={key} {...serverProps} {...resolvedComponent.serverProps} {...props} />
<Component
key={key}
{...serverProps}
{...resolvedComponent.serverProps}
{...componentServerProps}
{...clientProps}
/>
),
}
} else {
@@ -101,7 +114,7 @@ export function getCreateMappedComponent({
Component: resolvedComponent.Component,
props: {
...(resolvedComponent.clientProps || {}),
...props,
...clientProps,
},
}
}

View File

@@ -142,7 +142,9 @@ export const createClientGlobalConfig = ({
? global.admin.components.views.edit.default.Component
: null,
{
globalSlug: global.slug,
clientProps: {
globalSlug: global.slug,
},
},
DefaultEditView,
'global.admin.components.views.edit.default',
@@ -160,7 +162,9 @@ export const createClientGlobalConfig = ({
clientGlobal.admin.components.views.edit[key].Component = createMappedComponent(
view.Component,
{
globalSlug: global.slug,
clientProps: {
globalSlug: global.slug,
},
},
undefined,
'global.admin.components.views.edit.key.Component',

View File

@@ -1,12 +1,10 @@
'use client'
import type { DescriptionComponent, PayloadClientReactComponent } from 'payload'
import type { FieldDescriptionClientComponent } from 'payload'
import { useFieldProps, useFormFields } from '@payloadcms/ui'
import React from 'react'
export const FieldDescriptionComponent: PayloadClientReactComponent<
DescriptionComponent<'text'>
> = () => {
export const FieldDescriptionComponent: FieldDescriptionClientComponent = () => {
const { path } = useFieldProps()
const field = useFormFields(([fields]) => (fields && fields?.[path]) || null)
const { value } = field || {}

View File

@@ -1,9 +0,0 @@
import type { TextFieldDescriptionComponent } from 'payload'
import React from 'react'
export const CustomDescription: TextFieldDescriptionComponent = (props) => {
return (
<div id="custom-field-description">{`Description: the max length of this field is: ${props?.maxLength}`}</div>
)
}

View File

@@ -0,0 +1,10 @@
'use client'
import type { TextFieldDescriptionClientComponent } from 'payload'
import React from 'react'
export const CustomClientDescription: TextFieldDescriptionClientComponent = (props) => {
return (
<div id="custom-client-field-description">{`Description: the max length of this field is: ${props?.field?.maxLength}`}</div>
)
}

View File

@@ -0,0 +1,9 @@
import type { TextFieldDescriptionServerComponent } from 'payload'
import React from 'react'
export const CustomServerDescription: TextFieldDescriptionServerComponent = (props) => {
return (
<div id="custom-server-field-description">{`Description: the max length of this field is: ${props?.field?.maxLength}`}</div>
)
}

View File

@@ -1,9 +0,0 @@
import type { TextFieldLabelComponent } from 'payload'
import React from 'react'
export const CustomLabel: TextFieldLabelComponent = (props) => {
return (
<div id="custom-field-label">{`Label: the max length of this field is: ${props?.maxLength}`}</div>
)
}

View File

@@ -0,0 +1,10 @@
'use client'
import type { TextFieldLabelClientComponent } from 'payload'
import React from 'react'
export const CustomClientLabel: TextFieldLabelClientComponent = (props) => {
return (
<div id="custom-client-field-label">{`Label: the max length of this field is: ${props?.field?.maxLength}`}</div>
)
}

View File

@@ -0,0 +1,9 @@
import type { TextFieldLabelServerComponent } from 'payload'
import React from 'react'
export const CustomServerLabel: TextFieldLabelServerComponent = (props) => {
return (
<div id="custom-server-field-label">{`Label: the max length of this field is: ${props?.field?.maxLength}`}</div>
)
}

View File

@@ -6,7 +6,7 @@ export const CustomFields: CollectionConfig = {
slug: customFieldsSlug,
fields: [
{
name: 'customTextField',
name: 'customTextServerField',
type: 'text',
maxLength: 100,
admin: {
@@ -14,8 +14,26 @@ export const CustomFields: CollectionConfig = {
components: {
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
Label: '/collections/CustomFields/fields/Text/Label.js#CustomLabel',
Description: '/collections/CustomFields/fields/Text/Description.js#CustomDescription',
Label: '/collections/CustomFields/fields/Text/LabelServer.js#CustomServerLabel',
Description:
'/collections/CustomFields/fields/Text/DescriptionServer.js#CustomServerDescription',
Error: '/collections/CustomFields/CustomError.js#CustomError',
},
},
minLength: 3,
},
{
name: 'customTextClientField',
type: 'text',
maxLength: 100,
admin: {
placeholder: 'This is a placeholder',
components: {
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
Label: '/collections/CustomFields/fields/Text/LabelClient.js#CustomClientLabel',
Description:
'/collections/CustomFields/fields/Text/DescriptionClient.js#CustomClientDescription',
Error: '/collections/CustomFields/CustomError.js#CustomError',
},
},

View File

@@ -555,25 +555,47 @@ describe('admin1', () => {
test('renders custom label component', async () => {
await page.goto(customFieldsURL.create)
await page.waitForURL(customFieldsURL.create)
await expect(page.locator('#custom-field-label')).toBeVisible()
await expect(page.locator('#custom-client-field-label')).toBeVisible()
await expect(page.locator('#custom-server-field-label')).toBeVisible()
})
test('renders custom description component', async () => {
await page.goto(customFieldsURL.create)
await page.waitForURL(customFieldsURL.create)
await expect(page.locator('#custom-field-description')).toBeVisible()
await expect(page.locator('#custom-client-field-description')).toBeVisible()
await expect(page.locator('#custom-server-field-description')).toBeVisible()
})
// test('ensure custom components receive field props', async () => {
// await page.goto(customFieldsURL.create)
// await page.waitForURL(customFieldsURL.create)
// await expect(page.locator('#custom-field-label')).toContainText(
// 'The max length of this field is: 100',
// )
// await expect(page.locator('#custom-field-description')).toContainText(
// 'The max length of this field is: 100',
// )
// })
test('custom server components should receive field props', async () => {
await page.goto(customFieldsURL.create)
await page.waitForURL(customFieldsURL.create)
await expect(
page.locator('#custom-server-field-label', {
hasText: exactText('Label: the max length of this field is: 100'),
}),
).toBeVisible()
await expect(
page.locator('#custom-server-field-description', {
hasText: exactText('Description: the max length of this field is: 100'),
}),
).toBeVisible()
})
test('custom client components should receive field props', async () => {
await page.goto(customFieldsURL.create)
await page.waitForURL(customFieldsURL.create)
await expect(
page.locator('#custom-client-field-label', {
hasText: exactText('Label: the max length of this field is: 100'),
}),
).toBeVisible()
await expect(
page.locator('#custom-client-field-description', {
hasText: exactText('Description: the max length of this field is: 100'),
}),
).toBeVisible()
})
describe('field descriptions', () => {
test('should render static field description', async () => {
@@ -606,10 +628,10 @@ describe('admin1', () => {
test('should render custom error component', async () => {
await page.goto(customFieldsURL.create)
await page.waitForURL(customFieldsURL.create)
const input = page.locator('input[id="field-customTextField"]')
const input = page.locator('input[id="field-customTextClientField"]')
await input.fill('ab')
await expect(input).toHaveValue('ab')
const error = page.locator('.custom-error:near(input[id="field-customTextField"])')
const error = page.locator('.custom-error:near(input[id="field-customTextClientField"])')
const submit = page.locator('button[type="button"][id="action-save"]')
await submit.click()
await expect(error).toHaveText('#custom-error')
@@ -617,17 +639,19 @@ describe('admin1', () => {
test('should render beforeInput and afterInput', async () => {
await page.goto(customFieldsURL.create)
const input = page.locator('input[id="field-customTextField"]')
const input = page.locator('input[id="field-customTextClientField"]')
const prevSibling = await input.evaluateHandle((el) => {
return el.previousElementSibling
})
const prevSiblingText = await page.evaluate((el) => el?.textContent, prevSibling)
expect(prevSiblingText).toEqual('#before-input')
const nextSibling = await input.evaluateHandle((el) => {
return el.nextElementSibling
})
const nextSiblingText = await page.evaluate((el) => el?.textContent, nextSibling)
expect(nextSiblingText).toEqual('#after-input')
})

View File

@@ -172,7 +172,8 @@ export interface CustomViewsTwo {
*/
export interface CustomField {
id: string;
customTextField?: string | null;
customTextServerField?: string | null;
customTextClientField?: string | null;
descriptionAsString?: string | null;
descriptionAsFunction?: string | null;
descriptionAsComponent?: string | null;