Components
Block Context Menu

Block Context Menu

Display a context menu for blocks.

Installation

npx @udecode/plate-ui@latest add block-context-menu -r plate-ui

Examples

Block Menu

The Block Menu provides quick access to actions for individual blocks. You can open this menu by right-clicking on any block in the editor.
Key features of the Block Context Menu:
  • Right-click on any block to open the menu
  • Try using block selection to select multiple blocks, then open the menu for the selected blocks.
  • When you try to right-click the block at the cursor's location, the default menu will open.This allows users to use browser extensions or paste plain text, etc.
  • Options to duplicate, delete, or other what you want
  • Transform blocks into different types
import { useCallback, useState } from 'react';
import { AIChatPlugin } from '@udecode/plate-ai/react';
import { BlockquotePlugin } from '@udecode/plate-block-quote/react';
import { unsetNodes } from '@udecode/plate-common';
import {
  focusEditor,
  ParagraphPlugin,
  useEditorPlugin,
} from '@udecode/plate-common/react';
import { HEADING_KEYS } from '@udecode/plate-heading';
import { IndentListPlugin } from '@udecode/plate-indent-list/react';
import {
  BLOCK_CONTEXT_MENU_ID,
  BlockMenuPlugin,
  BlockSelectionPlugin,
} from '@udecode/plate-selection/react';
 
import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuTrigger,
} from './context-menu';
 
type Value = 'askAI' | null;
 
export function BlockContextMenu({ children }: { children: React.ReactNode }) {
  const { api, editor } = useEditorPlugin(BlockMenuPlugin);
  const [value, setValue] = useState<Value>(null);
 
  const handleTurnInto = useCallback(
    (type: string) => {
      editor
        .getApi(BlockSelectionPlugin)
        .blockSelection.getNodes()
        .forEach(([node, path]) => {
          if (node[IndentListPlugin.key]) {
            unsetNodes(editor, [IndentListPlugin.key, 'indent'], { at: path });
          }
 
          editor.tf.toggle.block({ type }, { at: path });
        });
    },
    [editor]
  );
 
  const handleAlign = useCallback(
    (align: 'center' | 'left' | 'right') => {
      editor
        .getTransforms(BlockSelectionPlugin)
        .blockSelection.setNodes({ align });
    },
    [editor]
  );
 
  return (
    <ContextMenu modal={false}>
      <ContextMenuTrigger
        onContextMenu={(event) => {
          const dataset = (event.target as HTMLElement).dataset;
 
          const disabled = dataset?.slateEditor === 'true';
 
          if (disabled) return event.preventDefault();
 
          api.blockMenu.show(BLOCK_CONTEXT_MENU_ID, {
            x: event.clientX,
            y: event.clientY,
          });
        }}
      >
        {children}
      </ContextMenuTrigger>
      <ContextMenuContent
        className="w-64"
        onCloseAutoFocus={(e) => {
          e.preventDefault();
 
          if (value === 'askAI') {
            editor.getApi(AIChatPlugin).aiChat.show();
          }
 
          setValue(null);
        }}
      >
        <ContextMenuItem
          onClick={() => {
            setValue('askAI');
          }}
        >
          Ask AI
        </ContextMenuItem>
        <ContextMenuItem
          onClick={() => {
            editor
              .getTransforms(BlockSelectionPlugin)
              .blockSelection.removeNodes();
            focusEditor(editor);
          }}
        >
          Delete
        </ContextMenuItem>
        <ContextMenuItem
          onClick={() => {
            editor
              .getTransforms(BlockSelectionPlugin)
              .blockSelection.duplicate(
                editor.getApi(BlockSelectionPlugin).blockSelection.getNodes()
              );
          }}
        >
          Duplicate
          <ContextMenuShortcut>⌘ + D</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuSub>
          <ContextMenuSubTrigger>Turn into</ContextMenuSubTrigger>
          <ContextMenuSubContent className="w-48">
            <ContextMenuItem
              onClick={() => handleTurnInto(ParagraphPlugin.key)}
            >
              Paragraph
            </ContextMenuItem>
 
            <ContextMenuItem onClick={() => handleTurnInto(HEADING_KEYS.h1)}>
              Heading 1
            </ContextMenuItem>
            <ContextMenuItem onClick={() => handleTurnInto(HEADING_KEYS.h2)}>
              Heading 2
            </ContextMenuItem>
            <ContextMenuItem onClick={() => handleTurnInto(HEADING_KEYS.h3)}>
              Heading 3
            </ContextMenuItem>
            <ContextMenuItem
              onClick={() => handleTurnInto(BlockquotePlugin.key)}
            >
              Blockquote
            </ContextMenuItem>
          </ContextMenuSubContent>
        </ContextMenuSub>
        <ContextMenuSeparator />
        <ContextMenuItem
          onClick={() =>
            editor
              .getTransforms(BlockSelectionPlugin)
              .blockSelection.setIndent(1)
          }
        >
          Indent
        </ContextMenuItem>
        <ContextMenuItem
          onClick={() =>
            editor
              .getTransforms(BlockSelectionPlugin)
              .blockSelection.setIndent(-1)
          }
        >
          Outdent
        </ContextMenuItem>
        <ContextMenuSub>
          <ContextMenuSubTrigger>Align</ContextMenuSubTrigger>
          <ContextMenuSubContent className="w-48">
            <ContextMenuItem onClick={() => handleAlign('left')}>
              Left
            </ContextMenuItem>
            <ContextMenuItem onClick={() => handleAlign('center')}>
              Center
            </ContextMenuItem>
            <ContextMenuItem onClick={() => handleAlign('right')}>
              Right
            </ContextMenuItem>
          </ContextMenuSubContent>
        </ContextMenuSub>
      </ContextMenuContent>
    </ContextMenu>
  );
}

Plus

In Plate Plus, We have provided a more advanced menu.

  1. More advanced menu items.
  2. Supports search functionality and carefully designed shortcuts.
  3. More refined styles and animations.
  4. You can open this menu in various ways, such as through the drag button.