diff --git a/packages/payload/src/fields/config/client.ts b/packages/payload/src/fields/config/client.ts index ded2b90fe..be2de5426 100644 --- a/packages/payload/src/fields/config/client.ts +++ b/packages/payload/src/fields/config/client.ts @@ -96,25 +96,6 @@ export const createClientField = ({ 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) { case 'array': case 'collapsible': diff --git a/packages/richtext-lexical/src/field/Field.tsx b/packages/richtext-lexical/src/field/Field.tsx index d629615d2..64455d543 100644 --- a/packages/richtext-lexical/src/field/Field.tsx +++ b/packages/richtext-lexical/src/field/Field.tsx @@ -2,7 +2,8 @@ import type { EditorState, SerializedEditorState } from 'lexical' 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 type { SanitizedClientEditorConfig } from '../lexical/config/types.js' @@ -22,9 +23,10 @@ const RichTextComponent: React.FC< > = (props) => { const { editorConfig, + field, field: { name, - admin: { className, readOnly: readOnlyFromAdmin, style, width } = {}, + admin: { className, readOnly: readOnlyFromAdmin } = {}, label, localized, required, @@ -87,15 +89,10 @@ const RichTextComponent: React.FC< [setValue], ) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return ( -
+
{Error} {Label || }
diff --git a/packages/richtext-slate/src/field/RichText.tsx b/packages/richtext-slate/src/field/RichText.tsx index 79538812e..705178aaa 100644 --- a/packages/richtext-slate/src/field/RichText.tsx +++ b/packages/richtext-slate/src/field/RichText.tsx @@ -7,6 +7,7 @@ import type { ReactEditor } from 'slate-react' import { getTranslation } from '@payloadcms/translations' import { FieldLabel, useEditDepth, useField, useTranslation, withCondition } from '@payloadcms/ui' +import { mergeFieldStyles } from '@payloadcms/ui/shared' import { isHotkey } from 'is-hotkey' import React, { useCallback, useEffect, useMemo, useRef } from 'react' 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 { richTextValidate } from '../data/validation.js' import { listTypes } from './elements/listTypes.js' -import { hotkeys } from './hotkeys.js' import './index.scss' +import { hotkeys } from './hotkeys.js' import { toggleLeaf } from './leaves/toggle.js' import { withEnterBreakOut } from './plugins/withEnterBreakOut.js' import { withHTML } from './plugins/withHTML.js' @@ -42,9 +43,10 @@ declare module 'slate' { const RichTextField: React.FC = (props) => { const { elements, + field, field: { name, - admin: { className, placeholder, readOnly: readOnlyFromAdmin, style, width } = {}, + admin: { className, placeholder, readOnly: readOnlyFromAdmin } = {}, label, required, }, @@ -274,6 +276,8 @@ const RichTextField: React.FC = (props) => { // } // }, [path, editor]); + const styles = useMemo(() => mergeFieldStyles(field), [field]) + const classes = [ baseClass, 'field-type', @@ -300,13 +304,7 @@ const RichTextField: React.FC = (props) => { } return ( -
+
{Label || }
{Error} diff --git a/packages/ui/src/exports/shared/index.ts b/packages/ui/src/exports/shared/index.ts index 8ac53f14c..33cad82d3 100644 --- a/packages/ui/src/exports/shared/index.ts +++ b/packages/ui/src/exports/shared/index.ts @@ -4,6 +4,7 @@ export { getInitialColumns } from '../../elements/TableColumns/getInitialColumns 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 { WithServerSideProps } from '../../elements/WithServerSideProps/index.js' +export { mergeFieldStyles } from '../../fields/mergeFieldStyles.js' export { reduceToSerializableFields } from '../../forms/Form/reduceToSerializableFields.js' export { PayloadIcon } from '../../graphics/Icon/index.js' export { PayloadLogo } from '../../graphics/Logo/index.js' diff --git a/packages/ui/src/fields/Checkbox/index.tsx b/packages/ui/src/fields/Checkbox/index.tsx index ca6421775..336a7285b 100644 --- a/packages/ui/src/fields/Checkbox/index.tsx +++ b/packages/ui/src/fields/Checkbox/index.tsx @@ -5,7 +5,7 @@ import type { CheckboxFieldValidation, } from 'payload' -import React, { useCallback } from 'react' +import React, { useCallback, useMemo } from 'react' 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 { useEditDepth } from '../../providers/EditDepth/index.js' import { generateFieldID } from '../../utilities/generateFieldID.js' -import { fieldBaseClass } from '../shared/index.js' +import { mergeFieldStyles } from '../mergeFieldStyles.js' import './index.scss' +import { fieldBaseClass } from '../shared/index.js' import { CheckboxInput } from './Input.js' const baseClass = 'checkbox' @@ -30,14 +31,9 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => { id, checked: checkedFromProps, disableFormData, + field, field: { - name, - admin: { - className, - description, - style, - width, - } = {} as CheckboxFieldClientProps['field']['admin'], + admin: { className, description } = {} as CheckboxFieldClientProps['field']['admin'], label, required, } = {} as CheckboxFieldClientProps['field'], @@ -85,6 +81,8 @@ const CheckboxFieldComponent: CheckboxFieldClientComponent = (props) => { const fieldID = id || generateFieldID(path, editDepth, uuid) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={styles} > { const { + field, field: { - name, - admin: { - className, - description, - editorOptions = {}, - language = 'javascript', - style, - width, - } = {}, + admin: { className, description, editorOptions = {}, language = 'javascript' } = {}, label, localized, required, @@ -60,6 +54,8 @@ const CodeFieldComponent: CodeFieldClientComponent = (props) => { validate: memoizedValidate, }) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={styles} > { void fetchInitialState() }, [getPreference, preferencesKey, fieldPreferencesKey, initCollapsed, path]) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + if (typeof collapsedOnMount !== 'boolean') { return null } - const style: AdminClient['style'] = { - ...field.admin?.style, - '--field-width': field.admin.width, - } - return ( @@ -120,7 +118,7 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => { .filter(Boolean) .join(' ')} id={`field-${fieldPreferencesKey}`} - style={style} + style={styles} > { const { + field, field: { - name, - admin: { className, date: datePickerProps, description, placeholder, style, width } = {}, + admin: { className, date: datePickerProps, description, placeholder } = {}, label, localized, required, @@ -52,6 +53,8 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => { validate: memoizedValidate, }) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={styles} > { const { + field, field: { - name, admin: { autoComplete, className, description, placeholder, - style, - width, } = {} as EmailFieldClientProps['field']['admin'], label, localized, @@ -60,15 +59,14 @@ const EmailFieldComponent: EmailFieldClientComponent = (props) => { validate: memoizedValidate, }) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ const { - field: { - name, - admin: { className, description, hideGutter, style, width } = {}, - fields, - label, - }, + field, + field: { name, admin: { className, description, hideGutter } = {}, fields, label }, path, permissions, readOnly, @@ -50,6 +47,8 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => { const isTopLevel = !(isWithinCollapsible || isWithinGroup || isWithinRow) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ .filter(Boolean) .join(' ')} id={`field-${path?.replace(/\./g, '__')}`} - style={{ - ...style, - width, - }} + style={styles} >
diff --git a/packages/ui/src/fields/JSON/index.tsx b/packages/ui/src/fields/JSON/index.tsx index 235d48e40..38ab2f8f8 100644 --- a/packages/ui/src/fields/JSON/index.tsx +++ b/packages/ui/src/fields/JSON/index.tsx @@ -1,7 +1,7 @@ 'use client' 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 { RenderCustomComponent } from '../../elements/RenderCustomComponent/index.js' @@ -10,16 +10,17 @@ import { withCondition } from '../../forms/withCondition/index.js' import { FieldDescription } from '../FieldDescription/index.js' import { FieldError } from '../FieldError/index.js' import { FieldLabel } from '../FieldLabel/index.js' -import { fieldBaseClass } from '../shared/index.js' +import { mergeFieldStyles } from '../mergeFieldStyles.js' import './index.scss' +import { fieldBaseClass } from '../shared/index.js' const baseClass = 'json-field' const JSONFieldComponent: JSONFieldClientComponent = (props) => { const { + field, field: { - name, - admin: { className, description, editorOptions, maxHeight, style, width } = {}, + admin: { className, description, editorOptions, maxHeight } = {}, jsonSchema, label, localized, @@ -105,6 +106,8 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => { setHasLoadedValue(true) }, [initialValue, value, hasLoadedValue]) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={styles} > { const { field, field: { - name, admin: { allowCreate }, collection, label, diff --git a/packages/ui/src/fields/Number/index.tsx b/packages/ui/src/fields/Number/index.tsx index 18a8d5cbd..6e320a83b 100644 --- a/packages/ui/src/fields/Number/index.tsx +++ b/packages/ui/src/fields/Number/index.tsx @@ -3,7 +3,7 @@ import type { NumberFieldClientComponent, NumberFieldClientProps } from 'payload import { getTranslation } from '@payloadcms/translations' 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' @@ -15,20 +15,19 @@ import { useTranslation } from '../../providers/Translation/index.js' import { FieldDescription } from '../FieldDescription/index.js' import { FieldError } from '../FieldError/index.js' import { FieldLabel } from '../FieldLabel/index.js' -import { fieldBaseClass } from '../shared/index.js' +import { mergeFieldStyles } from '../mergeFieldStyles.js' import './index.scss' +import { fieldBaseClass } from '../shared/index.js' const NumberFieldComponent: NumberFieldClientComponent = (props) => { const { + field, field: { - name, admin: { className, description, placeholder, step = 1, - style, - width, } = {} as NumberFieldClientProps['field']['admin'], hasMany = false, label, @@ -123,6 +122,8 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => { } }, [value, hasMany]) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={styles} > = (props) => { const { autoComplete, + field, field: { - name, admin: { className, disabled: disabledFromProps, placeholder, rtl, - style, - width, } = {} as PasswordFieldProps['field']['admin'], label, localized, @@ -86,6 +85,8 @@ const PasswordFieldComponent: React.FC = (props) => { localizationConfig: config.localization || undefined, }) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return ( = (props) => { required={required} rtl={renderRTL} showError={showError} - style={style} + style={styles} value={(value as string) || ''} - width={width} /> ) } diff --git a/packages/ui/src/fields/Point/index.tsx b/packages/ui/src/fields/Point/index.tsx index 3d75a3857..37f79b0cd 100644 --- a/packages/ui/src/fields/Point/index.tsx +++ b/packages/ui/src/fields/Point/index.tsx @@ -2,7 +2,7 @@ import type { PointFieldClientComponent, PointFieldValidation } from 'payload' import { getTranslation } from '@payloadcms/translations' -import React, { useCallback } from 'react' +import React, { useCallback, useMemo } from 'react' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/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 { withCondition } from '../../forms/withCondition/index.js' import { useTranslation } from '../../providers/Translation/index.js' -import { fieldBaseClass } from '../shared/index.js' +import { mergeFieldStyles } from '../mergeFieldStyles.js' import './index.scss' +import { fieldBaseClass } from '../shared/index.js' const baseClass = 'point' export const PointFieldComponent: PointFieldClientComponent = (props) => { const { + field, field: { - name, - admin: { className, description, placeholder, step, style, width } = {}, + admin: { className, description, placeholder, step } = {}, label, localized, required, @@ -71,6 +72,8 @@ export const PointFieldComponent: PointFieldClientComponent = (props) => { return `${fieldLabel}${fieldLabel ? ' - ' : ''}${suffix}` } + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
{ ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={styles} >
  • diff --git a/packages/ui/src/fields/RadioGroup/index.tsx b/packages/ui/src/fields/RadioGroup/index.tsx index c4a2db185..66b8cddc7 100644 --- a/packages/ui/src/fields/RadioGroup/index.tsx +++ b/packages/ui/src/fields/RadioGroup/index.tsx @@ -2,7 +2,7 @@ import type { RadioFieldClientComponent, RadioFieldClientProps } from 'payload' import { optionIsObject } from 'payload/shared' -import React, { useCallback } from 'react' +import React, { useCallback, useMemo } from 'react' import { RenderCustomComponent } from '../../elements/RenderCustomComponent/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 { useField } from '../../forms/useField/index.js' import { withCondition } from '../../forms/withCondition/index.js' -import { fieldBaseClass } from '../shared/index.js' +import { mergeFieldStyles } from '../mergeFieldStyles.js' import './index.scss' +import { fieldBaseClass } from '../shared/index.js' import { Radio } from './Radio/index.js' const baseClass = 'radio-group' @@ -20,13 +21,12 @@ const baseClass = 'radio-group' const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => { const { disableModifyingForm: disableModifyingFormFromProps, + field, field: { admin: { className, description, layout = 'horizontal', - style, - width, } = {} as RadioFieldClientProps['field']['admin'], label, localized, @@ -63,6 +63,8 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => { const value = valueFromContext || valueFromProps + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
    { ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={styles} > { const { + field, field: { - name, admin: { allowCreate = true, allowEdit = true, @@ -47,8 +48,6 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) => description, isSortable = true, sortOptions, - style, - width, } = {}, hasMany, label, @@ -575,6 +574,8 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) => valueToRender.value = null } + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return (
    .filter(Boolean) .join(' ')} id={`field-${path.replace(/\./g, '__')}`} - style={{ - ...style, - width, - }} + style={styles} > = (props) => { @@ -60,7 +59,6 @@ export const SelectInput: React.FC = (props) => { showError, style, value, - width, } = props const { i18n } = useTranslation() @@ -95,10 +93,7 @@ export const SelectInput: React.FC = (props) => { .filter(Boolean) .join(' ')} id={`field-${path.replace(/\./g, '__')}`} - style={{ - ...style, - width, - }} + style={style} > @@ -29,6 +30,7 @@ const formatOptions = (options: Option[]): OptionObject[] => const SelectFieldComponent: SelectFieldClientComponent = (props) => { const { + field, field: { name, admin: { @@ -36,8 +38,6 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => { description, isClearable = true, isSortable = true, - style, - width, } = {} as SelectFieldClientProps['field']['admin'], hasMany = false, label, @@ -96,6 +96,8 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => { [readOnly, hasMany, setValue, onChangeFromProps], ) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return ( { path={path} readOnly={readOnly} showError={showError} - style={style} + style={styles} value={value as string | string[]} - width={width} /> ) } diff --git a/packages/ui/src/fields/Text/Input.tsx b/packages/ui/src/fields/Text/Input.tsx index 09ea5650c..bc03c85bf 100644 --- a/packages/ui/src/fields/Text/Input.tsx +++ b/packages/ui/src/fields/Text/Input.tsx @@ -40,7 +40,6 @@ export const TextInput: React.FC = (props) => { style, value, valueToRender, - width, } = props const { i18n, t } = useTranslation() @@ -57,10 +56,7 @@ export const TextInput: React.FC = (props) => { ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={style} > { const { + field, field: { - name, - admin: { className, description, placeholder, rtl, style, width } = {}, + admin: { className, description, placeholder, rtl } = {}, hasMany, label, localized, @@ -109,6 +110,8 @@ const TextFieldComponent: TextFieldClientComponent = (props) => { } }, [value, hasMany]) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return ( { required={required} rtl={renderRTL} showError={showError} - style={style} + style={styles} value={(value as string) || ''} valueToRender={valueToRender as Option[]} - width={width} /> ) } diff --git a/packages/ui/src/fields/Text/types.ts b/packages/ui/src/fields/Text/types.ts index f36a94f76..aee602325 100644 --- a/packages/ui/src/fields/Text/types.ts +++ b/packages/ui/src/fields/Text/types.ts @@ -37,5 +37,4 @@ export type TextInputProps = { readonly style?: React.CSSProperties readonly value?: string readonly valueToRender?: Option[] - readonly width?: React.CSSProperties['width'] } & SharedTextFieldProps diff --git a/packages/ui/src/fields/Textarea/Input.tsx b/packages/ui/src/fields/Textarea/Input.tsx index 9156c1f13..765aa7602 100644 --- a/packages/ui/src/fields/Textarea/Input.tsx +++ b/packages/ui/src/fields/Textarea/Input.tsx @@ -33,7 +33,6 @@ export const TextareaInput: React.FC = (props) => { showError, style, value, - width, } = props const { i18n } = useTranslation() @@ -49,10 +48,7 @@ export const TextareaInput: React.FC = (props) => { ] .filter(Boolean) .join(' ')} - style={{ - ...style, - width, - }} + style={style} > { const { + field, field: { - name, - admin: { className, description, placeholder, rows, rtl, style, width } = {}, + admin: { className, description, placeholder, rows, rtl } = {}, label, localized, maxLength, @@ -67,6 +68,8 @@ const TextareaFieldComponent: TextareaFieldClientComponent = (props) => { validate: memoizedValidate, }) + const styles = useMemo(() => mergeFieldStyles(field), [field]) + return ( { rows={rows} rtl={isRTL} showError={showError} - style={style} + style={styles} value={value} - width={width} /> ) } diff --git a/packages/ui/src/fields/Textarea/types.ts b/packages/ui/src/fields/Textarea/types.ts index 1d1258dbb..e53c92a7b 100644 --- a/packages/ui/src/fields/Textarea/types.ts +++ b/packages/ui/src/fields/Textarea/types.ts @@ -26,5 +26,4 @@ export type TextAreaInputProps = { readonly style?: React.CSSProperties readonly value?: string readonly valueToRender?: string - readonly width?: React.CSSProperties['width'] } diff --git a/packages/ui/src/fields/Upload/Input.tsx b/packages/ui/src/fields/Upload/Input.tsx index 5f3c12047..6fd0e87df 100644 --- a/packages/ui/src/fields/Upload/Input.tsx +++ b/packages/ui/src/fields/Upload/Input.tsx @@ -73,7 +73,6 @@ export type UploadInputProps = { readonly showError?: boolean readonly style?: React.CSSProperties readonly value?: (number | string)[] | (number | string) - readonly width?: React.CSSProperties['width'] } export function UploadInput(props: UploadInputProps) { @@ -102,7 +101,6 @@ export function UploadInput(props: UploadInputProps) { showError, style, value, - width, } = props const [populatedDocs, setPopulatedDocs] = React.useState< @@ -169,7 +167,7 @@ export function UploadInput(props: UploadInputProps) { } return false - }, [activeRelationTo, permissions, readOnly, allowCreate]) + }, [activeRelationTo, permissions, allowCreate]) const onChange = React.useCallback( (newValue) => { @@ -447,10 +445,7 @@ export function UploadInput(props: UploadInputProps) { .filter(Boolean) .join(' ')} id={`field-${path?.replace(/\./g, '__')}`} - style={{ - ...style, - width, - }} + style={style} > mergeFieldStyles(field), [field]) + return ( ) } diff --git a/packages/ui/src/fields/mergeFieldStyles.ts b/packages/ui/src/fields/mergeFieldStyles.ts new file mode 100644 index 000000000..c7885c5fc --- /dev/null +++ b/packages/ui/src/fields/mergeFieldStyles.ts @@ -0,0 +1,20 @@ +import type { ClientField } from 'payload' + +export const mergeFieldStyles = ( + field: ClientField | Omit, +): 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, + } + : {}), +})