Skip to content

Commit 139cf3e

Browse files
authored
fix(ui): respect custom Cell components on richText fields in list view (#15762)
Fixes #13954 Fixes richText fields ignoring `admin.components.Cell` in list view. The `renderCell` function unconditionally used the editor's built-in `CellComponent` for richText fields without checking for a user-defined Cell first. Now it checks `admin.components.Cell` and falls back to the editor's default.
1 parent 418bb92 commit 139cf3e

File tree

7 files changed

+132
-2
lines changed

7 files changed

+132
-2
lines changed

packages/ui/src/providers/TableColumns/buildColumnState/renderCell.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,11 @@ export function renderCell({
177177
serverField.admin.components = {}
178178
}
179179

180+
const CustomCellComponent = serverField.admin.components.Cell
181+
180182
CustomCell = RenderServerComponent({
181183
clientProps: cellClientProps,
182-
Component: serverField.editor.CellComponent,
184+
Component: CustomCellComponent ?? serverField.editor.CellComponent,
183185
importMap: payload.importMap,
184186
serverProps: cellServerProps,
185187
})

test/lexical/baseConfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from './collections/Lexical/index.js'
1515
import { LexicalAccessControl } from './collections/LexicalAccessControl/index.js'
1616
import { LexicalAutosave } from './collections/LexicalAutosave/index.js'
17+
import { LexicalCustomCell } from './collections/LexicalCustomCell/index.js'
1718
import { LexicalHeadingFeature } from './collections/LexicalHeadingFeature/index.js'
1819
import { LexicalInBlock } from './collections/LexicalInBlock/index.js'
1920
import { LexicalJSXConverter } from './collections/LexicalJSXConverter/index.js'
@@ -67,6 +68,7 @@ export const baseConfig: Partial<Config> = {
6768
ArrayFields,
6869
OnDemandForm,
6970
OnDemandOutsideForm,
71+
LexicalCustomCell,
7072
],
7173
globals: [TabsWithRichText],
7274

test/lexical/collections/Lexical/e2e/main/e2e.spec.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { reInitializeDB } from '../../../../../__helpers/shared/clearAndSeed/reI
2828
import { initPayloadE2ENoConfig } from '../../../../../__helpers/shared/initPayloadE2ENoConfig.js'
2929
import { RESTClient } from '../../../../../__helpers/shared/rest.js'
3030
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../../../playwright.config.js'
31-
import { lexicalFieldsSlug } from '../../../../slugs.js'
31+
import { lexicalCustomCellSlug, lexicalFieldsSlug } from '../../../../slugs.js'
3232
import { lexicalDocData } from '../../data.js'
3333

3434
const filename = fileURLToPath(import.meta.url)
@@ -1813,4 +1813,40 @@ describe('lexicalMain', () => {
18131813
// before and after decoratorNodes and paragraphs. Tested manually,
18141814
// but these are complex cases.
18151815
})
1816+
1817+
test('should render custom Cell component for richText fields in list view', async () => {
1818+
const doc = await payload.create({
1819+
collection: lexicalCustomCellSlug,
1820+
data: {
1821+
title: 'Test Custom Cell',
1822+
richTextField: {
1823+
root: {
1824+
children: [
1825+
{
1826+
children: [{ text: 'Hello', type: 'text', version: 1 }],
1827+
direction: null,
1828+
format: '',
1829+
indent: 0,
1830+
type: 'paragraph',
1831+
version: 1,
1832+
},
1833+
],
1834+
direction: null,
1835+
format: '',
1836+
indent: 0,
1837+
type: 'root',
1838+
version: 1,
1839+
},
1840+
},
1841+
},
1842+
})
1843+
1844+
const url = new AdminUrlUtil(serverURL, lexicalCustomCellSlug)
1845+
await page.goto(url.list)
1846+
1847+
const customCells = page.locator('#custom-richtext-cell')
1848+
await expect(customCells.first()).toBeVisible()
1849+
// Both title (text) and richTextField (richText) should render the custom cell
1850+
await expect(customCells).toHaveCount(2)
1851+
})
18161852
})
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
import { lexicalEditor } from '@payloadcms/richtext-lexical'
4+
5+
import { lexicalCustomCellSlug } from '../../slugs.js'
6+
7+
export const LexicalCustomCell: CollectionConfig = {
8+
slug: lexicalCustomCellSlug,
9+
admin: {
10+
useAsTitle: 'title',
11+
defaultColumns: ['title', 'richTextField', 'createdAt'],
12+
},
13+
fields: [
14+
{
15+
name: 'title',
16+
type: 'text',
17+
required: true,
18+
admin: {
19+
components: {
20+
Cell: './components/CustomCell.js#CustomCell',
21+
},
22+
},
23+
},
24+
{
25+
name: 'richTextField',
26+
type: 'richText',
27+
editor: lexicalEditor(),
28+
admin: {
29+
components: {
30+
Cell: './components/CustomCell.js#CustomCell',
31+
},
32+
},
33+
},
34+
],
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client'
2+
3+
import type { DefaultCellComponentProps } from 'payload'
4+
5+
import React from 'react'
6+
7+
export const CustomCell: React.FC<DefaultCellComponentProps> = (props) => {
8+
return (
9+
<span id="custom-richtext-cell">{`Custom cell: ${props?.cellData ? 'has data' : 'no data'}`}</span>
10+
)
11+
}

test/lexical/payload-types.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export interface Config {
107107
'array-fields': ArrayField;
108108
OnDemandForm: OnDemandForm;
109109
OnDemandOutsideForm: OnDemandOutsideForm;
110+
'lexical-custom-cell': LexicalCustomCell;
110111
'payload-kv': PayloadKv;
111112
users: User;
112113
'payload-locked-documents': PayloadLockedDocument;
@@ -136,6 +137,7 @@ export interface Config {
136137
'array-fields': ArrayFieldsSelect<false> | ArrayFieldsSelect<true>;
137138
OnDemandForm: OnDemandFormSelect<false> | OnDemandFormSelect<true>;
138139
OnDemandOutsideForm: OnDemandOutsideFormSelect<false> | OnDemandOutsideFormSelect<true>;
140+
'lexical-custom-cell': LexicalCustomCellSelect<false> | LexicalCustomCellSelect<true>;
139141
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
140142
users: UsersSelect<false> | UsersSelect<true>;
141143
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
@@ -1086,6 +1088,31 @@ export interface OnDemandOutsideForm {
10861088
updatedAt: string;
10871089
createdAt: string;
10881090
}
1091+
/**
1092+
* This interface was referenced by `Config`'s JSON-Schema
1093+
* via the `definition` "lexical-custom-cell".
1094+
*/
1095+
export interface LexicalCustomCell {
1096+
id: string;
1097+
title: string;
1098+
richTextField?: {
1099+
root: {
1100+
type: string;
1101+
children: {
1102+
type: any;
1103+
version: number;
1104+
[k: string]: unknown;
1105+
}[];
1106+
direction: ('ltr' | 'rtl') | null;
1107+
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
1108+
indent: number;
1109+
version: number;
1110+
};
1111+
[k: string]: unknown;
1112+
} | null;
1113+
updatedAt: string;
1114+
createdAt: string;
1115+
}
10891116
/**
10901117
* This interface was referenced by `Config`'s JSON-Schema
10911118
* via the `definition` "payload-kv".
@@ -1219,6 +1246,10 @@ export interface PayloadLockedDocument {
12191246
relationTo: 'OnDemandOutsideForm';
12201247
value: string | OnDemandOutsideForm;
12211248
} | null)
1249+
| ({
1250+
relationTo: 'lexical-custom-cell';
1251+
value: string | LexicalCustomCell;
1252+
} | null)
12221253
| ({
12231254
relationTo: 'users';
12241255
value: string | User;
@@ -1676,6 +1707,16 @@ export interface OnDemandOutsideFormSelect<T extends boolean = true> {
16761707
updatedAt?: T;
16771708
createdAt?: T;
16781709
}
1710+
/**
1711+
* This interface was referenced by `Config`'s JSON-Schema
1712+
* via the `definition` "lexical-custom-cell_select".
1713+
*/
1714+
export interface LexicalCustomCellSelect<T extends boolean = true> {
1715+
title?: T;
1716+
richTextField?: T;
1717+
updatedAt?: T;
1718+
createdAt?: T;
1719+
}
16791720
/**
16801721
* This interface was referenced by `Config`'s JSON-Schema
16811722
* via the `definition` "payload-kv_select".

test/lexical/slugs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const uploads2Slug = 'uploads2'
2121

2222
export const arrayFieldsSlug = 'array-fields'
2323

24+
export const lexicalCustomCellSlug = 'lexical-custom-cell'
2425
export const lexicalNestedBlocksSlug = 'lexical-nested-blocks'
2526

2627
export const collectionSlugs = [
@@ -33,4 +34,6 @@ export const collectionSlugs = [
3334
textFieldsSlug,
3435
uploadsSlug,
3536
lexicalListsFeatureSlug,
37+
lexicalCustomCellSlug,
38+
lexicalNestedBlocksSlug,
3639
]

0 commit comments

Comments
 (0)