UNPKG

custom-rich-text-editor

Version:

A customizable React rich text editor with icon-based toolbar

90 lines (89 loc) 4.73 kB
"use client"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useRef, useState, useEffect } from "react"; import { FaBold, FaItalic, FaListUl, FaLink, FaParagraph, FaCode, } from "react-icons/fa"; import { LuHeading1, LuHeading2 } from "react-icons/lu"; const Editor = ({ initialContent = "", onChange }) => { const editorRef = useRef(null); const [showCode, setShowCode] = useState(true); const [htmlCode, setHtmlCode] = useState(initialContent); // Save selection position const saveSelection = () => { const selection = window.getSelection(); return selection === null || selection === void 0 ? void 0 : selection.getRangeAt(0); }; // Restore selection position const restoreSelection = (range) => { if (!range) return; const selection = window.getSelection(); if (selection) { selection.removeAllRanges(); selection.addRange(range); } }; // Handle paste events to prevent cursor jumping const handlePaste = (event) => { event.preventDefault(); const range = saveSelection(); const text = event.clipboardData.getData("text/plain"); document.execCommand("insertText", false, text); if (range) { restoreSelection(range); } }; useEffect(() => { if (editorRef.current && editorRef.current.innerHTML !== initialContent) { editorRef.current.innerHTML = initialContent; } setHtmlCode(initialContent); }, [initialContent]); const handleEditorInput = () => { if (editorRef.current) { const content = editorRef.current.innerHTML; if (content !== htmlCode) { setHtmlCode(content); onChange === null || onChange === void 0 ? void 0 : onChange(content); } } }; const handleCodeInput = (e) => { const html = e.target.value; setHtmlCode(html); if (editorRef.current) { editorRef.current.innerHTML = html; } onChange === null || onChange === void 0 ? void 0 : onChange(html); }; const format = (command, value) => { document.execCommand(command, false, value); }; const buttonStyle = { backgroundColor: "#f4f4f4", border: "none", borderRadius: "8px", padding: "10px", margin: "5px", cursor: "pointer", boxShadow: "0 2px 5px rgba(0, 0, 0, 0.1)", }; return (_jsx("div", { className: "main_layout", children: _jsxs("div", { className: "text_editor", children: [_jsxs("div", { className: "icon_bar", style: { marginBottom: "1rem", }, children: [_jsx("button", { onClick: () => format("formatBlock", "<p>"), style: buttonStyle, children: _jsx(FaParagraph, {}) }), _jsx("button", { onClick: () => format("bold"), style: buttonStyle, children: _jsx(FaBold, {}) }), _jsx("button", { onClick: () => format("italic"), style: buttonStyle, children: _jsx(FaItalic, {}) }), _jsx("button", { onClick: () => format("insertUnorderedList"), style: buttonStyle, children: _jsx(FaListUl, {}) }), _jsx("button", { onClick: () => format("formatBlock", "<h1>"), style: buttonStyle, children: _jsx(LuHeading1, {}) }), _jsx("button", { onClick: () => format("formatBlock", "<h2>"), style: buttonStyle, children: _jsx(LuHeading2, {}) }), _jsx("button", { onClick: () => format("createLink", "https://www.example.com"), style: buttonStyle, children: _jsx(FaLink, {}) }), _jsx("button", { onClick: () => setShowCode(!showCode), style: buttonStyle, children: _jsx(FaCode, {}) })] }), _jsx("div", { ref: editorRef, contentEditable: true, suppressContentEditableWarning: true, onInput: handleEditorInput, onPaste: handlePaste, style: { border: "1px solid #ccc", minHeight: "200px", padding: "1rem", borderRadius: "8px", outline: "none", marginBottom: "1rem", } }), showCode && (_jsx("div", { className: "html_code_editor", children: _jsx("textarea", { value: htmlCode, onChange: handleCodeInput, placeholder: "Write or paste HTML here...", style: { width: "100%", minHeight: "200px", padding: "1rem", borderRadius: "8px", border: "1px solid #ccc", fontFamily: "monospace", backgroundColor: "#fdfdfd", } }) }))] }) })); }; export default Editor;