Skip to content

Commit be9b142

Browse files
docs: Slash menu item grouping & ordering (BLO-1009) (#2700)
* Added docs for grouping & ordering slash menu items * Added example * Updated lock file
1 parent 73f4827 commit be9b142

11 files changed

Lines changed: 351 additions & 8 deletions

File tree

docs/content/docs/react/components/suggestion-menus.mdx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,70 @@ Passing `slashMenu={false}` to `BlockNoteView` tells BlockNote not to show the d
5858

5959
`getItems` should return the items that need to be shown in the Slash Menu, based on a `query` entered by the user (anything the user types after the `triggerCharacter`). In this case, we simply append the "Hello World" item to the default Slash Menu items, and use `filterSuggestionItems` to filter the full list of items based on the user query.
6060

61+
### Item Grouping & Ordering
62+
63+
Slash Menu items are rendered in the same order as the items returned from `getItems`. Adjacent items which share the same `group` attribute are rendered together in the same group under a single label.
64+
65+
#### Ordering
66+
67+
Items appear in the menu in the exact order of the array. Reordering the array reorders the menu:
68+
69+
```typescript
70+
getItems={async (query) =>
71+
filterSuggestionItems(
72+
[
73+
insertHelloWorldItem(editor), // Shown first
74+
...getDefaultReactSlashMenuItems(editor), // Shown after
75+
],
76+
query,
77+
)
78+
}
79+
```
80+
81+
#### Grouping
82+
83+
Items with the same `group` attribute must be **adjacent** in the array to be rendered as one group. If items with the same `group` are separated by items with a different `group`, they will be rendered as two separate groups, each with their own label:
84+
85+
```typescript
86+
// Renders as a single "Basic" group:
87+
[
88+
{ title: "Item A", group: "Basic", /* ... */ },
89+
{ title: "Item B", group: "Basic", /* ... */ },
90+
{ title: "Item C", group: "Other", /* ... */ },
91+
]
92+
93+
// Renders as two separate "Basic" groups, with "Other" between them:
94+
[
95+
{ title: "Item A", group: "Basic", /* ... */ },
96+
{ title: "Item C", group: "Other", /* ... */ },
97+
{ title: "Item B", group: "Basic", /* ... */ },
98+
]
99+
```
100+
101+
#### Finding, Inserting, Removing & Reordering Items
102+
103+
Use regular array operations to manipulate items. For example, to insert a custom item directly after the default `Heading 1` item:
104+
105+
```typescript
106+
const items = getDefaultReactSlashMenuItems(editor);
107+
const headingIndex = items.findIndex((item) => item.title === "Heading 1");
108+
items.splice(headingIndex + 1, 0, insertHelloWorldItem(editor));
109+
```
110+
111+
To remove an item:
112+
113+
```typescript
114+
const items = getDefaultReactSlashMenuItems(editor).filter(
115+
(item) => item.title !== "Heading 1",
116+
);
117+
```
118+
119+
To reorder items, sort or rearrange the array however you'd like before returning it from `getItems`.
120+
121+
The demo below combines these techniques to render only the "Basic blocks" and "Headings" groups, with their order swapped:
122+
123+
<Example name="ui-components/suggestion-menus-grouping-ordering" />
124+
61125
### Replacing the Slash Menu Component
62126

63127
You can replace the React component used for the Slash Menu with your own, as you can see in the demo below.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"playground": true,
3+
"docs": true,
4+
"author": "matthewlipski",
5+
"tags": [
6+
"Intermediate",
7+
"Blocks",
8+
"UI Components",
9+
"Suggestion Menus",
10+
"Slash Menu"
11+
]
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Slash Menu Grouping & Ordering
2+
3+
In this example, we filter and reorder the default Slash Menu items so that only the "Basic blocks" and "Headings" groups are shown, with "Basic blocks" appearing first.
4+
5+
**Try it out:** Press the "/" key to open the Slash Menu and see the reordered groups!
6+
7+
**Relevant Docs:**
8+
9+
- [Item Grouping & Ordering](/docs/react/components/suggestion-menus)
10+
- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)
11+
- [Editor Setup](/docs/getting-started/editor-setup)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<html lang="en">
2+
<head>
3+
<meta charset="UTF-8" />
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5+
<title>Slash Menu Grouping &amp; Ordering</title>
6+
<script>
7+
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
8+
</script>
9+
</head>
10+
<body>
11+
<div id="root"></div>
12+
<script type="module" src="./main.tsx"></script>
13+
</body>
14+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
2+
import React from "react";
3+
import { createRoot } from "react-dom/client";
4+
import App from "./src/App.jsx";
5+
6+
const root = createRoot(document.getElementById("root")!);
7+
root.render(
8+
<React.StrictMode>
9+
<App />
10+
</React.StrictMode>
11+
);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@blocknote/example-ui-components-suggestion-menus-grouping-ordering",
3+
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
4+
"type": "module",
5+
"private": true,
6+
"version": "0.12.4",
7+
"scripts": {
8+
"start": "vite",
9+
"dev": "vite",
10+
"build:prod": "tsc && vite build",
11+
"preview": "vite preview"
12+
},
13+
"dependencies": {
14+
"@blocknote/ariakit": "latest",
15+
"@blocknote/core": "latest",
16+
"@blocknote/mantine": "latest",
17+
"@blocknote/react": "latest",
18+
"@blocknote/shadcn": "latest",
19+
"@mantine/core": "^8.3.11",
20+
"@mantine/hooks": "^8.3.11",
21+
"@mantine/utils": "^6.0.22",
22+
"react": "^19.2.3",
23+
"react-dom": "^19.2.3"
24+
},
25+
"devDependencies": {
26+
"@types/react": "^19.2.3",
27+
"@types/react-dom": "^19.2.3",
28+
"@vitejs/plugin-react": "^6.0.1",
29+
"vite": "^8.0.8"
30+
}
31+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { BlockNoteEditor } from "@blocknote/core";
2+
import { filterSuggestionItems } from "@blocknote/core/extensions";
3+
import "@blocknote/core/fonts/inter.css";
4+
import { BlockNoteView } from "@blocknote/mantine";
5+
import "@blocknote/mantine/style.css";
6+
import {
7+
DefaultReactSuggestionItem,
8+
getDefaultReactSlashMenuItems,
9+
SuggestionMenuController,
10+
useCreateBlockNote,
11+
} from "@blocknote/react";
12+
13+
// Returns the default Slash Menu items, keeping only the "Basic blocks" and
14+
// "Headings" groups, with "Basic blocks" listed before "Headings".
15+
const getCustomSlashMenuItems = (
16+
editor: BlockNoteEditor,
17+
): DefaultReactSuggestionItem[] => {
18+
const defaultItems = getDefaultReactSlashMenuItems(editor);
19+
20+
const basicBlocks = defaultItems.filter(
21+
(item) => item.group === "Basic blocks",
22+
);
23+
const headings = defaultItems.filter((item) => item.group === "Headings");
24+
25+
return [...basicBlocks, ...headings];
26+
};
27+
28+
export default function App() {
29+
// Creates a new editor instance.
30+
const editor = useCreateBlockNote({
31+
initialContent: [
32+
{
33+
type: "paragraph",
34+
content: "Welcome to this demo!",
35+
},
36+
{
37+
type: "paragraph",
38+
content: "Press the '/' key to open the Slash Menu",
39+
},
40+
{
41+
type: "paragraph",
42+
content:
43+
"Notice that only 'Basic blocks' and 'Headings' are shown, in that order",
44+
},
45+
{
46+
type: "paragraph",
47+
},
48+
],
49+
});
50+
51+
// Renders the editor instance.
52+
return (
53+
<BlockNoteView editor={editor} slashMenu={false}>
54+
<SuggestionMenuController
55+
triggerCharacter={"/"}
56+
// Replaces the default Slash Menu items with our custom ones.
57+
getItems={async (query) =>
58+
filterSuggestionItems(getCustomSlashMenuItems(editor), query)
59+
}
60+
/>
61+
</BlockNoteView>
62+
);
63+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
3+
"compilerOptions": {
4+
"target": "ESNext",
5+
"useDefineForClassFields": true,
6+
"lib": [
7+
"DOM",
8+
"DOM.Iterable",
9+
"ESNext"
10+
],
11+
"allowJs": false,
12+
"skipLibCheck": true,
13+
"esModuleInterop": false,
14+
"allowSyntheticDefaultImports": true,
15+
"strict": true,
16+
"forceConsistentCasingInFileNames": true,
17+
"module": "ESNext",
18+
"moduleResolution": "bundler",
19+
"resolveJsonModule": true,
20+
"isolatedModules": true,
21+
"noEmit": true,
22+
"jsx": "react-jsx",
23+
"composite": true
24+
},
25+
"include": [
26+
"."
27+
],
28+
"__ADD_FOR_LOCAL_DEV_references": [
29+
{
30+
"path": "../../../packages/core/"
31+
},
32+
{
33+
"path": "../../../packages/react/"
34+
}
35+
]
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
2+
import react from "@vitejs/plugin-react";
3+
import * as fs from "fs";
4+
import * as path from "path";
5+
import { defineConfig } from "vite";
6+
// import eslintPlugin from "vite-plugin-eslint";
7+
// https://vitejs.dev/config/
8+
export default defineConfig((conf) => ({
9+
plugins: [react()],
10+
optimizeDeps: {},
11+
build: {
12+
sourcemap: true,
13+
},
14+
resolve: {
15+
alias:
16+
conf.command === "build" ||
17+
!fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
18+
? {}
19+
: ({
20+
// Comment out the lines below to load a built version of blocknote
21+
// or, keep as is to load live from sources with live reload working
22+
"@blocknote/core": path.resolve(
23+
__dirname,
24+
"../../packages/core/src/"
25+
),
26+
"@blocknote/react": path.resolve(
27+
__dirname,
28+
"../../packages/react/src/"
29+
),
30+
} as any),
31+
},
32+
}));

playground/src/examples.gen.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,29 @@
851851
"slug": "ui-components"
852852
},
853853
"readme": "This example demonstrates how to use the `DRAG_EXCLUSION_CLASSNAME` to create separate drag & drop areas that don't interfere with BlockNote's built-in block drag & drop functionality.\n\n## Features\n\n- **Drag Exclusion**: Elements with the `bn-drag-exclude` classname are treated as separate drag & drop operations\n- **Independent Drag Areas**: Create custom drag & drop functionality alongside BlockNote's editor\n- **No Interference**: Custom drag operations won't trigger BlockNote's block reordering\n- **Side-by-side Demo**: Shows the editor and custom drag area working independently\n\n## How It Works\n\nBy adding the `DRAG_EXCLUSION_CLASSNAME` (`bn-drag-exclude`) to an element, you tell BlockNote's drag & drop handlers to ignore all drag events within that element and its children. This allows you to implement your own custom drag & drop logic without conflicts.\n\nThe exclusion check works by traversing up the DOM tree from the drag event target, checking if any ancestor has the exclusion classname. If found, BlockNote's handlers return early, leaving your custom handlers in full control.\n\n## Code Highlights\n\n### Import the constant:\n\n```tsx\nimport { DRAG_EXCLUSION_CLASSNAME } from \"@blocknote/core\";\n```\n\n### Apply it to your custom drag area:\n\n```tsx\n<div className={\"drag-demo-section \" + DRAG_EXCLUSION_CLASSNAME}>\n {/* Your custom drag & drop UI */}\n <div draggable onDragStart={handleDragStart} onDrop={handleDrop}>\n Custom draggable items\n </div>\n</div>\n```\n\n## Use Cases\n\n- **Custom UI elements**: Add draggable components within or near the editor\n- **File upload areas**: Create drag-and-drop file upload zones\n- **Sortable lists**: Implement custom sortable lists alongside the editor\n- **External integrations**: Integrate with third-party drag & drop libraries\n\n**Relevant Docs:**\n\n- [Side Menu (Drag Handle)](/docs/react/components/side-menu)\n- [Editor Setup](/docs/getting-started/editor-setup)"
854+
},
855+
{
856+
"projectSlug": "suggestion-menus-grouping-ordering",
857+
"fullSlug": "ui-components/suggestion-menus-grouping-ordering",
858+
"pathFromRoot": "examples/03-ui-components/19-suggestion-menus-grouping-ordering",
859+
"config": {
860+
"playground": true,
861+
"docs": true,
862+
"author": "matthewlipski",
863+
"tags": [
864+
"Intermediate",
865+
"Blocks",
866+
"UI Components",
867+
"Suggestion Menus",
868+
"Slash Menu"
869+
]
870+
},
871+
"title": "Slash Menu Grouping & Ordering",
872+
"group": {
873+
"pathFromRoot": "examples/03-ui-components",
874+
"slug": "ui-components"
875+
},
876+
"readme": "In this example, we filter and reorder the default Slash Menu items so that only the \"Basic blocks\" and \"Headings\" groups are shown, with \"Basic blocks\" appearing first.\n\n**Try it out:** Press the \"/\" key to open the Slash Menu and see the reordered groups!\n\n**Relevant Docs:**\n\n- [Item Grouping & Ordering](/docs/react/components/suggestion-menus)\n- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)\n- [Editor Setup](/docs/getting-started/editor-setup)"
854877
}
855878
]
856879
},

0 commit comments

Comments
 (0)