@ceedcv-maya/shared-editor-react 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceedcv-maya/shared-editor-react",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -1,5 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
  import { sanitizeEditorHtml } from '../lib/dompurifyConfig';
3
+ import '../styles/maya-content.css';
3
4
 
4
5
  interface EditorContentHtmlProps {
5
6
  /** Pre-rendered HTML (typically from server-side TiptapHtmlRenderer). */
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Inline SVG icon set for the editor toolbar.
3
+ *
4
+ * TipTap is headless and ships no UI, so the toolbar (and therefore its icons)
5
+ * is entirely our own. These are Lucide-style 24×24 stroke icons drawn with
6
+ * `currentColor`, so they inherit the button's text colour (light/dark) and add
7
+ * no runtime dependency to the shared package.
8
+ */
9
+ import type { ReactNode } from 'react';
10
+
11
+ export type EditorIconName =
12
+ | 'undo'
13
+ | 'redo'
14
+ | 'bold'
15
+ | 'italic'
16
+ | 'underline'
17
+ | 'strike'
18
+ | 'code'
19
+ | 'link'
20
+ | 'textColor'
21
+ | 'highlight'
22
+ | 'alignLeft'
23
+ | 'alignCenter'
24
+ | 'alignRight'
25
+ | 'alignJustify'
26
+ | 'indent'
27
+ | 'outdent'
28
+ | 'bulletList'
29
+ | 'orderedList'
30
+ | 'taskList'
31
+ | 'blockquote'
32
+ | 'codeBlock'
33
+ | 'horizontalRule'
34
+ | 'table'
35
+ | 'image'
36
+ | 'iframe'
37
+ | 'comment'
38
+ | 'find'
39
+ | 'importDocx'
40
+ | 'exportDocx'
41
+ | 'htmlSource'
42
+ | 'fullscreen'
43
+ | 'exitFullscreen'
44
+ | 'columnAddBefore'
45
+ | 'columnAddAfter'
46
+ | 'rowAddBefore'
47
+ | 'rowAddAfter'
48
+ | 'columnDelete'
49
+ | 'rowDelete'
50
+ | 'headerRow'
51
+ | 'tableDelete';
52
+
53
+ /** Inner SVG geometry per icon (drawn inside a 0 0 24 24 viewBox). */
54
+ const PATHS: Record<EditorIconName, ReactNode> = {
55
+ undo: (
56
+ <>
57
+ <path d="M9 14 4 9l5-5" />
58
+ <path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H10" />
59
+ </>
60
+ ),
61
+ redo: (
62
+ <>
63
+ <path d="m15 14 5-5-5-5" />
64
+ <path d="M20 9H9.5a5.5 5.5 0 0 0 0 11H14" />
65
+ </>
66
+ ),
67
+ bold: (
68
+ <>
69
+ <path d="M6 4h7a4 4 0 0 1 0 8H6z" />
70
+ <path d="M6 12h8a4 4 0 0 1 0 8H6z" />
71
+ </>
72
+ ),
73
+ italic: (
74
+ <>
75
+ <line x1="19" y1="4" x2="10" y2="4" />
76
+ <line x1="14" y1="20" x2="5" y2="20" />
77
+ <line x1="15" y1="4" x2="9" y2="20" />
78
+ </>
79
+ ),
80
+ underline: (
81
+ <>
82
+ <path d="M6 4v6a6 6 0 0 0 12 0V4" />
83
+ <line x1="4" y1="21" x2="20" y2="21" />
84
+ </>
85
+ ),
86
+ strike: (
87
+ <>
88
+ <path d="M16 5H9.5a3.5 3.5 0 0 0-1 6.8" />
89
+ <path d="M8 19h7a3.5 3.5 0 0 0 1-6.8" />
90
+ <line x1="4" y1="12" x2="20" y2="12" />
91
+ </>
92
+ ),
93
+ code: (
94
+ <>
95
+ <polyline points="16 18 22 12 16 6" />
96
+ <polyline points="8 6 2 12 8 18" />
97
+ </>
98
+ ),
99
+ link: (
100
+ <>
101
+ <path d="M10 13a5 5 0 0 0 7.5.5l3-3a5 5 0 0 0-7-7l-1.7 1.7" />
102
+ <path d="M14 11a5 5 0 0 0-7.5-.5l-3 3a5 5 0 0 0 7 7l1.7-1.7" />
103
+ </>
104
+ ),
105
+ textColor: (
106
+ <>
107
+ <path d="m7 16 4.5-11 4.5 11" />
108
+ <line x1="8.5" y1="12.5" x2="14.5" y2="12.5" />
109
+ <line x1="5" y1="20" x2="19" y2="20" strokeWidth={3} />
110
+ </>
111
+ ),
112
+ highlight: (
113
+ <>
114
+ <path d="m9 11-6 6v3h3l6-6" />
115
+ <path d="m13 7 4 4" />
116
+ <path d="m15 5 4 4-7 7-4-4z" />
117
+ </>
118
+ ),
119
+ alignLeft: (
120
+ <>
121
+ <line x1="3" y1="6" x2="21" y2="6" />
122
+ <line x1="3" y1="12" x2="15" y2="12" />
123
+ <line x1="3" y1="18" x2="18" y2="18" />
124
+ </>
125
+ ),
126
+ alignCenter: (
127
+ <>
128
+ <line x1="3" y1="6" x2="21" y2="6" />
129
+ <line x1="7" y1="12" x2="17" y2="12" />
130
+ <line x1="5" y1="18" x2="19" y2="18" />
131
+ </>
132
+ ),
133
+ alignRight: (
134
+ <>
135
+ <line x1="3" y1="6" x2="21" y2="6" />
136
+ <line x1="9" y1="12" x2="21" y2="12" />
137
+ <line x1="6" y1="18" x2="21" y2="18" />
138
+ </>
139
+ ),
140
+ alignJustify: (
141
+ <>
142
+ <line x1="3" y1="6" x2="21" y2="6" />
143
+ <line x1="3" y1="12" x2="21" y2="12" />
144
+ <line x1="3" y1="18" x2="21" y2="18" />
145
+ </>
146
+ ),
147
+ indent: (
148
+ <>
149
+ <polyline points="3 8 7 12 3 16" />
150
+ <line x1="11" y1="6" x2="21" y2="6" />
151
+ <line x1="11" y1="12" x2="21" y2="12" />
152
+ <line x1="11" y1="18" x2="21" y2="18" />
153
+ </>
154
+ ),
155
+ outdent: (
156
+ <>
157
+ <polyline points="7 8 3 12 7 16" />
158
+ <line x1="11" y1="6" x2="21" y2="6" />
159
+ <line x1="11" y1="12" x2="21" y2="12" />
160
+ <line x1="11" y1="18" x2="21" y2="18" />
161
+ </>
162
+ ),
163
+ bulletList: (
164
+ <>
165
+ <line x1="8" y1="6" x2="21" y2="6" />
166
+ <line x1="8" y1="12" x2="21" y2="12" />
167
+ <line x1="8" y1="18" x2="21" y2="18" />
168
+ <circle cx="3.5" cy="6" r="1" />
169
+ <circle cx="3.5" cy="12" r="1" />
170
+ <circle cx="3.5" cy="18" r="1" />
171
+ </>
172
+ ),
173
+ orderedList: (
174
+ <>
175
+ <line x1="10" y1="6" x2="21" y2="6" />
176
+ <line x1="10" y1="12" x2="21" y2="12" />
177
+ <line x1="10" y1="18" x2="21" y2="18" />
178
+ <path d="M4 4h1v4" />
179
+ <path d="M3 8h3" />
180
+ <path d="M3 14h2a1 1 0 0 1 0 2H3l3 0" />
181
+ </>
182
+ ),
183
+ taskList: (
184
+ <>
185
+ <rect x="3" y="4" width="6" height="6" rx="1" />
186
+ <path d="m3.5 17 1.5 1.5L8 15.5" />
187
+ <line x1="13" y1="6" x2="21" y2="6" />
188
+ <line x1="13" y1="17" x2="21" y2="17" />
189
+ </>
190
+ ),
191
+ blockquote: (
192
+ <>
193
+ <path d="M6 7H4a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1c0 2-1 3-3 3" />
194
+ <path d="M15 7h-2a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1c0 2-1 3-3 3" />
195
+ <line x1="20" y1="5" x2="20" y2="19" />
196
+ </>
197
+ ),
198
+ codeBlock: (
199
+ <>
200
+ <rect x="3" y="4" width="18" height="16" rx="2" />
201
+ <polyline points="10 9 7 12 10 15" />
202
+ <polyline points="14 9 17 12 14 15" />
203
+ </>
204
+ ),
205
+ horizontalRule: (
206
+ <>
207
+ <line x1="4" y1="12" x2="20" y2="12" />
208
+ </>
209
+ ),
210
+ table: (
211
+ <>
212
+ <rect x="3" y="3" width="18" height="18" rx="2" />
213
+ <line x1="3" y1="9" x2="21" y2="9" />
214
+ <line x1="3" y1="15" x2="21" y2="15" />
215
+ <line x1="12" y1="3" x2="12" y2="21" />
216
+ </>
217
+ ),
218
+ image: (
219
+ <>
220
+ <rect x="3" y="3" width="18" height="18" rx="2" />
221
+ <circle cx="9" cy="9" r="2" />
222
+ <path d="m21 15-4-4-9 9" />
223
+ </>
224
+ ),
225
+ iframe: (
226
+ <>
227
+ <rect x="2" y="4" width="20" height="16" rx="2" />
228
+ <line x1="2" y1="9" x2="22" y2="9" />
229
+ <line x1="6" y1="6.5" x2="6.01" y2="6.5" />
230
+ <line x1="9" y1="6.5" x2="9.01" y2="6.5" />
231
+ </>
232
+ ),
233
+ comment: (
234
+ <>
235
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
236
+ </>
237
+ ),
238
+ find: (
239
+ <>
240
+ <circle cx="11" cy="11" r="7" />
241
+ <line x1="21" y1="21" x2="16.5" y2="16.5" />
242
+ </>
243
+ ),
244
+ importDocx: (
245
+ <>
246
+ <path d="M14 3v4a1 1 0 0 0 1 1h4" />
247
+ <path d="M5 13V5a2 2 0 0 1 2-2h7l5 5v3" />
248
+ <path d="M5 18h10" />
249
+ <path d="m12 15 3 3-3 3" />
250
+ </>
251
+ ),
252
+ exportDocx: (
253
+ <>
254
+ <path d="M14 3v4a1 1 0 0 0 1 1h4" />
255
+ <path d="M19 13V8l-5-5H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5" />
256
+ <path d="M15 18h6" />
257
+ <path d="m18 15 3 3-3 3" />
258
+ </>
259
+ ),
260
+ htmlSource: (
261
+ <>
262
+ <polyline points="16 18 22 12 16 6" />
263
+ <polyline points="8 6 2 12 8 18" />
264
+ <line x1="13" y1="4" x2="11" y2="20" />
265
+ </>
266
+ ),
267
+ fullscreen: (
268
+ <>
269
+ <path d="M8 3H5a2 2 0 0 0-2 2v3" />
270
+ <path d="M21 8V5a2 2 0 0 0-2-2h-3" />
271
+ <path d="M3 16v3a2 2 0 0 0 2 2h3" />
272
+ <path d="M16 21h3a2 2 0 0 0 2-2v-3" />
273
+ </>
274
+ ),
275
+ exitFullscreen: (
276
+ <>
277
+ <path d="M8 3v3a2 2 0 0 1-2 2H3" />
278
+ <path d="M21 8h-3a2 2 0 0 1-2-2V3" />
279
+ <path d="M3 16h3a2 2 0 0 1 2 2v3" />
280
+ <path d="M16 21v-3a2 2 0 0 1 2-2h3" />
281
+ </>
282
+ ),
283
+ // Table editing — a column/row block plus a + (insert) or × (delete).
284
+ columnAddBefore: (
285
+ <>
286
+ <rect x="13" y="4" width="8" height="16" rx="1" />
287
+ <line x1="5" y1="8" x2="5" y2="16" />
288
+ <line x1="1" y1="12" x2="9" y2="12" />
289
+ </>
290
+ ),
291
+ columnAddAfter: (
292
+ <>
293
+ <rect x="3" y="4" width="8" height="16" rx="1" />
294
+ <line x1="19" y1="8" x2="19" y2="16" />
295
+ <line x1="15" y1="12" x2="23" y2="12" />
296
+ </>
297
+ ),
298
+ rowAddBefore: (
299
+ <>
300
+ <rect x="4" y="13" width="16" height="8" rx="1" />
301
+ <line x1="12" y1="3" x2="12" y2="9" />
302
+ <line x1="9" y1="6" x2="15" y2="6" />
303
+ </>
304
+ ),
305
+ rowAddAfter: (
306
+ <>
307
+ <rect x="4" y="3" width="16" height="8" rx="1" />
308
+ <line x1="12" y1="15" x2="12" y2="21" />
309
+ <line x1="9" y1="18" x2="15" y2="18" />
310
+ </>
311
+ ),
312
+ columnDelete: (
313
+ <>
314
+ <rect x="3" y="4" width="8" height="16" rx="1" />
315
+ <line x1="14" y1="9" x2="20" y2="15" />
316
+ <line x1="20" y1="9" x2="14" y2="15" />
317
+ </>
318
+ ),
319
+ rowDelete: (
320
+ <>
321
+ <rect x="4" y="3" width="16" height="8" rx="1" />
322
+ <line x1="9" y1="14" x2="15" y2="20" />
323
+ <line x1="15" y1="14" x2="9" y2="20" />
324
+ </>
325
+ ),
326
+ headerRow: (
327
+ <>
328
+ <rect x="3" y="4" width="18" height="16" rx="1" />
329
+ <line x1="3" y1="9" x2="21" y2="9" strokeWidth={3} />
330
+ <line x1="9" y1="9" x2="9" y2="20" />
331
+ <line x1="15" y1="9" x2="15" y2="20" />
332
+ </>
333
+ ),
334
+ tableDelete: (
335
+ <>
336
+ <path d="M3 6h18" />
337
+ <path d="M19 6v13a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
338
+ <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
339
+ <line x1="10" y1="11" x2="10" y2="17" />
340
+ <line x1="14" y1="11" x2="14" y2="17" />
341
+ </>
342
+ ),
343
+ };
344
+
345
+ export interface EditorIconProps {
346
+ name: EditorIconName;
347
+ size?: number;
348
+ }
349
+
350
+ /** Renders a toolbar icon by name. Inherits colour via `currentColor`. */
351
+ export function EditorIcon({ name, size = 16 }: EditorIconProps) {
352
+ return (
353
+ <svg
354
+ width={size}
355
+ height={size}
356
+ viewBox="0 0 24 24"
357
+ fill="none"
358
+ stroke="currentColor"
359
+ strokeWidth={2}
360
+ strokeLinecap="round"
361
+ strokeLinejoin="round"
362
+ aria-hidden="true"
363
+ focusable="false"
364
+ >
365
+ {PATHS[name]}
366
+ </svg>
367
+ );
368
+ }
@@ -1,6 +1,8 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { Editor } from '@tiptap/react';
2
3
  import type { EditorMode } from '../types';
3
4
  import { Btn } from './EditorToolbarButton';
5
+ import { EditorIcon } from './EditorIcons';
4
6
  import {
5
7
  FormattingButtons,
6
8
  AdvancedFormattingButtons,
@@ -9,6 +11,7 @@ import {
9
11
  HeadingButtons,
10
12
  ListAndBlockButtons,
11
13
  TableAndMediaButtons,
14
+ TableButtons,
12
15
  DocumentButtons,
13
16
  ViewModeButtons,
14
17
  } from './EditorToolbarGroups';
@@ -81,9 +84,25 @@ export interface ToolbarLabels {
81
84
  findClose?: string;
82
85
  caseSensitive?: string;
83
86
  findNone?: string;
87
+ tableAddColumnBefore?: string;
88
+ tableAddColumnAfter?: string;
89
+ tableAddRowBefore?: string;
90
+ tableAddRowAfter?: string;
91
+ tableDeleteColumn?: string;
92
+ tableDeleteRow?: string;
93
+ tableToggleHeaderRow?: string;
94
+ tableDelete?: string;
95
+ groupHistory?: string;
96
+ groupFont?: string;
97
+ groupParagraph?: string;
98
+ groupStyles?: string;
99
+ groupInsert?: string;
100
+ groupTable?: string;
101
+ groupTools?: string;
102
+ groupView?: string;
84
103
  }
85
104
 
86
- const DEFAULT_LABELS: ToolbarLabels = {
105
+ export const DEFAULT_LABELS: ToolbarLabels = {
87
106
  bold: 'Bold',
88
107
  italic: 'Italic',
89
108
  underline: 'Underline',
@@ -135,6 +154,22 @@ const DEFAULT_LABELS: ToolbarLabels = {
135
154
  findClose: 'Close find',
136
155
  caseSensitive: 'Match case',
137
156
  findNone: 'No matches',
157
+ tableAddColumnBefore: 'Insert column left',
158
+ tableAddColumnAfter: 'Insert column right',
159
+ tableAddRowBefore: 'Insert row above',
160
+ tableAddRowAfter: 'Insert row below',
161
+ tableDeleteColumn: 'Delete column',
162
+ tableDeleteRow: 'Delete row',
163
+ tableToggleHeaderRow: 'Toggle header row',
164
+ tableDelete: 'Delete table',
165
+ groupHistory: 'History',
166
+ groupFont: 'Font',
167
+ groupParagraph: 'Paragraph',
168
+ groupStyles: 'Styles',
169
+ groupInsert: 'Insert',
170
+ groupTable: 'Table',
171
+ groupTools: 'Tools',
172
+ groupView: 'View',
138
173
  };
139
174
 
140
175
  /**
@@ -162,66 +197,116 @@ export function EditorToolbar({
162
197
 
163
198
  const isLite = mode === 'lite';
164
199
 
200
+ // Lite mode: a single flat row (no captions), as before.
201
+ if (isLite) {
202
+ return (
203
+ <div className="maya-editor-toolbar" role="toolbar" aria-label="Editor toolbar">
204
+ <Btn
205
+ disabled={!editor.can().undo()}
206
+ onClick={() => editor.chain().focus().undo().run()}
207
+ title={L.undo}
208
+ >
209
+ <EditorIcon name="undo" />
210
+ </Btn>
211
+ <Btn
212
+ disabled={!editor.can().redo()}
213
+ onClick={() => editor.chain().focus().redo().run()}
214
+ title={L.redo}
215
+ >
216
+ <EditorIcon name="redo" />
217
+ </Btn>
218
+ <span className="maya-editor-toolbar__sep" aria-hidden />
219
+ <FormattingButtons editor={editor} labels={L} />
220
+ </div>
221
+ );
222
+ }
223
+
224
+ // Full mode: Word-style ribbon — each group is captioned and separated.
165
225
  return (
166
- <div className="maya-editor-toolbar" role="toolbar" aria-label="Editor toolbar">
167
- <Btn
168
- disabled={!editor.can().undo()}
169
- onClick={() => editor.chain().focus().undo().run()}
170
- title={L.undo}
171
- >
172
-
173
- </Btn>
174
- <Btn
175
- disabled={!editor.can().redo()}
176
- onClick={() => editor.chain().focus().redo().run()}
177
- title={L.redo}
178
- >
179
-
180
- </Btn>
226
+ <div
227
+ className="maya-editor-toolbar maya-editor-ribbon"
228
+ role="toolbar"
229
+ aria-label="Editor toolbar"
230
+ >
231
+ <RibbonGroup label={L.groupHistory ?? 'History'}>
232
+ <Btn
233
+ disabled={!editor.can().undo()}
234
+ onClick={() => editor.chain().focus().undo().run()}
235
+ title={L.undo}
236
+ >
237
+ <EditorIcon name="undo" />
238
+ </Btn>
239
+ <Btn
240
+ disabled={!editor.can().redo()}
241
+ onClick={() => editor.chain().focus().redo().run()}
242
+ title={L.redo}
243
+ >
244
+ <EditorIcon name="redo" />
245
+ </Btn>
246
+ </RibbonGroup>
181
247
 
182
- <span className="maya-editor-toolbar__sep" aria-hidden />
183
- <FormattingButtons editor={editor} labels={L} />
248
+ <RibbonGroup label={L.groupFont ?? 'Font'}>
249
+ <FormattingButtons editor={editor} labels={L} />
250
+ <AdvancedFormattingButtons editor={editor} labels={L} />
251
+ </RibbonGroup>
184
252
 
185
- {!isLite && (
186
- <>
187
- <span className="maya-editor-toolbar__sep" aria-hidden />
188
- <AdvancedFormattingButtons editor={editor} labels={L} />
253
+ <RibbonGroup label={L.groupParagraph ?? 'Paragraph'}>
254
+ <AlignmentButtons editor={editor} labels={L} />
255
+ <span className="maya-editor-toolbar__sep" aria-hidden />
256
+ <IndentButtons editor={editor} labels={L} />
257
+ <span className="maya-editor-toolbar__sep" aria-hidden />
258
+ <ListAndBlockButtons editor={editor} labels={L} />
259
+ </RibbonGroup>
189
260
 
190
- <span className="maya-editor-toolbar__sep" aria-hidden />
191
- <AlignmentButtons editor={editor} labels={L} />
261
+ <RibbonGroup label={L.groupStyles ?? 'Styles'}>
262
+ <HeadingButtons editor={editor} labels={L} />
263
+ </RibbonGroup>
192
264
 
193
- <span className="maya-editor-toolbar__sep" aria-hidden />
194
- <IndentButtons editor={editor} labels={L} />
265
+ <RibbonGroup label={L.groupInsert ?? 'Insert'}>
266
+ <TableAndMediaButtons editor={editor} labels={L} onImage={onImage} />
267
+ </RibbonGroup>
195
268
 
196
- <span className="maya-editor-toolbar__sep" aria-hidden />
197
- <HeadingButtons editor={editor} labels={L} />
198
- <ListAndBlockButtons editor={editor} labels={L} />
199
- <TableAndMediaButtons editor={editor} labels={L} onImage={onImage} />
269
+ {editor.isActive('table') && (
270
+ <RibbonGroup label={L.groupTable ?? 'Table'}>
271
+ <TableButtons editor={editor} labels={L} />
272
+ </RibbonGroup>
273
+ )}
200
274
 
275
+ <RibbonGroup label={L.groupTools ?? 'Tools'}>
276
+ <DocumentButtons
277
+ editor={editor}
278
+ labels={L}
279
+ onImportDocx={onImportDocx}
280
+ onExportDocx={onExportDocx}
281
+ onAddComment={onAddComment}
282
+ onToggleFind={onToggleFind}
283
+ />
284
+ </RibbonGroup>
201
285
 
202
- <div className="flex items-end gap-0.5 shrink-0 pl-1 border-l border-ui-border dark:border-ui-dark-border ml-auto">
203
- <DocumentButtons
204
- editor={editor}
205
- labels={L}
206
- onImportDocx={onImportDocx}
207
- onExportDocx={onExportDocx}
208
- onAddComment={onAddComment}
209
- onToggleFind={onToggleFind}
210
- />
286
+ <RibbonGroup label={L.groupView ?? 'View'}>
287
+ <ViewModeButtons
288
+ editor={editor}
289
+ labels={L}
290
+ viewMode={viewMode}
291
+ isFullscreen={isFullscreen}
292
+ onInsertHtml={onInsertHtml}
293
+ onInsertMarkdown={onInsertMarkdown}
294
+ onToggleFullscreen={onToggleFullscreen}
295
+ />
296
+ </RibbonGroup>
297
+ </div>
298
+ );
299
+ }
211
300
 
212
- <span className="maya-editor-toolbar__sep" aria-hidden />
213
- <ViewModeButtons
214
- editor={editor}
215
- labels={L}
216
- viewMode={viewMode}
217
- isFullscreen={isFullscreen}
218
- onInsertHtml={onInsertHtml}
219
- onInsertMarkdown={onInsertMarkdown}
220
- onToggleFullscreen={onToggleFullscreen}
221
- />
222
- </div>
223
- </>
224
- )}
301
+ /**
302
+ * One captioned ribbon section: a row of buttons with a small grey label
303
+ * beneath, mirroring Word's ribbon groups (Font, Paragraph, …).
304
+ */
305
+ function RibbonGroup({ label, children }: { label: string; children: ReactNode }) {
306
+ return (
307
+ <div className="maya-editor-ribbon-group">
308
+ <div className="maya-editor-ribbon-group__row">{children}</div>
309
+ <div className="maya-editor-ribbon-group__label">{label}</div>
225
310
  </div>
226
311
  );
227
312
  }