Adding Block Type Select Items
In this example, we add an item to the Block Type Select, so that it works for a custom alert block we create.
Try it out: Select some text to open the Formatting Toolbar, and click "Alert" in the Block Type Select to change the selected block!
Relevant Docs:
import { defaultProps } from "@blocknote/core";
import { createReactBlockSpec } from "@blocknote/react";
import { Menu } from "@mantine/core";
import { MdCancel, MdCheckCircle, MdError, MdInfo } from "react-icons/md";
import "./styles.css";
// The types of alerts that users can choose from.
export const alertTypes = [
{
title: "Warning",
value: "warning",
icon: MdError,
color: "#e69819",
backgroundColor: {
light: "#fff6e6",
dark: "#805d20",
},
},
{
title: "Error",
value: "error",
icon: MdCancel,
color: "#d80d0d",
backgroundColor: {
light: "#ffe6e6",
dark: "#802020",
},
},
{
title: "Info",
value: "info",
icon: MdInfo,
color: "#507aff",
backgroundColor: {
light: "#e6ebff",
dark: "#203380",
},
},
{
title: "Success",
value: "success",
icon: MdCheckCircle,
color: "#0bc10b",
backgroundColor: {
light: "#e6ffe6",
dark: "#208020",
},
},
] as const;
// The Alert block.
export const Alert = createReactBlockSpec(
{
type: "alert",
propSchema: {
textAlignment: defaultProps.textAlignment,
textColor: defaultProps.textColor,
type: {
default: "warning",
values: ["warning", "error", "info", "success"],
},
},
content: "inline",
},
{
render: (props) => {
const alertType = alertTypes.find(
(a) => a.value === props.block.props.type,
)!;
const Icon = alertType.icon;
return (
<div className={"alert"} data-alert-type={props.block.props.type}>
{/*Icon which opens a menu to choose the Alert type*/}
<Menu withinPortal={false}>
<Menu.Target>
<div className={"alert-icon-wrapper"} contentEditable={false}>
<Icon
className={"alert-icon"}
data-alert-icon-type={props.block.props.type}
size={32}
/>
</div>
</Menu.Target>
{/*Dropdown to change the Alert type*/}
<Menu.Dropdown>
<Menu.Label>Alert Type</Menu.Label>
<Menu.Divider />
{alertTypes.map((type) => {
const ItemIcon = type.icon;
return (
<Menu.Item
key={type.value}
leftSection={
<ItemIcon
className={"alert-icon"}
data-alert-icon-type={type.value}
/>
}
onClick={() =>
props.editor.updateBlock(props.block, {
type: "alert",
props: { type: type.value },
})
}
>
{type.title}
</Menu.Item>
);
})}
</Menu.Dropdown>
</Menu>
{/*Rich text field for user to type in*/}
<div className={"inline-content"} ref={props.contentRef} />
</div>
);
},
},
);
import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
FormattingToolbarController,
blockTypeSelectItems,
useCreateBlockNote,
BlockTypeSelectItem,
FormattingToolbar,
} from "@blocknote/react";
import { RiAlertFill } from "react-icons/ri";
import { Alert } from "./Alert";
// Our schema with block specs, which contain the configs and implementations for
// blocks that we want our editor to use.
const schema = BlockNoteSchema.create({
blockSpecs: {
// Adds all default blocks.
...defaultBlockSpecs,
// Adds the Alert block.
alert: Alert,
},
});
export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
schema,
initialContent: [
{
type: "paragraph",
content: "Welcome to this demo!",
},
{
type: "paragraph",
content:
"Try selecting some text - you'll see the new 'Alert' item in the Block Type Select",
},
{
type: "alert",
content:
"Or select text in this alert - the Block Type Select also appears",
},
{
type: "paragraph",
},
],
});
// Renders the editor instance.
return (
<BlockNoteView editor={editor} formattingToolbar={false}>
{/* Replaces the default Formatting Toolbar */}
<FormattingToolbarController
formattingToolbar={() => (
// Uses the default Formatting Toolbar.
<FormattingToolbar
// Sets the items in the Block Type Select.
blockTypeSelectItems={[
// Gets the default Block Type Select items.
...blockTypeSelectItems(editor.dictionary),
// Adds an item for the Alert block.
{
name: "Alert",
type: "alert",
icon: RiAlertFill,
isSelected: (block) => block.type === "alert",
} satisfies BlockTypeSelectItem,
]}
/>
)}
/>
</BlockNoteView>
);
}
.alert {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
border-radius: 4px;
min-height: 48px;
padding: 4px;
}
.alert[data-alert-type="warning"] {
background-color: #fff6e6;
}
.alert[data-alert-type="error"] {
background-color: #ffe6e6;
}
.alert[data-alert-type="info"] {
background-color: #e6ebff;
}
.alert[data-alert-type="success"] {
background-color: #e6ffe6;
}
[data-color-scheme="dark"] .alert[data-alert-type="warning"] {
background-color: #805d20;
}
[data-color-scheme="dark"] .alert[data-alert-type="error"] {
background-color: #802020;
}
[data-color-scheme="dark"] .alert[data-alert-type="info"] {
background-color: #203380;
}
[data-color-scheme="dark"] .alert[data-alert-type="success"] {
background-color: #208020;
}
.alert-icon-wrapper {
border-radius: 16px;
display: flex;
justify-content: center;
align-items: center;
margin-left: 12px;
margin-right: 12px;
height: 18px;
width: 18px;
user-select: none;
cursor: pointer;
}
.alert-icon[data-alert-icon-type="warning"] {
color: #e69819;
}
.alert-icon[data-alert-icon-type="error"] {
color: #d80d0d;
}
.alert-icon[data-alert-icon-type="info"] {
color: #507aff;
}
.alert-icon[data-alert-icon-type="success"] {
color: #0bc10b;
}
.inline-content {
flex-grow: 1;
}