Alert Block with Title and Body
In this example, we extend the Alert block with a title and a body: the alert's text is its title, and its child blocks are the body.
The block has content: "inline", so the title is ordinary inline content: formatting, links, and multiplayer cursors all work. Its child blocks are in block.children at runtime. render draws the title row, the same as in the Alert example, and renderFrame draws the alert's box around the title and the body together. Its experimental_keyboard settings keep the body inside the alert: Enter in the title adds a first child block, Shift-Tab doesn't move child blocks out, and Enter in an empty last child block leaves the alert.
Try it out:
- Click the icon to change the alert type.
- Press Enter at the end of the alert's title to add a block to its body.
- Press Enter in an empty last block of the body to leave the alert.
- Press Backspace at the start of the first block in the body to merge it back into the title.
- Press "/" in the body and add a code block, heading, or list.
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, with a title (its text) and a body (its child blocks).export const createAlert = createReactBlockSpec( { type: "alert", propSchema: { textAlignment: defaultProps.textAlignment, textColor: defaultProps.textColor, type: { default: "warning", values: ["warning", "error", "info", "success"], }, }, content: "inline", }, { // Keeps the body inside the alert: Enter in the title adds a first child // block, child blocks can't be outdented out of the alert, and Enter in an // empty last child block leaves the alert. experimental_keyboard: { enter: "into-children", childrenCanOutdent: false, emptyChildEnter: "exit-at-end", }, // The title row. The child blocks (the body) go below it. render: (props) => { const alertType = alertTypes.find( (a) => a.value === props.block.props.type, )!; const Icon = alertType.icon; return ( <div className={"alert-title-row"}> {/*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> ); }, // The alert's box, around the title row and the body. renderFrame: (props) => ( <div className={"alert-frame"} data-alert-type={props.block.props.type}> <div ref={props.slotRef} /> </div> ), },);import { BlockNoteSchema } from "@blocknote/core";import "@blocknote/core/fonts/inter.css";import { BlockNoteView } from "@blocknote/mantine";import "@blocknote/mantine/style.css";import { useCreateBlockNote } from "@blocknote/react";import { createAlert } 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().extend({ blockSpecs: { // Creates an instance of the Alert block with a body and adds it to the schema. alert: createAlert(), },});export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ schema, initialContent: [ { type: "paragraph", content: "Welcome to this demo!", }, { type: "alert", content: "This is an example alert", children: [ { type: "paragraph", content: "This is the body of the alert", }, ], }, { type: "paragraph", content: "Click the '!' icon to change the alert type", }, ], }); // Renders the editor instance. return <BlockNoteView editor={editor} />;}.alert-frame { border-radius: 4px; padding: 4px;}.alert-title-row { display: flex; justify-content: center; align-items: center; flex-grow: 1; min-height: 48px;}.alert-frame[data-alert-type="warning"] { background-color: #fff6e6;}.alert-frame[data-alert-type="error"] { background-color: #ffe6e6;}.alert-frame[data-alert-type="info"] { background-color: #e6ebff;}.alert-frame[data-alert-type="success"] { background-color: #e6ffe6;}[data-color-scheme="dark"] .alert-frame[data-alert-type="warning"] { background-color: #805d20;}[data-color-scheme="dark"] .alert-frame[data-alert-type="error"] { background-color: #802020;}[data-color-scheme="dark"] .alert-frame[data-alert-type="info"] { background-color: #203380;}[data-color-scheme="dark"] .alert-frame[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;}.alert-title-row .inline-content { font-weight: 600;}/* Hides the indent line next to the blocks of the alert's body. Blocks that are nested deeper in the body keep their line. */.alert-frame .bn-block-group:not(.alert-frame .bn-block-group .bn-block-group) > .bn-block-outer:not([data-prev-depth-changed])::before { border-left: none;}