Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions docs/content/docs/react/components/suggestion-menus.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,70 @@ Passing `slashMenu={false}` to `BlockNoteView` tells BlockNote not to show the d

`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.

### Item Grouping & Ordering

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.

#### Ordering

Items appear in the menu in the exact order of the array. Reordering the array reorders the menu:

```typescript
getItems={async (query) =>
filterSuggestionItems(
[
insertHelloWorldItem(editor), // Shown first
...getDefaultReactSlashMenuItems(editor), // Shown after
],
query,
)
}
```

#### Grouping

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:

```typescript
// Renders as a single "Basic" group:
[
{ title: "Item A", group: "Basic", /* ... */ },
{ title: "Item B", group: "Basic", /* ... */ },
{ title: "Item C", group: "Other", /* ... */ },
]

// Renders as two separate "Basic" groups, with "Other" between them:
[
{ title: "Item A", group: "Basic", /* ... */ },
{ title: "Item C", group: "Other", /* ... */ },
{ title: "Item B", group: "Basic", /* ... */ },
]
```

#### Finding, Inserting, Removing & Reordering Items

Use regular array operations to manipulate items. For example, to insert a custom item directly after the default `Heading 1` item:

```typescript
const items = getDefaultReactSlashMenuItems(editor);
const headingIndex = items.findIndex((item) => item.title === "Heading 1");
items.splice(headingIndex + 1, 0, insertHelloWorldItem(editor));
```
Comment thread
matthewlipski marked this conversation as resolved.

To remove an item:

```typescript
const items = getDefaultReactSlashMenuItems(editor).filter(
(item) => item.title !== "Heading 1",
);
```

To reorder items, sort or rearrange the array however you'd like before returning it from `getItems`.

The demo below combines these techniques to render only the "Basic blocks" and "Headings" groups, with their order swapped:

<Example name="ui-components/suggestion-menus-grouping-ordering" />

### Replacing the Slash Menu Component

You can replace the React component used for the Slash Menu with your own, as you can see in the demo below.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"playground": true,
"docs": true,
"author": "matthewlipski",
"tags": [
"Intermediate",
"Blocks",
"UI Components",
"Suggestion Menus",
"Slash Menu"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Slash Menu Grouping & Ordering

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.

**Try it out:** Press the "/" key to open the Slash Menu and see the reordered groups!

**Relevant Docs:**

- [Item Grouping & Ordering](/docs/react/components/suggestion-menus)
- [Changing Slash Menu Items](/docs/react/components/suggestion-menus)
- [Editor Setup](/docs/getting-started/editor-setup)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html lang="en">
Comment thread
matthewlipski marked this conversation as resolved.
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Slash Menu Grouping &amp; Ordering</title>
<script>
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./src/App.jsx";

const root = createRoot(document.getElementById("root")!);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@blocknote/example-ui-components-suggestion-menus-grouping-ordering",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"type": "module",
"private": true,
"version": "0.12.4",
"scripts": {
"start": "vite",
"dev": "vite",
"build:prod": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@blocknote/ariakit": "latest",
"@blocknote/core": "latest",
"@blocknote/mantine": "latest",
"@blocknote/react": "latest",
"@blocknote/shadcn": "latest",
"@mantine/core": "^8.3.11",
"@mantine/hooks": "^8.3.11",
"@mantine/utils": "^6.0.22",
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"vite": "^8.0.8"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { BlockNoteEditor } from "@blocknote/core";
import { filterSuggestionItems } from "@blocknote/core/extensions";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
DefaultReactSuggestionItem,
getDefaultReactSlashMenuItems,
SuggestionMenuController,
useCreateBlockNote,
} from "@blocknote/react";

// Returns the default Slash Menu items, keeping only the "Basic blocks" and
// "Headings" groups, with "Basic blocks" listed before "Headings".
const getCustomSlashMenuItems = (
editor: BlockNoteEditor,
): DefaultReactSuggestionItem[] => {
const defaultItems = getDefaultReactSlashMenuItems(editor);

const basicBlocks = defaultItems.filter(
(item) => item.group === "Basic blocks",
);
const headings = defaultItems.filter((item) => item.group === "Headings");

return [...basicBlocks, ...headings];
};

export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
initialContent: [
{
type: "paragraph",
content: "Welcome to this demo!",
},
{
type: "paragraph",
content: "Press the '/' key to open the Slash Menu",
},
{
type: "paragraph",
content:
"Notice that only 'Basic blocks' and 'Headings' are shown, in that order",
},
{
type: "paragraph",
},
],
});

// Renders the editor instance.
return (
<BlockNoteView editor={editor} slashMenu={false}>
<SuggestionMenuController
triggerCharacter={"/"}
// Replaces the default Slash Menu items with our custom ones.
getItems={async (query) =>
filterSuggestionItems(getCustomSlashMenuItems(editor), query)
}
/>
</BlockNoteView>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"composite": true
},
"include": [
"."
],
"__ADD_FOR_LOCAL_DEV_references": [
{
"path": "../../../packages/core/"
},
{
"path": "../../../packages/react/"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
import react from "@vitejs/plugin-react";
import * as fs from "fs";
import * as path from "path";
import { defineConfig } from "vite";
// import eslintPlugin from "vite-plugin-eslint";
// https://vitejs.dev/config/
export default defineConfig((conf) => ({
plugins: [react()],
optimizeDeps: {},
build: {
sourcemap: true,
},
resolve: {
alias:
conf.command === "build" ||
!fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
? {}
: ({
// Comment out the lines below to load a built version of blocknote
// or, keep as is to load live from sources with live reload working
"@blocknote/core": path.resolve(
__dirname,
"../../packages/core/src/"
),
"@blocknote/react": path.resolve(
__dirname,
"../../packages/react/src/"
),
} as any),
},
}));
23 changes: 23 additions & 0 deletions playground/src/examples.gen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,29 @@
"slug": "ui-components"
},
"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)"
},
{
"projectSlug": "suggestion-menus-grouping-ordering",
"fullSlug": "ui-components/suggestion-menus-grouping-ordering",
"pathFromRoot": "examples/03-ui-components/19-suggestion-menus-grouping-ordering",
"config": {
"playground": true,
"docs": true,
"author": "matthewlipski",
"tags": [
"Intermediate",
"Blocks",
"UI Components",
"Suggestion Menus",
"Slash Menu"
]
},
"title": "Slash Menu Grouping & Ordering",
"group": {
"pathFromRoot": "examples/03-ui-components",
"slug": "ui-components"
},
"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)"
}
]
},
Expand Down
Loading
Loading