import * as React from 'react'; import { clsx } from 'clsx'; import type { Editor } from '@tiptap/core'; export interface ToolbarProps { editor: Editor | null; } interface ToolButton { label: string; /** aria-label / accessible name. */ name: string; isActive?: (e: Editor) => boolean; run: (e: Editor) => void; } const BUTTONS: ToolButton[] = [ { label: 'B', name: 'Bold', isActive: e => e.isActive('bold'), run: e => e.chain().focus().toggleBold().run(), }, { label: 'I', name: 'Italic', isActive: e => e.isActive('italic'), run: e => e.chain().focus().toggleItalic().run(), }, { label: 'H1', name: 'Heading 1', isActive: e => e.isActive('heading', { level: 1 }), run: e => e.chain().focus().toggleHeading({ level: 1 }).run(), }, { label: 'H2', name: 'Heading 2', isActive: e => e.isActive('heading', { level: 2 }), run: e => e.chain().focus().toggleHeading({ level: 2 }).run(), }, { label: '• List', name: 'Bullet list', isActive: e => e.isActive('bulletList'), run: e => e.chain().focus().toggleBulletList().run(), }, { label: '1. List', name: 'Numbered list', isActive: e => e.isActive('orderedList'), run: e => e.chain().focus().toggleOrderedList().run(), }, { label: '❝', name: 'Quote', isActive: e => e.isActive('blockquote'), run: e => e.chain().focus().toggleBlockquote().run(), }, { label: '', name: 'Code', isActive: e => e.isActive('code'), run: e => e.chain().focus().toggleCode().run(), }, { label: 'Link', name: 'Link', isActive: e => e.isActive('link'), run: e => { const prev = (e.getAttributes('link').href as string) ?? ''; const url = typeof window !== 'undefined' ? window.prompt('Link URL', prev) : prev; if (url === null) return; if (url === '') { e.chain().focus().extendMarkRange('link').unsetLink().run(); return; } e.chain().focus().extendMarkRange('link').setLink({ href: url }).run(); }, }, ]; /** Token-themed formatting toolbar bound to a Tiptap editor instance. */ export function Toolbar({ editor }: ToolbarProps) { // Re-render on selection/content changes so active states stay correct. const [, force] = React.useReducer((x: number) => x + 1, 0); React.useEffect(() => { if (!editor) return; editor.on('transaction', force); return () => { editor.off('transaction', force); }; }, [editor]); if (!editor) return null; return (
{BUTTONS.map(b => { const active = b.isActive?.(editor) ?? false; return ( ); })}
); }