fix(ui): automatically subscribes custom fields to conditional logic (#9928)
Currently, custom components do not respect `admin.condition` unless manually wrapped with the `withCondition` HOC, like all default fields currently do. This should not be a requirement of component authors. Instead, we can automatically detect custom client and server fields and wrap them with the underlying `WatchCondition` component which will subscribe to the `passesCondition` property within client-side form state. For my future self: there are potentially multiple instances where fields subscribe to conditions duplicately, such as when rendering a default Payload field within a custom field component. This was always a problem and it is non-breaking, but needs to be reevaluated and removed in the future for performance. Only the default fields that Payload renders client-side need to subscribe to field conditions in this way. When importing a Payload field into your custom field component, for example, it should not include the HOC, because custom components now watch conditions themselves.
This commit is contained in:
@@ -22,18 +22,18 @@ export const getJobsLocalAPI = (payload: Payload) => ({
|
|||||||
req?: PayloadRequest
|
req?: PayloadRequest
|
||||||
// TTaskOrWorkflowlug with keyof TypedJobs['workflows'] removed:
|
// TTaskOrWorkflowlug with keyof TypedJobs['workflows'] removed:
|
||||||
task: TTaskOrWorkflowSlug extends keyof TypedJobs['tasks'] ? TTaskOrWorkflowSlug : never
|
task: TTaskOrWorkflowSlug extends keyof TypedJobs['tasks'] ? TTaskOrWorkflowSlug : never
|
||||||
workflow?: never
|
|
||||||
waitUntil?: Date
|
waitUntil?: Date
|
||||||
|
workflow?: never
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
input: TypedJobs['workflows'][TTaskOrWorkflowSlug]['input']
|
input: TypedJobs['workflows'][TTaskOrWorkflowSlug]['input']
|
||||||
queue?: string
|
queue?: string
|
||||||
req?: PayloadRequest
|
req?: PayloadRequest
|
||||||
task?: never
|
task?: never
|
||||||
|
waitUntil?: Date
|
||||||
workflow: TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
|
workflow: TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
|
||||||
? TTaskOrWorkflowSlug
|
? TTaskOrWorkflowSlug
|
||||||
: never
|
: never
|
||||||
waitUntil?: Date
|
|
||||||
},
|
},
|
||||||
): Promise<
|
): Promise<
|
||||||
TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
|
TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
|
||||||
@@ -60,8 +60,8 @@ export const getJobsLocalAPI = (payload: Payload) => ({
|
|||||||
input: args.input,
|
input: args.input,
|
||||||
queue,
|
queue,
|
||||||
taskSlug: 'task' in args ? args.task : undefined,
|
taskSlug: 'task' in args ? args.task : undefined,
|
||||||
workflowSlug: 'workflow' in args ? args.workflow : undefined,
|
|
||||||
waitUntil: args.waitUntil?.toISOString() ?? undefined,
|
waitUntil: args.waitUntil?.toISOString() ?? undefined,
|
||||||
|
workflowSlug: 'workflow' in args ? args.workflow : undefined,
|
||||||
} as BaseJob,
|
} as BaseJob,
|
||||||
req: args.req,
|
req: args.req,
|
||||||
})) as TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
|
})) as TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
RenderCustomComponent,
|
RenderCustomComponent,
|
||||||
useEditDepth,
|
useEditDepth,
|
||||||
useField,
|
useField,
|
||||||
withCondition,
|
|
||||||
} from '@payloadcms/ui'
|
} from '@payloadcms/ui'
|
||||||
import { mergeFieldStyles } from '@payloadcms/ui/shared'
|
import { mergeFieldStyles } from '@payloadcms/ui/shared'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
@@ -143,4 +142,4 @@ function fallbackRender({ error }: { error: Error }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RichText: typeof RichTextComponent = withCondition(RichTextComponent)
|
export const RichText: typeof RichTextComponent = RichTextComponent
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
useEditDepth,
|
useEditDepth,
|
||||||
useField,
|
useField,
|
||||||
useTranslation,
|
useTranslation,
|
||||||
withCondition,
|
|
||||||
} from '@payloadcms/ui'
|
} from '@payloadcms/ui'
|
||||||
import { mergeFieldStyles } from '@payloadcms/ui/shared'
|
import { mergeFieldStyles } from '@payloadcms/ui/shared'
|
||||||
import { isHotkey } from 'is-hotkey'
|
import { isHotkey } from 'is-hotkey'
|
||||||
@@ -459,4 +458,4 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RichText = withCondition(RichTextField)
|
export const RichText = RichTextField
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ export { useField } from '../../forms/useField/index.js'
|
|||||||
export type { FieldType, Options } from '../../forms/useField/types.js'
|
export type { FieldType, Options } from '../../forms/useField/types.js'
|
||||||
|
|
||||||
export { withCondition } from '../../forms/withCondition/index.js'
|
export { withCondition } from '../../forms/withCondition/index.js'
|
||||||
|
export { WatchCondition } from '../../forms/withCondition/WatchCondition.js'
|
||||||
|
|
||||||
// graphics
|
// graphics
|
||||||
export { Account } from '../../graphics/Account/index.js'
|
export { Account } from '../../graphics/Account/index.js'
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ export function RenderField({
|
|||||||
readOnly,
|
readOnly,
|
||||||
schemaPath,
|
schemaPath,
|
||||||
}: RenderFieldProps) {
|
}: RenderFieldProps) {
|
||||||
const Field = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field)
|
const CustomField = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field)
|
||||||
|
|
||||||
if (Field !== undefined) {
|
if (CustomField !== undefined) {
|
||||||
return Field || null
|
return CustomField || null
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseFieldProps: Pick<ClientComponentProps, 'forceRender' | 'readOnly' | 'schemaPath'> = {
|
const baseFieldProps: Pick<ClientComponentProps, 'forceRender' | 'readOnly' | 'schemaPath'> = {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { RenderFieldMethod } from './types.js'
|
|||||||
import { RenderServerComponent } from '../../elements/RenderServerComponent/index.js'
|
import { RenderServerComponent } from '../../elements/RenderServerComponent/index.js'
|
||||||
|
|
||||||
// eslint-disable-next-line payload/no-imports-from-exports-dir -- need this to reference already existing bundle. Otherwise, bundle size increases., payload/no-imports-from-exports-dir
|
// eslint-disable-next-line payload/no-imports-from-exports-dir -- need this to reference already existing bundle. Otherwise, bundle size increases., payload/no-imports-from-exports-dir
|
||||||
import { FieldDescription } from '../../exports/client/index.js'
|
import { FieldDescription, WatchCondition } from '../../exports/client/index.js'
|
||||||
|
|
||||||
const defaultUIFieldComponentKeys: Array<'Cell' | 'Description' | 'Field' | 'Filter'> = [
|
const defaultUIFieldComponentKeys: Array<'Cell' | 'Description' | 'Field' | 'Filter'> = [
|
||||||
'Cell',
|
'Cell',
|
||||||
@@ -135,12 +135,16 @@ export const renderField: RenderFieldMethod = ({
|
|||||||
fieldConfig.admin.components = {}
|
fieldConfig.admin.components = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldState.customComponents.Field = RenderServerComponent({
|
fieldState.customComponents.Field = (
|
||||||
clientProps,
|
<WatchCondition path={path}>
|
||||||
Component: fieldConfig.editor.FieldComponent,
|
{RenderServerComponent({
|
||||||
importMap: req.payload.importMap,
|
clientProps,
|
||||||
serverProps,
|
Component: fieldConfig.editor.FieldComponent,
|
||||||
})
|
importMap: req.payload.importMap,
|
||||||
|
serverProps,
|
||||||
|
})}
|
||||||
|
</WatchCondition>
|
||||||
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -236,13 +240,17 @@ export const renderField: RenderFieldMethod = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ('Field' in fieldConfig.admin.components) {
|
if ('Field' in fieldConfig.admin.components) {
|
||||||
fieldState.customComponents.Field = RenderServerComponent({
|
fieldState.customComponents.Field = (
|
||||||
clientProps,
|
<WatchCondition path={path}>
|
||||||
Component: fieldConfig.admin.components.Field,
|
{RenderServerComponent({
|
||||||
importMap: req.payload.importMap,
|
clientProps,
|
||||||
key: 'field.admin.components.Field',
|
Component: fieldConfig.admin.components.Field,
|
||||||
serverProps,
|
importMap: req.payload.importMap,
|
||||||
})
|
key: 'field.admin.components.Field',
|
||||||
|
serverProps,
|
||||||
|
})}
|
||||||
|
</WatchCondition>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { useFormFields } from '../Form/context.js'
|
|||||||
|
|
||||||
export const WatchCondition: React.FC<{
|
export const WatchCondition: React.FC<{
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
indexPath: string
|
|
||||||
path: string
|
path: string
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { children, path } = props
|
const { children, path } = props
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export const withCondition = <P extends MarkOptional<FieldPaths, 'indexPath' | '
|
|||||||
Field: React.ComponentType<P>,
|
Field: React.ComponentType<P>,
|
||||||
): React.FC<P> => {
|
): React.FC<P> => {
|
||||||
const CheckForCondition: React.FC<P> = (props) => {
|
const CheckForCondition: React.FC<P> = (props) => {
|
||||||
const { indexPath, path } = props
|
const { path } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WatchCondition indexPath={indexPath} path={path}>
|
<WatchCondition path={path}>
|
||||||
<Field {...props} />
|
<Field {...props} />
|
||||||
</WatchCondition>
|
</WatchCondition>
|
||||||
)
|
)
|
||||||
|
|||||||
2
templates/_template/.vscode/settings.json
vendored
2
templates/_template/.vscode/settings.json
vendored
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,12 @@
|
|||||||
"sharp": "0.32.6"
|
"sharp": "0.32.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@types/react": "19.0.1",
|
"@types/react": "19.0.1",
|
||||||
"@types/react-dom": "19.0.1",
|
"@types/react-dom": "19.0.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "15.1.0",
|
"eslint-config-next": "15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
2
templates/blank/.vscode/settings.json
vendored
2
templates/blank/.vscode/settings.json
vendored
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,12 @@
|
|||||||
"sharp": "0.32.6"
|
"sharp": "0.32.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@types/react": "19.0.1",
|
"@types/react": "19.0.1",
|
||||||
"@types/react-dom": "19.0.1",
|
"@types/react-dom": "19.0.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "15.1.0",
|
"eslint-config-next": "15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
2
templates/website/.vscode/settings.json
vendored
2
templates/website/.vscode/settings.json
vendored
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
"@tailwindcss/typography": "^0.5.13",
|
||||||
"@types/escape-html": "^1.0.2",
|
"@types/escape-html": "^1.0.2",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
@@ -64,9 +65,8 @@
|
|||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "15.1.0",
|
"eslint-config-next": "15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,12 @@
|
|||||||
"sharp": "0.32.6"
|
"sharp": "0.32.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@types/react": "19.0.1",
|
"@types/react": "19.0.1",
|
||||||
"@types/react-dom": "19.0.1",
|
"@types/react-dom": "19.0.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "15.1.0",
|
"eslint-config-next": "15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
"sharp": "0.32.6"
|
"sharp": "0.32.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@types/react": "19.0.1",
|
"@types/react": "19.0.1",
|
||||||
"@types/react-dom": "19.0.1",
|
"@types/react-dom": "19.0.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "15.1.0",
|
"eslint-config-next": "15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,12 @@
|
|||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@types/react": "19.0.1",
|
"@types/react": "19.0.1",
|
||||||
"@types/react-dom": "19.0.1",
|
"@types/react-dom": "19.0.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "15.1.0",
|
"eslint-config-next": "15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
"react-dom": "19.0.0"
|
"react-dom": "19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"@types/react": "19.0.1",
|
"@types/react": "19.0.1",
|
||||||
"@types/react-dom": "19.0.1",
|
"@types/react-dom": "19.0.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "15.1.0",
|
"eslint-config-next": "15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,5 +36,5 @@
|
|||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@tailwindcss/typography": "^0.5.13",
|
"@tailwindcss/typography": "^0.5.13",
|
||||||
"@types/escape-html": "^1.0.2",
|
"@types/escape-html": "^1.0.2",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
@@ -66,9 +67,8 @@
|
|||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"eslint": "^9.16.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-next": "^15.1.0",
|
"eslint-config-next": "^15.1.0",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
|
||||||
"prettier": "^3.4.2",
|
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "5.7.2"
|
"typescript": "5.7.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { TextFieldClientComponent } from 'payload'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const CustomClientField: TextFieldClientComponent = () => {
|
||||||
|
return <div id="custom-client-field">Custom Client Field</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomClientField
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
import type { TextFieldClientComponent } from 'payload'
|
||||||
|
|
||||||
|
import { TextField } from '@payloadcms/ui'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const CustomFieldWithField: TextFieldClientComponent = (props) => {
|
||||||
|
return <TextField {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomFieldWithField
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
import type { TextFieldClientComponent } from 'payload'
|
||||||
|
|
||||||
|
import { TextField, withCondition } from '@payloadcms/ui'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const MyField: TextFieldClientComponent = (props) => {
|
||||||
|
return <TextField {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withCondition(MyField)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { TextFieldServerComponent } from 'payload'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const CustomServerField: TextFieldServerComponent = () => {
|
||||||
|
return <div id="custom-server-field">Custom Server Field</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomServerField
|
||||||
@@ -84,6 +84,49 @@ describe('Conditional Logic', () => {
|
|||||||
expect(true).toBe(true)
|
expect(true).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should conditionally render custom field that renders a Payload field', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
await toggleConditionAndCheckField(
|
||||||
|
'label[for=field-toggleField]',
|
||||||
|
'label[for=field-customFieldWithField]',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(true).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should conditionally render custom field that wraps itself with the withCondition HOC (legacy)', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
|
||||||
|
await toggleConditionAndCheckField(
|
||||||
|
'label[for=field-toggleField]',
|
||||||
|
'label[for=field-customFieldWithHOC]',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(true).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should toggle conditional custom client field', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
await toggleConditionAndCheckField('label[for=field-toggleField]', '#custom-client-field')
|
||||||
|
expect(true).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should conditionally render custom server field', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
await toggleConditionAndCheckField('label[for=field-toggleField]', '#custom-server-field')
|
||||||
|
expect(true).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should conditionally render rich text fields', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
await toggleConditionAndCheckField(
|
||||||
|
'label[for=field-toggleField]',
|
||||||
|
'.field-type.rich-text-lexical',
|
||||||
|
)
|
||||||
|
expect(true).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
test('should show conditional field based on user data', async () => {
|
test('should show conditional field based on user data', async () => {
|
||||||
await page.goto(url.create)
|
await page.goto(url.create)
|
||||||
const userConditional = page.locator('input#field-userConditional')
|
const userConditional = page.locator('input#field-userConditional')
|
||||||
|
|||||||
@@ -20,7 +20,54 @@ const ConditionalLogic: CollectionConfig = {
|
|||||||
{
|
{
|
||||||
name: 'fieldWithCondition',
|
name: 'fieldWithCondition',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
admin: {
|
||||||
|
condition: ({ toggleField }) => Boolean(toggleField),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFieldWithField',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
components: {
|
||||||
|
Field: '/collections/ConditionalLogic/CustomFieldWithField',
|
||||||
|
},
|
||||||
|
condition: ({ toggleField }) => Boolean(toggleField),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customFieldWithHOC',
|
||||||
|
label: 'Custom Field With HOC (legacy)',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
components: {
|
||||||
|
Field: '/collections/ConditionalLogic/CustomFieldWithHOC',
|
||||||
|
},
|
||||||
|
condition: ({ toggleField }) => Boolean(toggleField),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customClientFieldWithCondition',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
components: {
|
||||||
|
Field: '/collections/ConditionalLogic/CustomClientField',
|
||||||
|
},
|
||||||
|
condition: ({ toggleField }) => Boolean(toggleField),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customServerFieldWithCondition',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
components: {
|
||||||
|
Field: '/collections/ConditionalLogic/CustomServerField',
|
||||||
|
},
|
||||||
|
condition: ({ toggleField }) => Boolean(toggleField),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'conditionalRichText',
|
||||||
|
type: 'richText',
|
||||||
admin: {
|
admin: {
|
||||||
condition: ({ toggleField }) => Boolean(toggleField),
|
condition: ({ toggleField }) => Boolean(toggleField),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,5 +5,4 @@ import type { ConditionalLogic } from '../../payload-types.js'
|
|||||||
export const conditionalLogicDoc: RequiredDataFromCollection<ConditionalLogic> = {
|
export const conditionalLogicDoc: RequiredDataFromCollection<ConditionalLogic> = {
|
||||||
text: 'Seeded conditional logic document',
|
text: 'Seeded conditional logic document',
|
||||||
toggleField: true,
|
toggleField: true,
|
||||||
fieldWithCondition: 'spiderman',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { TextFieldServerComponent } from 'payload'
|
import type { TextFieldClientComponent } from 'payload'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export const CustomField: TextFieldServerComponent = ({ schemaPath }) => {
|
export const CustomField: TextFieldClientComponent = ({ schemaPath }) => {
|
||||||
return <div id="custom-field-schema-path">{schemaPath}</div>
|
return <div id="custom-field-schema-path">{schemaPath}</div>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user