Files
payloadcms/packages/ui/src/fields/Code/index.tsx
Jacob Fletcher 0b1a1b585b fix(ui): processing and initializing form does not disable standalone fields (#11714)
The form component's `initializing` and `processing` states do not
disable fields that are rendered outside of `DocumentFields`. Fields
currently rely on the `readOnly` prop provided by `DocumentFields` and
do not subscribe to these states for themselves. This means that fields
that are rendered outright, such as within the bulk edit drawer, they do
not receive a `readOnly` prop and are therefore never disabled.

The fix is add a `disabled` property to the `useField` hook. This
subscribes to the `initializing` and `processing` states in the same way
as `DocumentFields`, however, now each field can determine its own
disabled state instead of relying solely on the `readOnly` prop. Adding
this new prop has no overhead as `processing` and `initializing` is
already being subscribed to within `useField`.
2025-03-17 10:27:21 -04:00

105 lines
2.8 KiB
TypeScript

'use client'
import type { CodeFieldClientComponent } from 'payload'
import React, { useCallback, useMemo } from 'react'
import { CodeEditor } from '../../elements/CodeEditor/index.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { FieldDescription } from '../../fields/FieldDescription/index.js'
import { FieldError } from '../../fields/FieldError/index.js'
import { FieldLabel } from '../../fields/FieldLabel/index.js'
import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const prismToMonacoLanguageMap = {
js: 'javascript',
ts: 'typescript',
}
const baseClass = 'code-field'
const CodeFieldComponent: CodeFieldClientComponent = (props) => {
const {
field,
field: {
admin: { className, description, editorOptions = {}, language = 'javascript' } = {},
label,
localized,
required,
},
onMount,
path,
readOnly,
validate,
} = props
const memoizedValidate = useCallback(
(value, options) => {
if (typeof validate === 'function') {
return validate(value, { ...options, required })
}
},
[validate, required],
)
const {
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
disabled,
setValue,
showError,
value,
} = useField({
path,
validate: memoizedValidate,
})
const styles = useMemo(() => mergeFieldStyles(field), [field])
return (
<div
className={[
fieldBaseClass,
baseClass,
className,
showError && 'error',
(readOnly || disabled) && 'read-only',
]
.filter(Boolean)
.join(' ')}
style={styles}
>
<RenderCustomComponent
CustomComponent={Label}
Fallback={
<FieldLabel label={label} localized={localized} path={path} required={required} />
}
/>
<div className={`${fieldBaseClass}__wrap`}>
<RenderCustomComponent
CustomComponent={Error}
Fallback={<FieldError path={path} showError={showError} />}
/>
{BeforeInput}
<CodeEditor
defaultLanguage={prismToMonacoLanguageMap[language] || language}
onChange={readOnly || disabled ? () => null : (val) => setValue(val)}
onMount={onMount}
options={editorOptions}
readOnly={readOnly || disabled}
value={(value as string) || ''}
/>
{AfterInput}
</div>
<RenderCustomComponent
CustomComponent={Description}
Fallback={<FieldDescription description={description} path={path} />}
/>
</div>
)
}
export const CodeField = withCondition(CodeFieldComponent)