import type { Editor } from '@tiptap/core'; import { useEditorState } from '@tiptap/react'; import { useRef } from 'react'; import { useTranslation } from 'react-i18next'; import type { PluginBlockOption, SectionStyleOption } from '../plugins/use-pond-plugins'; import { LinkMenu } from './LinkMenu'; import { PluginBlockMenu } from './PluginBlockMenu'; import { SectionStyleMenu } from './SectionStyleMenu'; interface ToolbarProps { editor: Editor; /** Section styles offered by the pond's active plugins (issue #75); the * section group is omitted while empty. */ sectionStyles?: SectionStyleOption[]; /** Block types offered by the pond's active code plugins (issue #76); the * insert group is omitted while empty. */ pluginBlocks?: PluginBlockOption[]; } function ToolbarButton({ label, active, disabled, onClick, children, }: { label: string; active?: boolean; disabled?: boolean; onClick: () => void; children: React.ReactNode; }): React.JSX.Element { return ( ); } /** File-picker path into the same async upload as paste/drop (issue #28) — * a hidden native file input triggered by a normal toolbar button, since * that is the only way to open the OS file dialog from a click handler. */ function ImageInsertButton({ editor, label, }: { editor: Editor; label: string; }): React.JSX.Element { const inputRef = useRef(null); return ( <> inputRef.current?.click()}> 🖼 { const file = event.target.files?.[0]; if (file) editor.chain().focus().insertImageFile(file).run(); event.target.value = ''; }} /> ); } /** Arrow-key navigation between toolbar controls (#164, role="toolbar" * convention). Native selects keep their own arrow-key handling. */ function onToolbarArrowKey(event: React.KeyboardEvent): void { if (event.key !== 'ArrowRight' && event.key !== 'ArrowLeft') return; const target = event.target as HTMLElement; if (target.tagName === 'SELECT' || target.tagName === 'INPUT') return; const controls = [ ...event.currentTarget.querySelectorAll('button:not([disabled]), select'), ]; const index = controls.indexOf(target); if (index === -1) return; event.preventDefault(); const step = event.key === 'ArrowRight' ? 1 : -1; controls[(index + step + controls.length) % controls.length]?.focus(); } /** Keyboard-accessible toolbar for the page editor (issue #25). Table row/ * column controls stay visible but disabled outside a table, so the * toolbar's layout and tab order never shift while typing. */ export function Toolbar({ editor, sectionStyles = [], pluginBlocks = [], }: ToolbarProps): React.JSX.Element { const { t } = useTranslation('editor'); const state = useEditorState({ editor, selector: ({ editor: e }) => ({ paragraph: e.isActive('paragraph'), heading1: e.isActive('heading', { level: 1 }), heading2: e.isActive('heading', { level: 2 }), heading3: e.isActive('heading', { level: 3 }), heading4: e.isActive('heading', { level: 4 }), bold: e.isActive('bold'), italic: e.isActive('italic'), code: e.isActive('code'), strikethrough: e.isActive('strikethrough'), bulletList: e.isActive('bullet_list'), orderedList: e.isActive('ordered_list'), taskList: e.isActive('task_list'), blockquote: e.isActive('blockquote'), codeBlock: e.isActive('code_block'), canAddRow: e.can().addRowAfter(), canDeleteRow: e.can().deleteRow(), canAddColumn: e.can().addColumnAfter(), canDeleteColumn: e.can().deleteColumn(), canDeleteTable: e.can().deleteTable(), canToggleHeaderRow: e.can().toggleHeaderRow(), canUndo: e.can().undo(), canRedo: e.can().redo(), }), }); return (
editor.chain().focus().setParagraph().run()} > P {([1, 2, 3, 4] as const).map((level) => ( editor.chain().focus().toggleHeading(level).run()} > H{level} ))}
editor.chain().focus().toggleBold().run()} > B editor.chain().focus().toggleItalic().run()} > I editor.chain().focus().toggleCode().run()} > {''} editor.chain().focus().toggleStrikethrough().run()} > S
editor.chain().focus().toggleBulletList().run()} > • editor.chain().focus().toggleOrderedList().run()} > 1. editor.chain().focus().toggleTaskList().run()} > ☑
editor.chain().focus().toggleBlockquote().run()} > ❝ editor.chain().focus().toggleCodeBlock().run()} > {'{ }'} editor.chain().focus().setHorizontalRule().run()} > ―
editor.chain().focus().insertTable().run()} > ⊞ editor.chain().focus().addColumnBefore().run()} > ⊞← editor.chain().focus().addColumnAfter().run()} > ⊞→ editor.chain().focus().deleteColumn().run()} > ⊟↕ editor.chain().focus().addRowBefore().run()} > ⊞↑ editor.chain().focus().addRowAfter().run()} > ⊞↓ editor.chain().focus().deleteRow().run()} > ⊟↔ editor.chain().focus().toggleHeaderRow().run()} > ⊤ editor.chain().focus().deleteTable().run()} > ⊠
editor.chain().focus().undo().run()} > ↺ editor.chain().focus().redo().run()} > ↻
); }