Skip to content

Commit b43c6b8

Browse files
fix: Custom UI element API (#177)
* Temp changes * Updated custom UI element API * Updated docs
1 parent 47d84f1 commit b43c6b8

File tree

5 files changed

+72
-41
lines changed

5 files changed

+72
-41
lines changed

packages/core/src/BlockNoteEditor.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ export type BlockNoteEditorOptions = {
4141
enableBlockNoteExtensions: boolean;
4242
disableHistoryExtension: boolean;
4343
/**
44-
* Factories used to create a custom UI for BlockNote
44+
* UI element factories for creating a custom UI, including custom positioning
45+
* & rendering.
4546
*/
4647
uiFactories: UiFactories;
4748
/**

packages/react/src/hooks/useBlockNote.ts

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { BlockNoteEditor, BlockNoteEditorOptions } from "@blocknote/core";
2-
import { DependencyList, useEffect, useState } from "react";
2+
import { DependencyList, FC, useEffect, useState } from "react";
33
import { createReactBlockSideMenuFactory } from "../BlockSideMenu/BlockSideMenuFactory";
44
import { createReactFormattingToolbarFactory } from "../FormattingToolbar/FormattingToolbarFactory";
55
import { createReactHyperlinkToolbarFactory } from "../HyperlinkToolbar/HyperlinkToolbarFactory";
66
import { createReactSlashMenuFactory } from "../SlashMenu/SlashMenuFactory";
77
import { defaultReactSlashMenuItems } from "../SlashMenu/defaultReactSlashMenuItems";
88
import { getBlockNoteTheme } from "../BlockNoteTheme";
9+
import { DragHandleMenuProps } from "../BlockSideMenu/components/DragHandleMenu";
10+
911
//based on https://github.com/ueberdosis/tiptap/blob/main/packages/react/src/useEditor.ts
1012

13+
type CustomElements = Partial<{
14+
formattingToolbar: FC<{ editor: BlockNoteEditor }>;
15+
dragHandleMenu: FC<DragHandleMenuProps>;
16+
}>;
17+
1118
function useForceUpdate() {
1219
const [, setValue] = useState(0);
1320

@@ -18,7 +25,11 @@ function useForceUpdate() {
1825
* Main hook for importing a BlockNote editor into a React project
1926
*/
2027
export const useBlockNote = (
21-
options: Partial<BlockNoteEditorOptions> = {},
28+
options: Partial<
29+
BlockNoteEditorOptions & {
30+
customElements: CustomElements;
31+
}
32+
> = {},
2233
deps: DependencyList = []
2334
) => {
2435
const [editor, setEditor] = useState<BlockNoteEditor | null>(null);
@@ -33,25 +44,41 @@ export const useBlockNote = (
3344
slashCommands: defaultReactSlashMenuItems,
3445
...options,
3546
};
36-
if (!newOptions.uiFactories) {
37-
newOptions = {
38-
...newOptions,
39-
uiFactories: {
40-
formattingToolbarFactory: createReactFormattingToolbarFactory(
41-
getBlockNoteTheme(newOptions.theme === "dark")
42-
),
43-
hyperlinkToolbarFactory: createReactHyperlinkToolbarFactory(
44-
getBlockNoteTheme(newOptions.theme === "dark")
45-
),
46-
slashMenuFactory: createReactSlashMenuFactory(
47-
getBlockNoteTheme(newOptions.theme === "dark")
48-
),
49-
blockSideMenuFactory: createReactBlockSideMenuFactory(
50-
getBlockNoteTheme(newOptions.theme === "dark")
51-
),
52-
},
47+
48+
let uiFactories: any;
49+
50+
if (newOptions.customElements && newOptions.uiFactories) {
51+
console.warn(
52+
"BlockNote editor initialized with both `customElements` and `uiFactories` options, prioritizing `uiFactories`."
53+
);
54+
}
55+
56+
if (newOptions.uiFactories) {
57+
uiFactories = newOptions.uiFactories;
58+
} else {
59+
uiFactories = {
60+
formattingToolbarFactory: createReactFormattingToolbarFactory(
61+
getBlockNoteTheme(newOptions.theme === "dark"),
62+
newOptions.customElements?.formattingToolbar
63+
),
64+
hyperlinkToolbarFactory: createReactHyperlinkToolbarFactory(
65+
getBlockNoteTheme(newOptions.theme === "dark")
66+
),
67+
slashMenuFactory: createReactSlashMenuFactory(
68+
getBlockNoteTheme(newOptions.theme === "dark")
69+
),
70+
blockSideMenuFactory: createReactBlockSideMenuFactory(
71+
getBlockNoteTheme(newOptions.theme === "dark"),
72+
newOptions.customElements?.dragHandleMenu
73+
),
5374
};
5475
}
76+
77+
newOptions = {
78+
...newOptions,
79+
uiFactories: uiFactories,
80+
};
81+
5582
console.log("create new blocknote instance");
5683
const instance = new BlockNoteEditor(
5784
newOptions as Partial<BlockNoteEditorOptions>

packages/website/docs/docs/editor.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Customizing the Editor
22

3-
While you can get started with BlockNote in minutes, it's likely that you'll want to customize its features and functionality to better suit your app.
3+
While you can get started with BlockNote in minutes, it's likely that you'll
4+
want to customize its features and functionality to better suit your app.
45

56
## Editor Options
67

7-
There are a number of options that you can pass to `useBlockNote()`, which you can use to customize the editor. You can find the full list of these below:
8+
There are a number of options that you can pass to `useBlockNote()`, which you
9+
can use to customize the editor. You can find the full list of these below:
810

911
```typescript
1012
export type BlockNoteEditorOptions = Partial<{
@@ -15,8 +17,10 @@ export type BlockNoteEditorOptions = Partial<{
1517
onEditorContentChange: (editor: BlockNoteEditor) => void;
1618
onTextCursorPositionChange: (editor: BlockNoteEditor) => void;
1719
slashMenuItems: ReactSlashMenuItem[];
20+
customElements: CustomElements;
1821
uiFactories: UiFactories;
1922
defaultStyles: boolean;
23+
theme: "light" | "dark";
2024
}>;
2125
```
2226

@@ -34,10 +38,14 @@ export type BlockNoteEditorOptions = Partial<{
3438

3539
`slashMenuItems:` The commands that are listed in the editor's [Slash Menu](/docs/slash-menu). If this option isn't defined, a default list of commands is loaded.
3640

37-
`uiFactories:` Factories used to create a custom UI for BlockNote, which you can find out more about in [Creating Your Own UI Elements](/docs/vanilla-js#creating-your-own-ui-elements).
41+
`customElements:` React components for a custom [Formatting Toolbar](/docs/formatting-toolbar#custom-formatting-toolbar) and/or [Drag Handle Menu](/docs/side-menu#custom-drag-handle-menu) to use.
42+
43+
`uiFactories:` UI element factories for creating a custom UI, including custom positioning & rendering. You can find out more about UI factories in [Creating Your Own UI Elements](/docs/vanilla-js#creating-your-own-ui-elements).
3844

3945
`defaultStyles`: Whether to use the default font and reset the styles of `<p>`, `<li>`, `<h1>`, etc. elements that are used in BlockNote. Defaults to true if undefined.
4046

47+
`theme:` Whether to use the light or dark theme.
48+
4149
## Demo: Saving & Restoring Editor Contents
4250

4351
By default, BlockNote doesn't preserve the editor contents when your app is reopened or refreshed. However, using the editor options, you can change this by using the editor options.

packages/website/docs/docs/formatting-toolbar.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@ type CustomFormattingToolbarProps = {
1616
const CustomFormattingToolbar = (props: CustomFormattingToolbarProps): JSX.Element => ...;
1717
```
1818

19-
You can then tell BlockNote to use your custom Formatting Toolbar using the `uiFactories` option in `useBlockNote`, but you first have to turn it into a `FormattingToolbarFactory`. Fortunately, you can easily do this using the `createReactFormattingToolbarFactory` function:
19+
You can then tell BlockNote to use your custom Formatting Toolbar using
20+
the `customElements` option in `useBlockNote`:
2021

2122
```typescript
2223
const editor = useBlockNote({
23-
uiFactories: {
24-
formattingToolbarFactory: createReactFormattingToolbarFactory(
25-
CustomFormattingToolbar
26-
),
24+
customElements: {
25+
formattingToolbar: CustomFormattingToolbar
2726
},
2827
});
2928
```
@@ -180,12 +179,10 @@ const CustomFormattingToolbar = (props: { editor: BlockNoteEditor }) => {
180179
export default function App() {
181180
// Creates a new editor instance.
182181
const editor: BlockNoteEditor = useBlockNote({
183-
uiFactories: {
182+
customElements: {
184183
// Makes the editor instance use the custom toolbar.
185-
formattingToolbarFactory: createReactFormattingToolbarFactory(
186-
CustomFormattingToolbar
187-
),
188-
},
184+
formattingToolbar: CustomFormattingToolbar
185+
}
189186
});
190187

191188
// Renders the editor instance.

packages/website/docs/docs/side-menu.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,9 @@ const CustomDragHandleMenu = (props: {
5353
export default function App() {
5454
// Creates a new editor instance.
5555
const editor: BlockNoteEditor = useBlockNote({
56-
uiFactories: {
56+
customElements: {
5757
// Makes the editor instance use the custom menu.
58-
blockSideMenuFactory:
59-
createReactBlockSideMenuFactory(CustomDragHandleMenu),
58+
dragHandleMenu: CustomDragHandleMenu
6059
},
6160
});
6261
// Renders the editor instance.
@@ -77,14 +76,13 @@ type CustomDragHandleMenuProps = {
7776
const CustomDragHandleMenu = (props: CustomDragHandleMenuProps): JSX.Element => ...;
7877
```
7978

80-
You can then tell BlockNote to use your custom Drag Handle Menu using the `uiFactories` option in `useBlockNote`, but you first have to turn it into a `SideMenuFactory` that uses it. Fortunately, you can easily do this using the `createReactSideMenuFactory` function:
79+
You can then tell BlockNote to use your custom Drag Handle Menu using
80+
the `customElements` option in `useBlockNote`:
8181

8282
```typescript
8383
const editor = useBlockNote({
84-
uiFactories: {
85-
blockSideMenuFactory: createReactBlockSideMenuFactory(
86-
CustomBlockSideMenu
87-
),
84+
customElements: {
85+
blockSideMenuFactory: CustomBlockSideMenu
8886
},
8987
});
9088
```

0 commit comments

Comments
 (0)