카테고리 없음

[React/TypeScript + tailwindCSS] Tiptap 에디터 적용

chaeon1 2025. 5. 24. 17:05

✉️ 기술 스택

React, TypeScript, Zustand, Tiptap, DOMPurify, tailwindCSS, Flowbite

Tiptap Editor

Tiptap Rich Text Editor - the Headless WYSIWYG Editor

✔️ 쉽게 확장 가능
✔️ ProseMirror 기반: 구조적 편집 가능
✔️ 확장 기능 커스터마이징 가능
✔️ 공식 문서 설명이 잘 되어 있어서 따라하기 편함


커스터마이징 에디터 구성

✏️ 에디터 설정

  • 필요한 extension (StarterKit, Underline, Highlight) 선택적으로 추가
  • prose 기반 스타일링 적용
  • debounce 적용으로 입력 최적화
const editor = useEditor({
  editorProps: {
    attributes: {
      class:
        'prose prose-sm dark:prose-invert prose-headings:my-2 prose-p:m-0 min-h-[120px] focus:outline-none',
    },
  },
  extensions: [StarterKit, Underline, Highlight],
  content,
  onUpdate({ editor }) {
    debouncedOnChange(editor.getHTML());
  },
});

 

 

✏️ 툴바 기능

  • Format: Paragraph, Heading 1, Heading 2
  • Bold, Italic, Underline, Highlight
{/* Bold */}
<button
  type="button"
  onClick={() => editor.chain().focus().toggleBold().run()}
  className={`...`}
>
  <BoldIcon />
  <span className="sr-only">Bold</span>
</button>

HTML 렌더링

작성된 HTML 콘텐츠는 <p><strong>텍스트</strong></p> 형식으로 저장됨
이를 안전하게 보여주기 위해 DOMPurify.sanitize 적용

const photoTalkMessage = DOMPurify.sanitize(photoTalk.message);

<p dangerouslySetInnerHTML={{ __html: photoTalkMessage }} />
  • dangerouslySetInnerHTML: WYSIWYG 작성 콘텐츠를 그대로 표현
  • DOMPurify.sanitize: script injection과 같은 보안 문제 방지

에디터 구조 및 확장 적용

에디터를 다른 영역에도 재사용 가능하도록 설계했으며, 교통 안내 입력 등에서도 동일한 에디터 UI 활용

📦components
 ┣ 📂common
 ┃ ┣ 📂Editor
 ┃ ┃ ┣ 📜MenuBar.tsx
 ┃ ┃ ┗ 📜TiptapEditor.tsx
// TiptapEditor.tsx
return (
  <div className="border rounded-md bg-white dark:bg-gray-800">
    {editor && (
      <>
        <MenuBar editor={editor} />
        <div className="p-4 h-[160px] overflow-y-auto">
          <EditorContent editor={editor} />
        </div>
      </>
    )}
  </div>
);


// PhotoTalkEditor.tsx
import TipTapEditor from '@/components/common/Editor/TiptapEditor';

<TipTapEditor
  content={form.message}
  onChange={(value) => {
    setForm((prev) => ({ ...prev, message: value }));
  }}
/>


// TransportationItem.tsx
import TipTapEditor from '@/components/common/Editor/TiptapEditor';

<TipTapEditor
  content={transportationInputs[inputKey] || ''}
  onChange={(value) => {
    updateTransportationInput(inputKey, value);
  }}
/>