Bridge Training
Rich Text

Overview

Rich text editing and rendering for bridge content, backed by Plate.js.

@workspace/rich-text provides rich text editing, rendering, and serialization for bridge training content. It wraps Plate.js internally but never exposes Plate types to consumers. All public APIs use the domain RichText type.

Architecture

@workspace/rich-text
  model/        Domain types & constants (RichText, Suit, etc.)
  lib/          Serialization: toHTML, fromHTML, toString, fromString
  types/        Exported TypeScript types (handles, config, options)
  components/   React components (RichEditor, LocalizedEditor, RichTextDisplay, RichCommentField, CommentPreview)
  plugins/      Plate plugin configuration (internal, suit/)
  plate/        Plate abstraction layer (internal, not exported)

Design Principles

  • Domain boundary: consumers work with RichText (domain type), never Plate/Slate nodes
  • Uncontrolled editor: defaultValue on mount, imperative ref for reads/writes (per Plate docs)
  • Composable: RichEditor is a compound component (Root + Toolbar + Area + Input), consumers compose the layout
  • No barrels for components: explicit imports like @workspace/rich-text/components/rich-editor

Quick Start

Editing

import { RichEditor } from '@workspace/rich-text/components/rich-editor';
import { MarkToolbarButton } from '@workspace/rich-text/components/mark-toolbar-button';
import { SuitSymbolButton } from '@workspace/rich-text/plugins/suit/suit-symbol-button';
import type { RichEditorHandle } from '@workspace/rich-text/types';

const ref = useRef<RichEditorHandle>(null);

<RichEditor.Root config={{ mode: 'default', lists: true }} defaultValue={richText} ref={ref}>
  <RichEditor.Toolbar>
    <MarkToolbarButton nodeType="bold">B</MarkToolbarButton>
    <MarkToolbarButton nodeType="italic">I</MarkToolbarButton>
    <SuitSymbolButton suit="S" />
    <SuitSymbolButton suit="H" />
  </RichEditor.Toolbar>
  <RichEditor.Area variant="edge">
    <RichEditor.Input placeholder="Write..." variant="edge" />
  </RichEditor.Area>
</RichEditor.Root>

Read-only Display

import { RichTextDisplay } from '@workspace/rich-text/components/rich-text-display';

<RichTextDisplay value={richText} />

Serialization

import { toHTML, fromHTML, toString, fromString } from '@workspace/rich-text/lib';

const html = toHTML(richText);         // RichText -> HTML
const rt = fromHTML(html);             // HTML -> RichText
const text = toString(richText);       // RichText -> plain text
const rt2 = fromString(markdown);      // Markdown -> RichText

Exports

PathTypeDescription
@workspace/rich-text/modelBarrelDomain types, constants, suit utilities
@workspace/rich-text/libBarrelSerialization functions (toHTML, fromHTML, toString, fromString)
@workspace/rich-text/typesBarrelTypeScript types (RichEditorHandle, RichEditorConfig, etc.)
@workspace/rich-text/renderAliasSame as lib — alternative import path for serialization
@workspace/rich-text/components/*ExplicitReact components (rich-editor, localized-editor, rich-comment-field, comment-preview, etc.)
@workspace/rich-text/plugins/*ExplicitPlugin config (create-rich-text-plugins)
@workspace/rich-text/plugins/suit/*ExplicitSuit plugin components (suit-symbol-button, suit-icons, etc.)

On this page