Breaking Changes: - Globals config: `admin.description` no longer accepts a custom component. You will have to move it to `admin.components.elements.Description` - Collections config: `admin.description` no longer accepts a custom component. You will have to move it to `admin.components.edit.Description` - All Fields: `field.admin.description` no longer accepts a custom component. You will have to move it to `field.admin.components.Description` - Collapsible Field: `field.label` no longer accepts a custom component. You will have to move it to `field.admin.components.RowLabel` - Array Field: `field.admin.components.RowLabel` no longer accepts strings or records - If you are using our exported field components in your own app, their `labelProps` property has been stripped down and no longer contains the `label` and `required` prop. Those can now only be configured at the top-level
157 lines
4.1 KiB
TypeScript
157 lines
4.1 KiB
TypeScript
/* eslint-disable react/destructuring-assignment */
|
|
/* eslint-disable react-hooks/exhaustive-deps */
|
|
'use client'
|
|
import type { Option } from 'payload/types'
|
|
|
|
import { optionIsObject } from 'payload/types'
|
|
import React, { useCallback } from 'react'
|
|
|
|
import { FieldLabel } from '../../forms/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 { Radio } from './Radio/index.js'
|
|
import './index.scss'
|
|
|
|
const baseClass = 'radio-group'
|
|
|
|
import { FieldDescription } from '@payloadcms/ui/forms/FieldDescription'
|
|
import { FieldError } from '@payloadcms/ui/forms/FieldError'
|
|
import { useFieldProps } from '@payloadcms/ui/forms/FieldPropsProvider'
|
|
|
|
import type { FormFieldBase } from '../shared/index.js'
|
|
|
|
export type RadioFieldProps = FormFieldBase & {
|
|
layout?: 'horizontal' | 'vertical'
|
|
name?: string
|
|
onChange?: OnChange
|
|
options?: Option[]
|
|
path?: string
|
|
value?: string
|
|
width?: string
|
|
}
|
|
|
|
export type OnChange<T = string> = (value: T) => void
|
|
|
|
const RadioGroupField: React.FC<RadioFieldProps> = (props) => {
|
|
const {
|
|
name,
|
|
CustomDescription,
|
|
CustomError,
|
|
CustomLabel,
|
|
className,
|
|
descriptionProps,
|
|
errorProps,
|
|
label,
|
|
labelProps,
|
|
layout = 'horizontal',
|
|
onChange: onChangeFromProps,
|
|
options = [],
|
|
path: pathFromProps,
|
|
readOnly: readOnlyFromProps,
|
|
required,
|
|
style,
|
|
validate,
|
|
value: valueFromProps,
|
|
width,
|
|
} = props
|
|
|
|
const { uuid } = useForm()
|
|
|
|
const memoizedValidate = useCallback(
|
|
(value, validationOptions) => {
|
|
if (typeof validate === 'function')
|
|
return validate(value, { ...validationOptions, options, required })
|
|
},
|
|
[validate, options, required],
|
|
)
|
|
|
|
const { path: pathFromContext, readOnly: readOnlyFromContext } = useFieldProps()
|
|
const readOnly = readOnlyFromProps || readOnlyFromContext
|
|
|
|
const {
|
|
path,
|
|
setValue,
|
|
showError,
|
|
value: valueFromContext,
|
|
} = useField<string>({
|
|
path: pathFromContext || pathFromProps || name,
|
|
validate: memoizedValidate,
|
|
})
|
|
|
|
const value = valueFromContext || valueFromProps
|
|
|
|
return (
|
|
<div
|
|
className={[
|
|
fieldBaseClass,
|
|
baseClass,
|
|
className,
|
|
`${baseClass}--layout-${layout}`,
|
|
showError && 'error',
|
|
readOnly && `${baseClass}--read-only`,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ')}
|
|
style={{
|
|
...style,
|
|
width,
|
|
}}
|
|
>
|
|
<div className={`${baseClass}__error-wrap`}>
|
|
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
|
|
</div>
|
|
<FieldLabel
|
|
CustomLabel={CustomLabel}
|
|
label={label}
|
|
required={required}
|
|
{...(labelProps || {})}
|
|
/>
|
|
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>
|
|
{options.map((option) => {
|
|
let optionValue = ''
|
|
|
|
if (optionIsObject(option)) {
|
|
optionValue = option.value
|
|
} else {
|
|
optionValue = option
|
|
}
|
|
|
|
const isSelected = String(optionValue) === String(value)
|
|
|
|
const id = `field-${path}-${optionValue}${uuid ? `-${uuid}` : ''}`
|
|
|
|
return (
|
|
<li key={`${path} - ${optionValue}`}>
|
|
<Radio
|
|
id={id}
|
|
isSelected={isSelected}
|
|
onChange={() => {
|
|
if (typeof onChangeFromProps === 'function') {
|
|
onChangeFromProps(optionValue)
|
|
}
|
|
|
|
if (!readOnly) {
|
|
setValue(optionValue)
|
|
}
|
|
}}
|
|
option={optionIsObject(option) ? option : { label: option, value: option }}
|
|
path={path}
|
|
uuid={uuid}
|
|
/>
|
|
</li>
|
|
)
|
|
})}
|
|
</ul>
|
|
{CustomDescription !== undefined ? (
|
|
CustomDescription
|
|
) : (
|
|
<FieldDescription {...(descriptionProps || {})} />
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const RadioGroup = withCondition(RadioGroupField)
|