fix(ui): properly extracts label from field in FieldLabel component (#8190)
Although the `<FieldLabel />` component receives a `field` prop, it does
not use this prop to extract the `label` from the field. This is
currently only an issue when rendering this component directly, such as
within `admin.components.Label`. The label simply won't appear unless
explicitly provided, despite it being passed as `field.label`. This is
not an issue when rendering field components themselves, because they
properly thread this value through as a top-level prop.
Here's an example of the issue:
```tsx
import type { TextFieldLabelServerComponent } from 'payload'
import { FieldLabel } from '@payloadcms/ui'
import React from 'react'
export const MyCustomLabelComponent: TextFieldLabelServerComponent = ({ clientField }) => {
return (
<FieldLabel
field={clientField}
label={clientField.label} // this should not be needed!
/>
)
}
```
Here is the end result:
```tsx
import type { TextFieldLabelServerComponent } from 'payload'
import { FieldLabel } from '@payloadcms/ui'
import React from 'react'
export const MyCustomLabelComponent: TextFieldLabelServerComponent = ({ clientField }) => {
return <FieldLabel field={clientField} />
}
```
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { ServerProps, StaticLabel } from '../../config/types.js'
|
||||
import type { ClientField, Field } from '../../fields/config/types.js'
|
||||
import type { Field } from '../../fields/config/types.js'
|
||||
import type { MappedComponent } from '../types.js'
|
||||
import type { ClientFieldWithOptionalType } from './Field.js'
|
||||
|
||||
@@ -27,7 +27,7 @@ export type FieldLabelServerProps<
|
||||
} & GenericLabelProps &
|
||||
Partial<ServerProps>
|
||||
|
||||
export type SanitizedLabelProps<TFieldClient extends ClientField> = Omit<
|
||||
export type SanitizedLabelProps<TFieldClient extends ClientFieldWithOptionalType> = Omit<
|
||||
FieldLabelClientProps<TFieldClient>,
|
||||
'label' | 'required'
|
||||
>
|
||||
|
||||
@@ -41,7 +41,6 @@ const RichTextComponent: React.FC<
|
||||
style,
|
||||
width,
|
||||
} = {},
|
||||
label,
|
||||
required,
|
||||
},
|
||||
field,
|
||||
@@ -101,13 +100,7 @@ const RichTextComponent: React.FC<
|
||||
{...(errorProps || {})}
|
||||
alignCaret="left"
|
||||
/>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={Label} {...(labelProps || {})} />
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<ErrorBoundary fallbackRender={fallbackRender} onReset={() => {}}>
|
||||
<LexicalProvider
|
||||
|
||||
@@ -66,7 +66,6 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
|
||||
style,
|
||||
width,
|
||||
} = {},
|
||||
label,
|
||||
required,
|
||||
},
|
||||
labelProps,
|
||||
@@ -321,13 +320,7 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
Label={Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
field={field}
|
||||
/>
|
||||
<FieldLabel Label={Label} {...(labelProps || {})} field={field} />
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<FieldError CustomError={Error} field={field} path={path} {...(errorProps || {})} />
|
||||
<Slate
|
||||
|
||||
@@ -101,14 +101,7 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
? field.admin.components.Label
|
||||
: undefined
|
||||
|
||||
const Label = (
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={CustomLabelToRender}
|
||||
label={'label' in field ? (field.label as StaticLabel) : undefined}
|
||||
unstyled
|
||||
/>
|
||||
)
|
||||
const Label = <FieldLabel field={field} Label={CustomLabelToRender} unstyled />
|
||||
|
||||
const fieldAffectsDataSubFields =
|
||||
field &&
|
||||
|
||||
@@ -239,8 +239,6 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
|
||||
as="span"
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
unstyled
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
|
||||
@@ -231,8 +231,6 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
|
||||
as="span"
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Description}
|
||||
label={label}
|
||||
required={required}
|
||||
unstyled
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
|
||||
@@ -83,13 +83,7 @@ const CodeFieldComponent: CodeFieldClientComponent = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={field?.admin?.components?.Label} {...(labelProps || {})} />
|
||||
<div className={`${fieldBaseClass}__wrap`}>
|
||||
<FieldError
|
||||
CustomError={field?.admin?.components?.Error}
|
||||
|
||||
@@ -81,13 +81,7 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={field?.admin?.components?.Label} {...(labelProps || {})} />
|
||||
<div className={`${fieldBaseClass}__wrap`} id={`field-${path.replace(/\./g, '__')}`}>
|
||||
<FieldError
|
||||
CustomError={field?.admin?.components?.Error}
|
||||
|
||||
@@ -77,13 +77,7 @@ const EmailFieldComponent: EmailFieldClientComponent = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={field?.admin?.components?.Label} {...(labelProps || {})} />
|
||||
<div className={`${fieldBaseClass}__wrap`}>
|
||||
<FieldError
|
||||
CustomError={field?.admin?.components?.Error}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { FieldLabelClientComponent, GenericLabelProps } from 'payload'
|
||||
import type { FieldLabelClientComponent, GenericLabelProps, StaticLabel } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
@@ -44,9 +44,25 @@ const DefaultFieldLabel: React.FC<GenericLabelProps> = (props) => {
|
||||
export const FieldLabel: FieldLabelClientComponent = (props) => {
|
||||
const { Label, ...rest } = props
|
||||
|
||||
// Don't get `Label` from `field.admin.components.Label` here because
|
||||
// this will cause an infinite loop when threading field through custom usages of `FieldLabel`
|
||||
if (Label) {
|
||||
return <RenderComponent clientProps={rest} mappedComponent={Label} />
|
||||
}
|
||||
|
||||
return <DefaultFieldLabel {...rest} />
|
||||
return (
|
||||
<DefaultFieldLabel
|
||||
{...rest}
|
||||
label={
|
||||
typeof props?.label !== 'undefined'
|
||||
? props.label
|
||||
: props?.field && 'label' in props.field && (props.field.label as StaticLabel) // type assertion needed for `row` fields
|
||||
}
|
||||
required={
|
||||
typeof props.required !== 'undefined'
|
||||
? props.required
|
||||
: props?.field && 'required' in props.field && (props.field?.required as boolean) // type assertion needed for `group` fields
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -133,13 +133,7 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={field?.admin?.components?.Label} {...(labelProps || {})} />
|
||||
<div className={`${fieldBaseClass}__wrap`}>
|
||||
<FieldError
|
||||
CustomError={field?.admin?.components?.Error}
|
||||
|
||||
@@ -147,13 +147,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={field?.admin?.components?.Label} {...(labelProps || {})} />
|
||||
<div className={`${fieldBaseClass}__wrap`}>
|
||||
<FieldError
|
||||
CustomError={field?.admin?.components?.Error}
|
||||
|
||||
@@ -110,6 +110,7 @@ export const PointFieldComponent: PointFieldClientComponent = (props) => {
|
||||
<ul className={`${baseClass}__wrap`}>
|
||||
<li>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
{...getCoordinateFieldLabel('longitude')}
|
||||
/>
|
||||
|
||||
@@ -99,13 +99,7 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
|
||||
{...(errorProps || {})}
|
||||
alignCaret="left"
|
||||
/>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={field?.admin?.components?.Label} {...(labelProps || {})} />
|
||||
<div className={`${fieldBaseClass}__wrap`}>
|
||||
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>
|
||||
{options.map((option) => {
|
||||
|
||||
@@ -55,7 +55,6 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
width,
|
||||
} = {},
|
||||
hasMany,
|
||||
label,
|
||||
relationTo,
|
||||
required,
|
||||
},
|
||||
@@ -602,13 +601,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<FieldLabel
|
||||
field={field}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
required={required}
|
||||
{...(labelProps || {})}
|
||||
/>
|
||||
<FieldLabel field={field} Label={field?.admin?.components?.Label} {...(labelProps || {})} />
|
||||
<div className={`${fieldBaseClass}__wrap`}>
|
||||
<FieldError
|
||||
CustomError={field?.admin?.components?.Error}
|
||||
|
||||
@@ -36,7 +36,7 @@ export type SelectInputProps = {
|
||||
readonly isClearable?: boolean
|
||||
readonly isSortable?: boolean
|
||||
readonly Label?: MappedComponent
|
||||
readonly label: StaticLabel
|
||||
readonly label?: StaticLabel
|
||||
readonly labelProps?: Record<string, unknown>
|
||||
readonly name: string
|
||||
readonly onChange?: ReactSelectAdapterProps['onChange']
|
||||
|
||||
@@ -44,7 +44,6 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
|
||||
width,
|
||||
} = {} as SelectFieldClientProps['field']['admin'],
|
||||
hasMany = false,
|
||||
label,
|
||||
options: optionsFromProps = [],
|
||||
required,
|
||||
},
|
||||
@@ -111,7 +110,6 @@ const SelectFieldComponent: SelectFieldClientComponent = (props) => {
|
||||
isClearable={isClearable}
|
||||
isSortable={isSortable}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
|
||||
@@ -33,7 +33,6 @@ const TextFieldComponent: TextFieldClientComponent = (props) => {
|
||||
width,
|
||||
} = {},
|
||||
hasMany,
|
||||
label,
|
||||
localized,
|
||||
maxLength,
|
||||
maxRows,
|
||||
@@ -131,7 +130,6 @@ const TextFieldComponent: TextFieldClientComponent = (props) => {
|
||||
hasMany={hasMany}
|
||||
inputRef={inputRef}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
maxRows={maxRows}
|
||||
minRows={minRows}
|
||||
onChange={
|
||||
|
||||
@@ -27,7 +27,7 @@ export type TextInputProps = {
|
||||
readonly field?: MarkOptional<TextFieldClient, 'type'>
|
||||
readonly inputRef?: React.RefObject<HTMLInputElement>
|
||||
readonly Label?: MappedComponent
|
||||
readonly label: StaticLabel
|
||||
readonly label?: StaticLabel
|
||||
readonly labelProps?: Record<string, unknown>
|
||||
readonly maxRows?: number
|
||||
readonly minRows?: number
|
||||
|
||||
@@ -35,7 +35,6 @@ const TextareaFieldComponent: TextareaFieldClientComponent = (props) => {
|
||||
style,
|
||||
width,
|
||||
} = {},
|
||||
label,
|
||||
localized,
|
||||
maxLength,
|
||||
minLength,
|
||||
@@ -91,7 +90,6 @@ const TextareaFieldComponent: TextareaFieldClientComponent = (props) => {
|
||||
Error={field?.admin?.components?.Error}
|
||||
errorProps={errorProps}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
labelProps={labelProps}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value)
|
||||
|
||||
@@ -24,7 +24,7 @@ export type TextAreaInputProps = {
|
||||
readonly field?: MarkOptional<TextareaFieldClient, 'type'>
|
||||
readonly inputRef?: React.RefObject<HTMLInputElement>
|
||||
readonly Label?: MappedComponent
|
||||
readonly label: StaticLabel
|
||||
readonly label?: StaticLabel
|
||||
readonly labelProps?: FieldLabelClientProps<MarkOptional<TextareaFieldClient, 'type'>>
|
||||
readonly onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
|
||||
readonly onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>
|
||||
|
||||
@@ -63,7 +63,7 @@ export type UploadInputProps = {
|
||||
readonly hasMany?: boolean
|
||||
readonly isSortable?: boolean
|
||||
readonly Label?: MappedComponent
|
||||
readonly label: StaticLabel
|
||||
readonly label?: StaticLabel
|
||||
readonly labelProps?: FieldLabelClientProps<MarkOptional<UploadFieldClient, 'type'>>
|
||||
readonly maxRows?: number
|
||||
readonly onChange?: (e) => void
|
||||
|
||||
@@ -21,7 +21,6 @@ export function UploadComponent(props: UploadFieldClientProps) {
|
||||
_path,
|
||||
admin: { className, isSortable, readOnly: readOnlyFromAdmin, style, width } = {},
|
||||
hasMany,
|
||||
label,
|
||||
maxRows,
|
||||
relationTo,
|
||||
required,
|
||||
@@ -70,7 +69,6 @@ export function UploadComponent(props: UploadFieldClientProps) {
|
||||
hasMany={hasMany}
|
||||
isSortable={isSortable}
|
||||
Label={field?.admin?.components?.Label}
|
||||
label={label}
|
||||
maxRows={maxRows}
|
||||
onChange={setValue}
|
||||
path={path}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
'use client'
|
||||
|
||||
import type { EmailFieldClientComponent } from 'payload'
|
||||
|
||||
import { useFieldProps } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const CustomLabel = ({ schemaPath }) => {
|
||||
export const CustomLabel: EmailFieldClientComponent = ({ field }) => {
|
||||
const { path: pathFromContext } = useFieldProps()
|
||||
|
||||
const path = pathFromContext ?? schemaPath // pathFromContext will be undefined in list view
|
||||
const path = pathFromContext ?? field?._schemaPath // pathFromContext will be undefined in list view
|
||||
|
||||
return (
|
||||
<label className="custom-label" htmlFor={`field-${path.replace(/\./g, '__')}`}>
|
||||
|
||||
Reference in New Issue
Block a user