diff --git a/package.json b/package.json index 7d2bb1c..5179a2c 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "i18next": "^23.11.5", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.5.2", + "quill": "^2.0.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^14.1.2", diff --git a/src/components/Editor/custom.snow.css b/src/components/Editor/custom.snow.css new file mode 100644 index 0000000..3634988 --- /dev/null +++ b/src/components/Editor/custom.snow.css @@ -0,0 +1,14 @@ +.quill-editor .ql-container{ + font-size: 14px; +} +.quill-editor .ql-container.ql-snow { + min-height: 12rem; + border-color: #d9d9d9; +} +.quill-editor .ql-toolbar.ql-snow { + border-radius: 6px 6px 0 0; + border-color: #d9d9d9; +} +.quill-editor .ql-editor{ + min-height: 12rem; +} diff --git a/src/components/Editor/index.jsx b/src/components/Editor/index.jsx new file mode 100644 index 0000000..9351c3c --- /dev/null +++ b/src/components/Editor/index.jsx @@ -0,0 +1,130 @@ +import React, { forwardRef, useEffect, useLayoutEffect, useRef } from 'react'; +import Quill from 'quill'; +import 'quill/dist/quill.snow.css'; +// import 'quill/dist/quill.bubble.css'; +import './custom.snow.css'; + +const toolbarOptions = [ + [{ 'header': [1, 2, 3, 4, 5, 6, false] }], + // [{ 'font': [] }], + ['bold', 'italic', 'underline', 'strike'], // toggled buttons + // ['blockquote'], // 'code-block' + ['link', ], // 'image' 'video', 'formula' + + [{ 'list': 'ordered' }, { 'list': 'bullet' }], // { 'list': 'check' } + [{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme + + // [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown + // [{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent + // [{ 'align': [] }], + + ['clean'], // remove formatting button +]; + +// Editor is an uncontrolled React component +const Editor = forwardRef(({ readOnly, defaultValue, value, onChange, onTextChange, onSelectionChange }, ref) => { + const containerRef = useRef(null); + const cntContainerRef = useRef(null); + const defaultValueRef = useRef(defaultValue); + const valueRef = useRef(value); + const onTextChangeRef = useRef(onTextChange); + const onChangeRef = useRef(onChange); + const onSelectionChangeRef = useRef(onSelectionChange); + + useLayoutEffect(() => { + onTextChangeRef.current = onTextChange; + onChangeRef.current = onChange; + onSelectionChangeRef.current = onSelectionChange; + }); + + useEffect(() => { + const editor = ref.current; + if (!editor) return; + + valueRef.current = value; + const current = editor.root.innerHTML; + const incoming = value || defaultValue || ''; + // treat quill empty markup as empty string + const currentNormalized = current === '