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, } 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;}