'use client'; import * as React from 'react'; import { clsx } from 'clsx'; import { EditorContent, useEditor } from '@tiptap/react'; import type { AnyExtension, JSONContent } from '@tiptap/core'; import { buildExtensions } from './extensions.js'; import { SlashCommands } from './slashMenu.js'; import { createMention, type UserSearchFn } from './mention.js'; import { Toolbar } from './Toolbar.js'; export interface RichTextEditorProps { /** Initial document — Tiptap JSON or an HTML string. */ content?: JSONContent | string; /** Called with the latest JSON document on every change. */ onChange?: (doc: JSONContent) => void; /** Placeholder shown when empty. */ placeholder?: string; /** Enable the `/` slash command menu (default true). */ enableSlashMenu?: boolean; /** Provide to enable `@`-mentions backed by this people search. */ mentionSearch?: UserSearchFn; /** Read-only mode. */ editable?: boolean; className?: string; ariaLabel?: string; } /** * Full editor surface: token-themed toolbar + Tiptap editing area, with an * optional slash menu and async mentions. SSR-safe (`immediatelyRender: * false`) so it hydrates cleanly under Next. */ export function RichTextEditor({ content, onChange, placeholder, enableSlashMenu = true, mentionSearch, editable = true, className, ariaLabel = 'Rich text editor', }: RichTextEditorProps) { const extra = React.useMemo(() => { const list: AnyExtension[] = []; if (enableSlashMenu) list.push(SlashCommands); if (mentionSearch) list.push(createMention(mentionSearch)); // Extensions are fixed for a mount; deliberately not reactive. return list; }, []); const editor = useEditor({ immediatelyRender: false, editable, extensions: buildExtensions({ placeholder, extra }), content, editorProps: { attributes: { role: 'textbox', 'aria-multiline': 'true', 'aria-label': ariaLabel, class: 'bl-rich-text-content min-h-40 p-3 outline-none', }, }, onUpdate: ({ editor: e }) => onChange?.(e.getJSON()), }); React.useEffect(() => { editor?.setEditable(editable); }, [editor, editable]); return (
{editable && }
); }