mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 19:51:17 +00:00
203 lines
5.5 KiB
TypeScript
203 lines
5.5 KiB
TypeScript
"use client";
|
|
import { useEditor, EditorContent } from "@tiptap/react";
|
|
import StarterKit from "@tiptap/starter-kit";
|
|
import Placeholder from "@tiptap/extension-placeholder";
|
|
import Underline from "@tiptap/extension-underline";
|
|
import { Label } from "../ui/label";
|
|
import {
|
|
Bold,
|
|
Italic,
|
|
Underline as UnderlineIcon,
|
|
List,
|
|
ListOrdered,
|
|
Heading1,
|
|
Heading2,
|
|
Heading3,
|
|
} from "lucide-react";
|
|
|
|
interface RichTextEditorProps {
|
|
label: string;
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
placeholder?: string;
|
|
error?: string;
|
|
}
|
|
|
|
const RichTextEditor: React.FC<RichTextEditorProps> = ({
|
|
label,
|
|
value,
|
|
onChange,
|
|
placeholder = "Text eingeben...",
|
|
error,
|
|
}) => {
|
|
const editor = useEditor({
|
|
extensions: [
|
|
StarterKit,
|
|
Underline,
|
|
Placeholder.configure({
|
|
placeholder,
|
|
}),
|
|
],
|
|
content: value,
|
|
immediatelyRender: false,
|
|
onUpdate: ({ editor }) => {
|
|
onChange(editor.getHTML());
|
|
},
|
|
editorProps: {
|
|
attributes: {
|
|
class: "focus:outline-none min-h-[150px] p-4",
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!editor) {
|
|
return null;
|
|
}
|
|
|
|
const MenuButton = ({
|
|
onClick,
|
|
active,
|
|
children,
|
|
}: {
|
|
onClick: () => void;
|
|
active: boolean;
|
|
children: React.ReactNode;
|
|
}) => (
|
|
<button
|
|
onClick={onClick}
|
|
type="button"
|
|
className={`p-2 rounded hover:bg-gray-100 transition-colors ${
|
|
active ? "bg-blue/10 text-blue" : "text-gray-600"
|
|
}`}
|
|
>
|
|
{children}
|
|
</button>
|
|
);
|
|
|
|
return (
|
|
<div className="w-full">
|
|
<Label className="p-1 mb-2 text-blue text-lg font-semibold">{label}</Label>
|
|
<style jsx global>{`
|
|
.editor-content .ProseMirror {
|
|
outline: none;
|
|
}
|
|
.editor-content h1 {
|
|
font-size: 2em;
|
|
font-weight: bold;
|
|
margin-top: 0.5em;
|
|
margin-bottom: 0.5em;
|
|
line-height: 1.2;
|
|
}
|
|
.editor-content h2 {
|
|
font-size: 1.5em;
|
|
font-weight: bold;
|
|
margin-top: 0.5em;
|
|
margin-bottom: 0.5em;
|
|
line-height: 1.3;
|
|
}
|
|
.editor-content h3 {
|
|
font-size: 1.25em;
|
|
font-weight: bold;
|
|
margin-top: 0.5em;
|
|
margin-bottom: 0.5em;
|
|
line-height: 1.4;
|
|
}
|
|
.editor-content ul {
|
|
list-style-type: disc;
|
|
padding-left: 1.5em;
|
|
margin-top: 0.5em;
|
|
margin-bottom: 0.5em;
|
|
}
|
|
.editor-content ol {
|
|
list-style-type: decimal;
|
|
padding-left: 1.5em;
|
|
margin-top: 0.5em;
|
|
margin-bottom: 0.5em;
|
|
}
|
|
.editor-content li {
|
|
margin-bottom: 0.25em;
|
|
}
|
|
.editor-content strong {
|
|
font-weight: bold;
|
|
}
|
|
.editor-content em {
|
|
font-style: italic;
|
|
}
|
|
.editor-content u {
|
|
text-decoration: underline;
|
|
}
|
|
.editor-content p {
|
|
margin-bottom: 0.5em;
|
|
}
|
|
.editor-content .ProseMirror p.is-editor-empty:first-child::before {
|
|
color: #adb5bd;
|
|
content: attr(data-placeholder);
|
|
float: left;
|
|
height: 0;
|
|
pointer-events: none;
|
|
}
|
|
`}</style>
|
|
<div className="border border-blue rounded-xl overflow-hidden">
|
|
{/* Toolbar */}
|
|
<div className="flex gap-1 p-2 border-b border-gray-200 bg-gray-50 flex-wrap">
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
active={editor.isActive("bold")}
|
|
>
|
|
<Bold size={18} />
|
|
</MenuButton>
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
active={editor.isActive("italic")}
|
|
>
|
|
<Italic size={18} />
|
|
</MenuButton>
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
|
active={editor.isActive("underline")}
|
|
>
|
|
<UnderlineIcon size={18} />
|
|
</MenuButton>
|
|
<div className="w-px h-6 bg-gray-300 mx-1 self-center" />
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
|
|
active={editor.isActive("heading", { level: 1 })}
|
|
>
|
|
<Heading1 size={18} />
|
|
</MenuButton>
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
|
|
active={editor.isActive("heading", { level: 2 })}
|
|
>
|
|
<Heading2 size={18} />
|
|
</MenuButton>
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
|
|
active={editor.isActive("heading", { level: 3 })}
|
|
>
|
|
<Heading3 size={18} />
|
|
</MenuButton>
|
|
<div className="w-px h-6 bg-gray-300 mx-1 self-center" />
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
|
active={editor.isActive("bulletList")}
|
|
>
|
|
<List size={18} />
|
|
</MenuButton>
|
|
<MenuButton
|
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
|
active={editor.isActive("orderedList")}
|
|
>
|
|
<ListOrdered size={18} />
|
|
</MenuButton>
|
|
</div>
|
|
|
|
{/* Editor Content */}
|
|
<EditorContent editor={editor} className="bg-white editor-content" />
|
|
</div>
|
|
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RichTextEditor;
|