리치 텍스트 에디터
리치 텍스트 에디터 (Rich text editor)
Tiptap 기반 리치 텍스트 에디터예요. @mantine/tiptap 패키지로 제공되며 MIT 라이선스로 배포돼요.
출처: 문서
본문
설치
yarn으로 설치해요:
yarn add @mantine/tiptap @mantine/core @mantine/hooks @tiptap/react @tiptap/pm @tiptap/extension-link @tiptap/starter-kit
설치 후 애플리케이션의 루트에서 패키지 스타일을 import 해요:
import '@mantine/core/styles.css';
// ‼️ tiptap 스타일은 core 패키지 스타일 다음에 import 하세요
import '@mantine/tiptap/styles.css';
TipTap 에디터
@mantine/tiptap 패키지는 Tiptap의 UI를 제공해요. RichTextEditor 컴포넌트는 tiptap의 Editor 인스턴스와 함께 동작해요. 즉 useEditor hook으로 에디터의 상태와 구성을 완전히 제어할 수 있어요.
다시 말해 RichTextEditor 컴포넌트는 상태를 관리하지 않아요; 컨트롤은 Editor 인스턴스에서 연산을 실행할 뿐이에요. 상태나 컴포넌트 값과 관련된 것(예: 제어 모드, 값을 HTML/Markdown으로 변환)을 구현하고 싶다면 tiptap.dev 웹사이트에서 문서를 찾아봐야 해요.
사용법
import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
const content =
'<h2>Welcome to Mantine rich text editor</h2><p>`RichTextEditor` component focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. `RichTextEditor` is based on [Tiptap.dev](https://tiptap.dev/) and supports all of its features:</p><ul><li>General text formatting: bold, italic, underline, strike-through</li><li>Headings (h1-h6)</li><li>Sub and super scripts (<sub> and <sup> tags)</li><li>Ordered and bullet lists</li><li>Text align</li><li>And all other extensions</li></ul>';
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [
StarterKit.configure({ link: false }),
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Underline />
<RichTextEditor.Strikethrough />
<RichTextEditor.ClearFormatting />
<RichTextEditor.Highlight />
<RichTextEditor.ColorPicker colors={[]} />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
Subtle variant
variant="subtle"은 컨트롤 그룹에서 테두리를 제거하고, 컨트롤을 더 크게 만들며, 툴바의 간격을 줄여요:
import Highlight from '@tiptap/extension-highlight';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
const content = '<p>Subtle rich text editor variant</p>';
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [StarterKit, Highlight],
content,
});
return (
<RichTextEditor editor={editor} variant="subtle">
<RichTextEditor.Toolbar>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Highlight />
</RichTextEditor.ControlsGroup>
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
제어(Controlled)
에디터 상태를 제어하려면 래퍼 컴포넌트를 만들고 onChange 핸들러를 useEditor 훅에 전달해요:
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor as MantineRichTextEditor } from '@mantine/tiptap';
interface RichTextEditorProps {
value: string;
onChange: (value: string) => void;
}
export function RichTextEditor({
value,
onChange,
}: RichTextEditorProps) {
const editor = useEditor({
extensions: [StarterKit],
content: value,
onUpdate: ({ editor }) => {
onChange(editor.getHTML());
},
});
return (
<MantineRichTextEditor editor={editor}>
<MantineRichTextEditor.Content />
</MantineRichTextEditor>
);
}
컨트롤과 extensions
일부 컨트롤은 추가적인 Tiptap extensions 설치를 요구해요. 예를 들어 RichTextEditor.Superscript 컨트롤을 사용하려면 @tiptap/extension-superscript 패키지를 설치해야 해요:
yarn add @tiptap/extension-superscript
@tiptap/starter-kit에 포함된 것(기본으로 설치해야 함):
RichTextEditor.H1RichTextEditor.H2RichTextEditor.H3RichTextEditor.H4RichTextEditor.H5RichTextEditor.H6RichTextEditor.BulletListRichTextEditor.OrderedListRichTextEditor.BoldRichTextEditor.ItalicRichTextEditor.StrikethroughRichTextEditor.ClearFormattingRichTextEditor.BlockquoteRichTextEditor.CodeRichTextEditor.CodeBlockRichTextEditor.HrRichTextEditor.UndoRichTextEditor.RedoRichTextEditor.UnderlineRichTextEditor.Unlink
@tiptap/extension-text-align extension이 필요한 컨트롤:
RichTextEditor.AlignLeftRichTextEditor.AlignRightRichTextEditor.AlignCenterRichTextEditor.AlignJustify
@tiptap/extension-color과 @tiptap/extension-text-style extensions이 필요한 컨트롤:
RichTextEditor.ColorPickerRichTextEditor.ColorRichTextEditor.UnsetColor
필요한 extensions이 있는 다른 컨트롤:
RichTextEditor.Superscript에는 @tiptap/extension-superscript 필요RichTextEditor.Subscript에는 @tiptap/extension-subscript 필요RichTextEditor.Highlight에는 @tiptap/extension-highlight 필요
Placeholder
placeholder를 사용하려면 @tiptap/extension-placeholder 패키지를 설치해야 해요:
yarn add @tiptap/extension-placeholder
import { RichTextEditor } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Placeholder from '@tiptap/extension-placeholder';
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [StarterKit, Placeholder.configure({ placeholder: 'This is placeholder' })],
content: '',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Content />
</RichTextEditor>
);
}
Link extension
@mantine/tiptap 패키지는 Ctrl + K 키보드 단축키가 동작하려면 @tiptap/extension-link 대신 사용해야 하는 커스텀 Link extension을 제공해요:
// Use Link extension exported from the @mantine/tiptap package
import { useEditor } from '@tiptap/react';
import { Link, RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [
Link,
// ... other extensions
],
});
return <RichTextEditor editor={editor}>...</RichTextEditor>;
}
텍스트 색상
텍스트 색상을 사용하려면 추가 패키지를 설치해야 해요:
yarn add @tiptap/extension-color @tiptap/extension-text-style
텍스트 색상을 바꾸는 데 다음 컨트롤을 사용할 수 있어요:
-
RichTextEditor.ColorPicker– 주어진 미리 정의된 색상 견본과 ColorPicker 컴포넌트로 색상을 고를 수 있게 해줘요 -
RichTextEditor.Color– 한 번의 클릭으로 주어진 색상을 적용할 수 있게 해줘요 -
RichTextEditor.UnsetColor– 색상 스타일을 지워요
import { useEditor } from '@tiptap/react';
import { EyedropperIcon } from '@phosphor-icons/react';
import { Color } from '@tiptap/extension-color';
import { TextStyle } from '@tiptap/extension-text-style';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, TextStyle, Color],
content: '<p>Apply some colors to this text</p>',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.ColorsControl colors={[]} />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
코드 하이라이트
코드 하이라이트를 사용하려면 추가 패키지를 설치해야 해요:
yarn add lowlight @tiptap/extension-code-block-lowlight
import { RichTextEditor } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
import { createLowlight } from 'lowlight';
import ts from 'highlight.js/lib/languages/typescript';
const lowlight = createLowlight();
// register languages that you are planning to use
lowlight.register({ ts });
function escapeHtml(unsafe: string) {
return unsafe
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
const codeExample =
escapeHtml(`// Valid braces Kata – https://www.codewars.com/kata/5277c8a221e209d3f6000b56
const pairs: Record<string, string> = {
'[': ']',
'{': '}',
'(': ')',
};
const openBraces = Object.keys(pairs);
export function validBraces(braces: string) {
const opened: string[] = [];
for (let i = 0; i < braces.length; i += 1) {
...
}
}`);
function Demo() {
const editor = useEditor({
extensions: [StarterKit, CodeBlockLowlight.configure({ lowlight })],
content: codeExample,
});
return <RichTextEditor editor={editor}>...</RichTextEditor>;
}
소스 코드 모드
다음 컨트롤로 에디터 콘텐츠의 소스 코드를 보고 편집할 수 있어요:
RichTextEditor.SourceCode– 소스 코드 모드를 켜고 끌 수 있게 해줘요
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
import { useState } from 'react';
function Demo() {
const [isSourceCodeModeActive, onSourceCodeTextSwitch] = useState(false)
const editor = useEditor({
extensions: [StarterKit],
shouldRerenderOnTransaction: true,
content: '<p>Source code control example</p><p>New line with <strong>bold</strong> text</p><p>New line with <em>italic</em> text</p>',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.SourceCode isSourceCodeModeActive={isSourceCodeModeActive} onSourceCodeTextSwitch={onSourceCodeTextSwitch} />
</RichTextEditor.Toolbar>
{!isSourceCodeModeActive && (
<RichTextEditor.Content />
)}
</RichTextEditor>
);
}
Tasks
tasks를 사용하려면 추가 패키지를 설치해야 해요:
yarn add @tiptap/extension-task-item @tiptap/extension-task-list
import TaskItem from '@tiptap/extension-task-item';
import TipTapTaskList from '@tiptap/extension-task-list';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor, getTaskListExtension } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [
StarterKit,
getTaskListExtension(TipTapTaskList),
TaskItem.configure({
nested: true,
HTMLAttributes: {
class: 'test-item',
},
}),
],
content: `
<ul data-type="taskList">
<li data-type="taskItem" data-checked="false">A list item</li>
<li data-type="taskItem" data-checked="false">And another one</li>
</ul>
`,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.TaskList />
<RichTextEditor.TaskListLift />
<RichTextEditor.TaskListSink />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
Table
테이블 지원을 추가하려면 Tiptap table extension을 설치하고 에디터에 등록해요. TableKit은 필요한 모든 노드 extensions(Table, TableRow, TableHeader, TableCell)을 하나의 패키지로 묶어요:
yarn add @tiptap/extension-table
import { TableKit } from '@tiptap/extension-table';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, TableKit],
content: `
<table>
<tbody>
<tr><th>Framework</th><th>Language</th></tr>
<tr><td>Mantine</td><td>TypeScript</td></tr>
<tr><td>Tiptap</td><td>TypeScript</td></tr>
</tbody>
</table>
`,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.Table />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
RichTextEditor는 다음 테이블 컨트롤을 제공해요. 모두 테이블 extension이 설치되지 않으면 비활성화되고, RichTextEditor.TableInsert를 제외한 모든 컨트롤은 커서가 테이블 안에 없을 때 추가로 비활성화돼요:
-
RichTextEditor.TableInsert– 테이블을 삽입하고, 크기를 고르는 그리드를 열어요. 새 테이블이 삽입되는 곳이므로 테이블 밖에서 활성화돼요.maxColumns,maxRows,withHeaderRow,popoverPropsprops를 지원해요. -
RichTextEditor.TableDelete– 전체 테이블을 삭제해요 -
RichTextEditor.TableColumnBefore/RichTextEditor.TableColumnAfter– 앞/뒤에 열 추가 -
RichTextEditor.TableColumnDelete– 현재 열 삭제 -
RichTextEditor.TableRowBefore/RichTextEditor.TableRowAfter– 앞/뒤에 행 추가 -
RichTextEditor.TableRowDelete– 현재 행 삭제 -
RichTextEditor.TableToggleHeaderRow/RichTextEditor.TableToggleHeaderColumn– 헤더 행/열 토글 -
RichTextEditor.TableMergeCells/RichTextEditor.TableSplitCell– 선택된 셀 병합 / 병합된 셀 분할
Details
접을 수 있는 섹션(/)을 추가하려면 Tiptap details extension을 설치하고 그것이 제공하는 세 노드 – Details, DetailsSummary, DetailsContent – 를 모두 등록해요:
yarn add @tiptap/extension-details
import { Details, DetailsSummary, DetailsContent } from '@tiptap/extension-details';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, Details, DetailsSummary, DetailsContent],
content: `
<details>
<summary>Shipping and delivery</summary>
<p>Orders are processed within 1–2 business days and delivered in 3–5 business days.</p>
</details>
<details>
<summary>Returns and refunds</summary>
<p>You can return any item within 30 days of delivery for a full refund.</p>
</details>
`,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.Details />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
RichTextEditor.Details는 접을 수 있는 섹션을 토글해요: 현재 블록을 details 노드로 감싸거나, 커서가 이미 안에 있으면 details 노드를 제거해요. 컨트롤은 details extension이 설치되지 않으면 자동으로 비활성화돼요.
보이지 않는 문자
공백, 문단 구분, 하드 브레이크 같은 서식 표시를 표시하려면 Tiptap invisible characters extension을 설치하고 등록해요. 컨트롤을 토글할 때까지 표시를 숨기려면 visible: false를 설정해요:
yarn add @tiptap/extension-invisible-characters
import InvisibleCharacters from '@tiptap/extension-invisible-characters';
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [StarterKit, InvisibleCharacters.configure({ visible: false })],
content: `
<p>Toggle the control to reveal spaces and paragraph breaks.</p>
<p>Each space becomes a dot and every paragraph ends with a pilcrow.</p>
`,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.InvisibleCharacters />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
RichTextEditor.InvisibleCharacters는 서식 표시의 표시 여부를 토글해요. 컨트롤은 현재 표시 여부를 활성 상태로 반영하며 extension이 설치되지 않으면 자동으로 비활성화돼요.
단락 래퍼 제거
Tiptap은 일부 노드의 내용을 기본적으로 \n 요소로 감싸서, 테이블 셀의 경우 |\nText\n|, 목록 항목의 경우 - Text\n 같은 마크업을 만들어요. 이것은 @mantine/tiptap이 아니라 노드 스키마(테이블 셀은 content: 'block+', 목록 항목은 content: 'paragraph block*')로 정의돼요 – 에디터는 스키마가 만든 마크업만 렌더링해요.
텍스트를 노드에 직접 배치하는 것을 선호한다면 해당 extension을 확장해 content를 inline*로 재정의해요. 테이블 셀:
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { TableKit, TableCell, TableHeader } from '@tiptap/extension-table';
const editor = useEditor({
extensions: [
StarterKit,
TableKit.configure({ tableCell: false, tableHeader: false }),
TableCell.extend({ content: 'inline*' }),
TableHeader.extend({ content: 'inline*' }),
],
});
// → <td>Text</td> instead of <td>\n<p>Text</p>\n</td>
inline* 콘텐츠는 이 노드들을 인라인 콘텐츠로만 제한해요: 더 이상 여러 단락, blockquote 또는 다른 블록 노드를 담을 수 없어요. 단일 줄 인라인 노드가 특별히 필요하지 않다면 기본 블록 콘텐츠를 유지해요.
같은 기법이 목록 항목(ListItem.extend({ content: 'inline*' }))에도 적용되지만 주의해야 해요: inline* 목록 항목은 중첩 목록을 담을 수 없어서 목록 중첩이 깨져요 – Tab 단축키와 들여쓰기 컨트롤(예: RichTextEditor.TaskListSink)이 TransformError를 던질 거예요. 중첩도 비활성화할 때만 목록에서 래퍼를 제거해요(예: 목록 키보드 단축키 제거 및 들여쓰기 컨트롤 미렌더링).
타이포그래피 스타일
기본적으로 RichTextEditor는 Typography와 몇 가지 추가 스타일로 콘텐츠를 렌더링해요. withTypographyStyles={false}로 이 스타일들을 비활성화할 수 있어요:
import { useEditor } from '@tiptap/react';
import { RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [
// ... your extensions
],
});
return (
<RichTextEditor editor={editor} withTypographyStyles={false}>
...
</RichTextEditor>
);
}
그러면 전역 스타일이나 Styles API로 자신만의 스타일을 추가할 수 있어요:
.root {
h2 {
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
font-size: var(--mantine-font-size-xl);
}
p {
font-size: var(--mantine-font-size-lg);
}
a {
color: var(--mantine-color-red-6);
}
}
Bubble menu
어떤 RichTextEditor 컨트롤과든 BubbleMenu 컴포넌트를 사용할 수 있어요. bubble menu는 텍스트 선택 근처에 나타나요:
import { useEditor } from '@tiptap/react';
import { BubbleMenu } from '@tiptap/react/menus';
import StarterKit from '@tiptap/starter-kit';
import { Link, RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [StarterKit.configure({ link: false }), Link],
content: '<p>Select some text to see bubble menu</p>',
});
return (
<>
{editor && (
<BubbleMenu editor={editor}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.Bold />
<RichTextEditor.Italic />
<RichTextEditor.Link />
</RichTextEditor.ControlsGroup>
</BubbleMenu>
)}
<RichTextEditor editor={editor} />
</>
);
}
Floating menu
어떤 RichTextEditor 컨트롤과든 FloatingMenu 컴포넌트를 사용할 수 있어요. floating menu는 빈 줄에 나타나요:
import { useEditor } from '@tiptap/react';
import { FloatingMenu } from '@tiptap/react/menus';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor, Link } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [StarterKit.configure({ link: false }), Link],
content: '<p>Enter a new line to see floating menu</p>',
});
return (
<>
{editor && (
<FloatingMenu editor={editor}>
<RichTextEditor.ControlsGroup>
<RichTextEditor.H2 />
<RichTextEditor.H3 />
</RichTextEditor.ControlsGroup>
</FloatingMenu>
)}
<RichTextEditor editor={editor} />
</>
);
}
고정 툴바
RichTextEditor.Toolbar 컴포넌트에 sticky prop을 설정해 툴바를 고정(sticky) 만들 수 있어요; stickyOffset으로 top 속성을 제어해요. 예를 들어 mantine.dev 문서 웹사이트에는 var(--docs-header-height) 높이의 헤더가 있어요. 이 경우 고정된 위치 요소와 함께 sticky 위치가 올바르게 동작하도록 stickyOffset="var(--docs-header-height)"을 설정해야 해요.
import { RichTextEditor, Link } from '@mantine/tiptap';
import { useEditor } from '@tiptap/react';
import Highlight from '@tiptap/extension-highlight';
import StarterKit from '@tiptap/starter-kit';
import TextAlign from '@tiptap/extension-text-align';
import Superscript from '@tiptap/extension-superscript';
import SubScript from '@tiptap/extension-subscript';
const content = '<h2>Welcome to Mantine rich text editor</h2><p>...</p>';
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [
StarterKit.configure({ link: false }),
Link,
Superscript,
SubScript,
Highlight,
TextAlign.configure({ types: ['heading', 'paragraph'] }),
],
content,
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar sticky stickyOffset={60}>
...
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
Editor context
useRichTextEditorContext 훅으로 콘텍스트에서 Editor를 얻어요. 이 훅은 커스텀 컨트롤을 만들거나 Tiptap editor API가 지원하는 어떤 연산이든 실행하는 데 쓸 수 있어요.
import { Button } from '@mantine/core';
import { useRichTextEditorContext } from '@mantine/tiptap';
function Demo() {
const { editor } = useRichTextEditorContext();
return (
<Button onClick={() => editor?.chain().focus().toggleBold().run()}>
Make bold
</Button>
);
}
커스텀 컨트롤
RichTextEditor.Control 컴포넌트로 커스텀 컨트롤을 만들어요. button 요소가 지원하는 모든 props를 지원하며 활성 상태를 나타내는 active prop이 있어요. 스크린 리더가 컨트롤을 볼 수 있게 하려면 aria-label 속성을 설정해야 한다는 점에 주의하세요.
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor, useRichTextEditorContext } from '@mantine/tiptap';
import { StarIcon } from '@phosphor-icons/react';
function InsertStarControl() {
const { editor } = useRichTextEditorContext();
return (
<RichTextEditor.Control
onClick={() => editor?.commands.insertContent('⭐')}
aria-label="Insert star emoji"
title="Insert star emoji"
>
<StarIcon size={14} />
</RichTextEditor.Control>
);
}
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [StarterKit],
content: '<p>Click control to insert star emoji</p>',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<InsertStarControl />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
아이콘 변경
컨트롤의 아이콘은 icon prop을 설정해 바꿀 수 있어요. size prop을 처리해야 하는 컴포넌트를 받아요:
import { useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { RichTextEditor } from '@mantine/tiptap';
import { TextBIcon, TextItalicIcon } from '@phosphor-icons/react';
const BoldIcon = () => <TextBIcon />;
const ItalicIcon = () => <TextItalicIcon />;
function Demo() {
const editor = useEditor({
shouldRerenderOnTransaction: true,
extensions: [StarterKit],
content: '<p>Customize icons with icon prop</p>',
});
return (
<RichTextEditor editor={editor}>
<RichTextEditor.Toolbar>
<RichTextEditor.Bold icon={BoldIcon} />
<RichTextEditor.Italic icon={ItalicIcon} />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
Labels와 현지화
RichTextEditor는 labels prop으로 모든 컨트롤의 라벨을 바꾸는 것을 지원해요:
import { useEditor } from '@tiptap/react';
import { RichTextEditor } from '@mantine/tiptap';
function Demo() {
const editor = useEditor({
extensions: [
// ... your extensions
],
});
return (
<RichTextEditor editor={editor} labels={{ boldControlLabel: 'Fett' }}>
<RichTextEditor.Toolbar>
<RichTextEditor.Bold />
</RichTextEditor.Toolbar>
<RichTextEditor.Content />
</RichTextEditor>
);
}
대부분의 라벨은 컨트롤에 aria-label과 title 속성을 추가하는 데 사용되고, 일부 라벨은 문자열을 반환하는 함수일 수 있어요. 모든 라벨을 제공하지 않으면 기본 라벨과 병합돼요.
사용 가능한 모든 라벨:
// RichTextEditorLabels type can be imported from @mantine/tiptap package
export interface RichTextEditorLabels {
/** RichTextEditor.Bold control aria-label */
boldControlLabel: string;
/** RichTextEditor.Hr control aria-label */
hrControlLabel: string;
/** RichTextEditor.Italic control aria-label */
italicControlLabel: string;
/** RichTextEditor.Underline control aria-label */
underlineControlLabel: string;
/** RichTextEditor.Strike control aria-label */
strikeControlLabel: string;
/** RichTextEditor.ClearFormatting control aria-label */
clearFormattingControlLabel: string;
/** RichTextEditor.Link control aria-label */
linkControlLabel: string;
/** RichTextEditor.Unlink control aria-label */
unlinkControlLabel: string;
/** RichTextEditor.BulletList control aria-label */
bulletListControlLabel: string;
/** RichTextEditor.OrderedList control aria-label */
orderedListControlLabel: string;
/** RichTextEditor.H1 control aria-label */
h1ControlLabel: string;
/** RichTextEditor.H2 control aria-label */
h2ControlLabel: string;
/** RichTextEditor.H3 control aria-label */
h3ControlLabel: string;
/** RichTextEditor.H4 control aria-label */
h4ControlLabel: string;
/** RichTextEditor.H5 control aria-label */
h5ControlLabel: string;
/** RichTextEditor.H6 control aria-label */
h6ControlLabel: string;
/** RichTextEditor.Blockquote control aria-label */
blockquoteControlLabel: string;
/** RichTextEditor.AlignLeft control aria-label */
alignLeftControlLabel: string;
/** RichTextEditor.AlignCenter control aria-label */
alignCenterControlLabel: string;
/** RichTextEditor.AlignRight control aria-label */
alignRightControlLabel: string;
/** RichTextEditor.AlignJustify control aria-label */
alignJustifyControlLabel: string;
/** RichTextEditor.Code control aria-label */
codeControlLabel: string;
/** RichTextEditor.CodeBlock control aria-label */
codeBlockControlLabel: string;
/** RichTextEditor.Subscript control aria-label */
subscriptControlLabel: string;
/** RichTextEditor.Superscript control aria-label */
superscriptControlLabel: string;
/** RichTextEditor.ColorPicker control aria-label */
colorPickerControlLabel: string;
/** RichTextEditor.UnsetColor control aria-label */
unsetColorControlLabel: string;
/** RichTextEditor.Highlight control aria-label */
highlightControlLabel: string;
/** RichTextEditor.Undo control aria-label */
undoControlLabel: string;
/** RichTextEditor.Redo control aria-label */
redoControlLabel: string;
/** A function go get RichTextEditor.Color control aria-label based on color that control applies */
colorControlLabel: (color: string) => string;
/** aria-label for link editor url input */
linkEditorInputLabel: string;
/** placeholder for link editor url input */
linkEditorInputPlaceholder: string;
/** Content of external button tooltip in link editor when the link was chosen to open in a new tab */
linkEditorExternalLink: string;
/** Content of external button tooltip in link editor when the link was chosen to open in the same tab */
linkEditorInternalLink: string;
/** Save button content in link editor */
linkEditorSave: string;
/** Cancel button title text in color picker control */
colorPickerCancel: string;
/** Clear button title text in color picker control */
colorPickerClear: string;
/** Color picker button title text in color picker control */
colorPickerColorPicker: string;
/** Palette button title text in color picker control */
colorPickerPalette: string;
/** Save button title text in color picker control */
colorPickerSave: string;
/** aria-label for color palette colors */
colorPickerColorLabel: (color: string) => string;
}
기본 라벨(@mantine/tiptap 패키지에서 import 할 수 있음):
import { RichTextEditorLabels } from '@mantine/tiptap';
export const DEFAULT_LABELS: RichTextEditorLabels = {
// Controls labels
linkControlLabel: 'Link',
colorPickerControlLabel: 'Text color',
highlightControlLabel: 'Highlight text',
colorControlLabel: (color) => `Set text color ${color}`,
boldControlLabel: 'Bold',
italicControlLabel: 'Italic',
underlineControlLabel: 'Underline',
strikeControlLabel: 'Strikethrough',
clearFormattingControlLabel: 'Clear formatting',
unlinkControlLabel: 'Remove link',
bulletListControlLabel: 'Bullet list',
orderedListControlLabel: 'Ordered list',
h1ControlLabel: 'Heading 1',
h2ControlLabel: 'Heading 2',
h3ControlLabel: 'Heading 3',
h4ControlLabel: 'Heading 4',
h5ControlLabel: 'Heading 5',
h6ControlLabel: 'Heading 6',
blockquoteControlLabel: 'Blockquote',
alignLeftControlLabel: 'Align text: left',
alignCenterControlLabel: 'Align text: center',
alignRightControlLabel: 'Align text: right',
alignJustifyControlLabel: 'Align text: justify',
codeControlLabel: 'Code',
codeBlockControlLabel: 'Code block',
subscriptControlLabel: 'Subscript',
superscriptControlLabel: 'Superscript',
unsetColorControlLabel: 'Unset color',
hrControlLabel: 'Horizontal line',
undoControlLabel: 'Undo',
redoControlLabel: 'Redo',
// Task list
tasksControlLabel: 'Task list',
tasksSinkLabel: 'Decrease task level',
tasksLiftLabel: 'Increase task level',
// Link editor
linkEditorInputLabel: 'Enter URL',
linkEditorInputPlaceholder: 'https://example.com/',
linkEditorExternalLink: 'Open link in a new tab',
linkEditorInternalLink: 'Open link in the same tab',
linkEditorSave: 'Save',
// Color picker control
colorPickerCancel: 'Cancel',
colorPickerClear: 'Clear color',
colorPickerColorPicker: 'Color picker',
colorPickerPalette: 'Color palette',
colorPickerSave: 'Save',
colorPickerColorLabel: (color) => `Set text color ${color}`,
};