Skip to content

werk1/w1-system-payload-richtext-renderer

Repository files navigation

@werk1/w1-system-payload-richtext-renderer

Payload CMS RichText rendering for W1 System with complete CSS module override support.

Features

  • âś… Complete CSS Module Override - Following W1Block pattern
  • âś… Virtualized Rendering - Optimized for large content
  • âś… Flexible Content Patterns - Styled arrays and Lexical nodes
  • âś… TypeScript Support - Full type safety
  • âś… Server Actions - Next.js App Router compatible
  • âś… Locale Support - Multi-language content
  • âś… Abort Support - Request cancellation and cleanup

Installation

npm install @werk1/w1-system-payload-richtext-renderer

Setup

1. Wrap your app with PayloadConfigProvider

// src/app/layout.tsx or src/components/ClientLayout.tsx
import { PayloadConfigProvider } from '@werk1/w1-system-payload-richtext-renderer';
import configPromise from '@/payload.config';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <PayloadConfigProvider
          payloadConfig={configPromise}
          defaultLocale="en"
          getCurrentLocale={() => 'en'} // Optional: dynamic locale
        >
          {children}
        </PayloadConfigProvider>
      </body>
    </html>
  );
}

2. With Zustand BoundStore (for locale)

import { useBoundStore } from '@/stores/boundStore';

<PayloadConfigProvider
  payloadConfig={configPromise}
  getCurrentLocale={() => useBoundStore.getState().ui.currentLocale}
>
  {children}
</PayloadConfigProvider>

Basic Usage

Simple Content Loading

import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';

<PayloadTextContentLoader contentKey="homepage" />

With Virtualization

<PayloadTextContentLoader
  contentKey="large-article"
  virtualized={true}
  chunkSize={5}
  overscan={2}
  heightVariant="medium"
/>

Complete CSS Module Override (W1Block Pattern)

import customStyles from './custom.module.css';

<PayloadTextContentLoader
  contentKey="content"
  externalCSSModule={customStyles}
/>

This replaces ALL CSS classes with your custom module:

  • Container styles
  • Heading styles (h1-h6)
  • Paragraph, quote, link styles
  • Text formatting (bold, italic, etc.)
  • Virtualization styles

Advanced Usage

Custom Content Components

// components/textbar/content/WeineContent.tsx
import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';
import weineStyles from './weine.module.css';

export function WeineContent() {
  return (
    <>
      <PayloadTextContentLoader
        contentKey="weine_text"
        externalCSSModule={weineStyles}
        virtualized={true}
        heightVariant="medium"
      />
      <PayloadTextContentLoader
        contentKey="weine_list"
        externalCSSModule={weineStyles}
        virtualized={true}
        heightVariant="medium"
      />
    </>
  );
}

Direct Renderer Usage

import { RichTextRenderer, useRichText } from '@werk1/w1-system-payload-richtext-renderer';
import configPromise from '@/payload.config';

export function CustomComponent() {
  const { content, isLoading } = useRichText('my-content', {
    payloadConfig: configPromise,
    locale: 'de',
    timeout: 5000,
  });

  if (isLoading) return <div>Loading...</div>;

  return (
    <RichTextRenderer
      content={content.arrayContent}
      externalCSSModule={customStyles}
    />
  );
}

Array Index Access

// Render only specific array item
<PayloadTextContentLoader
  contentKey="menu-sections"
  arrayIndex={0}  // First section only
  externalCSSModule={menuStyles}
/>

API Reference

PayloadTextContentLoader Props

interface PayloadTextContentLoaderProps {
  contentKey?: string;
  routeMapping?: Record<string, string>;
  virtualized?: boolean;
  chunkSize?: number;
  overscan?: number;
  heightVariant?: 'small' | 'medium' | 'default' | 'large' | 'full';
  singleCSSContainerClass?: string;
  textLoaderClassName?: string;
  arrayIndex?: number;
  externalCSSModule?: Record<string, string>;
  locale?: string;
  collectionName?: string;
  timeout?: number;
}

RichTextRenderer Props

interface RichTextRendererProps {
  content: RichTextContent;
  singleCSSContainerClass?: string;
  externalCSSModule?: Record<string, string>;
  onError?: (error: Error) => void;
}

VirtualizedRichText Props

interface VirtualizedRichTextProps {
  content: RichTextContent;
  singleCSSContainerClass?: string;
  externalCSSModule?: Record<string, string>;
  onError?: (error: Error) => void;
  chunkSize?: number;
  overscan?: number;
  heightVariant?: 'small' | 'medium' | 'default' | 'large' | 'full';
}

useRichText Hook

const { content, isLoading, error, arrayLength } = useRichText(contentKey, {
  payloadConfig: configPromise,
  locale?: string,
  timeout?: number,
  collectionName?: string,
});

CSS Module Override

Your custom CSS module should export the same class names as the default module:

/* custom.module.css */

/* Container */
.richTextContainer {
  max-width: 800px;
  padding: 20px;
}

/* Headings */
.h1 {
  font-size: 3rem;
  font-family: 'YourCustomFont';
  color: #333;
}

.h2 { /* ... */ }
.h3 { /* ... */ }
.h4 { /* ... */ }
.h5 { /* ... */ }
.h6 { /* ... */ }

/* Text elements */
.paragraph { /* ... */ }
.bold { /* ... */ }
.italic { /* ... */ }
.underline { /* ... */ }
.strikethrough { /* ... */ }

/* Links */
.link { /* ... */ }
.invalidLink { /* ... */ }

/* Other */
.quote { /* ... */ }

/* Virtualization (if using virtualized mode) */
.virtualizedContainer { /* ... */ }
.virtualizedContainerSmall { /* ... */ }
.virtualizedContainerMedium { /* ... */ }
.virtualizedContainerLarge { /* ... */ }
.virtualizedContainerFull { /* ... */ }
.contentChunk { /* ... */ }
.placeholder { /* ... */ }
.placeholderDefault { /* ... */ }
.placeholderMedium { /* ... */ }
.placeholderLarge { /* ... */ }

/* Default style (fallback) */
.richTextDefaultStyle { /* ... */ }

Content Structure

Styled Content Arrays

// In your Payload collection
{
  richTextArrayWithStyle: [
    {
      style: 'heroTitle',  // Maps to CSS class .heroTitle
      content: [/* Lexical nodes */]
    },
    {
      style: 'copy',       // Maps to CSS class .copy
      content: [/* Lexical nodes */]
    }
  ]
}

Standard Lexical Content

{
  richText: {
    root: {
      children: [
        { type: 'heading', tag: 'h1', children: [...] },
        { type: 'paragraph', children: [...] },
        { type: 'link', url: '...', children: [...] }
      ]
    }
  }
}

Examples

Example 1: Wine List with Custom Styling

// components/textbar/content/WeineContent.tsx
import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';
import weineStyles from './weine.module.css';

export function WeineContent() {
  return (
    <PayloadTextContentLoader
      contentKey="weine"
      externalCSSModule={weineStyles}
      virtualized={true}
      heightVariant="medium"
    />
  );
}

Example 2: Multiple Content Sections

export function MenuContent() {
  return (
    <>
      <PayloadTextContentLoader contentKey="menu_starters" arrayIndex={0} />
      <PayloadTextContentLoader contentKey="menu_mains" arrayIndex={1} />
      <PayloadTextContentLoader contentKey="menu_desserts" arrayIndex={2} />
    </>
  );
}

Example 3: Dynamic Locale

export function LocalizedContent() {
  const currentLocale = useBoundStore(state => state.ui.currentLocale);

  return (
    <PayloadTextContentLoader
      contentKey="about"
      locale={currentLocale}
    />
  );
}

Migration from Local Implementation

Before (local implementation)

import { PayloadTextContentLoader } from '@/payload';

After (package)

import { PayloadTextContentLoader } from '@werk1/w1-system-payload-richtext-renderer';

Setup changes

  1. Add PayloadConfigProvider to your root layout
  2. Delete local src/payload/ directory (except config files)
  3. Update all imports
  4. Build and test

Troubleshooting

"usePayloadConfig must be used within PayloadConfigProvider"

Solution: Wrap your app with PayloadConfigProvider:

<PayloadConfigProvider payloadConfig={configPromise}>
  <App />
</PayloadConfigProvider>

Custom styles not applying

Solution: Ensure your CSS module exports all required class names. Missing classes will fall back to default styles.

Virtualization not working

Solution: Content must have more than 10 items to trigger virtualization. Use virtualized={true} prop.

Related Packages

  • @werk1/w1-system-block - Block foundation with CSS override pattern
  • @werk1/w1-system-media-manager - Media management system
  • @werk1/w1-system-device-info - Device detection
  • payload - Payload CMS (peer dependency)

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published