fix: ensures select & radio field option labels accept JSX elements (#11658)
### What This PR ensures that `select` and `radio` field option labels properly accept and render JSX elements. ### Why Previously, JSX elements could be passed as option labels, but the type definition for options only allowed `LabelFunction` or `StaticLabel`, resulting in type errors. Additionally: - JSX labels did not render correctly in the list view but now do. - In the versions diff view, JSX labels were not supported since it only accepts strings. To address this, we now fallback to the option `value` when the label is a JSX element.
This commit is contained in:
21
test/fields/collections/Radio/CustomJSXLabel.tsx
Normal file
21
test/fields/collections/Radio/CustomJSXLabel.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
export const CustomJSXLabel = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic-icon"
|
||||
height="20px"
|
||||
id="payload-logo"
|
||||
viewBox="0 0 25 25"
|
||||
width="20px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.8673 21.2336L4.40922 16.9845C4.31871 16.9309 4.25837 16.8355 4.25837 16.7282V10.1609C4.25837 10.0477 4.38508 9.97616 4.48162 10.0298L13.1404 14.9642C13.2611 15.0358 13.412 14.9464 13.412 14.8093V11.6091C13.412 11.4839 13.3456 11.3647 13.2309 11.2992L2.81624 5.36353C2.72573 5.30989 2.60505 5.30989 2.51454 5.36353L1.15085 6.14422C1.06034 6.19786 1 6.29321 1 6.40048V18.5995C1 18.7068 1.06034 18.8021 1.15085 18.8558L11.8491 24.9583C11.9397 25.0119 12.0603 25.0119 12.1509 24.9583L21.1355 19.8331C21.2562 19.7616 21.2562 19.5948 21.1355 19.5232L18.3357 17.9261C18.2211 17.8605 18.0883 17.8605 17.9737 17.9261L12.175 21.2336C12.0845 21.2872 11.9638 21.2872 11.8733 21.2336H11.8673Z"
|
||||
fill="var(--theme-elevation-1000)"
|
||||
/>
|
||||
<path
|
||||
d="M22.8491 6.13827L12.1508 0.0417218C12.0603 -0.0119135 11.9397 -0.0119135 11.8491 0.0417218L6.19528 3.2658C6.0746 3.33731 6.0746 3.50418 6.19528 3.57569L8.97092 5.16091C9.08557 5.22647 9.21832 5.22647 9.33296 5.16091L11.8672 3.71872C11.9578 3.66508 12.0784 3.66508 12.1689 3.71872L19.627 7.96782C19.7175 8.02146 19.7778 8.11681 19.7778 8.22408V14.8212C19.7778 14.9464 19.8442 15.0656 19.9589 15.1311L22.7345 16.7104C22.8552 16.7819 23.006 16.6925 23.006 16.5554V6.40048C23.006 6.29321 22.9457 6.19786 22.8552 6.14423L22.8491 6.13827Z"
|
||||
fill="var(--theme-elevation-1000)"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -75,4 +75,16 @@ describe('Radio', () => {
|
||||
'Value One',
|
||||
)
|
||||
})
|
||||
|
||||
test('should show custom JSX label in list', async () => {
|
||||
await page.goto(url.list)
|
||||
await expect(page.locator('.cell-radioWithJsxLabelOption svg#payload-logo')).toBeVisible()
|
||||
})
|
||||
|
||||
test('should show custom JSX label while editing', async () => {
|
||||
await page.goto(url.create)
|
||||
await expect(
|
||||
page.locator('label[for="field-radioWithJsxLabelOption-three"] svg#payload-logo'),
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { radioFieldsSlug } from '../../slugs.js'
|
||||
import { CustomJSXLabel } from './CustomJSXLabel.js'
|
||||
|
||||
const RadioFields: CollectionConfig = {
|
||||
slug: radioFieldsSlug,
|
||||
@@ -27,6 +28,26 @@ const RadioFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'radioWithJsxLabelOption',
|
||||
label: 'Radio with JSX label option',
|
||||
type: 'radio',
|
||||
defaultValue: 'three',
|
||||
options: [
|
||||
{
|
||||
label: 'Value One',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: CustomJSXLabel,
|
||||
value: 'three',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
21
test/fields/collections/Select/CustomJSXLabel.tsx
Normal file
21
test/fields/collections/Select/CustomJSXLabel.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
export const CustomJSXLabel = () => {
|
||||
return (
|
||||
<svg
|
||||
className="graphic-icon"
|
||||
height="20px"
|
||||
id="payload-logo"
|
||||
viewBox="0 0 25 25"
|
||||
width="20px"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.8673 21.2336L4.40922 16.9845C4.31871 16.9309 4.25837 16.8355 4.25837 16.7282V10.1609C4.25837 10.0477 4.38508 9.97616 4.48162 10.0298L13.1404 14.9642C13.2611 15.0358 13.412 14.9464 13.412 14.8093V11.6091C13.412 11.4839 13.3456 11.3647 13.2309 11.2992L2.81624 5.36353C2.72573 5.30989 2.60505 5.30989 2.51454 5.36353L1.15085 6.14422C1.06034 6.19786 1 6.29321 1 6.40048V18.5995C1 18.7068 1.06034 18.8021 1.15085 18.8558L11.8491 24.9583C11.9397 25.0119 12.0603 25.0119 12.1509 24.9583L21.1355 19.8331C21.2562 19.7616 21.2562 19.5948 21.1355 19.5232L18.3357 17.9261C18.2211 17.8605 18.0883 17.8605 17.9737 17.9261L12.175 21.2336C12.0845 21.2872 11.9638 21.2872 11.8733 21.2336H11.8673Z"
|
||||
fill="var(--theme-elevation-1000)"
|
||||
/>
|
||||
<path
|
||||
d="M22.8491 6.13827L12.1508 0.0417218C12.0603 -0.0119135 11.9397 -0.0119135 11.8491 0.0417218L6.19528 3.2658C6.0746 3.33731 6.0746 3.50418 6.19528 3.57569L8.97092 5.16091C9.08557 5.22647 9.21832 5.22647 9.33296 5.16091L11.8672 3.71872C11.9578 3.66508 12.0784 3.66508 12.1689 3.71872L19.627 7.96782C19.7175 8.02146 19.7778 8.11681 19.7778 8.22408V14.8212C19.7778 14.9464 19.8442 15.0656 19.9589 15.1311L22.7345 16.7104C22.8552 16.7819 23.006 16.6925 23.006 16.5554V6.40048C23.006 6.29321 22.9457 6.19786 22.8552 6.14423L22.8491 6.13827Z"
|
||||
fill="var(--theme-elevation-1000)"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -31,7 +31,7 @@ let page: Page
|
||||
let serverURL: string
|
||||
let url: AdminUrlUtil
|
||||
|
||||
describe('Radio', () => {
|
||||
describe('Select', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
@@ -75,4 +75,24 @@ describe('Radio', () => {
|
||||
await saveDocAndAssert(page)
|
||||
await expect(field.locator('.rs__value-container')).toContainText('One')
|
||||
})
|
||||
|
||||
test('should show custom JSX option label in edit', async () => {
|
||||
await page.goto(url.create)
|
||||
|
||||
const svgLocator = page.locator('#field-selectWithJsxLabelOption svg#payload-logo')
|
||||
|
||||
await expect(svgLocator).toBeVisible()
|
||||
})
|
||||
|
||||
test('should show custom JSX option label in list', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
const columnsButton = page.locator('button:has-text("Columns")')
|
||||
|
||||
await columnsButton.click()
|
||||
|
||||
await page.locator('text=Select with JSX label option').click()
|
||||
|
||||
await expect(page.locator('.cell-selectWithJsxLabelOption svg#payload-logo')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { selectFieldsSlug } from '../../slugs.js'
|
||||
import { CustomJSXLabel } from './CustomJSXLabel.js'
|
||||
|
||||
const SelectFields: CollectionConfig = {
|
||||
slug: selectFieldsSlug,
|
||||
versions: true,
|
||||
fields: [
|
||||
{
|
||||
name: 'select',
|
||||
@@ -221,6 +223,26 @@ const SelectFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'selectWithJsxLabelOption',
|
||||
label: 'Select with JSX label option',
|
||||
type: 'select',
|
||||
defaultValue: 'three',
|
||||
options: [
|
||||
{
|
||||
label: 'Value One',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: CustomJSXLabel,
|
||||
value: 'three',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -1305,6 +1305,7 @@ export interface EmailField {
|
||||
export interface RadioField {
|
||||
id: string;
|
||||
radio?: ('one' | 'two' | 'three') | null;
|
||||
radioWithJsxLabelOption?: ('one' | 'two' | 'three') | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -1800,6 +1801,7 @@ export interface SelectField {
|
||||
settings?: {
|
||||
category?: ('a' | 'b')[] | null;
|
||||
};
|
||||
selectWithJsxLabelOption?: ('one' | 'two' | 'three') | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -1841,6 +1843,10 @@ export interface TabsField {
|
||||
* When active, the nested conditional tab should be visible. When inactive, it should be hidden.
|
||||
*/
|
||||
nestedConditionalTabVisible?: boolean | null;
|
||||
conditionalTabGroup?: {
|
||||
conditionalTabGroupTitle?: string | null;
|
||||
conditionalTab?: {};
|
||||
};
|
||||
nestedUnconditionalTabInput?: string | null;
|
||||
nestedConditionalTabInput?: string | null;
|
||||
};
|
||||
@@ -3075,6 +3081,7 @@ export interface EmailFieldsSelect<T extends boolean = true> {
|
||||
*/
|
||||
export interface RadioFieldsSelect<T extends boolean = true> {
|
||||
radio?: T;
|
||||
radioWithJsxLabelOption?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
@@ -3437,6 +3444,7 @@ export interface SelectFieldsSelect<T extends boolean = true> {
|
||||
| {
|
||||
category?: T;
|
||||
};
|
||||
selectWithJsxLabelOption?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
@@ -3471,6 +3479,12 @@ export interface TabsFieldsSelect<T extends boolean = true> {
|
||||
| {
|
||||
conditionalTabField?: T;
|
||||
nestedConditionalTabVisible?: T;
|
||||
conditionalTabGroup?:
|
||||
| T
|
||||
| {
|
||||
conditionalTabGroupTitle?: T;
|
||||
conditionalTab?: T | {};
|
||||
};
|
||||
nestedUnconditionalTabInput?: T;
|
||||
nestedConditionalTabInput?: T;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user