@jhb.software/astro-payload-richtext-lexical
v0.3.2
Published
Renders Payload CMS Lexical rich text content to Astro elements.
Readme
JHB Software - Astro Payload RichText Lexical Renderer
Renders Payload CMS Lexical rich text content to Astro elements.
Installation
pnpm add @jhb.software/astro-payload-richtext-lexicalUsage
---
import RichTextLexical from '@jhb.software/astro-payload-richtext-lexical/RichTextLexical.astro'
import CustomUpload from '../components/CustomUpload.astro'
import CustomBlock from '../components/CustomBlock.astro'
// content is the Lexical JSON from Payload CMS
const { content } = Astro.props
---
<RichTextLexical
nodes={content.root.children}
UploadRenderer={CustomUpload}
BlockRenderer={CustomBlock}
/>Props
| Prop | Type | Required | Description |
| --------------------- | --------------------------- | -------- | ------------------------------------------------------- |
| nodes | LexicalNode[] | Yes | The array of Lexical nodes from content.root.children |
| class | string | No | CSS class to apply to the wrapper div |
| UploadRenderer | AstroComponentFactory | No | Custom component to render upload nodes |
| BlockRenderer | AstroComponentFactory | No | Custom component to render block and inline block nodes |
| slugifyHeadingId | SlugifyHeadingId \| false | No | Custom function for heading IDs, or false to disable |
| resolveInternalLink | ResolveInternalLink | No | Custom function to resolve internal link hrefs |
Usage with Tailwind CSS Typography
The component outputs semantic HTML without any styling. You can use Tailwind CSS Typography (@tailwindcss/typography) to style the content by adding the prose class to the wrapper div.
<RichTextLexical
nodes={content.root.children}
class="prose"
UploadRenderer={CustomUpload}
BlockRenderer={CustomBlock}
/>Custom Renderers
UploadRenderer
The UploadRenderer component receives:
type Props = {
node: UploadNode
}
interface UploadNode {
type: 'upload'
relationTo: string
value: {
id: string | number
url: string
width: number
height: number
filename: string
[key: string]: unknown
}
}BlockRenderer
The BlockRenderer component receives:
type Props = {
node: BlockNode | InlineBlockNode
inline: boolean // true for inline blocks, false for regular blocks
}
interface BlockNode {
type: 'block'
fields: {
id: string
blockName: string
blockType: string
[key: string]: unknown
}
}Configuration Options
slugifyHeadingId
By default, h2 and h3 headings automatically get an id attribute generated from their text content (useful for anchor links and table of contents).
<!-- Default behavior: h2/h3 get auto-generated IDs -->
<RichTextLexical nodes={content.root.children} />
<!-- Disable heading IDs entirely -->
<RichTextLexical nodes={content.root.children} slugifyHeadingId={false} />
<!-- Custom slugify function -->
<RichTextLexical
nodes={content.root.children}
slugifyHeadingId={(text, tag) => {
// Generate IDs for all headings, not just h2/h3
return text.toLowerCase().replace(/\s+/g, '-')
}}
/>The function signature:
type SlugifyHeadingId = (text: string, tag: string) => string | undefinedresolveInternalLink
Internal links in Payload CMS reference documents by their ID. By default, the renderer uses the path field from the populated document (works with @jhb.software/payload-pages-plugin).
For custom URL structures, provide your own resolver:
---
import RichTextLexical from '@jhb.software/astro-payload-richtext-lexical/RichTextLexical.astro'
import type { ResolveInternalLink } from '@jhb.software/astro-payload-richtext-lexical'
const resolveInternalLink: ResolveInternalLink = (doc, relationTo) => {
// Use path field if available
if ('path' in doc && typeof doc.path === 'string') {
return doc.path
}
// Fallback to slug for blog posts
if ('slug' in doc && typeof doc.slug === 'string') {
if (relationTo === 'posts') {
return `/blog/${doc.slug}`
}
return `/${doc.slug}`
}
return undefined
}
---
<RichTextLexical nodes={content.root.children} resolveInternalLink={resolveInternalLink} />The function signature:
type ResolveInternalLink = (doc: Record<string, unknown>, relationTo: string) => string | undefinedSupported Node Types
heading(h1-h6)paragraphtext(with formatting: bold, italic, underline, strikethrough, code, subscript, superscript)link/autolinklist(ordered and unordered)listitemquote(blockquote)tableupload(requiresUploadRenderer)block/inlineBlock(requiresBlockRenderer)horizontalrulelinebreak
Types
You can import types for use in your custom renderers and configuration:
import type {
LexicalNode,
HeadingNode,
ParagraphNode,
TextNode,
LinkNode,
ListNode,
UploadNode,
BlockNode,
InlineBlockNode,
SlugifyHeadingId,
ResolveInternalLink,
} from '@jhb.software/astro-payload-richtext-lexical'Contributing
We welcome contributions! Please open an issue to report bugs or suggest improvements, or submit a pull request with your changes.
