feat(ui): split up Select component into Select and SelectInput (#6471)

This commit is contained in:
Alessio Gravili
2024-05-23 11:36:57 -04:00
committed by GitHub
parent 72c0534008
commit 661a4a099d
4 changed files with 164 additions and 78 deletions

View File

@@ -7,7 +7,7 @@ import { FieldLabel } from '../../forms/FieldLabel/index.js'
import { Check } from '../../icons/Check/index.js'
import { Line } from '../../icons/Line/index.js'
type Props = {
export type CheckboxInputProps = {
AfterInput?: React.ReactNode
BeforeInput?: React.ReactNode
CustomLabel?: React.ReactNode
@@ -26,7 +26,7 @@ type Props = {
export const inputBaseClass = 'checkbox-input'
export const CheckboxInput: React.FC<Props> = ({
export const CheckboxInput: React.FC<CheckboxInputProps> = ({
id,
name,
AfterInput,

View File

@@ -3,6 +3,7 @@ import type { ClientValidate } from 'payload/types'
import React, { useCallback } from 'react'
import type { CheckboxInputProps } from './Input.js'
import type { CheckboxFieldProps } from './types.js'
import { FieldDescription } from '../../forms/FieldDescription/index.js'
@@ -19,7 +20,7 @@ import './index.scss'
const baseClass = 'checkbox'
export { CheckboxFieldProps, CheckboxInput }
export { CheckboxFieldProps, CheckboxInput, type CheckboxInputProps }
const CheckboxField: React.FC<CheckboxFieldProps> = (props) => {
const {

View File

@@ -0,0 +1,131 @@
'use client'
import type { OptionObject } from 'payload/types'
import { getTranslation } from '@payloadcms/translations'
import React from 'react'
import type { SelectFieldProps } from './index.js'
import { ReactSelect } from '../../elements/ReactSelect/index.js'
import { FieldDescription } from '../../forms/FieldDescription/index.js'
import { FieldError } from '../../forms/FieldError/index.js'
import { FieldLabel } from '../../forms/FieldLabel/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { fieldBaseClass } from '../shared/index.js'
import './index.scss'
export type SelectInputProps = Omit<
SelectFieldProps,
| 'custom'
| 'disabled'
| 'docPreferences'
| 'locale'
| 'localized'
| 'options'
| 'rtl'
| 'type'
| 'user'
| 'validate'
| 'value'
> & {
options?: OptionObject[]
showError?: boolean
value?: string | string[]
}
export const SelectInput: React.FC<SelectInputProps> = (props) => {
const {
AfterInput,
BeforeInput,
CustomDescription,
CustomError,
CustomLabel,
className,
descriptionProps,
errorProps,
hasMany = false,
isClearable = true,
isSortable = true,
label,
labelProps,
onChange,
options,
path,
readOnly,
required,
showError,
style,
value,
width,
} = props
const { i18n } = useTranslation()
let valueToRender
if (hasMany && Array.isArray(value)) {
valueToRender = value.map((val) => {
const matchingOption = options.find((option) => option.value === val)
return {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : val,
value: matchingOption?.value ?? val,
}
})
} else if (value) {
const matchingOption = options.find((option) => option.value === value)
valueToRender = {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : value,
value: matchingOption?.value ?? value,
}
}
return (
<div
className={[
fieldBaseClass,
'select',
className,
showError && 'error',
readOnly && 'read-only',
]
.filter(Boolean)
.join(' ')}
id={`field-${path.replace(/\./g, '__')}`}
style={{
...style,
width,
}}
>
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<ReactSelect
disabled={readOnly}
isClearable={isClearable}
isMulti={hasMany}
isSortable={isSortable}
onChange={onChange}
options={options.map((option) => ({
...option,
label: getTranslation(option.label, i18n),
}))}
showError={showError}
value={valueToRender as OptionObject}
/>
{AfterInput}
</div>
{CustomDescription !== undefined ? (
CustomDescription
) : (
<FieldDescription {...(descriptionProps || {})} />
)}
</div>
)
}

View File

@@ -2,21 +2,15 @@
'use client'
import type { ClientValidate, Option, OptionObject } from 'payload/types'
import { getTranslation } from '@payloadcms/translations'
import React, { useCallback, useState } from 'react'
import type { FormFieldBase } from '../shared/index.js'
import type { SelectInputProps } from './Input.js'
import { ReactSelect } from '../../elements/ReactSelect/index.js'
import { FieldDescription } from '../../forms/FieldDescription/index.js'
import { FieldError } from '../../forms/FieldError/index.js'
import { FieldLabel } from '../../forms/FieldLabel/index.js'
import { useFieldProps } from '../../forms/FieldPropsProvider/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 './index.scss'
import { SelectInput } from './Input.js'
export type SelectFieldProps = FormFieldBase & {
hasMany?: boolean
@@ -68,8 +62,6 @@ const SelectField: React.FC<SelectFieldProps> = (props) => {
width,
} = props
const { i18n } = useTranslation()
const [options] = useState(formatOptions(optionsFromProps))
const memoizedValidate: ClientValidate = useCallback(
@@ -88,24 +80,6 @@ const SelectField: React.FC<SelectFieldProps> = (props) => {
validate: memoizedValidate,
})
let valueToRender
if (hasMany && Array.isArray(value)) {
valueToRender = value.map((val) => {
const matchingOption = options.find((option) => option.value === val)
return {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : val,
value: matchingOption?.value ?? val,
}
})
} else if (value) {
const matchingOption = options.find((option) => option.value === value)
valueToRender = {
label: matchingOption ? getTranslation(matchingOption.label, i18n) : value,
value: matchingOption?.value ?? value,
}
}
const onChange = useCallback(
(selectedOption) => {
if (!readOnly) {
@@ -133,54 +107,34 @@ const SelectField: React.FC<SelectFieldProps> = (props) => {
)
return (
<div
className={[
fieldBaseClass,
'select',
className,
showError && 'error',
readOnly && 'read-only',
]
.filter(Boolean)
.join(' ')}
id={`field-${path.replace(/\./g, '__')}`}
style={{
...style,
width,
}}
>
<FieldLabel
CustomLabel={CustomLabel}
label={label}
required={required}
{...(labelProps || {})}
/>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
<div className={`${fieldBaseClass}__wrap`}>
<FieldError CustomError={CustomError} path={path} {...(errorProps || {})} />
{BeforeInput}
<ReactSelect
disabled={readOnly}
isClearable={isClearable}
isMulti={hasMany}
isSortable={isSortable}
onChange={onChange}
options={options.map((option) => ({
...option,
label: getTranslation(option.label, i18n),
}))}
showError={showError}
value={valueToRender as OptionObject}
/>
{AfterInput}
</div>
{CustomDescription !== undefined ? (
CustomDescription
) : (
<FieldDescription {...(descriptionProps || {})} />
)}
</div>
<SelectInput
AfterInput={AfterInput}
BeforeInput={BeforeInput}
CustomDescription={CustomDescription}
CustomError={CustomError}
CustomLabel={CustomLabel}
className={className}
descriptionProps={descriptionProps}
errorProps={errorProps}
hasMany={hasMany}
isClearable={isClearable}
isSortable={isSortable}
label={label}
labelProps={labelProps}
name={name}
onChange={onChange}
options={options}
path={path}
readOnly={readOnly}
required={required}
showError={showError}
style={style}
value={value as string | string[]}
width={width}
/>
)
}
export const Select = withCondition(SelectField)
export { SelectInput, type SelectInputProps }