diff --git a/src/components/documents/rich-text-editor.tsx b/src/components/documents/rich-text-editor.tsx new file mode 100644 index 0000000..330cd27 --- /dev/null +++ b/src/components/documents/rich-text-editor.tsx @@ -0,0 +1,149 @@ +import { useEditor, EditorContent, Editor } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import Underline from "@tiptap/extension-underline"; +import TextAlign from "@tiptap/extension-text-align"; +import { useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { + Bold, Italic, Underline as UnderlineIcon, List, ListOrdered, + AlignLeft, AlignCenter, AlignRight, AlignJustify, Quote, Undo, Redo, + Heading2, Superscript as SupIcon, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface RichTextEditorProps { + value: string; // HTML + onChange: (html: string) => void; + onInsertFootnote?: () => void; + className?: string; + minHeight?: number; +} + +function ToolbarButton({ + onClick, active, disabled, children, title, +}: { + onClick: () => void; active?: boolean; disabled?: boolean; children: React.ReactNode; title: string; +}) { + return ( + + ); +} + +export function RichTextEditor({ + value, onChange, onInsertFootnote, className, minHeight = 400, +}: RichTextEditorProps) { + const editor = useEditor({ + extensions: [ + StarterKit.configure({ heading: { levels: [2, 3] } }), + Underline, + TextAlign.configure({ types: ["heading", "paragraph"] }), + ], + content: value || "
", + onUpdate: ({ editor }) => onChange(editor.getHTML()), + editorProps: { + attributes: { + class: "prose prose-sm max-w-none focus:outline-none px-10 py-8 bg-white text-black", + style: 'font-family: "Bookman Old Style", "URW Bookman", Georgia, serif; font-size: 12pt; line-height: 1.5;', + }, + }, + }); + + // Keep editor in sync when value is reset externally + useEffect(() => { + if (editor && value !== editor.getHTML()) { + editor.commands.setContent(value || "", { emitUpdate: false }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value === ""]); + + if (!editor) return null; + + return ( ++