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:
defaultValueon mount, imperativereffor reads/writes (per Plate docs) - Composable:
RichEditoris 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 -> RichTextExports
| Path | Type | Description |
|---|---|---|
@workspace/rich-text/model | Barrel | Domain types, constants, suit utilities |
@workspace/rich-text/lib | Barrel | Serialization functions (toHTML, fromHTML, toString, fromString) |
@workspace/rich-text/types | Barrel | TypeScript types (RichEditorHandle, RichEditorConfig, etc.) |
@workspace/rich-text/render | Alias | Same as lib — alternative import path for serialization |
@workspace/rich-text/components/* | Explicit | React components (rich-editor, localized-editor, rich-comment-field, comment-preview, etc.) |
@workspace/rich-text/plugins/* | Explicit | Plugin config (create-rich-text-plugins) |
@workspace/rich-text/plugins/suit/* | Explicit | Suit plugin components (suit-symbol-button, suit-icons, etc.) |