perf!: removes unnecessary field styles from initial page response (#9286)

Optimizes initial page responses by removing unnecessary inline field
styles that were being sent through the HTML response. The Client Config
contains a large number of duplicates of the string:
`"style\":{\"flex\":\"1 1 auto\"}`, one for every single field within
the entirely of the config. This leads to hundreds or potentially
thousands of instances of this same string, depending on the number of
fields within the config itself. This is regardless of custom field
widths being defined. Instead, we can do this entirely client-side,
preventing this string from ever being transmitted over the network in
the first place.

## Breaking Changes

This only effects those who are importing Payload's field components
into your own Custom Components or front-end application. The `width`
prop no longer exists. It has been consolidated into the existing
`style` prop. To migrate, simply move this prop as follows:

```diff
import { TextInput } from '@payloadcms/ui

export const MyCustomComponent = () => {
  return (
    <TextInput 
-      width="60%"
       style={{
+        width: "60%,
       }}
    />
  )
}
```
This commit is contained in:
Jacob Fletcher
2024-11-18 10:03:26 -05:00
committed by GitHub
parent 665b3536d3
commit 30947d2173
28 changed files with 159 additions and 201 deletions

View File

@@ -96,25 +96,6 @@ export const createClientField = ({
clientField.label = incomingField.label({ t: i18n.t }) clientField.label = incomingField.label({ t: i18n.t })
} }
if (!(clientField.admin instanceof Object)) {
clientField.admin = {} as AdminClient
}
if ('admin' in incomingField && 'width' in incomingField.admin) {
clientField.admin.style = {
...clientField.admin.style,
'--field-width': clientField.admin.width,
}
delete clientField.admin.style.width // avoid needlessly adding this to the element's style attribute
} else {
if (!(clientField.admin.style instanceof Object)) {
clientField.admin.style = {}
}
clientField.admin.style.flex = '1 1 auto'
}
switch (incomingField.type) { switch (incomingField.type) {
case 'array': case 'array':
case 'collapsible': case 'collapsible':

View File

@@ -2,7 +2,8 @@
import type { EditorState, SerializedEditorState } from 'lexical' import type { EditorState, SerializedEditorState } from 'lexical'
import { FieldLabel, useEditDepth, useField, withCondition } from '@payloadcms/ui' import { FieldLabel, useEditDepth, useField, withCondition } from '@payloadcms/ui'
import React, { useCallback } from 'react' import { mergeFieldStyles } from '@payloadcms/ui/shared'
import React, { useCallback, useMemo } from 'react'
import { ErrorBoundary } from 'react-error-boundary' import { ErrorBoundary } from 'react-error-boundary'
import type { SanitizedClientEditorConfig } from '../lexical/config/types.js' import type { SanitizedClientEditorConfig } from '../lexical/config/types.js'
@@ -22,9 +23,10 @@ const RichTextComponent: React.FC<
> = (props) => { > = (props) => {
const { const {
editorConfig, editorConfig,
field,
field: { field: {
name, name,
admin: { className, readOnly: readOnlyFromAdmin, style, width } = {}, admin: { className, readOnly: readOnlyFromAdmin } = {},
label, label,
localized, localized,
required, required,
@@ -87,15 +89,10 @@ const RichTextComponent: React.FC<
[setValue], [setValue],
) )
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div className={classes} key={pathWithEditDepth} style={styles}>
className={classes}
key={pathWithEditDepth}
style={{
...style,
width,
}}
>
{Error} {Error}
{Label || <FieldLabel label={label} localized={localized} required={required} />} {Label || <FieldLabel label={label} localized={localized} required={required} />}
<div className={`${baseClass}__wrap`}> <div className={`${baseClass}__wrap`}>

View File

@@ -7,6 +7,7 @@ import type { ReactEditor } from 'slate-react'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { FieldLabel, useEditDepth, useField, useTranslation, withCondition } from '@payloadcms/ui' import { FieldLabel, useEditDepth, useField, useTranslation, withCondition } from '@payloadcms/ui'
import { mergeFieldStyles } from '@payloadcms/ui/shared'
import { isHotkey } from 'is-hotkey' import { isHotkey } from 'is-hotkey'
import React, { useCallback, useEffect, useMemo, useRef } from 'react' import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { createEditor, Node, Element as SlateElement, Text, Transforms } from 'slate' import { createEditor, Node, Element as SlateElement, Text, Transforms } from 'slate'
@@ -19,8 +20,8 @@ import type { LoadedSlateFieldProps } from './types.js'
import { defaultRichTextValue } from '../data/defaultValue.js' import { defaultRichTextValue } from '../data/defaultValue.js'
import { richTextValidate } from '../data/validation.js' import { richTextValidate } from '../data/validation.js'
import { listTypes } from './elements/listTypes.js' import { listTypes } from './elements/listTypes.js'
import { hotkeys } from './hotkeys.js'
import './index.scss' import './index.scss'
import { hotkeys } from './hotkeys.js'
import { toggleLeaf } from './leaves/toggle.js' import { toggleLeaf } from './leaves/toggle.js'
import { withEnterBreakOut } from './plugins/withEnterBreakOut.js' import { withEnterBreakOut } from './plugins/withEnterBreakOut.js'
import { withHTML } from './plugins/withHTML.js' import { withHTML } from './plugins/withHTML.js'
@@ -42,9 +43,10 @@ declare module 'slate' {
const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => { const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
const { const {
elements, elements,
field,
field: { field: {
name, name,
admin: { className, placeholder, readOnly: readOnlyFromAdmin, style, width } = {}, admin: { className, placeholder, readOnly: readOnlyFromAdmin } = {},
label, label,
required, required,
}, },
@@ -274,6 +276,8 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
// } // }
// }, [path, editor]); // }, [path, editor]);
const styles = useMemo(() => mergeFieldStyles(field), [field])
const classes = [ const classes = [
baseClass, baseClass,
'field-type', 'field-type',
@@ -300,13 +304,7 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
} }
return ( return (
<div <div className={classes} style={styles}>
className={classes}
style={{
...style,
width,
}}
>
{Label || <FieldLabel label={label} required={required} />} {Label || <FieldLabel label={label} required={required} />}
<div className={`${baseClass}__wrap`}> <div className={`${baseClass}__wrap`}>
{Error} {Error}

View File

@@ -4,6 +4,7 @@ export { getInitialColumns } from '../../elements/TableColumns/getInitialColumns
export { Translation } from '../../elements/Translation/index.js' export { Translation } from '../../elements/Translation/index.js'
export { withMergedProps } from '../../elements/withMergedProps/index.js' // cannot be within a 'use client', thus we export this from shared export { withMergedProps } from '../../elements/withMergedProps/index.js' // cannot be within a 'use client', thus we export this from shared
export { WithServerSideProps } from '../../elements/WithServerSideProps/index.js' export { WithServerSideProps } from '../../elements/WithServerSideProps/index.js'
export { mergeFieldStyles } from '../../fields/mergeFieldStyles.js'
export { reduceToSerializableFields } from '../../forms/Form/reduceToSerializableFields.js' export { reduceToSerializableFields } from '../../forms/Form/reduceToSerializableFields.js'
export { PayloadIcon } from '../../graphics/Icon/index.js' export { PayloadIcon } from '../../graphics/Icon/index.js'
export { PayloadLogo } from '../../graphics/Logo/index.js' export { PayloadLogo } from '../../graphics/Logo/index.js'

View File

@@ -5,7 +5,7 @@ import type {
CheckboxFieldValidation, CheckboxFieldValidation,
} from 'payload' } from 'payload'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import type { CheckboxInputProps } from './Input.js' import type { CheckboxInputProps } from './Input.js'
@@ -17,8 +17,9 @@ import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useEditDepth } from '../../providers/EditDepth/index.js' import { useEditDepth } from '../../providers/EditDepth/index.js'
import { generateFieldID } from '../../utilities/generateFieldID.js' import { generateFieldID } from '../../utilities/generateFieldID.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
import { CheckboxInput } from './Input.js' import { CheckboxInput } from './Input.js'
const baseClass = 'checkbox' const baseClass = 'checkbox'
@@ -30,14 +31,9 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {
id, id,
checked: checkedFromProps, checked: checkedFromProps,
disableFormData, disableFormData,
field,
field: { field: {
name, admin: { className, description } = {} as CheckboxFieldClientProps['field']['admin'],
admin: {
className,
description,
style,
width,
} = {} as CheckboxFieldClientProps['field']['admin'],
label, label,
required, required,
} = {} as CheckboxFieldClientProps['field'], } = {} as CheckboxFieldClientProps['field'],
@@ -85,6 +81,8 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {
const fieldID = id || generateFieldID(path, editDepth, uuid) const fieldID = id || generateFieldID(path, editDepth, uuid)
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -97,10 +95,7 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Error} CustomComponent={Error}

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import type { CodeFieldClientComponent } from 'payload' import type { CodeFieldClientComponent } from 'payload'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import { CodeEditor } from '../../elements/CodeEditor/index.js' import { CodeEditor } from '../../elements/CodeEditor/index.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
@@ -10,8 +10,9 @@ import { FieldError } from '../../fields/FieldError/index.js'
import { FieldLabel } from '../../fields/FieldLabel/index.js' import { FieldLabel } from '../../fields/FieldLabel/index.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const prismToMonacoLanguageMap = { const prismToMonacoLanguageMap = {
js: 'javascript', js: 'javascript',
@@ -22,16 +23,9 @@ const baseClass = 'code-field'
const CodeFieldComponent: CodeFieldClientComponent = (props) => { const CodeFieldComponent: CodeFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, admin: { className, description, editorOptions = {}, language = 'javascript' } = {},
admin: {
className,
description,
editorOptions = {},
language = 'javascript',
style,
width,
} = {},
label, label,
localized, localized,
required, required,
@@ -60,6 +54,8 @@ const CodeFieldComponent: CodeFieldClientComponent = (props) => {
validate: memoizedValidate, validate: memoizedValidate,
}) })
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -71,10 +67,7 @@ const CodeFieldComponent: CodeFieldClientComponent = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -1,8 +1,8 @@
'use client' 'use client'
import type { AdminClient, CollapsibleFieldClientComponent, DocumentPreferences } from 'payload' import type { CollapsibleFieldClientComponent, DocumentPreferences } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { Fragment, useCallback, useEffect, useState } from 'react' import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
import { Collapsible as CollapsibleElement } from '../../elements/Collapsible/index.js' import { Collapsible as CollapsibleElement } from '../../elements/Collapsible/index.js'
import { ErrorPill } from '../../elements/ErrorPill/index.js' import { ErrorPill } from '../../elements/ErrorPill/index.js'
@@ -16,8 +16,9 @@ import { withCondition } from '../../forms/withCondition/index.js'
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js' import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
import { usePreferences } from '../../providers/Preferences/index.js' import { usePreferences } from '../../providers/Preferences/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const baseClass = 'collapsible-field' const baseClass = 'collapsible-field'
@@ -98,15 +99,12 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
void fetchInitialState() void fetchInitialState()
}, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed, path]) }, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed, path])
const styles = useMemo(() => mergeFieldStyles(field), [field])
if (typeof collapsedOnMount !== 'boolean') { if (typeof collapsedOnMount !== 'boolean') {
return null return null
} }
const style: AdminClient['style'] = {
...field.admin?.style,
'--field-width': field.admin.width,
}
return ( return (
<Fragment> <Fragment>
<WatchChildErrors fields={fields} path={path.split('.')} setErrorCount={setErrorCount} /> <WatchChildErrors fields={fields} path={path.split('.')} setErrorCount={setErrorCount} />
@@ -120,7 +118,7 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
id={`field-${fieldPreferencesKey}`} id={`field-${fieldPreferencesKey}`}
style={style} style={styles}
> >
<CollapsibleElement <CollapsibleElement
className={`${baseClass}__collapsible`} className={`${baseClass}__collapsible`}

View File

@@ -2,7 +2,7 @@
import type { DateFieldClientComponent, DateFieldValidation } from 'payload' import type { DateFieldClientComponent, DateFieldValidation } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import { DatePickerField } from '../../elements/DatePicker/index.js' import { DatePickerField } from '../../elements/DatePicker/index.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
@@ -12,16 +12,17 @@ import { FieldLabel } from '../../fields/FieldLabel/index.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const baseClass = 'date-time-field' const baseClass = 'date-time-field'
const DateTimeFieldComponent: DateFieldClientComponent = (props) => { const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, admin: { className, date: datePickerProps, description, placeholder } = {},
admin: { className, date: datePickerProps, description, placeholder, style, width } = {},
label, label,
localized, localized,
required, required,
@@ -52,6 +53,8 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
validate: memoizedValidate, validate: memoizedValidate,
}) })
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -63,10 +66,7 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -6,7 +6,7 @@ import type {
} from 'payload' } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { FieldDescription } from '../../fields/FieldDescription/index.js' import { FieldDescription } from '../../fields/FieldDescription/index.js'
@@ -15,20 +15,19 @@ import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { FieldLabel } from '../FieldLabel/index.js' import { FieldLabel } from '../FieldLabel/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const EmailFieldComponent: EmailFieldClientComponent = (props) => { const EmailFieldComponent: EmailFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name,
admin: { admin: {
autoComplete, autoComplete,
className, className,
description, description,
placeholder, placeholder,
style,
width,
} = {} as EmailFieldClientProps['field']['admin'], } = {} as EmailFieldClientProps['field']['admin'],
label, label,
localized, localized,
@@ -60,15 +59,14 @@ const EmailFieldComponent: EmailFieldClientComponent = (props) => {
validate: memoizedValidate, validate: memoizedValidate,
}) })
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[fieldBaseClass, 'email', className, showError && 'error', readOnly && 'read-only'] className={[fieldBaseClass, 'email', className, showError && 'error', readOnly && 'read-only']
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -3,7 +3,7 @@
import type { GroupFieldClientComponent } from 'payload' import type { GroupFieldClientComponent } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React from 'react' import React, { useMemo } from 'react'
import { useCollapsible } from '../../elements/Collapsible/provider.js' import { useCollapsible } from '../../elements/Collapsible/provider.js'
import { ErrorPill } from '../../elements/ErrorPill/index.js' import { ErrorPill } from '../../elements/ErrorPill/index.js'
@@ -15,22 +15,19 @@ import { RenderFields } from '../../forms/RenderFields/index.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { useRow } from '../Row/provider.js' import { useRow } from '../Row/provider.js'
import { fieldBaseClass } from '../shared/index.js' import { fieldBaseClass } from '../shared/index.js'
import { useTabs } from '../Tabs/provider.js'
import './index.scss' import './index.scss'
import { useTabs } from '../Tabs/provider.js'
import { GroupProvider, useGroup } from './provider.js' import { GroupProvider, useGroup } from './provider.js'
const baseClass = 'group-field' const baseClass = 'group-field'
export const GroupFieldComponent: GroupFieldClientComponent = (props) => { export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
const { const {
field: { field,
name, field: { name, admin: { className, description, hideGutter } = {}, fields, label },
admin: { className, description, hideGutter, style, width } = {},
fields,
label,
},
path, path,
permissions, permissions,
readOnly, readOnly,
@@ -50,6 +47,8 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
const isTopLevel = !(isWithinCollapsible || isWithinGroup || isWithinRow) const isTopLevel = !(isWithinCollapsible || isWithinGroup || isWithinRow)
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -67,10 +66,7 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
id={`field-${path?.replace(/\./g, '__')}`} id={`field-${path?.replace(/\./g, '__')}`}
style={{ style={styles}
...style,
width,
}}
> >
<GroupProvider> <GroupProvider>
<div className={`${baseClass}__wrap`}> <div className={`${baseClass}__wrap`}>

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import type { JSONFieldClientComponent } from 'payload' import type { JSONFieldClientComponent } from 'payload'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { CodeEditor } from '../../elements/CodeEditor/index.js' import { CodeEditor } from '../../elements/CodeEditor/index.js'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
@@ -10,16 +10,17 @@ import { withCondition } from '../../forms/withCondition/index.js'
import { FieldDescription } from '../FieldDescription/index.js' import { FieldDescription } from '../FieldDescription/index.js'
import { FieldError } from '../FieldError/index.js' import { FieldError } from '../FieldError/index.js'
import { FieldLabel } from '../FieldLabel/index.js' import { FieldLabel } from '../FieldLabel/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const baseClass = 'json-field' const baseClass = 'json-field'
const JSONFieldComponent: JSONFieldClientComponent = (props) => { const JSONFieldComponent: JSONFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, admin: { className, description, editorOptions, maxHeight } = {},
admin: { className, description, editorOptions, maxHeight, style, width } = {},
jsonSchema, jsonSchema,
label, label,
localized, localized,
@@ -105,6 +106,8 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
setHasLoadedValue(true) setHasLoadedValue(true)
}, [initialValue, value, hasLoadedValue]) }, [initialValue, value, hasLoadedValue])
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -116,10 +119,7 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -15,7 +15,6 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
const { const {
field, field,
field: { field: {
name,
admin: { allowCreate }, admin: { allowCreate },
collection, collection,
label, label,

View File

@@ -3,7 +3,7 @@ import type { NumberFieldClientComponent, NumberFieldClientProps } from 'payload
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import { isNumber } from 'payload/shared' import { isNumber } from 'payload/shared'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import type { Option } from '../../elements/ReactSelect/types.js' import type { Option } from '../../elements/ReactSelect/types.js'
@@ -15,20 +15,19 @@ import { useTranslation } from '../../providers/Translation/index.js'
import { FieldDescription } from '../FieldDescription/index.js' import { FieldDescription } from '../FieldDescription/index.js'
import { FieldError } from '../FieldError/index.js' import { FieldError } from '../FieldError/index.js'
import { FieldLabel } from '../FieldLabel/index.js' import { FieldLabel } from '../FieldLabel/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const NumberFieldComponent: NumberFieldClientComponent = (props) => { const NumberFieldComponent: NumberFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name,
admin: { admin: {
className, className,
description, description,
placeholder, placeholder,
step = 1, step = 1,
style,
width,
} = {} as NumberFieldClientProps['field']['admin'], } = {} as NumberFieldClientProps['field']['admin'],
hasMany = false, hasMany = false,
label, label,
@@ -123,6 +122,8 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
} }
}, [value, hasMany]) }, [value, hasMany])
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -135,10 +136,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -2,7 +2,7 @@
import type { PasswordFieldValidation, PayloadRequest } from 'payload' import type { PasswordFieldValidation, PayloadRequest } from 'payload'
import { password } from 'payload/shared' import { password } from 'payload/shared'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import type { PasswordFieldProps } from './types.js' import type { PasswordFieldProps } from './types.js'
@@ -11,22 +11,21 @@ import { withCondition } from '../../forms/withCondition/index.js'
import { useConfig } from '../../providers/Config/index.js' import { useConfig } from '../../providers/Config/index.js'
import { useLocale } from '../../providers/Locale/index.js' import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { isFieldRTL } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { isFieldRTL } from '../shared/index.js'
import { PasswordInput } from './input.js' import { PasswordInput } from './input.js'
const PasswordFieldComponent: React.FC<PasswordFieldProps> = (props) => { const PasswordFieldComponent: React.FC<PasswordFieldProps> = (props) => {
const { const {
autoComplete, autoComplete,
field,
field: { field: {
name,
admin: { admin: {
className, className,
disabled: disabledFromProps, disabled: disabledFromProps,
placeholder, placeholder,
rtl, rtl,
style,
width,
} = {} as PasswordFieldProps['field']['admin'], } = {} as PasswordFieldProps['field']['admin'],
label, label,
localized, localized,
@@ -86,6 +85,8 @@ const PasswordFieldComponent: React.FC<PasswordFieldProps> = (props) => {
localizationConfig: config.localization || undefined, localizationConfig: config.localization || undefined,
}) })
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<PasswordInput <PasswordInput
AfterInput={AfterInput} AfterInput={AfterInput}
@@ -107,9 +108,8 @@ const PasswordFieldComponent: React.FC<PasswordFieldProps> = (props) => {
required={required} required={required}
rtl={renderRTL} rtl={renderRTL}
showError={showError} showError={showError}
style={style} style={styles}
value={(value as string) || ''} value={(value as string) || ''}
width={width}
/> />
) )
} }

View File

@@ -2,7 +2,7 @@
import type { PointFieldClientComponent, PointFieldValidation } from 'payload' import type { PointFieldClientComponent, PointFieldValidation } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { FieldDescription } from '../../fields/FieldDescription/index.js' import { FieldDescription } from '../../fields/FieldDescription/index.js'
@@ -11,16 +11,17 @@ import { FieldLabel } from '../../fields/FieldLabel/index.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
const baseClass = 'point' const baseClass = 'point'
export const PointFieldComponent: PointFieldClientComponent = (props) => { export const PointFieldComponent: PointFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, admin: { className, description, placeholder, step } = {},
admin: { className, description, placeholder, step, style, width } = {},
label, label,
localized, localized,
required, required,
@@ -71,6 +72,8 @@ export const PointFieldComponent: PointFieldClientComponent = (props) => {
return `${fieldLabel}${fieldLabel ? ' - ' : ''}${suffix}` return `${fieldLabel}${fieldLabel ? ' - ' : ''}${suffix}`
} }
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -82,10 +85,7 @@ export const PointFieldComponent: PointFieldClientComponent = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<ul className={`${baseClass}__wrap`}> <ul className={`${baseClass}__wrap`}>
<li> <li>

View File

@@ -2,7 +2,7 @@
import type { RadioFieldClientComponent, RadioFieldClientProps } from 'payload' import type { RadioFieldClientComponent, RadioFieldClientProps } from 'payload'
import { optionIsObject } from 'payload/shared' import { optionIsObject } from 'payload/shared'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js'
import { FieldDescription } from '../../fields/FieldDescription/index.js' import { FieldDescription } from '../../fields/FieldDescription/index.js'
@@ -11,8 +11,9 @@ import { FieldLabel } from '../../fields/FieldLabel/index.js'
import { useForm } from '../../forms/Form/context.js' import { useForm } from '../../forms/Form/context.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { fieldBaseClass } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { fieldBaseClass } from '../shared/index.js'
import { Radio } from './Radio/index.js' import { Radio } from './Radio/index.js'
const baseClass = 'radio-group' const baseClass = 'radio-group'
@@ -20,13 +21,12 @@ const baseClass = 'radio-group'
const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => { const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
const { const {
disableModifyingForm: disableModifyingFormFromProps, disableModifyingForm: disableModifyingFormFromProps,
field,
field: { field: {
admin: { admin: {
className, className,
description, description,
layout = 'horizontal', layout = 'horizontal',
style,
width,
} = {} as RadioFieldClientProps['field']['admin'], } = {} as RadioFieldClientProps['field']['admin'],
label, label,
localized, localized,
@@ -63,6 +63,8 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
const value = valueFromContext || valueFromProps const value = valueFromContext || valueFromProps
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -75,10 +77,7 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Error} CustomComponent={Error}

View File

@@ -3,7 +3,7 @@ import type { PaginatedDocs, RelationshipFieldClientComponent, Where } from 'pay
import { wordBoundariesRegex } from 'payload/shared' import { wordBoundariesRegex } from 'payload/shared'
import * as qs from 'qs-esm' import * as qs from 'qs-esm'
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react' import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
import type { DocumentDrawerProps } from '../../elements/DocumentDrawer/types.js' import type { DocumentDrawerProps } from '../../elements/DocumentDrawer/types.js'
import type { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js' import type { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js'
@@ -24,10 +24,11 @@ import { useAuth } from '../../providers/Auth/index.js'
import { useConfig } from '../../providers/Config/index.js' import { useConfig } from '../../providers/Config/index.js'
import { useLocale } from '../../providers/Locale/index.js' import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { fieldBaseClass } from '../shared/index.js' import { fieldBaseClass } from '../shared/index.js'
import { createRelationMap } from './createRelationMap.js' import { createRelationMap } from './createRelationMap.js'
import { findOptionsByValue } from './findOptionsByValue.js'
import './index.scss' import './index.scss'
import { findOptionsByValue } from './findOptionsByValue.js'
import { optionsReducer } from './optionsReducer.js' import { optionsReducer } from './optionsReducer.js'
import { MultiValueLabel } from './select-components/MultiValueLabel/index.js' import { MultiValueLabel } from './select-components/MultiValueLabel/index.js'
import { SingleValue } from './select-components/SingleValue/index.js' import { SingleValue } from './select-components/SingleValue/index.js'
@@ -38,8 +39,8 @@ const baseClass = 'relationship'
const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) => { const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name,
admin: { admin: {
allowCreate = true, allowCreate = true,
allowEdit = true, allowEdit = true,
@@ -47,8 +48,6 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
description, description,
isSortable = true, isSortable = true,
sortOptions, sortOptions,
style,
width,
} = {}, } = {},
hasMany, hasMany,
label, label,
@@ -575,6 +574,8 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
valueToRender.value = null valueToRender.value = null
} }
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<div <div
className={[ className={[
@@ -589,10 +590,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
id={`field-${path.replace(/\./g, '__')}`} id={`field-${path.replace(/\./g, '__')}`}
style={{ style={styles}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -36,7 +36,6 @@ export type SelectInputProps = {
readonly showError?: boolean readonly showError?: boolean
readonly style?: React.CSSProperties readonly style?: React.CSSProperties
readonly value?: string | string[] readonly value?: string | string[]
readonly width?: React.CSSProperties['width']
} }
export const SelectInput: React.FC<SelectInputProps> = (props) => { export const SelectInput: React.FC<SelectInputProps> = (props) => {
@@ -60,7 +59,6 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
showError, showError,
style, style,
value, value,
width,
} = props } = props
const { i18n } = useTranslation() const { i18n } = useTranslation()
@@ -95,10 +93,7 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
id={`field-${path.replace(/\./g, '__')}`} id={`field-${path.replace(/\./g, '__')}`}
style={{ style={style}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -6,13 +6,14 @@ import type {
SelectFieldClientProps, SelectFieldClientProps,
} from 'payload' } from 'payload'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import type { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js' import type { ReactSelectAdapterProps } from '../../elements/ReactSelect/types.js'
import type { SelectInputProps } from './Input.js' import type { SelectInputProps } from './Input.js'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { SelectInput } from './Input.js' import { SelectInput } from './Input.js'
const formatOptions = (options: Option[]): OptionObject[] => const formatOptions = (options: Option[]): OptionObject[] =>
@@ -29,6 +30,7 @@ const formatOptions = (options: Option[]): OptionObject[] =>
const SelectFieldComponent: SelectFieldClientComponent = (props) => { const SelectFieldComponent: SelectFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, name,
admin: { admin: {
@@ -36,8 +38,6 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
description, description,
isClearable = true, isClearable = true,
isSortable = true, isSortable = true,
style,
width,
} = {} as SelectFieldClientProps['field']['admin'], } = {} as SelectFieldClientProps['field']['admin'],
hasMany = false, hasMany = false,
label, label,
@@ -96,6 +96,8 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
[readOnly, hasMany, setValue, onChangeFromProps], [readOnly, hasMany, setValue, onChangeFromProps],
) )
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<SelectInput <SelectInput
AfterInput={AfterInput} AfterInput={AfterInput}
@@ -116,9 +118,8 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
path={path} path={path}
readOnly={readOnly} readOnly={readOnly}
showError={showError} showError={showError}
style={style} style={styles}
value={value as string | string[]} value={value as string | string[]}
width={width}
/> />
) )
} }

View File

@@ -40,7 +40,6 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
style, style,
value, value,
valueToRender, valueToRender,
width,
} = props } = props
const { i18n, t } = useTranslation() const { i18n, t } = useTranslation()
@@ -57,10 +56,7 @@ export const TextInput: React.FC<TextInputProps> = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={style}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import type { TextFieldClientComponent } from 'payload' import type { TextFieldClientComponent } from 'payload'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react'
import type { Option } from '../../elements/ReactSelect/types.js' import type { Option } from '../../elements/ReactSelect/types.js'
import type { TextInputProps } from './types.js' import type { TextInputProps } from './types.js'
@@ -10,17 +10,18 @@ import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useConfig } from '../../providers/Config/index.js' import { useConfig } from '../../providers/Config/index.js'
import { useLocale } from '../../providers/Locale/index.js' import { useLocale } from '../../providers/Locale/index.js'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { isFieldRTL } from '../shared/index.js' import { isFieldRTL } from '../shared/index.js'
import { TextInput } from './Input.js'
import './index.scss' import './index.scss'
import { TextInput } from './Input.js'
export { TextInput, TextInputProps } export { TextInput, TextInputProps }
const TextFieldComponent: TextFieldClientComponent = (props) => { const TextFieldComponent: TextFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, admin: { className, description, placeholder, rtl } = {},
admin: { className, description, placeholder, rtl, style, width } = {},
hasMany, hasMany,
label, label,
localized, localized,
@@ -109,6 +110,8 @@ const TextFieldComponent: TextFieldClientComponent = (props) => {
} }
}, [value, hasMany]) }, [value, hasMany])
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<TextInput <TextInput
AfterInput={AfterInput} AfterInput={AfterInput}
@@ -137,10 +140,9 @@ const TextFieldComponent: TextFieldClientComponent = (props) => {
required={required} required={required}
rtl={renderRTL} rtl={renderRTL}
showError={showError} showError={showError}
style={style} style={styles}
value={(value as string) || ''} value={(value as string) || ''}
valueToRender={valueToRender as Option[]} valueToRender={valueToRender as Option[]}
width={width}
/> />
) )
} }

View File

@@ -37,5 +37,4 @@ export type TextInputProps = {
readonly style?: React.CSSProperties readonly style?: React.CSSProperties
readonly value?: string readonly value?: string
readonly valueToRender?: Option[] readonly valueToRender?: Option[]
readonly width?: React.CSSProperties['width']
} & SharedTextFieldProps } & SharedTextFieldProps

View File

@@ -33,7 +33,6 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
showError, showError,
style, style,
value, value,
width,
} = props } = props
const { i18n } = useTranslation() const { i18n } = useTranslation()
@@ -49,10 +48,7 @@ export const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
] ]
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
style={{ style={style}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -2,7 +2,7 @@
import type { TextareaFieldClientComponent, TextareaFieldValidation } from 'payload' import type { TextareaFieldClientComponent, TextareaFieldValidation } from 'payload'
import { getTranslation } from '@payloadcms/translations' import { getTranslation } from '@payloadcms/translations'
import React, { useCallback } from 'react' import React, { useCallback, useMemo } from 'react'
import type { TextAreaInputProps } from './types.js' import type { TextAreaInputProps } from './types.js'
@@ -11,17 +11,18 @@ import { withCondition } from '../../forms/withCondition/index.js'
import { useConfig } from '../../providers/Config/index.js' import { useConfig } from '../../providers/Config/index.js'
import { useLocale } from '../../providers/Locale/index.js' import { useLocale } from '../../providers/Locale/index.js'
import { useTranslation } from '../../providers/Translation/index.js' import { useTranslation } from '../../providers/Translation/index.js'
import { isFieldRTL } from '../shared/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js'
import './index.scss' import './index.scss'
import { isFieldRTL } from '../shared/index.js'
import { TextareaInput } from './Input.js' import { TextareaInput } from './Input.js'
export { TextareaInput, TextAreaInputProps } export { TextareaInput, TextAreaInputProps }
const TextareaFieldComponent: TextareaFieldClientComponent = (props) => { const TextareaFieldComponent: TextareaFieldClientComponent = (props) => {
const { const {
field,
field: { field: {
name, admin: { className, description, placeholder, rows, rtl } = {},
admin: { className, description, placeholder, rows, rtl, style, width } = {},
label, label,
localized, localized,
maxLength, maxLength,
@@ -67,6 +68,8 @@ const TextareaFieldComponent: TextareaFieldClientComponent = (props) => {
validate: memoizedValidate, validate: memoizedValidate,
}) })
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<TextareaInput <TextareaInput
AfterInput={AfterInput} AfterInput={AfterInput}
@@ -88,9 +91,8 @@ const TextareaFieldComponent: TextareaFieldClientComponent = (props) => {
rows={rows} rows={rows}
rtl={isRTL} rtl={isRTL}
showError={showError} showError={showError}
style={style} style={styles}
value={value} value={value}
width={width}
/> />
) )
} }

View File

@@ -26,5 +26,4 @@ export type TextAreaInputProps = {
readonly style?: React.CSSProperties readonly style?: React.CSSProperties
readonly value?: string readonly value?: string
readonly valueToRender?: string readonly valueToRender?: string
readonly width?: React.CSSProperties['width']
} }

View File

@@ -73,7 +73,6 @@ export type UploadInputProps = {
readonly showError?: boolean readonly showError?: boolean
readonly style?: React.CSSProperties readonly style?: React.CSSProperties
readonly value?: (number | string)[] | (number | string) readonly value?: (number | string)[] | (number | string)
readonly width?: React.CSSProperties['width']
} }
export function UploadInput(props: UploadInputProps) { export function UploadInput(props: UploadInputProps) {
@@ -102,7 +101,6 @@ export function UploadInput(props: UploadInputProps) {
showError, showError,
style, style,
value, value,
width,
} = props } = props
const [populatedDocs, setPopulatedDocs] = React.useState< const [populatedDocs, setPopulatedDocs] = React.useState<
@@ -169,7 +167,7 @@ export function UploadInput(props: UploadInputProps) {
} }
return false return false
}, [activeRelationTo, permissions, readOnly, allowCreate]) }, [activeRelationTo, permissions, allowCreate])
const onChange = React.useCallback( const onChange = React.useCallback(
(newValue) => { (newValue) => {
@@ -447,10 +445,7 @@ export function UploadInput(props: UploadInputProps) {
.filter(Boolean) .filter(Boolean)
.join(' ')} .join(' ')}
id={`field-${path?.replace(/\./g, '__')}`} id={`field-${path?.replace(/\./g, '__')}`}
style={{ style={style}
...style,
width,
}}
> >
<RenderCustomComponent <RenderCustomComponent
CustomComponent={Label} CustomComponent={Label}

View File

@@ -2,12 +2,13 @@
import type { UploadFieldClientProps } from 'payload' import type { UploadFieldClientProps } from 'payload'
import React from 'react' import React, { useMemo } from 'react'
import { useField } from '../../forms/useField/index.js' import { useField } from '../../forms/useField/index.js'
import { withCondition } from '../../forms/withCondition/index.js' import { withCondition } from '../../forms/withCondition/index.js'
import { useConfig } from '../../providers/Config/index.js' import { useConfig } from '../../providers/Config/index.js'
import './index.scss' import './index.scss'
import { mergeFieldStyles } from '../mergeFieldStyles.js'
import { UploadInput } from './Input.js' import { UploadInput } from './Input.js'
export { UploadInput } from './Input.js' export { UploadInput } from './Input.js'
@@ -17,9 +18,9 @@ export const baseClass = 'upload'
export function UploadComponent(props: UploadFieldClientProps) { export function UploadComponent(props: UploadFieldClientProps) {
const { const {
field,
field: { field: {
name, admin: { allowCreate, className, description, isSortable } = {},
admin: { allowCreate, className, description, isSortable, style, width } = {},
hasMany, hasMany,
label, label,
localized, localized,
@@ -54,6 +55,8 @@ export function UploadComponent(props: UploadFieldClientProps) {
validate: memoizedValidate, validate: memoizedValidate,
}) })
const styles = useMemo(() => mergeFieldStyles(field), [field])
return ( return (
<UploadInput <UploadInput
AfterInput={AfterInput} AfterInput={AfterInput}
@@ -78,9 +81,8 @@ export function UploadComponent(props: UploadFieldClientProps) {
required={required} required={required}
serverURL={config.serverURL} serverURL={config.serverURL}
showError={showError} showError={showError}
style={style} style={styles}
value={value} value={value}
width={width}
/> />
) )
} }

View File

@@ -0,0 +1,20 @@
import type { ClientField } from 'payload'
export const mergeFieldStyles = (
field: ClientField | Omit<ClientField, 'type'>,
): React.CSSProperties => ({
...(field?.admin?.style || {}),
...(field?.admin?.width
? {
'--field-width': field.admin.width,
}
: {
flex: '1 1 auto',
}),
// allow flex overrides to still take precedence over the fallback
...(field?.admin?.style?.flex
? {
flex: field.admin.style.flex,
}
: {}),
})