From 7ba0fed94b97835fa22110d6506576497c8aa96d Mon Sep 17 00:00:00 2001 From: Condor Hero Date: Wed, 8 Jan 2025 11:51:31 +0800 Subject: [PATCH] feat: support custom toolbars --- docs/.vitepress/locale.ts | 4 ++ docs/guide/getting-started.md | 4 +- docs/guide/toolbar.md | 55 ++++++++++++++++++ src/components/RichTextEditor.tsx | 6 +- src/components/Toolbar.tsx | 97 ++++++++++++++++++------------- src/types.ts | 22 +++++++ 6 files changed, 143 insertions(+), 45 deletions(-) create mode 100644 docs/guide/toolbar.md diff --git a/docs/.vitepress/locale.ts b/docs/.vitepress/locale.ts index c9fb874..95a3610 100644 --- a/docs/.vitepress/locale.ts +++ b/docs/.vitepress/locale.ts @@ -61,6 +61,10 @@ export function getLocaleConfig(lang: string) { text: t('Getting Started'), link: `${urlPrefix}/guide/getting-started`, }, + { + text: t('Toolbar'), + link: `${urlPrefix}/guide/toolbar`, + }, { text: t('Bubble Menu'), link: `${urlPrefix}/guide/bubble-menu`, diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 681b448..61411cd 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -2,8 +2,8 @@ description: How to install reactjs-tiptap-editor next: - text: Bubble Menu - link: /guide/bubble-menu.md + text: Toolbar + link: /guide/toolbar.md --- # Installation diff --git a/docs/guide/toolbar.md b/docs/guide/toolbar.md new file mode 100644 index 0000000..8fb65cc --- /dev/null +++ b/docs/guide/toolbar.md @@ -0,0 +1,55 @@ +--- +description: Toolbar + +next: + text: Bubble Menu + link: /guide/bubble-menu.md +--- + +# Toolbar + +The toolbar of the rich text editor. + +## Usage + +Hide a certain toolbar: + +```js +Bold.configure({ + toolbar: false, +}) +``` + +## Customizing the Toolbar + +```jsx + { + return containerDom(domContent) + } + }} +/> +``` + +## ToolbarProps + +```ts +export interface ToolbarItemProps { + button: { + component: React.ComponentType + componentProps: Record + } + divider: boolean + spacer: boolean + type: string + name: string +} +export interface ToolbarRenderProps { + editor: Editor + disabled: boolean +} +export interface ToolbarProps { + render?: (props: ToolbarRenderProps, dom: ToolbarItemProps[], domContent: React.ReactNode, containerDom: (innerContent: React.ReactNode) => React.ReactNode) => React.ReactNode +} +``` diff --git a/src/components/RichTextEditor.tsx b/src/components/RichTextEditor.tsx index 1bfa7ff..9543222 100644 --- a/src/components/RichTextEditor.tsx +++ b/src/components/RichTextEditor.tsx @@ -6,7 +6,7 @@ import type { UseEditorOptions } from '@tiptap/react' import { EditorContent, useEditor } from '@tiptap/react' import { differenceBy, throttle } from 'lodash-es' -import type { BubbleMenuProps } from '@/types' +import type { BubbleMenuProps, ToolbarProps } from '@/types' import { BubbleMenu, Toolbar, TooltipProvider } from '@/components' import { EDITOR_UPDATE_WATCH_THROTTLE_WAIT_TIME } from '@/constants' import { RESET_CSS } from '@/constants/resetCSS' @@ -59,6 +59,8 @@ export interface RichTextEditorProps { onChangeContent?: (val: any) => void /** Bubble menu props */ bubbleMenu?: BubbleMenuProps + /** Toolbar props */ + toolbar?: ToolbarProps /** Use editor options */ useEditorOptions?: UseEditorOptions @@ -168,7 +170,7 @@ function RichTextEditor(props: RichTextEditorProps, ref: React.ForwardedRef<{ ed
- {!props?.hideToolbar && } + {!props?.hideToolbar && } diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index f060e3f..a668643 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -1,26 +1,18 @@ -/* eslint-disable react/no-duplicate-key */ import React, { useMemo } from 'react' import type { Editor } from '@tiptap/core' +import type { ToolbarItemProps, ToolbarProps } from '@/types' import { Separator } from '@/components' import { useLocale } from '@/locales' import { isFunction } from '@/utils/utils' -export interface ToolbarProps { +export interface ToolbarComponentProps { editor: Editor disabled?: boolean + toolbar?: ToolbarProps } -interface ToolbarItemProps { - button: { - component: React.ComponentType - componentProps: Record - } - divider: boolean - spacer: boolean -} - -function Toolbar({ editor, disabled }: ToolbarProps) { +function Toolbar({ editor, disabled, toolbar }: ToolbarComponentProps) { const { t, lang } = useLocale() const items = useMemo(() => { @@ -34,7 +26,12 @@ function Toolbar({ editor, disabled }: ToolbarProps) { let menus: ToolbarItemProps[] = [] for (const extension of sortExtensions) { - const { button, divider = false, spacer = false, toolbar = true } = extension.options as any + const { + button, + divider = false, + spacer = false, + toolbar = true, + } = extension.options if (!button || !isFunction(button) || !toolbar) { continue } @@ -50,44 +47,62 @@ function Toolbar({ editor, disabled }: ToolbarProps) { button: k, divider: i === _button.length - 1 ? divider : false, spacer: i === 0 ? spacer : false, + type: extension.type, + name: extension.name, })) menus = [...menus, ...menu] continue } - menus.push({ button: _button, divider, spacer }) + menus.push({ + button: _button, + divider, + spacer, + type: extension.type, + name: extension.name, + }) } return menus }, [editor, t, lang]) - return ( -
-
- {items.map((item: ToolbarItemProps, key) => { - const ButtonComponent = item.button.component - - return ( -
- {item?.spacer && } - - - - {item?.divider && } -
- ) - })} + const containerDom = (innerContent: React.ReactNode) => { + return ( +
+
+ {innerContent} +
-
- ) + ) + } + + const domContent = items.map((item: ToolbarItemProps, key) => { + const ButtonComponent = item.button.component + + return ( +
+ {item?.spacer && } + + + + {item?.divider && } +
+ ) + }) + + if (toolbar && toolbar?.render) { + return toolbar.render({ editor, disabled: disabled || false }, items, domContent, containerDom) + } + + return containerDom(domContent) } export { Toolbar } diff --git a/src/types.ts b/src/types.ts index 13e5c8b..e79eac5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -216,6 +216,28 @@ export interface BubbleMenuProps { render?: (props: BubbleMenuRenderProps, dom: React.ReactNode) => React.ReactNode } +/** + * Represents the ToolbarItemProps. + */ +export interface ToolbarItemProps { + button: { + component: React.ComponentType + componentProps: Record + } + divider: boolean + spacer: boolean + type: string + name: string +} + +export interface ToolbarRenderProps { + editor: Editor + disabled: boolean +} +export interface ToolbarProps { + render?: (props: ToolbarRenderProps, dom: ToolbarItemProps[], domContent: React.ReactNode, containerDom: (innerContent: React.ReactNode) => React.ReactNode) => React.ReactNode +} + export interface NameValueOption { name: string value: T