chore(next): ssr blocks field (#4942)
This commit is contained in:
@@ -144,27 +144,6 @@ export const Pages: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'json',
|
||||
label: 'JSON',
|
||||
type: 'json',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
label: 'Hidden',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
label: 'Code',
|
||||
type: 'code',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
// TODO: fix this
|
||||
// label: ({ data }) => `This is ${data?.title || 'Untitled'}`,
|
||||
@@ -211,6 +190,51 @@ export const Pages: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
label: 'Blocks',
|
||||
type: 'blocks',
|
||||
required: true,
|
||||
minRows: 1,
|
||||
maxRows: 2,
|
||||
blocks: [
|
||||
{
|
||||
slug: 'text',
|
||||
labels: {
|
||||
singular: 'Text Block',
|
||||
plural: 'Text Blocks',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
label: 'Text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
components: {
|
||||
beforeInput: [BeforeInput],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'textarea',
|
||||
labels: {
|
||||
singular: 'Textarea Block',
|
||||
plural: 'Textarea Blocks',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'textarea',
|
||||
label: 'Textarea',
|
||||
type: 'textarea',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tabs',
|
||||
type: 'tabs',
|
||||
@@ -262,5 +286,26 @@ export const Pages: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'json',
|
||||
label: 'JSON',
|
||||
type: 'json',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
label: 'Code',
|
||||
type: 'code',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
label: 'Hidden',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: {
|
||||
hidden: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -13,6 +13,14 @@ export const sanitizeField = (f) => {
|
||||
field.fields = sanitizeFields(field.fields)
|
||||
}
|
||||
|
||||
if ('blocks' in field) {
|
||||
field.blocks = field.blocks.map((block) => {
|
||||
const sanitized = { ...block }
|
||||
sanitized.fields = sanitizeFields(sanitized.fields)
|
||||
return sanitized
|
||||
})
|
||||
}
|
||||
|
||||
if ('tabs' in field) {
|
||||
field.tabs = field.tabs.map((tab) => sanitizeField(tab))
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import type * as React from 'react'
|
||||
|
||||
import useThrottledEffect from '../../hooks/useThrottledEffect'
|
||||
|
||||
type Props = {
|
||||
buildRowErrors: () => void
|
||||
}
|
||||
export const WatchFormErrors: React.FC<Props> = ({ buildRowErrors }) => {
|
||||
useThrottledEffect(
|
||||
() => {
|
||||
buildRowErrors()
|
||||
},
|
||||
250,
|
||||
[buildRowErrors],
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -26,11 +26,11 @@ type Args = {
|
||||
state: FormState
|
||||
t: TFunction
|
||||
user: User
|
||||
errorPaths: Set<string>
|
||||
}
|
||||
|
||||
export const addFieldStatePromise = async ({
|
||||
id,
|
||||
|
||||
data,
|
||||
field,
|
||||
fullData,
|
||||
@@ -42,15 +42,16 @@ export const addFieldStatePromise = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths: parentErrorPaths,
|
||||
}: Args): Promise<void> => {
|
||||
if (fieldAffectsData(field)) {
|
||||
const validate = operation === 'update' ? field.validate : undefined
|
||||
|
||||
const fieldState: FormField = {
|
||||
initialValue: undefined,
|
||||
passesCondition,
|
||||
valid: true,
|
||||
value: undefined,
|
||||
errorPaths: new Set(),
|
||||
}
|
||||
|
||||
const valueWithDefault = await getDefaultValue({
|
||||
@@ -81,6 +82,7 @@ export const addFieldStatePromise = async ({
|
||||
if (typeof validationResult === 'string') {
|
||||
fieldState.errorMessage = validationResult
|
||||
fieldState.valid = false
|
||||
parentErrorPaths.add(`${path}${field.name}`)
|
||||
} else {
|
||||
fieldState.valid = true
|
||||
}
|
||||
@@ -88,6 +90,7 @@ export const addFieldStatePromise = async ({
|
||||
switch (field.type) {
|
||||
case 'array': {
|
||||
const arrayValue = Array.isArray(valueWithDefault) ? valueWithDefault : []
|
||||
|
||||
const { promises, rowMetadata } = arrayValue.reduce(
|
||||
(acc, row, i) => {
|
||||
const rowPath = `${path}${field.name}.${i}.`
|
||||
@@ -113,6 +116,7 @@ export const addFieldStatePromise = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths: fieldState.errorPaths,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -120,7 +124,7 @@ export const addFieldStatePromise = async ({
|
||||
|
||||
acc.rowMetadata.push({
|
||||
id: row.id,
|
||||
childErrorPaths: new Set(),
|
||||
errorPaths: fieldState.errorPaths,
|
||||
collapsed:
|
||||
collapsedRowIDs === undefined
|
||||
? field.admin.initCollapsed
|
||||
@@ -201,6 +205,7 @@ export const addFieldStatePromise = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths: fieldState.errorPaths,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -209,7 +214,7 @@ export const addFieldStatePromise = async ({
|
||||
acc.rowMetadata.push({
|
||||
id: row.id,
|
||||
blockType: row.blockType,
|
||||
childErrorPaths: new Set(),
|
||||
errorPaths: fieldState.errorPaths,
|
||||
collapsed:
|
||||
collapsedRowIDs === undefined
|
||||
? field.admin.initCollapsed
|
||||
@@ -262,6 +267,7 @@ export const addFieldStatePromise = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths: parentErrorPaths,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -361,6 +367,7 @@ export const addFieldStatePromise = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths: parentErrorPaths,
|
||||
})
|
||||
} else if (field.type === 'tabs') {
|
||||
const promises = field.tabs.map((tab) =>
|
||||
@@ -377,6 +384,7 @@ export const addFieldStatePromise = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths: parentErrorPaths,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths: new Set(),
|
||||
})
|
||||
|
||||
return state
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TFunction } from '@payloadcms/translations'
|
||||
|
||||
import type { User } from 'payload/auth'
|
||||
import type { Field as FieldSchema, SanitizedConfig, Data } from 'payload/types'
|
||||
import type { Field as FieldSchema, Data } from 'payload/types'
|
||||
import type { FormState } from '../types'
|
||||
import { fieldIsPresentationalOnly } from 'payload/types'
|
||||
import { addFieldStatePromise } from './addFieldStatePromise'
|
||||
@@ -21,6 +21,7 @@ type Args = {
|
||||
state: FormState
|
||||
t: TFunction
|
||||
user: User
|
||||
errorPaths: Set<string>
|
||||
}
|
||||
|
||||
export const iterateFields = async ({
|
||||
@@ -36,6 +37,7 @@ export const iterateFields = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths,
|
||||
}: Args): Promise<void> => {
|
||||
const promises = []
|
||||
|
||||
@@ -63,9 +65,11 @@ export const iterateFields = async ({
|
||||
state,
|
||||
t,
|
||||
user,
|
||||
errorPaths,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ObjectID from 'bson-objectid'
|
||||
import equal from 'deep-equal'
|
||||
|
||||
import type { FieldAction, FormState, FormField } from './types'
|
||||
import type { FieldAction, FormState, FormField, Row } from './types'
|
||||
|
||||
import { deepCopyObject } from 'payload/utilities'
|
||||
import { flattenRows, separateRows } from './rows'
|
||||
@@ -102,17 +102,13 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
|
||||
const withNewRow = [...(state[path]?.rows || [])]
|
||||
|
||||
withNewRow.splice(
|
||||
rowIndex,
|
||||
0,
|
||||
// new row
|
||||
{
|
||||
id: new ObjectID().toHexString(),
|
||||
blockType: blockType || undefined,
|
||||
childErrorPaths: new Set(),
|
||||
collapsed: false,
|
||||
},
|
||||
)
|
||||
const newRow: Row = {
|
||||
id: new ObjectID().toHexString(),
|
||||
blockType: blockType || undefined,
|
||||
collapsed: false,
|
||||
}
|
||||
|
||||
withNewRow.splice(rowIndex, 0, newRow)
|
||||
|
||||
if (blockType) {
|
||||
subFieldState.blockType = {
|
||||
@@ -151,7 +147,6 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
rowsMetadata[rowIndex] = {
|
||||
id: new ObjectID().toHexString(),
|
||||
blockType: blockType || undefined,
|
||||
childErrorPaths: new Set(),
|
||||
collapsed: false,
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,10 @@ import type {
|
||||
Context as FormContextType,
|
||||
GetDataByPath,
|
||||
Props,
|
||||
Row,
|
||||
SubmitOptions,
|
||||
} from './types'
|
||||
|
||||
import { splitPathByArrayFields, setsAreEqual, wait, isNumber } from 'payload/utilities'
|
||||
import { wait } from 'payload/utilities'
|
||||
import { requests } from '../../utilities/api'
|
||||
import useThrottledEffect from '../../hooks/useThrottledEffect'
|
||||
import { useAuth } from '../../providers/Auth'
|
||||
@@ -25,7 +24,6 @@ import { useConfig } from '../../providers/Config'
|
||||
import { useDocumentInfo } from '../../providers/DocumentInfo'
|
||||
import { useLocale } from '../../providers/Locale'
|
||||
import { useOperation } from '../../providers/OperationProvider'
|
||||
import { WatchFormErrors } from './WatchFormErrors'
|
||||
import buildStateFromSchema from './buildStateFromSchema'
|
||||
import {
|
||||
FormContext,
|
||||
@@ -94,62 +92,6 @@ const Form: React.FC<Props> = (props) => {
|
||||
|
||||
contextRef.current.fields = fields
|
||||
|
||||
// Build a current set of child errors for all rows in form state
|
||||
const buildRowErrors = useCallback(() => {
|
||||
const existingFieldRows: { [path: string]: Row[] } = {}
|
||||
const newFieldRows: { [path: string]: Row[] } = {}
|
||||
|
||||
Object.entries(fields).forEach(([path, field]) => {
|
||||
const pathSegments = splitPathByArrayFields(path)
|
||||
|
||||
for (let i = 0; i < pathSegments.length; i += 1) {
|
||||
const fieldPath = pathSegments.slice(0, i + 1).join('.')
|
||||
const formField = fields?.[fieldPath]
|
||||
|
||||
// Is this an array or blocks field?
|
||||
if (Array.isArray(formField?.rows)) {
|
||||
// Keep a reference to the existing row state
|
||||
existingFieldRows[fieldPath] = formField.rows
|
||||
|
||||
// A new row state will be used to compare
|
||||
// against the old state later,
|
||||
// to see if we need to dispatch an update
|
||||
if (!newFieldRows[fieldPath]) {
|
||||
newFieldRows[fieldPath] = formField.rows.map((existingRow) => ({
|
||||
...existingRow,
|
||||
childErrorPaths: new Set(),
|
||||
}))
|
||||
}
|
||||
|
||||
const rowIndex = pathSegments[i + 1]
|
||||
const childFieldPath = pathSegments.slice(i + 1).join('.')
|
||||
|
||||
if (field.valid === false && childFieldPath) {
|
||||
newFieldRows[fieldPath][rowIndex].childErrorPaths.add(`${fieldPath}.${childFieldPath}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Now loop over all fields with rows -
|
||||
// if anything changed, dispatch an update for the field
|
||||
// with the new row state
|
||||
Object.entries(newFieldRows).forEach(([path, newRows]) => {
|
||||
const stateMatches = newRows.every((newRow, i) => {
|
||||
const existingRowErrorPaths = existingFieldRows[path][i]?.childErrorPaths
|
||||
return setsAreEqual(newRow.childErrorPaths, existingRowErrorPaths)
|
||||
})
|
||||
|
||||
if (!stateMatches) {
|
||||
dispatchFields({
|
||||
path,
|
||||
rows: newRows,
|
||||
type: 'UPDATE',
|
||||
})
|
||||
}
|
||||
})
|
||||
}, [fields, dispatchFields])
|
||||
|
||||
const validateForm = useCallback(async () => {
|
||||
const validatedFieldState = {}
|
||||
let isValid = true
|
||||
@@ -229,7 +171,6 @@ const Form: React.FC<Props> = (props) => {
|
||||
if (waitForAutocomplete) await wait(100)
|
||||
|
||||
const isValid = skipValidation ? true : await contextRef.current.validateForm()
|
||||
contextRef.current.buildRowErrors()
|
||||
|
||||
if (!skipValidation) setSubmitted(true)
|
||||
|
||||
@@ -480,7 +421,6 @@ const Form: React.FC<Props> = (props) => {
|
||||
contextRef.current.formRef = formRef
|
||||
contextRef.current.reset = reset
|
||||
contextRef.current.replaceState = replaceState
|
||||
contextRef.current.buildRowErrors = buildRowErrors
|
||||
contextRef.current.dispatchFields = dispatchFields
|
||||
|
||||
useEffect(() => {
|
||||
@@ -553,7 +493,6 @@ const Form: React.FC<Props> = (props) => {
|
||||
<ProcessingContext.Provider value={processing}>
|
||||
<ModifiedContext.Provider value={modified}>
|
||||
<FormFieldsContext.Provider value={fieldsReducer}>
|
||||
<WatchFormErrors buildRowErrors={buildRowErrors} />
|
||||
{children}
|
||||
</FormFieldsContext.Provider>
|
||||
</ModifiedContext.Provider>
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Data } from 'payload/types'
|
||||
|
||||
export type Row = {
|
||||
blockType?: string
|
||||
childErrorPaths?: Set<string>
|
||||
errorPaths?: Set<string>
|
||||
collapsed?: boolean
|
||||
id: string
|
||||
}
|
||||
@@ -21,6 +21,7 @@ export type FormField = {
|
||||
valid: boolean
|
||||
value: unknown
|
||||
validate?: Validate
|
||||
errorPaths?: Set<string>
|
||||
}
|
||||
|
||||
export type FormState = {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
import type { Field, FieldWithPath, TabsField } from 'payload/types'
|
||||
import type { BlockField, Field, FieldWithPath, TabsField } from 'payload/types'
|
||||
import DefaultError from '../Error'
|
||||
import DefaultLabel from '../Label'
|
||||
import DefaultDescription from '../FieldDescription'
|
||||
import { fieldAffectsData, fieldIsPresentationalOnly } from 'payload/types'
|
||||
import { fieldTypes } from '../field-types'
|
||||
import { FormFieldBase } from '../field-types/shared'
|
||||
import { FormState } from '../../forms/Form/types'
|
||||
|
||||
export type ReducedField = {
|
||||
type: keyof typeof fieldTypes
|
||||
@@ -19,7 +18,13 @@ export type ReducedField = {
|
||||
name: string
|
||||
readOnly: boolean
|
||||
isSidebar: boolean
|
||||
/**
|
||||
* On `array`, `blocks`, `group`, `collapsible`, and `tabs` fields only
|
||||
*/
|
||||
subfields?: ReducedField[]
|
||||
/**
|
||||
* On `tabs` fields only
|
||||
*/
|
||||
tabs?: ReducedTab[]
|
||||
}
|
||||
|
||||
@@ -29,6 +34,14 @@ export type ReducedTab = {
|
||||
subfields?: ReducedField[]
|
||||
}
|
||||
|
||||
export type ReducedBlock = {
|
||||
slug: string
|
||||
subfields: ReducedField[]
|
||||
labels: BlockField['labels']
|
||||
imageAltText?: string
|
||||
imageURL?: string
|
||||
}
|
||||
|
||||
export const buildFieldMap = (args: {
|
||||
fieldSchema: FieldWithPath[]
|
||||
filter?: (field: Field) => boolean
|
||||
@@ -176,11 +189,39 @@ export const buildFieldMap = (args: {
|
||||
parentPath: path,
|
||||
})
|
||||
|
||||
return {
|
||||
const reducedTab: ReducedTab = {
|
||||
name: 'name' in tab ? tab.name : undefined,
|
||||
label: 'label' in tab ? tab.label : undefined,
|
||||
label: tab.label,
|
||||
subfields: tabFieldMap,
|
||||
}
|
||||
|
||||
return reducedTab
|
||||
})
|
||||
|
||||
// `blocks` fields require a field map of each of its block's nested fields
|
||||
const blocks =
|
||||
'blocks' in field &&
|
||||
field.blocks &&
|
||||
Array.isArray(field.blocks) &&
|
||||
field.blocks.map((block) => {
|
||||
const blockFieldMap = buildFieldMap({
|
||||
fieldSchema: block.fields,
|
||||
filter,
|
||||
operation,
|
||||
permissions,
|
||||
readOnly: readOnlyOverride,
|
||||
parentPath: path,
|
||||
})
|
||||
|
||||
const reducedBlock: ReducedBlock = {
|
||||
slug: block.slug,
|
||||
subfields: blockFieldMap,
|
||||
labels: block.labels,
|
||||
imageAltText: block.imageAltText,
|
||||
imageURL: block.imageURL,
|
||||
}
|
||||
|
||||
return reducedBlock
|
||||
})
|
||||
|
||||
// TODO: these types can get cleaned up
|
||||
@@ -203,6 +244,7 @@ export const buildFieldMap = (args: {
|
||||
max: 'max' in field ? field.max : undefined,
|
||||
options: 'options' in field ? field.options : undefined,
|
||||
tabs,
|
||||
blocks,
|
||||
}
|
||||
|
||||
const Field = <FieldComponent {...fieldComponentProps} />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from '../../../providers/Translation'
|
||||
|
||||
import type { UseDraggableSortableReturn } from '../../../elements/DraggableSortable/useDraggableSortable/types'
|
||||
import type { Row } from '../../Form/types'
|
||||
import type { RowLabel as RowLabelType } from 'payload/types'
|
||||
import type { ArrayField, RowLabel as RowLabelType } from 'payload/types'
|
||||
|
||||
import { ArrayAction } from '../../../elements/ArrayAction'
|
||||
import { Collapsible } from '../../../elements/Collapsible'
|
||||
@@ -16,6 +16,7 @@ import { buildFieldMap } from '../../RenderFields/buildFieldMap'
|
||||
import { FieldPermissions } from 'payload/auth'
|
||||
|
||||
import './index.scss'
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
const baseClass = 'array-field'
|
||||
|
||||
@@ -25,6 +26,7 @@ type ArrayRowProps = UseDraggableSortableReturn & {
|
||||
duplicateRow: (rowIndex: number) => void
|
||||
forceRender?: boolean
|
||||
hasMaxRows?: boolean
|
||||
labels: ArrayField['labels']
|
||||
moveRow: (fromIndex: number, toIndex: number) => void
|
||||
readOnly?: boolean
|
||||
removeRow: (rowIndex: number) => void
|
||||
@@ -46,6 +48,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
||||
forceRender = false,
|
||||
hasMaxRows,
|
||||
indexPath,
|
||||
labels,
|
||||
listeners,
|
||||
moveRow,
|
||||
path: parentPath,
|
||||
@@ -64,13 +67,13 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
||||
const { i18n } = useTranslation()
|
||||
const hasSubmitted = useFormSubmitted()
|
||||
|
||||
// const fallbackLabel = `${getTranslation(labels.singular, i18n)} ${String(rowIndex + 1).padStart(
|
||||
// 2,
|
||||
// '0',
|
||||
// )}`
|
||||
const fallbackLabel = `${getTranslation(labels.singular, i18n)} ${String(rowIndex + 1).padStart(
|
||||
2,
|
||||
'0',
|
||||
)}`
|
||||
|
||||
const childErrorPathsCount = row.childErrorPaths?.size
|
||||
const fieldHasErrors = hasSubmitted && childErrorPathsCount > 0
|
||||
const errorCount = row.errorPaths?.size
|
||||
const fieldHasErrors = errorCount > 0 && hasSubmitted
|
||||
|
||||
const classNames = [
|
||||
`${baseClass}__row`,
|
||||
@@ -117,7 +120,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
|
||||
path={path}
|
||||
rowNumber={rowIndex + 1}
|
||||
/> */}
|
||||
{fieldHasErrors && <ErrorPill count={childErrorPathsCount} withMessage i18n={i18n} />}
|
||||
{fieldHasErrors && <ErrorPill count={errorCount} withMessage i18n={i18n} />}
|
||||
</div>
|
||||
}
|
||||
onToggle={(collapsed) => setCollapse(row.id, collapsed)}
|
||||
|
||||
@@ -62,9 +62,9 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
|
||||
// Handle labeling for Arrays, Global Arrays, and Blocks
|
||||
const getLabels = (p: Props) => {
|
||||
if (p?.labels) return p.labels
|
||||
if (p?.label) return { plural: undefined, singular: p.label }
|
||||
return { plural: t('fields:rows'), singular: t('fields:row') }
|
||||
if ('labels' in p && p?.labels) return p.labels
|
||||
if ('label' in p && p?.label) return { plural: undefined, singular: p.label }
|
||||
return { plural: t('general:rows'), singular: t('general:row') }
|
||||
}
|
||||
|
||||
const labels = getLabels(props)
|
||||
@@ -75,7 +75,9 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
if (!editingDefaultLocale && value === null) {
|
||||
return true
|
||||
}
|
||||
return validate(value, { ...options, maxRows, minRows, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, maxRows, minRows, required })
|
||||
}
|
||||
},
|
||||
[maxRows, minRows, required, validate, editingDefaultLocale],
|
||||
)
|
||||
@@ -154,7 +156,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
const hasMaxRows = maxRows && rows.length >= maxRows
|
||||
|
||||
const fieldErrorCount =
|
||||
rows.reduce((total, row) => total + (row?.childErrorPaths?.size || 0), 0) + (valid ? 0 : 1)
|
||||
rows.reduce((total, row) => total + (row?.errorPaths?.size || 0), 0) + (valid ? 0 : 1)
|
||||
|
||||
const fieldHasErrors = submitted && fieldErrorCount > 0
|
||||
|
||||
@@ -227,6 +229,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
hasMaxRows={hasMaxRows}
|
||||
indexPath={indexPath}
|
||||
moveRow={moveRow}
|
||||
labels={labels}
|
||||
path={path}
|
||||
permissions={permissions}
|
||||
readOnly={readOnly}
|
||||
|
||||
@@ -1,46 +1,52 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { useTranslation } from '../../../providers/Translation'
|
||||
|
||||
import type { Block } from 'payload/types'
|
||||
import type { UseDraggableSortableReturn } from '../../../elements/DraggableSortable/useDraggableSortable/types'
|
||||
import type { Row } from '../../Form/types'
|
||||
import type { Props } from './types'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { Collapsible } from '../../../elements/Collapsible'
|
||||
import { ErrorPill } from '../../../elements/ErrorPill'
|
||||
import Pill from '../../../elements/Pill'
|
||||
import { useFormSubmitted } from '../../Form/context'
|
||||
import { createNestedFieldPath } from '../../Form/createNestedFieldPath'
|
||||
import RenderFields from '../../RenderFields'
|
||||
import HiddenInput from '../HiddenInput'
|
||||
import { RowActions } from './RowActions'
|
||||
import SectionTitle from './SectionTitle'
|
||||
import { ReducedBlock } from '../../RenderFields/buildFieldMap'
|
||||
import { FieldPathProvider } from '../../FieldPathProvider'
|
||||
import { Labels } from 'payload/types'
|
||||
import { FieldPermissions } from 'payload/auth'
|
||||
|
||||
const baseClass = 'blocks-field'
|
||||
|
||||
type BlockFieldProps = UseDraggableSortableReturn &
|
||||
Pick<Props, 'blocks' | 'fieldTypes' | 'indexPath' | 'labels' | 'path' | 'permissions'> & {
|
||||
addRow: (rowIndex: number, blockType: string) => void
|
||||
blockToRender: Block
|
||||
duplicateRow: (rowIndex: number) => void
|
||||
forceRender?: boolean
|
||||
hasMaxRows?: boolean
|
||||
moveRow: (fromIndex: number, toIndex: number) => void
|
||||
readOnly: boolean
|
||||
removeRow: (rowIndex: number) => void
|
||||
row: Row
|
||||
rowCount: number
|
||||
rowIndex: number
|
||||
setCollapse: (id: string, collapsed: boolean) => void
|
||||
}
|
||||
type BlockFieldProps = UseDraggableSortableReturn & {
|
||||
addRow: (rowIndex: number, blockType: string) => void
|
||||
blocks: ReducedBlock[]
|
||||
block: ReducedBlock
|
||||
duplicateRow: (rowIndex: number) => void
|
||||
forceRender?: boolean
|
||||
hasMaxRows?: boolean
|
||||
moveRow: (fromIndex: number, toIndex: number) => void
|
||||
readOnly: boolean
|
||||
removeRow: (rowIndex: number) => void
|
||||
row: Row
|
||||
rowCount: number
|
||||
rowIndex: number
|
||||
setCollapse: (id: string, collapsed: boolean) => void
|
||||
indexPath: string
|
||||
path: string
|
||||
labels: Labels
|
||||
permissions: FieldPermissions
|
||||
}
|
||||
|
||||
export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
addRow,
|
||||
attributes,
|
||||
blockToRender,
|
||||
blocks,
|
||||
block,
|
||||
duplicateRow,
|
||||
fieldTypes,
|
||||
forceRender,
|
||||
hasMaxRows,
|
||||
indexPath,
|
||||
@@ -62,8 +68,8 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
const { i18n } = useTranslation()
|
||||
const hasSubmitted = useFormSubmitted()
|
||||
|
||||
const childErrorPathsCount = row.childErrorPaths?.size
|
||||
const fieldHasErrors = hasSubmitted && childErrorPathsCount > 0
|
||||
const errorCount = row.errorPaths?.size
|
||||
const fieldHasErrors = errorCount > 0 && hasSubmitted
|
||||
|
||||
const classNames = [
|
||||
`${baseClass}__row`,
|
||||
@@ -87,7 +93,6 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
<RowActions
|
||||
addRow={addRow}
|
||||
blockType={row.blockType}
|
||||
blocks={blocks}
|
||||
duplicateRow={duplicateRow}
|
||||
hasMaxRows={hasMaxRows}
|
||||
labels={labels}
|
||||
@@ -95,6 +100,8 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
removeRow={removeRow}
|
||||
rowCount={rowCount}
|
||||
rowIndex={rowIndex}
|
||||
blocks={blocks}
|
||||
fieldMap={block.subfields}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
@@ -115,30 +122,27 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
|
||||
className={`${baseClass}__block-pill ${baseClass}__block-pill-${row.blockType}`}
|
||||
pillStyle="white"
|
||||
>
|
||||
{getTranslation(blockToRender.labels.singular, i18n)}
|
||||
{getTranslation(block.labels.singular, i18n)}
|
||||
</Pill>
|
||||
<SectionTitle path={`${path}.blockName`} readOnly={readOnly} />
|
||||
{fieldHasErrors && <ErrorPill count={childErrorPathsCount} withMessage />}
|
||||
{fieldHasErrors && <ErrorPill count={errorCount} withMessage i18n={i18n} />}
|
||||
</div>
|
||||
}
|
||||
key={row.id}
|
||||
onToggle={(collapsed) => setCollapse(row.id, collapsed)}
|
||||
>
|
||||
<HiddenInput name={`${path}.id`} value={row.id} />
|
||||
[RenderFields]
|
||||
{/* <RenderFields
|
||||
className={`${baseClass}__fields`}
|
||||
fieldSchema={blockToRender.fields.map((field) => ({
|
||||
...field,
|
||||
path: createNestedFieldPath(path, field),
|
||||
}))}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
margins="small"
|
||||
permissions={permissions?.blocks?.[row.blockType]?.fields}
|
||||
readOnly={readOnly}
|
||||
/> */}
|
||||
<FieldPathProvider path={path}>
|
||||
<RenderFields
|
||||
className={`${baseClass}__fields`}
|
||||
fieldMap={block.subfields}
|
||||
forceRender={forceRender}
|
||||
indexPath={indexPath}
|
||||
margins="small"
|
||||
permissions={permissions?.blocks?.[row.blockType]?.fields}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</FieldPathProvider>
|
||||
</Collapsible>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useModal } from '@faceless-ui/modal'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useTranslation } from '../../../../providers/Translation'
|
||||
|
||||
import type { Block } from 'payload/types'
|
||||
import type { Props } from './types'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
@@ -12,11 +11,13 @@ import { Drawer } from '../../../../elements/Drawer'
|
||||
import { ThumbnailCard } from '../../../../elements/ThumbnailCard'
|
||||
import DefaultBlockImage from '../../../../graphics/DefaultBlockImage'
|
||||
import BlockSearch from './BlockSearch'
|
||||
import { ReducedBlock } from '../../../RenderFields/buildFieldMap'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'blocks-drawer'
|
||||
|
||||
const getBlockLabel = (block: Block, i18n: I18n) => {
|
||||
const getBlockLabel = (block: ReducedBlock, i18n: I18n) => {
|
||||
if (typeof block.labels.singular === 'string') return block.labels.singular.toLowerCase()
|
||||
if (typeof block.labels.singular === 'object') {
|
||||
return getTranslation(block.labels.singular, i18n).toLowerCase()
|
||||
@@ -41,7 +42,7 @@ export const BlocksDrawer: React.FC<Props> = (props) => {
|
||||
useEffect(() => {
|
||||
const searchTermToUse = searchTerm.toLowerCase()
|
||||
|
||||
const matchingBlocks = blocks.reduce((matchedBlocks, block) => {
|
||||
const matchingBlocks = blocks?.reduce((matchedBlocks, block) => {
|
||||
const blockLabel = getBlockLabel(block, i18n)
|
||||
if (blockLabel.includes(searchTermToUse)) matchedBlocks.push(block)
|
||||
return matchedBlocks
|
||||
@@ -65,7 +66,7 @@ export const BlocksDrawer: React.FC<Props> = (props) => {
|
||||
<li className={`${baseClass}__block`} key={index}>
|
||||
<ThumbnailCard
|
||||
alignLabel="center"
|
||||
label={getTranslation(blockLabels.singular, i18n)}
|
||||
label={getTranslation(blockLabels?.singular, i18n)}
|
||||
onClick={() => {
|
||||
addRow(addRowIndex, slug)
|
||||
closeModal(drawerSlug)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Block, Labels } from 'payload/types'
|
||||
import type { Labels } from 'payload/types'
|
||||
import { ReducedBlock } from '../../../RenderFields/buildFieldMap'
|
||||
|
||||
export type Props = {
|
||||
addRow: (index: number, blockType?: string) => void
|
||||
addRowIndex: number
|
||||
blocks: Block[]
|
||||
blocks: ReducedBlock[]
|
||||
drawerSlug: string
|
||||
labels: Labels
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
'use client'
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import React from 'react'
|
||||
|
||||
import type { Block, Labels } from 'payload/types'
|
||||
import type { Labels, RowField } from 'payload/types'
|
||||
|
||||
import { ArrayAction } from '../../../elements/ArrayAction'
|
||||
import { useDrawerSlug } from '../../../elements/Drawer/useDrawerSlug'
|
||||
import { BlocksDrawer } from './BlocksDrawer'
|
||||
import { ReducedBlock, buildFieldMap } from '../../RenderFields/buildFieldMap'
|
||||
|
||||
export const RowActions: React.FC<{
|
||||
addRow: (rowIndex: number, blockType: string) => void
|
||||
blockType: string
|
||||
blocks: Block[]
|
||||
fieldMap: ReturnType<typeof buildFieldMap>
|
||||
duplicateRow: (rowIndex: number, blockType: string) => void
|
||||
hasMaxRows?: boolean
|
||||
labels: Labels
|
||||
@@ -18,11 +20,11 @@ export const RowActions: React.FC<{
|
||||
removeRow: (rowIndex: number) => void
|
||||
rowCount: number
|
||||
rowIndex: number
|
||||
blocks: ReducedBlock[]
|
||||
}> = (props) => {
|
||||
const {
|
||||
addRow,
|
||||
blockType,
|
||||
blocks,
|
||||
duplicateRow,
|
||||
hasMaxRows,
|
||||
labels,
|
||||
@@ -30,6 +32,7 @@ export const RowActions: React.FC<{
|
||||
removeRow,
|
||||
rowCount,
|
||||
rowIndex,
|
||||
blocks,
|
||||
} = props
|
||||
|
||||
const { closeModal, openModal } = useModal()
|
||||
|
||||
@@ -16,8 +16,6 @@ import { ErrorPill } from '../../../elements/ErrorPill'
|
||||
import { useConfig } from '../../../providers/Config'
|
||||
import { useDocumentInfo } from '../../../providers/DocumentInfo'
|
||||
import { useLocale } from '../../../providers/Locale'
|
||||
import Error from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import { useForm, useFormSubmitted } from '../../Form/context'
|
||||
import { NullifyLocaleField } from '../../NullifyField'
|
||||
import useField from '../../useField'
|
||||
@@ -33,26 +31,27 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
name,
|
||||
admin: { className, description, readOnly },
|
||||
blocks,
|
||||
fieldTypes,
|
||||
className,
|
||||
readOnly,
|
||||
forceRender = false,
|
||||
indexPath,
|
||||
label,
|
||||
labels: labelsFromProps,
|
||||
localized,
|
||||
maxRows,
|
||||
minRows,
|
||||
Description,
|
||||
Error,
|
||||
Label,
|
||||
path: pathFromProps,
|
||||
permissions,
|
||||
required,
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const path = pathFromProps || name
|
||||
const minRows = 'minRows' in props ? props.minRows : 0
|
||||
const maxRows = 'maxRows' in props ? props.maxRows : undefined
|
||||
const blocks = 'blocks' in props ? props.blocks : undefined
|
||||
const labelsFromProps = 'labels' in props ? props.labels : undefined
|
||||
|
||||
const { setDocFieldPreferences } = useDocumentInfo()
|
||||
const { addFieldRow, dispatchFields, removeFieldRow, setModified } = useForm()
|
||||
const { dispatchFields, setModified } = useForm()
|
||||
const { code: locale } = useLocale()
|
||||
const { localization } = useConfig()
|
||||
const drawerSlug = useDrawerSlug('blocks-drawer')
|
||||
@@ -79,39 +78,41 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
if (!editingDefaultLocale && value === null) {
|
||||
return true
|
||||
}
|
||||
return validate(value, { ...options, maxRows, minRows, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, maxRows, minRows, required })
|
||||
}
|
||||
},
|
||||
[maxRows, minRows, required, validate, editingDefaultLocale],
|
||||
)
|
||||
|
||||
const {
|
||||
errorMessage,
|
||||
rows = [],
|
||||
showError,
|
||||
valid,
|
||||
value,
|
||||
path,
|
||||
} = useField<number>({
|
||||
hasRows: true,
|
||||
path,
|
||||
path: pathFromProps || name,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
|
||||
const addRow = useCallback(
|
||||
async (rowIndex: number, blockType: string) => {
|
||||
await addFieldRow({
|
||||
data: {
|
||||
blockType,
|
||||
},
|
||||
dispatchFields({
|
||||
blockType,
|
||||
path,
|
||||
rowIndex,
|
||||
type: 'ADD_ROW',
|
||||
})
|
||||
|
||||
setModified(true)
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToID(`${path}-row-${rowIndex + 1}`)
|
||||
}, 0)
|
||||
},
|
||||
[addFieldRow, path, setModified],
|
||||
[path, setModified],
|
||||
)
|
||||
|
||||
const duplicateRow = useCallback(
|
||||
@@ -128,10 +129,15 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
|
||||
const removeRow = useCallback(
|
||||
(rowIndex: number) => {
|
||||
removeFieldRow({ path, rowIndex })
|
||||
dispatchFields({
|
||||
path,
|
||||
rowIndex,
|
||||
type: 'REMOVE_ROW',
|
||||
})
|
||||
|
||||
setModified(true)
|
||||
},
|
||||
[path, removeFieldRow, setModified],
|
||||
[path, dispatchFields, setModified],
|
||||
)
|
||||
|
||||
const moveRow = useCallback(
|
||||
@@ -158,7 +164,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
|
||||
const hasMaxRows = maxRows && rows.length >= maxRows
|
||||
|
||||
const fieldErrorCount = rows.reduce((total, row) => total + (row?.childErrorPaths?.size || 0), 0)
|
||||
const fieldErrorCount = rows.reduce((total, row) => total + (row?.errorPaths?.size || 0), 0)
|
||||
const fieldHasErrors = submitted && fieldErrorCount + (valid ? 0 : 1) > 0
|
||||
|
||||
const showMinRows = rows.length < minRows || (required && rows.length === 0)
|
||||
@@ -176,18 +182,13 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
.join(' ')}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
>
|
||||
{showError && (
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
</div>
|
||||
)}
|
||||
{showError && <div className={`${baseClass}__error-wrap`}>{Error}</div>}
|
||||
<header className={`${baseClass}__header`}>
|
||||
<div className={`${baseClass}__header-wrap`}>
|
||||
<div className={`${baseClass}__heading-with-error`}>
|
||||
<h3>{getTranslation(label || name, i18n)}</h3>
|
||||
|
||||
<h3>{Label}</h3>
|
||||
{fieldHasErrors && fieldErrorCount > 0 && (
|
||||
<ErrorPill count={fieldErrorCount} withMessage />
|
||||
<ErrorPill count={fieldErrorCount} withMessage i18n={i18n} />
|
||||
)}
|
||||
</div>
|
||||
{rows.length > 0 && (
|
||||
@@ -213,7 +214,7 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
<FieldDescription description={description} path={path} value={value} />
|
||||
{Description}
|
||||
</header>
|
||||
<NullifyLocaleField fieldValue={value} localized={localized} path={path} />
|
||||
{(rows.length > 0 || (!valid && (showRequired || showMinRows))) && (
|
||||
@@ -232,11 +233,10 @@ const BlocksField: React.FC<Props> = (props) => {
|
||||
{(draggableSortableItemProps) => (
|
||||
<BlockRow
|
||||
{...draggableSortableItemProps}
|
||||
addRow={addRow}
|
||||
blockToRender={blockToRender}
|
||||
blocks={blocks}
|
||||
addRow={addRow}
|
||||
block={blockToRender}
|
||||
duplicateRow={duplicateRow}
|
||||
fieldTypes={fieldTypes}
|
||||
forceRender={forceRender}
|
||||
hasMaxRows={hasMaxRows}
|
||||
indexPath={indexPath}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { FieldTypes } from 'payload/config'
|
||||
import type { FieldPermissions } from 'payload/auth'
|
||||
import type { BlockField } from 'payload/types'
|
||||
import { FormFieldBase } from '../shared'
|
||||
|
||||
export type Props = Omit<BlockField, 'type'> & {
|
||||
fieldTypes: FieldTypes
|
||||
export type Props = FormFieldBase & {
|
||||
name?: string
|
||||
forceRender?: boolean
|
||||
indexPath: string
|
||||
path?: string
|
||||
permissions: FieldPermissions
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, required })
|
||||
}
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { Props } from './types'
|
||||
import { Collapsible } from '../../../elements/Collapsible'
|
||||
import { useDocumentInfo } from '../../../providers/DocumentInfo'
|
||||
import { usePreferences } from '../../../providers/Preferences'
|
||||
import { useFormSubmitted } from '../../Form/context'
|
||||
import RenderFields from '../../RenderFields'
|
||||
import { withCondition } from '../../withCondition'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
@@ -42,8 +41,7 @@ const CollapsibleField: React.FC<Props> = (props) => {
|
||||
const [collapsedOnMount, setCollapsedOnMount] = useState<boolean>()
|
||||
const fieldPreferencesKey = `collapsible-${path.replace(/\./g, '__')}`
|
||||
const [errorCount, setErrorCount] = useState(0)
|
||||
const submitted = useFormSubmitted()
|
||||
const fieldHasErrors = errorCount > 0 && submitted
|
||||
const fieldHasErrors = errorCount > 0
|
||||
|
||||
const onToggle = useCallback(
|
||||
async (newCollapsedState: boolean) => {
|
||||
|
||||
@@ -38,7 +38,9 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, required })
|
||||
}
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
@@ -34,7 +34,9 @@ export const Email: React.FC<Props> = (props) => {
|
||||
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, required })
|
||||
}
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
@@ -8,7 +8,6 @@ import { withCondition } from '../../withCondition'
|
||||
import { useCollapsible } from '../../../elements/Collapsible/provider'
|
||||
import { useRow } from '../Row/provider'
|
||||
import { useTabs } from '../Tabs/provider'
|
||||
import { useFormSubmitted } from '../../../forms/Form/context'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { useFieldPath } from '../../FieldPathProvider'
|
||||
import { WatchChildErrors } from '../../WatchChildErrors'
|
||||
@@ -25,13 +24,12 @@ const Group: React.FC<Props> = (props) => {
|
||||
const path = useFieldPath()
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const hasSubmitted = useFormSubmitted()
|
||||
const isWithinCollapsible = useCollapsible()
|
||||
const isWithinGroup = useGroup()
|
||||
const isWithinRow = useRow()
|
||||
const isWithinTab = useTabs()
|
||||
const [errorCount, setErrorCount] = React.useState(undefined)
|
||||
const fieldHasErrors = errorCount > 0 && hasSubmitted
|
||||
const fieldHasErrors = errorCount > 0
|
||||
|
||||
const isTopLevel = !(isWithinCollapsible || isWithinGroup || isWithinRow)
|
||||
|
||||
|
||||
@@ -41,7 +41,9 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, max, min, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, max, min, required })
|
||||
}
|
||||
},
|
||||
[validate, min, max, required],
|
||||
)
|
||||
|
||||
@@ -26,7 +26,9 @@ export const Password: React.FC<Props> = (props) => {
|
||||
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, required })
|
||||
}
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
@@ -38,7 +38,9 @@ const PointField: React.FC<Props> = (props) => {
|
||||
|
||||
const memoizedValidate: Validate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, required })
|
||||
}
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getTranslation } from '@payloadcms/translations'
|
||||
import React, { useState } from 'react'
|
||||
import { ErrorPill } from '../../../../elements/ErrorPill'
|
||||
import { WatchChildErrors } from '../../../WatchChildErrors'
|
||||
import { useFormSubmitted, useTranslation } from '../../../..'
|
||||
import { useTranslation } from '../../../..'
|
||||
import { ReducedTab } from '../../../RenderFields/buildFieldMap'
|
||||
|
||||
import './index.scss'
|
||||
@@ -23,10 +23,9 @@ export const TabComponent: React.FC<TabProps> = ({ isActive, parentPath, setIsAc
|
||||
const { i18n } = useTranslation()
|
||||
const [errorCount, setErrorCount] = useState(undefined)
|
||||
const hasName = 'name' in tab
|
||||
const hasSubmitted = useFormSubmitted()
|
||||
|
||||
const path = `${parentPath ? `${parentPath}.` : ''}${'name' in tab ? name : ''}`
|
||||
const fieldHasErrors = errorCount > 0 && hasSubmitted
|
||||
const fieldHasErrors = errorCount > 0
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
@@ -78,10 +78,10 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
>
|
||||
{Error}
|
||||
{Label}
|
||||
{BeforeInput}
|
||||
<label className="textarea-outer" htmlFor={`field-${path.replace(/\./g, '__')}`}>
|
||||
<div className="textarea-inner">
|
||||
<div className="textarea-clone" data-value={value || placeholder || ''} />
|
||||
{BeforeInput}
|
||||
<textarea
|
||||
className="textarea-element"
|
||||
data-rtl={isRTL}
|
||||
@@ -93,9 +93,9 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
rows={rows}
|
||||
value={value || ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
</label>
|
||||
{AfterInput}
|
||||
{Description}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -38,7 +38,9 @@ const Upload: React.FC<Props> = (props) => {
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
(value, options) => {
|
||||
if (typeof validate === 'function') return validate(value, { ...options, required })
|
||||
if (typeof validate === 'function') {
|
||||
return validate(value, { ...options, required })
|
||||
}
|
||||
},
|
||||
[validate, required],
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
|
||||
import { User } from 'payload/auth'
|
||||
import {
|
||||
ArrayField,
|
||||
BlockField,
|
||||
CodeField,
|
||||
DateField,
|
||||
DocumentPreferences,
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
RowLabel,
|
||||
Validate,
|
||||
} from 'payload/types'
|
||||
import { ReducedTab, buildFieldMap } from '../RenderFields/buildFieldMap'
|
||||
import { ReducedBlock, ReducedTab, buildFieldMap } from '../RenderFields/buildFieldMap'
|
||||
import { Option } from 'payload/types'
|
||||
import { FormState } from '../..'
|
||||
|
||||
@@ -86,6 +87,15 @@ export type FormFieldBase = {
|
||||
// For `array` fields
|
||||
minRows?: ArrayField['minRows']
|
||||
maxRows?: ArrayField['maxRows']
|
||||
labels?: ArrayField['labels']
|
||||
}
|
||||
| {
|
||||
// For `blocks` fields
|
||||
slug?: string
|
||||
minRows?: BlockField['minRows']
|
||||
maxRows?: BlockField['maxRows']
|
||||
labels?: BlockField['labels']
|
||||
blocks?: ReducedBlock[]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user