A deployable markdown editor that connects with your self hosted files and lets you edit in a beautiful interface
1import { Editor } from '@tiptap/react';
2
3interface MenuBarProps {
4 editor: Editor;
5}
6
7export function MenuBar({ editor }: MenuBarProps) {
8 const buttonClass = (isActive: boolean) =>
9 `px-3 py-1.5 text-sm font-semibold transition-colors border-2 border-transparent ${
10 isActive
11 ? 'bg-gray-900 text-white'
12 : 'bg-gray-100 text-gray-900 hover:bg-gray-200'
13 }`;
14
15 return (
16 <div className="border-b-2 border-gray-900 p-3 bg-amber-50 flex flex-wrap gap-1">
17 <button
18 onClick={() => editor.chain().focus().toggleBold().run()}
19 disabled={!editor.can().chain().focus().toggleBold().run()}
20 className={buttonClass(editor.isActive('bold'))}
21 title="Bold (Cmd+B)"
22 >
23 <strong>B</strong>
24 </button>
25
26 <button
27 onClick={() => editor.chain().focus().toggleItalic().run()}
28 disabled={!editor.can().chain().focus().toggleItalic().run()}
29 className={buttonClass(editor.isActive('italic'))}
30 title="Italic (Cmd+I)"
31 >
32 <em>I</em>
33 </button>
34
35 <button
36 onClick={() => editor.chain().focus().toggleCode().run()}
37 disabled={!editor.can().chain().focus().toggleCode().run()}
38 className={buttonClass(editor.isActive('code'))}
39 title="Inline Code"
40 >
41 {'</>'}
42 </button>
43
44 <div className="w-px bg-gray-900 mx-1"></div>
45
46 <button
47 onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
48 className={buttonClass(editor.isActive('heading', { level: 1 }))}
49 title="Heading 1"
50 >
51 H1
52 </button>
53
54 <button
55 onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
56 className={buttonClass(editor.isActive('heading', { level: 2 }))}
57 title="Heading 2"
58 >
59 H2
60 </button>
61
62 <button
63 onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
64 className={buttonClass(editor.isActive('heading', { level: 3 }))}
65 title="Heading 3"
66 >
67 H3
68 </button>
69
70 <div className="w-px bg-gray-900 mx-1"></div>
71
72 <button
73 onClick={() => editor.chain().focus().toggleBulletList().run()}
74 className={buttonClass(editor.isActive('bulletList'))}
75 title="Bullet List"
76 >
77 • List
78 </button>
79
80 <button
81 onClick={() => editor.chain().focus().toggleOrderedList().run()}
82 className={buttonClass(editor.isActive('orderedList'))}
83 title="Numbered List"
84 >
85 1. List
86 </button>
87
88 <button
89 onClick={() => editor.chain().focus().toggleBlockquote().run()}
90 className={buttonClass(editor.isActive('blockquote'))}
91 title="Quote"
92 >
93 " Quote
94 </button>
95
96 <button
97 onClick={() => editor.chain().focus().toggleCodeBlock().run()}
98 className={buttonClass(editor.isActive('codeBlock'))}
99 title="Code Block"
100 >
101 {'{ } Code'}
102 </button>
103
104 <div className="w-px bg-gray-900 mx-1"></div>
105
106 <button
107 onClick={() => editor.chain().focus().setHorizontalRule().run()}
108 className={buttonClass(false)}
109 title="Horizontal Rule"
110 >
111 ───
112 </button>
113
114 <button
115 onClick={() => editor.chain().focus().setHardBreak().run()}
116 className={buttonClass(false)}
117 title="Line Break"
118 >
119 ↵
120 </button>
121
122 <div className="w-px bg-gray-900 mx-1"></div>
123
124 <button
125 onClick={() => editor.chain().focus().undo().run()}
126 disabled={!editor.can().chain().focus().undo().run()}
127 className="px-3 py-1.5 text-sm font-semibold bg-gray-100 text-gray-900 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed border-2 border-transparent transition-colors"
128 title="Undo (Cmd+Z)"
129 >
130 ↶ Undo
131 </button>
132
133 <button
134 onClick={() => editor.chain().focus().redo().run()}
135 disabled={!editor.can().chain().focus().redo().run()}
136 className="px-3 py-1.5 text-sm font-semibold bg-gray-100 text-gray-900 hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed border-2 border-transparent transition-colors"
137 title="Redo (Cmd+Shift+Z)"
138 >
139 ↷ Redo
140 </button>
141 </div>
142 );
143}