|
4 | 4 | ref="toolbarRef" |
5 | 5 | class="toolbar" |
6 | 6 | role="toolbar" |
| 7 | + :style="{ |
| 8 | + backgroundColor: $themePalette.grey.v_50, |
| 9 | + borderBottom: `1px solid ${$themeTokens.fineLine}`, |
| 10 | + }" |
7 | 11 | :aria-label="textFormattingToolbar$()" |
8 | 12 | > |
9 | 13 | <!-- History buttons --> |
10 | 14 | <div |
11 | 15 | role="group" |
| 16 | + class="toolbar-group" |
12 | 17 | :aria-label="historyActions$()" |
13 | 18 | > |
14 | 19 | <ToolbarButton |
|
27 | 32 | <!-- Format dropdown --> |
28 | 33 | <div |
29 | 34 | role="group" |
| 35 | + class="toolbar-group" |
30 | 36 | :aria-label="textFormattingOptions$()" |
31 | 37 | > |
32 | 38 | <FormatDropdown /> |
|
73 | 79 | </template> |
74 | 80 |
|
75 | 81 | <template #more="{ overflowItems }"> |
76 | | - <button class="more-button"> |
| 82 | + <button |
| 83 | + class="more-button" |
| 84 | + :class=" |
| 85 | + $computedClass({ |
| 86 | + ':is([aria-expanded=\'true\'])': { |
| 87 | + color: $themePalette.blue.v_600, |
| 88 | + background: $themePalette.blue.v_100, |
| 89 | + }, |
| 90 | + }) |
| 91 | + " |
| 92 | + > |
77 | 93 | <span>{{ moreButtonText$() }}</span> |
78 | 94 | <img |
79 | 95 | :src="require('../../assets/icon-chevron-down.svg')" |
|
87 | 103 | <template #option="{ option }"> |
88 | 104 | <div |
89 | 105 | class="overflow-item" |
90 | | - :class="{ active: option.isActive }" |
| 106 | + :style=" |
| 107 | + option.isActive |
| 108 | + ? { |
| 109 | + color: $themePalette.blue.v_600, |
| 110 | + backgroundColor: $themePalette.blue.v_100, |
| 111 | + } |
| 112 | + : null |
| 113 | + " |
91 | 114 | > |
92 | 115 | <img |
93 | 116 | :src="option.icon" |
|
103 | 126 | </KListWithOverflow> |
104 | 127 |
|
105 | 128 | <ToolbarButton |
| 129 | + class="minimize-button" |
106 | 130 | :title="minimizeAction.title" |
107 | 131 | :icon="minimizeAction.icon" |
108 | 132 | @click="minimizeAction.handler" |
|
114 | 138 |
|
115 | 139 | <script> |
116 | 140 |
|
117 | | - import { defineComponent, ref, computed } from 'vue'; |
118 | | - import KListWithOverflow from 'kolibri-design-system/lib/KListWithOverflow.vue'; |
| 141 | + import { ref, computed } from 'vue'; |
119 | 142 | import { useToolbarActions } from '../composables/useToolbarActions'; |
120 | 143 | import { getTipTapEditorStrings } from '../TipTapEditorStrings'; |
121 | 144 | import { useDropdowns } from '../composables/useDropdowns'; |
|
124 | 147 | import PasteDropdown from './toolbar/PasteDropdown.vue'; |
125 | 148 | import ToolbarDivider from './toolbar/ToolbarDivider.vue'; |
126 | 149 |
|
127 | | - export default defineComponent({ |
| 150 | + export default { |
128 | 151 | name: 'EditorToolbar', |
129 | 152 | components: { |
130 | | - KListWithOverflow, |
131 | 153 | ToolbarButton, |
132 | 154 | FormatDropdown, |
133 | 155 | PasteDropdown, |
|
165 | 187 | moreButtonText$, |
166 | 188 | } = getTipTapEditorStrings(); |
167 | 189 |
|
168 | | - const onToolClick = (tool, event, { fromOverflow } = {}) => { |
| 190 | + const onInsertToolClick = (tool, event, { fromOverflow } = {}) => { |
169 | 191 | const target = fromOverflow ? null : event.currentTarget; |
170 | 192 | if (tool.name === 'image') { |
171 | 193 | emit('insert-image', target); |
|
180 | 202 |
|
181 | 203 | // Toolbar groups in visual order (left-to-right). |
182 | 204 | // KListWithOverflow will collapse items from the end first. |
183 | | - // Note: Don't include dividers here - KListWithOverflow renders |
184 | | - // them automatically via #divider slot. |
185 | | - // |
| 205 | + // Note: Don't include dividers here - this will be the source of truth. |
| 206 | +
|
186 | 207 | // Each group describes its actions via `groupActions`. A `label` makes |
187 | 208 | // the wrapper a role="group"; omitting it renders a plain div (for |
188 | 209 | // single-action groups that don't need grouping semantics). |
189 | 210 | // Actions with a `component` key render that component instead of a |
190 | | - // ToolbarButton (e.g. PasteDropdown). Actions with an `onClick` key |
191 | | - // receive the click event; otherwise `handler()` is called. |
| 211 | + // ToolbarButton (e.g. PasteDropdown). |
192 | 212 | const toolbarGroups = computed(() => [ |
193 | 213 | { |
194 | 214 | name: 'textFormat', |
|
254 | 274 | label: insertTools$(), |
255 | 275 | groupActions: insertTools.value.map(tool => ({ |
256 | 276 | ...tool, |
257 | | - handler: (e, { fromOverflow } = {}) => onToolClick(tool, e, { fromOverflow }), |
| 277 | + handler: (e, { fromOverflow } = {}) => onInsertToolClick(tool, e, { fromOverflow }), |
258 | 278 | })), |
259 | 279 | }, |
260 | 280 | ]); |
|
290 | 310 | return options; |
291 | 311 | }; |
292 | 312 |
|
| 313 | + /** |
| 314 | + * Place a divider element between toolbar groups in the overflow menu. |
| 315 | + * This is necessary to tell KListWithOverflow where to render dividers |
| 316 | + * between groups. |
| 317 | + */ |
293 | 318 | const toolbarGroupsWithDividers = computed(() => { |
294 | 319 | const groups = []; |
295 | 320 | toolbarGroups.value.forEach((group, index) => { |
|
305 | 330 | }); |
306 | 331 |
|
307 | 332 | const onOverflowSelect = (option, event) => { |
| 333 | + // Stop propagation to avoid triggering RTE on outside click handler that |
| 334 | + // minimizes the editor because KDropdownMenu is attached to an overlay layer, |
| 335 | + // i.e. not a descendant of the editor. |
308 | 336 | event.stopPropagation(); |
309 | 337 | option.handler(event, { fromOverflow: true }); |
310 | 338 | }; |
|
322 | 350 | moreButtonText$, |
323 | 351 | }; |
324 | 352 | }, |
325 | | - }); |
| 353 | + }; |
326 | 354 |
|
327 | 355 | </script> |
328 | 356 |
|
|
335 | 363 | gap: 6px; |
336 | 364 | align-items: center; |
337 | 365 | padding: 8px; |
338 | | - background: #f8f9fa; |
339 | | - border-bottom: 1px solid #e1e5e9; |
340 | 366 | border-radius: 8px 8px 0 0; |
341 | 367 | } |
342 | 368 |
|
343 | | - .toolbar > :last-child { |
| 369 | + .minimize-button { |
| 370 | + /* Push the minimize button to the far right. */ |
344 | 371 | margin-left: auto; |
345 | 372 | } |
346 | 373 |
|
347 | | - [role='group'] { |
348 | | - display: flex; |
349 | | - gap: 2px; |
350 | | - align-items: center; |
351 | | - } |
352 | | -
|
353 | 374 | .overflow-list { |
354 | 375 | flex: 1; |
355 | 376 | min-width: 0; |
|
383 | 404 | outline: 2px solid #0097f2; |
384 | 405 | } |
385 | 406 |
|
386 | | - .more-button[aria-expanded='true'] { |
387 | | - color: #4368f5; |
388 | | - background: #d9e1fd; |
389 | | - } |
390 | | -
|
391 | 407 | .more-button-icon { |
392 | 408 | width: 16px; |
393 | 409 | height: 16px; |
|
406 | 422 | padding: 8px 12px; |
407 | 423 | font-size: 1.2rem; |
408 | 424 | line-height: 140%; |
409 | | -
|
410 | | - &.active { |
411 | | - color: #3730a3; |
412 | | - background-color: #e0e7ff; |
413 | | - } |
414 | 425 | } |
415 | 426 |
|
416 | 427 | .list-with-overflow-divider { |
|
0 commit comments