diff --git a/packages/next/src/pages/List/Default/Cell/fields/Richtext/index.tsx b/packages/next/src/pages/List/Default/Cell/fields/Richtext/index.tsx deleted file mode 100644 index e3951d9cf..000000000 --- a/packages/next/src/pages/List/Default/Cell/fields/Richtext/index.tsx +++ /dev/null @@ -1,33 +0,0 @@ -'use client' -import type { CellComponentProps, RichTextAdapter, RichTextField } from 'payload/types' - -import React, { useMemo } from 'react' - -export const RichTextCell: React.FC> = (props) => { - // eslint-disable-next-line react/destructuring-assignment - const editor: RichTextAdapter = props.cellData.editor - - const isLazy = 'LazyCellComponent' in editor - - const ImportedCellComponent: React.FC = useMemo(() => { - return isLazy - ? React.lazy(() => { - return editor.LazyCellComponent().then((resolvedComponent) => ({ - default: resolvedComponent, - })) - }) - : null - }, [editor, isLazy]) - - if (isLazy) { - return ( - ImportedCellComponent && ( - - - - ) - ) - } - - return -} diff --git a/packages/next/src/pages/List/Default/Cell/fields/index.tsx b/packages/next/src/pages/List/Default/Cell/fields/index.tsx index 508bbd408..9144fc4b0 100644 --- a/packages/next/src/pages/List/Default/Cell/fields/index.tsx +++ b/packages/next/src/pages/List/Default/Cell/fields/index.tsx @@ -6,7 +6,6 @@ import { DateCell } from './Date' import { FileCell } from './File' import { JSONCell } from './JSON' import { RelationshipCell } from './Relationship' -import { RichTextCell } from './Richtext' import { SelectCell } from './Select' import { TextareaCell } from './Textarea' @@ -20,7 +19,6 @@ export default { json: JSONCell, radio: SelectCell, relationship: RelationshipCell, - richText: RichTextCell, select: SelectCell, textarea: TextareaCell, upload: RelationshipCell, diff --git a/packages/next/src/pages/List/Default/Cell/index.tsx b/packages/next/src/pages/List/Default/Cell/index.tsx index 21842be6a..89522fbb5 100644 --- a/packages/next/src/pages/List/Default/Cell/index.tsx +++ b/packages/next/src/pages/List/Default/Cell/index.tsx @@ -13,6 +13,7 @@ import { CodeCell } from './fields/Code' export const DefaultCell: React.FC = (props) => { const { name, + CellComponentOverride, className: classNameFromProps, fieldType, isFieldAffectingData, @@ -76,7 +77,8 @@ export const DefaultCell: React.FC = (props) => { ) } - let CellComponent: React.FC = cellData && cellComponents[fieldType] + let CellComponent: React.FC = + cellData && (CellComponentOverride ? CellComponentOverride : cellComponents[fieldType]) if (!CellComponent) { if (customCellContext.uploadConfig && isFieldAffectingData && name === 'filename') { diff --git a/packages/payload/src/admin/elements/Cell.ts b/packages/payload/src/admin/elements/Cell.ts index 4dd670769..9b95478b0 100644 --- a/packages/payload/src/admin/elements/Cell.ts +++ b/packages/payload/src/admin/elements/Cell.ts @@ -10,6 +10,13 @@ import type { } from '../../fields/config/types' export type CellProps = { + /** + * A custom component to override the default cell component. If this is not set, the React component will be + * taken from cellComponents based on the field type. + * + * This is used to provide the RichText cell component for the RichText field. + */ + CellComponentOverride?: React.ComponentType blocks?: { labels: BlockField['labels'] slug: string diff --git a/packages/richtext-slate/src/cell/index.tsx b/packages/richtext-slate/src/cell/index.tsx index 256ae7b9a..6398deeb3 100644 --- a/packages/richtext-slate/src/cell/index.tsx +++ b/packages/richtext-slate/src/cell/index.tsx @@ -1,14 +1,10 @@ 'use client' -import type { CellComponentProps, RichTextField } from 'payload/types' +import type { CellComponentProps } from 'payload/types' import React from 'react' -import type { AdapterArguments } from '../types' - -const RichTextCell: React.FC< - CellComponentProps, any> -> = ({ data }) => { - const flattenedText = data?.map((i) => i?.children?.map((c) => c.text)).join(' ') +const RichTextCell: React.FC> = ({ cellData }) => { + const flattenedText = cellData?.map((i) => i?.children?.map((c) => c.text)).join(' ') // Limiting the number of characters shown is done in a CSS rule return {flattenedText} diff --git a/packages/richtext-slate/src/index.tsx b/packages/richtext-slate/src/index.tsx index 083d6e282..f6a7a74c2 100644 --- a/packages/richtext-slate/src/index.tsx +++ b/packages/richtext-slate/src/index.tsx @@ -1,6 +1,5 @@ import type { RichTextAdapter } from 'payload/types' -import { withMergedProps } from '@payloadcms/ui/utilities' import { withNullableJSONSchemaType } from 'payload/utilities' import type { AdapterArguments } from './types' @@ -14,22 +13,16 @@ import { getGenerateSchemaMap } from './generateSchemaMap' export function slateEditor(args: AdapterArguments): RichTextAdapter { return { - CellComponent: withMergedProps({ - Component: RichTextCell, - toMergeIntoProps: args, - }), - FieldComponent: withMergedProps({ - Component: RichTextField, - toMergeIntoProps: args, - }), + CellComponent: RichTextCell, + FieldComponent: RichTextField, generateComponentMap: getGenerateComponentMap(args), generateSchemaMap: getGenerateSchemaMap(args), outputSchema: ({ isRequired }) => { return { + type: withNullableJSONSchemaType('array', isRequired), items: { type: 'object', }, - type: withNullableJSONSchemaType('array', isRequired), } }, populationPromise({ diff --git a/packages/ui/src/utilities/buildComponentMap/mapFields.tsx b/packages/ui/src/utilities/buildComponentMap/mapFields.tsx index 308d31556..f2225a319 100644 --- a/packages/ui/src/utilities/buildComponentMap/mapFields.tsx +++ b/packages/ui/src/utilities/buildComponentMap/mapFields.tsx @@ -80,7 +80,7 @@ export const mapFields = (args: { const labelProps: LabelProps = { htmlFor: 'TODO', // TODO: fix types - // @ts-ignore-next-line + // @ts-expect-error-next-line label: 'label' in field ? field.label : null, required: 'required' in field ? field.required : undefined, } @@ -153,10 +153,10 @@ export const mapFields = (args: { }) const reducedBlock: ReducedBlock = { + slug: block.slug, imageAltText: block.imageAltText, imageURL: block.imageURL, labels: block.labels, - slug: block.slug, subfields: blockFieldMap, } @@ -245,8 +245,8 @@ export const mapFields = (args: { blocks: 'blocks' in field && field.blocks.map((b) => ({ - labels: b.labels, slug: b.slug, + labels: b.labels, })), dateDisplayFormat: 'date' in field.admin ? field.admin.date.displayFormat : undefined, fieldType: field.type, @@ -259,13 +259,18 @@ export const mapFields = (args: { options: 'options' in field ? field.options : undefined, } + /** + * Handle RichText Field Components, Cell Components, and component maps + */ if (field.type === 'richText' && 'editor' in field) { - let RichTextComponent + let RichTextFieldComponent + let RichTextCellComponent - const isLazy = 'LazyFieldComponent' in field.editor + const isLazyField = 'LazyFieldComponent' in field.editor + const isLazyCell = 'LazyCellComponent' in field.editor - if (isLazy) { - RichTextComponent = React.lazy(() => { + if (isLazyField) { + RichTextFieldComponent = React.lazy(() => { return 'LazyFieldComponent' in field.editor ? field.editor.LazyFieldComponent().then((resolvedComponent) => ({ default: resolvedComponent, @@ -273,22 +278,39 @@ export const mapFields = (args: { : null }) } else if ('FieldComponent' in field.editor) { - RichTextComponent = field.editor.FieldComponent + RichTextFieldComponent = field.editor.FieldComponent + } + + if (isLazyCell) { + RichTextCellComponent = React.lazy(() => { + return 'LazyCellComponent' in field.editor + ? field.editor.LazyCellComponent().then((resolvedComponent) => ({ + default: resolvedComponent, + })) + : null + }) + } else if ('CellComponent' in field.editor) { + RichTextCellComponent = field.editor.CellComponent } if (typeof field.editor.generateComponentMap === 'function') { const result = field.editor.generateComponentMap({ config, schemaPath: path }) - // @ts-ignore-next-line // TODO: the `richTextComponentMap` is not found on the union type + // @ts-expect-error-next-line // TODO: the `richTextComponentMap` is not found on the union type fieldComponentProps.richTextComponentMap = result } - if (RichTextComponent) { - Field = + if (RichTextFieldComponent) { + Field = + } + + if (RichTextCellComponent) { + cellComponentProps.CellComponentOverride = RichTextCellComponent } } const reducedField: MappedField = { name: 'name' in field ? field.name : '', + type: field.type, Cell: ( : null, Field: , Heading: , @@ -355,7 +377,6 @@ export const mapFields = (args: { readOnly: false, subfields: [], tabs: [], - type: 'text', }) } diff --git a/test/fields/lexical.int.spec.ts b/test/fields/lexical.int.spec.ts index a9a854cc7..fee8b071b 100644 --- a/test/fields/lexical.int.spec.ts +++ b/test/fields/lexical.int.spec.ts @@ -225,7 +225,7 @@ describe('Lexical', () => { }) }) describe('converters and migrations', () => { - it('hTMLConverter: should output correct HTML for top-level lexical field', async () => { + it('htmlConverter: should output correct HTML for top-level lexical field', async () => { const lexicalDoc: LexicalMigrateField = ( await payload.find({ collection: lexicalMigrateFieldsSlug, @@ -241,7 +241,7 @@ describe('Lexical', () => { const htmlField: string = lexicalDoc?.lexicalSimple_html expect(htmlField).toStrictEqual('

simple

') }) - it('hTMLConverter: should output correct HTML for lexical field nested in group', async () => { + it('htmlConverter: should output correct HTML for lexical field nested in group', async () => { const lexicalDoc: LexicalMigrateField = ( await payload.find({ collection: lexicalMigrateFieldsSlug, @@ -257,7 +257,7 @@ describe('Lexical', () => { const htmlField: string = lexicalDoc?.groupWithLexicalField?.lexicalInGroupField_html expect(htmlField).toStrictEqual('

group

') }) - it('hTMLConverter: should output correct HTML for lexical field nested in array', async () => { + it('htmlConverter: should output correct HTML for lexical field nested in array', async () => { const lexicalDoc: LexicalMigrateField = ( await payload.find({ collection: lexicalMigrateFieldsSlug,