Skip to content

Commit cb769fe

Browse files
authored
Merge pull request #137 from condorheroblog/main
2 parents ea806f6 + 5859132 commit cb769fe

File tree

6 files changed

+144
-46
lines changed

6 files changed

+144
-46
lines changed

docs/.vitepress/locale.ts

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export function getLocaleConfig(lang: string) {
6161
text: t('Getting Started'),
6262
link: `${urlPrefix}/guide/getting-started`,
6363
},
64+
{
65+
text: t('Toolbar'),
66+
link: `${urlPrefix}/guide/toolbar`,
67+
},
6468
{
6569
text: t('Bubble Menu'),
6670
link: `${urlPrefix}/guide/bubble-menu`,

docs/guide/getting-started.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
description: How to install reactjs-tiptap-editor
33

44
next:
5-
text: Bubble Menu
6-
link: /guide/bubble-menu.md
5+
text: Toolbar
6+
link: /guide/toolbar.md
77
---
88

99
# Installation

docs/guide/toolbar.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
description: Toolbar
3+
4+
next:
5+
text: Bubble Menu
6+
link: /guide/bubble-menu.md
7+
---
8+
9+
# Toolbar
10+
11+
The toolbar of the rich text editor.
12+
13+
## Usage
14+
15+
Hide a certain toolbar:
16+
17+
```js
18+
Bold.configure({
19+
toolbar: false,
20+
})
21+
```
22+
23+
## Customizing the Toolbar
24+
25+
```jsx
26+
<RichTextEditor
27+
toolbar={{
28+
render: (props, toolbarItems, dom, containerDom) => {
29+
return containerDom(dom)
30+
}
31+
}}
32+
/>
33+
```
34+
35+
## ToolbarProps
36+
37+
```ts
38+
export interface ToolbarItemProps {
39+
button: {
40+
component: React.ComponentType<any>
41+
componentProps: Record<string, any>
42+
}
43+
divider: boolean
44+
spacer: boolean
45+
type: string
46+
name: string
47+
}
48+
export interface ToolbarRenderProps {
49+
editor: Editor
50+
disabled: boolean
51+
}
52+
export interface ToolbarProps {
53+
render?: (props: ToolbarRenderProps, toolbarItems: ToolbarItemProps[], dom: JSX.Element[], containerDom: (innerContent: React.ReactNode) => React.ReactNode) => React.ReactNode
54+
}
55+
```

src/components/RichTextEditor.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { UseEditorOptions } from '@tiptap/react'
66
import { EditorContent, useEditor } from '@tiptap/react'
77
import { differenceBy, throttle } from 'lodash-es'
88

9-
import type { BubbleMenuProps } from '@/types'
9+
import type { BubbleMenuProps, ToolbarProps } from '@/types'
1010
import { BubbleMenu, Toolbar, TooltipProvider } from '@/components'
1111
import { EDITOR_UPDATE_WATCH_THROTTLE_WAIT_TIME } from '@/constants'
1212
import { RESET_CSS } from '@/constants/resetCSS'
@@ -59,6 +59,8 @@ export interface RichTextEditorProps {
5959
onChangeContent?: (val: any) => void
6060
/** Bubble menu props */
6161
bubbleMenu?: BubbleMenuProps
62+
/** Toolbar props */
63+
toolbar?: ToolbarProps
6264

6365
/** Use editor options */
6466
useEditorOptions?: UseEditorOptions
@@ -168,7 +170,7 @@ function RichTextEditor(props: RichTextEditorProps, ref: React.ForwardedRef<{ ed
168170
<div className="richtext-rounded-[0.5rem] richtext-bg-background richtext-shadow richtext-overflow-hidden richtext-outline richtext-outline-1">
169171

170172
<div className="richtext-flex richtext-flex-col richtext-w-full richtext-max-h-full">
171-
{!props?.hideToolbar && <Toolbar editor={editor} disabled={!!props?.disabled} />}
173+
{!props?.hideToolbar && <Toolbar editor={editor} disabled={!!props?.disabled} toolbar={props.toolbar} />}
172174

173175
<EditorContent className={`richtext-relative ${props?.contentClass || ''}`} editor={editor} />
174176

src/components/Toolbar.tsx

+57-42
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
1-
/* eslint-disable react/no-duplicate-key */
21
import React, { useMemo } from 'react'
32
import type { Editor } from '@tiptap/core'
3+
import type { ToolbarItemProps, ToolbarProps } from '@/types'
44

55
import { Separator } from '@/components'
66
import { useLocale } from '@/locales'
77
import { isFunction } from '@/utils/utils'
88

9-
export interface ToolbarProps {
9+
export interface ToolbarComponentProps {
1010
editor: Editor
1111
disabled?: boolean
12+
toolbar?: ToolbarProps
1213
}
1314

14-
interface ToolbarItemProps {
15-
button: {
16-
component: React.ComponentType<any>
17-
componentProps: Record<string, any>
18-
}
19-
divider: boolean
20-
spacer: boolean
21-
}
22-
23-
function Toolbar({ editor, disabled }: ToolbarProps) {
15+
function Toolbar({ editor, disabled, toolbar }: ToolbarComponentProps) {
2416
const { t, lang } = useLocale()
2517

26-
const items = useMemo(() => {
18+
const toolbarItems = useMemo(() => {
2719
const extensions = [...editor.extensionManager.extensions]
2820
const sortExtensions = extensions.sort((arr, acc) => {
2921
const a = (arr.options).sort ?? -1
@@ -34,7 +26,12 @@ function Toolbar({ editor, disabled }: ToolbarProps) {
3426
let menus: ToolbarItemProps[] = []
3527

3628
for (const extension of sortExtensions) {
37-
const { button, divider = false, spacer = false, toolbar = true } = extension.options as any
29+
const {
30+
button,
31+
divider = false,
32+
spacer = false,
33+
toolbar = true,
34+
} = extension.options
3835
if (!button || !isFunction(button) || !toolbar) {
3936
continue
4037
}
@@ -50,44 +47,62 @@ function Toolbar({ editor, disabled }: ToolbarProps) {
5047
button: k,
5148
divider: i === _button.length - 1 ? divider : false,
5249
spacer: i === 0 ? spacer : false,
50+
type: extension.type,
51+
name: extension.name,
5352
}))
5453
menus = [...menus, ...menu]
5554
continue
5655
}
5756

58-
menus.push({ button: _button, divider, spacer })
57+
menus.push({
58+
button: _button,
59+
divider,
60+
spacer,
61+
type: extension.type,
62+
name: extension.name,
63+
})
5964
}
6065
return menus
6166
}, [editor, t, lang])
6267

63-
return (
64-
<div
65-
className="richtext-px-1 richtext-py-2 !richtext-border-b"
66-
style={{
67-
pointerEvents: disabled ? 'none' : 'auto',
68-
opacity: disabled ? 0.5 : 1,
69-
}}
70-
>
71-
<div className="richtext-relative richtext-flex richtext-flex-wrap richtext-h-auto richtext-gap-y-1 richtext-gap-x-1">
72-
{items.map((item: ToolbarItemProps, key) => {
73-
const ButtonComponent = item.button.component
74-
75-
return (
76-
<div className="richtext-flex richtext-items-center" key={`toolbar-item-${key}`}>
77-
{item?.spacer && <Separator orientation="vertical" className="!richtext-h-[16px] !richtext-mx-[10px]" />}
78-
79-
<ButtonComponent
80-
{...item.button.componentProps}
81-
disabled={disabled || item?.button?.componentProps?.disabled}
82-
/>
83-
84-
{item?.divider && <Separator orientation="vertical" className="!richtext-h-auto !richtext-mx-2" />}
85-
</div>
86-
)
87-
})}
68+
const containerDom = (innerContent: React.ReactNode) => {
69+
return (
70+
<div
71+
className="richtext-px-1 richtext-py-2 !richtext-border-b"
72+
style={{
73+
pointerEvents: disabled ? 'none' : 'auto',
74+
opacity: disabled ? 0.5 : 1,
75+
}}
76+
>
77+
<div className="richtext-relative richtext-flex richtext-flex-wrap richtext-h-auto richtext-gap-y-1 richtext-gap-x-1">
78+
{innerContent}
79+
</div>
8880
</div>
89-
</div>
90-
)
81+
)
82+
}
83+
84+
const dom = toolbarItems.map((item: ToolbarItemProps, key) => {
85+
const ButtonComponent = item.button.component
86+
87+
return (
88+
<div className="richtext-flex richtext-items-center" key={`toolbar-item-${key}`}>
89+
{item?.spacer && <Separator orientation="vertical" className="!richtext-h-[16px] !richtext-mx-[10px]" />}
90+
91+
<ButtonComponent
92+
{...item.button.componentProps}
93+
disabled={disabled || item?.button?.componentProps?.disabled}
94+
/>
95+
96+
{item?.divider && <Separator orientation="vertical" className="!richtext-h-auto !richtext-mx-2" />}
97+
</div>
98+
)
99+
})
100+
101+
if (toolbar && toolbar?.render) {
102+
return toolbar.render({ editor, disabled: disabled || false }, toolbarItems, dom, containerDom)
103+
}
104+
105+
return containerDom(dom)
91106
}
92107

93108
export { Toolbar }

src/types.ts

+22
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,28 @@ export interface BubbleMenuProps {
216216
render?: (props: BubbleMenuRenderProps, dom: React.ReactNode) => React.ReactNode
217217
}
218218

219+
/**
220+
* Represents the ToolbarItemProps.
221+
*/
222+
export interface ToolbarItemProps {
223+
button: {
224+
component: React.ComponentType<any>
225+
componentProps: Record<string, any>
226+
}
227+
divider: boolean
228+
spacer: boolean
229+
type: string
230+
name: string
231+
}
232+
233+
export interface ToolbarRenderProps {
234+
editor: Editor
235+
disabled: boolean
236+
}
237+
export interface ToolbarProps {
238+
render?: (props: ToolbarRenderProps, toolbarItems: ToolbarItemProps[], dom: JSX.Element[], containerDom: (innerContent: React.ReactNode) => React.ReactNode) => React.ReactNode
239+
}
240+
219241
export interface NameValueOption<T = string> {
220242
name: string
221243
value: T

0 commit comments

Comments
 (0)