feat(richtext-lexical): allow replacing entire blocks with custom components (#9234)

With this PR, you can now customize the way that `blocks` and
`inlineBlocks` are rendered within Lexical's `BlocksFeature` by passing
your own React components.

This is super helpful when you need to create "previews" or more
accurate UI for your Lexical blocks.

For example, let's say you have a `gallery` block where your admins
select a bunch of images. By default, Lexical would just render a
collapsible with your block's fields in it. But now you can customize
the `admin.components.Block` property on your `block` config by passing
it a custom React component for us to render instead.

So using that, with this `gallery` example, you could make a dynamic
gallery React component that shows the images to your editors - and then
render our built-in `BlockEditButton` to allow your editors to manage
your gallery in a drawer.


Here is an example where the BlockEditButton is added to the default
Block Collapsible/Header:


![image](https://github.com/user-attachments/assets/db8c13f1-2650-4b33-bc11-2582bb937f3d)

---------

Co-authored-by: James <james@trbl.design>
This commit is contained in:
Alessio Gravili
2024-11-16 15:30:18 -07:00
committed by GitHub
parent 55d5edda6b
commit 63cc9668df
32 changed files with 1230 additions and 474 deletions

View File

@@ -0,0 +1,20 @@
'use client'
import {
BlockCollapsible,
BlockEditButton,
BlockRemoveButton,
} from '@payloadcms/richtext-lexical/client'
import { useFormFields } from '@payloadcms/ui'
import React from 'react'
export const BlockComponent: React.FC = () => {
const key = useFormFields(([fields]) => fields.key)
return (
<BlockCollapsible>
MY BLOCK COMPONENT. Value: {(key?.value as string) ?? '<no value>'}
Edit: <BlockEditButton />
<BlockRemoveButton />
</BlockCollapsible>
)
}

View File

@@ -0,0 +1,10 @@
'use client'
import { useFormFields } from '@payloadcms/ui'
import React from 'react'
export const LabelComponent: React.FC = () => {
const key = useFormFields(([fields]) => fields.key)
return <div>{(key?.value as string) ?? '<no value>'}yaya</div>
}

View File

@@ -82,13 +82,137 @@ const editorConfig: ServerEditorConfig = {
ConditionalLayoutBlock,
TabBlock,
CodeBlock,
{
slug: 'myBlock',
admin: {
components: {},
},
fields: [
{
name: 'key',
label: () => {
return 'Key'
},
type: 'select',
options: ['value1', 'value2', 'value3'],
},
],
},
{
slug: 'myBlockWithLabel',
admin: {
components: {
Label: '/collections/Lexical/blockComponents/LabelComponent.js#LabelComponent',
},
},
fields: [
{
name: 'key',
label: () => {
return 'Key'
},
type: 'select',
options: ['value1', 'value2', 'value3'],
},
],
},
{
slug: 'myBlockWithBlock',
admin: {
components: {
Block: '/collections/Lexical/blockComponents/BlockComponent.js#BlockComponent',
},
},
fields: [
{
name: 'key',
label: () => {
return 'Key'
},
type: 'select',
options: ['value1', 'value2', 'value3'],
},
],
},
{
slug: 'myBlockWithBlockAndLabel',
admin: {
components: {
Block: '/collections/Lexical/blockComponents/BlockComponent.js#BlockComponent',
Label: '/collections/Lexical/blockComponents/LabelComponent.js#LabelComponent',
},
},
fields: [
{
name: 'key',
label: () => {
return 'Key'
},
type: 'select',
options: ['value1', 'value2', 'value3'],
},
],
},
],
inlineBlocks: [
{
slug: 'myInlineBlock',
admin: {
components: {},
},
fields: [
{
name: 'key',
label: () => {
return 'Key'
},
type: 'select',
options: ['value1', 'value2', 'value3'],
},
],
},
{
slug: 'myInlineBlockWithLabel',
admin: {
components: {
Label: '/collections/Lexical/LabelComponent.js#LabelComponent',
Label: '/collections/Lexical/inlineBlockComponents/LabelComponent.js#LabelComponent',
},
},
fields: [
{
name: 'key',
label: () => {
return 'Key'
},
type: 'select',
options: ['value1', 'value2', 'value3'],
},
],
},
{
slug: 'myInlineBlockWithBlock',
admin: {
components: {
Block: '/collections/Lexical/inlineBlockComponents/BlockComponent.js#BlockComponent',
},
},
fields: [
{
name: 'key',
label: () => {
return 'Key'
},
type: 'select',
options: ['value1', 'value2', 'value3'],
},
],
},
{
slug: 'myInlineBlockWithBlockAndLabel',
admin: {
components: {
Block: '/collections/Lexical/inlineBlockComponents/BlockComponent.js#BlockComponent',
Label: '/collections/Lexical/inlineBlockComponents/LabelComponent.js#LabelComponent',
},
},
fields: [

View File

@@ -0,0 +1,18 @@
import {
InlineBlockContainer,
InlineBlockEditButton,
InlineBlockLabel,
InlineBlockRemoveButton,
} from '@payloadcms/richtext-lexical/client'
import React from 'react'
export const BlockComponent: React.FC = () => {
return (
<InlineBlockContainer>
<p>Test</p>
<InlineBlockEditButton />
<InlineBlockLabel />
<InlineBlockRemoveButton />
</InlineBlockContainer>
)
}

View File

@@ -0,0 +1,10 @@
'use client'
import { useFormFields } from '@payloadcms/ui'
import React from 'react'
export const LabelComponent: React.FC = () => {
const key = useFormFields(([fields]) => fields.key)
return <div>{(key?.value as string) ?? '<no value>'}yaya</div>
}