diff --git a/packages/payload/src/admin/fields/Select.ts b/packages/payload/src/admin/fields/Select.ts index 8ebe8453c..10d5ed440 100644 --- a/packages/payload/src/admin/fields/Select.ts +++ b/packages/payload/src/admin/fields/Select.ts @@ -25,7 +25,7 @@ type SelectFieldBaseClientProps = { readonly onChange?: (e: string | string[]) => void readonly path: string readonly validate?: SelectFieldValidation - readonly value?: string + readonly value?: string | string[] } type SelectFieldBaseServerProps = Pick diff --git a/test/admin/collections/CustomFields/fields/Select/CustomMultiSelect.tsx b/test/admin/collections/CustomFields/fields/Select/CustomMultiSelect.tsx new file mode 100644 index 000000000..6eb339ce2 --- /dev/null +++ b/test/admin/collections/CustomFields/fields/Select/CustomMultiSelect.tsx @@ -0,0 +1,41 @@ +'use client' + +import type { Option, SelectFieldClientComponent } from 'payload' + +import { SelectField, useField } from '@payloadcms/ui' +import React from 'react' + +export const CustomMultiSelect: SelectFieldClientComponent = (props) => { + const { path } = props + const { setValue, value } = useField({ path }) + const [options, setOptions] = React.useState([]) + + React.useEffect(() => { + const fetchOptions = () => { + const fetched: Option[] = [ + { label: 'Label 1', value: 'value1' }, + { label: 'Label 2', value: 'value2' }, + ] + setOptions(fetched) + } + void fetchOptions() + }, []) + + const onChange = (val: string | string[]) => { + setValue(Array.isArray(val) ? val : val ? [val] : []) + } + + return ( + + ) +} diff --git a/test/admin/collections/CustomFields/fields/Select/index.tsx b/test/admin/collections/CustomFields/fields/Select/index.tsx index 5c785bf0f..8a717c942 100644 --- a/test/admin/collections/CustomFields/fields/Select/index.tsx +++ b/test/admin/collections/CustomFields/fields/Select/index.tsx @@ -8,28 +8,21 @@ import React from 'react' export const CustomSelect: SelectFieldClientComponent = (props) => { const { path } = props const { setValue, value } = useField({ path }) - const [options, setOptions] = React.useState<{ label: string; value: string }[]>([]) + const [options, setOptions] = React.useState([]) React.useEffect(() => { const fetchOptions = () => { - const fetchedOptions = [ - { - label: 'Label 1', - value: 'value1', - }, - { - label: 'Label 2', - value: 'value2', - }, + const fetchedOptions: Option[] = [ + { label: 'Label 1', value: 'value1' }, + { label: 'Label 2', value: 'value2' }, ] setOptions(fetchedOptions) } void fetchOptions() }, []) - const onChange = (selected: Option | Option[]) => { - const options = Array.isArray(selected) ? selected : [selected] - setValue(options.map((option) => (typeof option === 'string' ? option : option.value))) + const onChange = (val: string | string[]) => { + setValue(Array.isArray(val) ? (val[0] ?? '') : val) } return ( @@ -38,12 +31,10 @@ export const CustomSelect: SelectFieldClientComponent = (props) => { {...props} field={{ ...props.field, - name: path, - hasMany: true, options, }} onChange={onChange} - value={value} + value={value ?? ''} /> ) diff --git a/test/admin/collections/CustomFields/index.ts b/test/admin/collections/CustomFields/index.ts index c8bc7527c..05f09132d 100644 --- a/test/admin/collections/CustomFields/index.ts +++ b/test/admin/collections/CustomFields/index.ts @@ -82,6 +82,16 @@ export const CustomFields: CollectionConfig = { }, }, }, + { + name: 'customMultiSelectField', + type: 'text', + hasMany: true, + admin: { + components: { + Field: '/collections/CustomFields/fields/Select/CustomMultiSelect.js#CustomMultiSelect', + }, + }, + }, { name: 'relationshipFieldWithBeforeAfterInputs', type: 'relationship', diff --git a/test/admin/e2e/document-view/e2e.spec.ts b/test/admin/e2e/document-view/e2e.spec.ts index 64383c571..a4705ae0e 100644 --- a/test/admin/e2e/document-view/e2e.spec.ts +++ b/test/admin/e2e/document-view/e2e.spec.ts @@ -649,6 +649,26 @@ describe('Document View', () => { await page.locator('#field-customSelectField .rs__control').click() await expect(page.locator('#field-customSelectField .rs__option')).toHaveCount(2) }) + + test('should render custom multi select options', async () => { + await page.goto(customFieldsURL.create) + await page.locator('#field-customMultiSelectField .rs__control').click() + await expect(page.locator('#field-customMultiSelectField .rs__option')).toHaveCount(2) + }) + + test('should allow selecting multiple values in custom multi select', async () => { + await page.goto(customFieldsURL.create) + + const control = page.locator('#field-customMultiSelectField .rs__control') + + await control.click() + await page.locator('.rs__option', { hasText: 'Label 1' }).click() + await expect(page.locator('#field-customMultiSelectField .rs__multi-value')).toHaveCount(1) + + await control.click() + await page.locator('.rs__option', { hasText: 'Label 2' }).click() + await expect(page.locator('#field-customMultiSelectField .rs__multi-value')).toHaveCount(2) + }) }) }) diff --git a/test/admin/payload-types.ts b/test/admin/payload-types.ts index d0b1ebfad..b7fab557c 100644 --- a/test/admin/payload-types.ts +++ b/test/admin/payload-types.ts @@ -384,6 +384,7 @@ export interface CustomField { descriptionAsComponent?: string | null; customSelectField?: string | null; customSelectInput?: string | null; + customMultiSelectField?: string[] | null; relationshipFieldWithBeforeAfterInputs?: (string | null) | Post; arrayFieldWithBeforeAfterInputs?: | { @@ -927,6 +928,7 @@ export interface CustomFieldsSelect { descriptionAsComponent?: T; customSelectField?: T; customSelectInput?: T; + customMultiSelectField?: T; relationshipFieldWithBeforeAfterInputs?: T; arrayFieldWithBeforeAfterInputs?: | T