@bendyline/squisq-editor-react 1.1.0 → 1.2.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.
Files changed (50) hide show
  1. package/dist/EditorContext.d.ts +6 -2
  2. package/dist/EditorContext.d.ts.map +1 -1
  3. package/dist/EditorContext.js +3 -1
  4. package/dist/EditorContext.js.map +1 -1
  5. package/dist/EditorShell.d.ts +11 -1
  6. package/dist/EditorShell.d.ts.map +1 -1
  7. package/dist/EditorShell.js +9 -7
  8. package/dist/EditorShell.js.map +1 -1
  9. package/dist/ImageNodeView.d.ts +15 -0
  10. package/dist/ImageNodeView.d.ts.map +1 -0
  11. package/dist/ImageNodeView.js +52 -0
  12. package/dist/ImageNodeView.js.map +1 -0
  13. package/dist/PreviewControls.d.ts +41 -0
  14. package/dist/PreviewControls.d.ts.map +1 -0
  15. package/dist/PreviewControls.js +201 -0
  16. package/dist/PreviewControls.js.map +1 -0
  17. package/dist/PreviewPanel.d.ts +7 -7
  18. package/dist/PreviewPanel.d.ts.map +1 -1
  19. package/dist/PreviewPanel.js +183 -199
  20. package/dist/PreviewPanel.js.map +1 -1
  21. package/dist/Toolbar.d.ts +8 -1
  22. package/dist/Toolbar.d.ts.map +1 -1
  23. package/dist/Toolbar.js +145 -20
  24. package/dist/Toolbar.js.map +1 -1
  25. package/dist/WysiwygEditor.d.ts.map +1 -1
  26. package/dist/WysiwygEditor.js +3 -1
  27. package/dist/WysiwygEditor.js.map +1 -1
  28. package/dist/__tests__/tiptapBridge.test.d.ts +2 -0
  29. package/dist/__tests__/tiptapBridge.test.d.ts.map +1 -0
  30. package/dist/__tests__/tiptapBridge.test.js +241 -0
  31. package/dist/__tests__/tiptapBridge.test.js.map +1 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +1 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/tiptapBridge.d.ts.map +1 -1
  37. package/dist/tiptapBridge.js +146 -5
  38. package/dist/tiptapBridge.js.map +1 -1
  39. package/package.json +5 -4
  40. package/src/EditorContext.tsx +8 -1
  41. package/src/EditorShell.tsx +71 -32
  42. package/src/ImageNodeView.tsx +70 -0
  43. package/src/PreviewControls.tsx +340 -0
  44. package/src/PreviewPanel.tsx +216 -287
  45. package/src/Toolbar.tsx +449 -17
  46. package/src/WysiwygEditor.tsx +3 -1
  47. package/src/__tests__/tiptapBridge.test.ts +290 -0
  48. package/src/index.ts +6 -0
  49. package/src/styles/editor.css +257 -16
  50. package/src/tiptapBridge.ts +164 -6
@@ -0,0 +1,290 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { markdownToTiptap, tiptapToMarkdown } from '../tiptapBridge';
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // markdownToTiptap
6
+ // ---------------------------------------------------------------------------
7
+
8
+ describe('markdownToTiptap', () => {
9
+ it('returns empty paragraph for empty input', () => {
10
+ expect(markdownToTiptap('')).toBe('<p></p>');
11
+ expect(markdownToTiptap(' ')).toBe('<p></p>');
12
+ });
13
+
14
+ it('converts a plain paragraph', () => {
15
+ const html = markdownToTiptap('Hello world');
16
+ expect(html).toContain('<p>');
17
+ expect(html).toContain('Hello world');
18
+ });
19
+
20
+ it('converts headings h1-h3', () => {
21
+ expect(markdownToTiptap('# Title')).toContain('<h1');
22
+ expect(markdownToTiptap('## Subtitle')).toContain('<h2');
23
+ expect(markdownToTiptap('### Section')).toContain('<h3');
24
+ });
25
+
26
+ it('converts bold text', () => {
27
+ const html = markdownToTiptap('This is **bold** text');
28
+ expect(html).toContain('<strong>bold</strong>');
29
+ });
30
+
31
+ it('converts italic text', () => {
32
+ const html = markdownToTiptap('This is *italic* text');
33
+ expect(html).toContain('<em>italic</em>');
34
+ });
35
+
36
+ it('converts strikethrough text', () => {
37
+ const html = markdownToTiptap('This is ~~deleted~~ text');
38
+ expect(html).toContain('<s>deleted</s>');
39
+ });
40
+
41
+ it('converts inline code', () => {
42
+ const html = markdownToTiptap('Use `console.log` here');
43
+ expect(html).toContain('<code>console.log</code>');
44
+ });
45
+
46
+ it('converts links', () => {
47
+ const html = markdownToTiptap('Visit [Example](https://example.com)');
48
+ expect(html).toContain('href="https://example.com"');
49
+ expect(html).toContain('Example');
50
+ });
51
+
52
+ it('converts images', () => {
53
+ const html = markdownToTiptap('![Logo](logo.png)');
54
+ expect(html).toContain('alt="Logo"');
55
+ expect(html).toContain('src="logo.png"');
56
+ });
57
+
58
+ it('converts fenced code blocks', () => {
59
+ const md = '```javascript\nconst x = 1;\n```';
60
+ const html = markdownToTiptap(md);
61
+ expect(html).toContain('<pre>');
62
+ expect(html).toContain('<code');
63
+ expect(html).toContain('language-javascript');
64
+ expect(html).toContain('const x = 1;');
65
+ });
66
+
67
+ it('converts unordered lists', () => {
68
+ const md = '- Item one\n- Item two\n- Item three';
69
+ const html = markdownToTiptap(md);
70
+ expect(html).toContain('<ul>');
71
+ expect(html).toContain('<li>');
72
+ expect(html).toContain('Item one');
73
+ expect(html).toContain('Item two');
74
+ });
75
+
76
+ it('converts ordered lists', () => {
77
+ const md = '1. First\n2. Second\n3. Third';
78
+ const html = markdownToTiptap(md);
79
+ expect(html).toContain('<ol>');
80
+ expect(html).toContain('<li>');
81
+ expect(html).toContain('First');
82
+ });
83
+
84
+ it('converts blockquotes', () => {
85
+ const md = '> This is a quote';
86
+ const html = markdownToTiptap(md);
87
+ expect(html).toContain('<blockquote>');
88
+ expect(html).toContain('This is a quote');
89
+ });
90
+
91
+ it('converts horizontal rules', () => {
92
+ const md = 'Before\n\n---\n\nAfter';
93
+ const html = markdownToTiptap(md);
94
+ expect(html).toContain('<hr');
95
+ });
96
+
97
+ it('converts markdown tables', () => {
98
+ const md = '| A | B |\n| --- | --- |\n| 1 | 2 |';
99
+ const html = markdownToTiptap(md);
100
+ expect(html).toContain('<table>');
101
+ expect(html).toContain('<thead>');
102
+ expect(html).toContain('<th>');
103
+ expect(html).toContain('<td>');
104
+ });
105
+
106
+ it('handles table column alignment', () => {
107
+ const md = '| Left | Center | Right |\n| :--- | :---: | ---: |\n| a | b | c |';
108
+ const html = markdownToTiptap(md);
109
+ expect(html).toContain('text-align: left');
110
+ expect(html).toContain('text-align: center');
111
+ expect(html).toContain('text-align: right');
112
+ });
113
+
114
+ it('escapes HTML special characters in text', () => {
115
+ const html = markdownToTiptap('Use <div> & "quotes"');
116
+ expect(html).toContain('&lt;div&gt;');
117
+ expect(html).toContain('&amp;');
118
+ });
119
+
120
+ it('normalizes \\r\\n line endings', () => {
121
+ const html = markdownToTiptap('Line one\r\nLine two');
122
+ // Should not contain raw \r
123
+ expect(html).not.toContain('\r');
124
+ });
125
+ });
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // tiptapToMarkdown
129
+ // ---------------------------------------------------------------------------
130
+
131
+ describe('tiptapToMarkdown', () => {
132
+ it('returns empty string for empty paragraph', () => {
133
+ expect(tiptapToMarkdown('')).toBe('');
134
+ expect(tiptapToMarkdown('<p></p>')).toBe('');
135
+ });
136
+
137
+ it('converts a paragraph to plain text', () => {
138
+ const md = tiptapToMarkdown('<p>Hello world</p>');
139
+ expect(md).toContain('Hello world');
140
+ });
141
+
142
+ it('converts headings', () => {
143
+ expect(tiptapToMarkdown('<h1>Title</h1>')).toContain('# Title');
144
+ expect(tiptapToMarkdown('<h2>Sub</h2>')).toContain('## Sub');
145
+ expect(tiptapToMarkdown('<h3>Section</h3>')).toContain('### Section');
146
+ });
147
+
148
+ it('converts strong tags to bold markdown', () => {
149
+ const md = tiptapToMarkdown('<p>This is <strong>bold</strong> text</p>');
150
+ expect(md).toContain('**bold**');
151
+ });
152
+
153
+ it('converts em tags to italic markdown', () => {
154
+ const md = tiptapToMarkdown('<p>This is <em>italic</em> text</p>');
155
+ expect(md).toContain('*italic*');
156
+ });
157
+
158
+ it('converts s/del tags to strikethrough', () => {
159
+ const md = tiptapToMarkdown('<p>This is <s>deleted</s> text</p>');
160
+ expect(md).toContain('~~deleted~~');
161
+ });
162
+
163
+ it('converts code tags to inline code', () => {
164
+ const md = tiptapToMarkdown('<p>Use <code>foo</code> here</p>');
165
+ expect(md).toContain('`foo`');
166
+ });
167
+
168
+ it('converts links', () => {
169
+ const md = tiptapToMarkdown('<p><a href="https://example.com">Example</a></p>');
170
+ expect(md).toContain('[Example](https://example.com)');
171
+ });
172
+
173
+ it('converts code blocks with language', () => {
174
+ const md = tiptapToMarkdown('<pre><code class="language-js">const x = 1;</code></pre>');
175
+ expect(md).toContain('```js');
176
+ expect(md).toContain('const x = 1;');
177
+ expect(md).toContain('```');
178
+ });
179
+
180
+ it('converts code blocks without language', () => {
181
+ const md = tiptapToMarkdown('<pre><code>plain code</code></pre>');
182
+ expect(md).toContain('```');
183
+ expect(md).toContain('plain code');
184
+ });
185
+
186
+ it('converts blockquotes', () => {
187
+ const md = tiptapToMarkdown('<blockquote><p>A wise quote</p></blockquote>');
188
+ expect(md).toContain('> ');
189
+ expect(md).toContain('A wise quote');
190
+ });
191
+
192
+ it('converts horizontal rules', () => {
193
+ const md = tiptapToMarkdown('<p>Before</p><hr><p>After</p>');
194
+ expect(md).toContain('---');
195
+ });
196
+
197
+ it('converts unordered lists', () => {
198
+ const md = tiptapToMarkdown('<ul><li><p>Alpha</p></li><li><p>Beta</p></li></ul>');
199
+ expect(md).toContain('- Alpha');
200
+ expect(md).toContain('- Beta');
201
+ });
202
+
203
+ it('converts ordered lists', () => {
204
+ const md = tiptapToMarkdown('<ol><li><p>First</p></li><li><p>Second</p></li></ol>');
205
+ expect(md).toContain('1. First');
206
+ expect(md).toContain('2. Second');
207
+ });
208
+
209
+ it('converts tables', () => {
210
+ const html =
211
+ '<table><thead><tr><th>Name</th><th>Age</th></tr></thead>' +
212
+ '<tbody><tr><td>Alice</td><td>30</td></tr></tbody></table>';
213
+ const md = tiptapToMarkdown(html);
214
+ expect(md).toContain('| Name | Age |');
215
+ expect(md).toContain('| --- | --- |');
216
+ expect(md).toContain('| Alice | 30 |');
217
+ });
218
+
219
+ it('preserves template annotations in headings', () => {
220
+ const html = '<h2 data-template="statHighlight" data-template-params="stat=42%">Stats</h2>';
221
+ const md = tiptapToMarkdown(html);
222
+ expect(md).toContain('## Stats');
223
+ expect(md).toContain('{[statHighlight');
224
+ expect(md).toContain('stat=42%');
225
+ });
226
+
227
+ it('unescapes HTML entities', () => {
228
+ const md = tiptapToMarkdown('<pre><code>&lt;div&gt; &amp; &quot;test&quot;</code></pre>');
229
+ expect(md).toContain('<div>');
230
+ expect(md).toContain('&');
231
+ expect(md).toContain('"test"');
232
+ });
233
+ });
234
+
235
+ // ---------------------------------------------------------------------------
236
+ // Round-trip tests
237
+ // ---------------------------------------------------------------------------
238
+
239
+ describe('round-trip: markdownToTiptap → tiptapToMarkdown', () => {
240
+ const roundTrip = (md: string) => tiptapToMarkdown(markdownToTiptap(md));
241
+
242
+ it('preserves plain text', () => {
243
+ expect(roundTrip('Hello world')).toContain('Hello world');
244
+ });
245
+
246
+ it('preserves headings', () => {
247
+ const result = roundTrip('## My Heading');
248
+ expect(result).toContain('## My Heading');
249
+ });
250
+
251
+ it('preserves bold text', () => {
252
+ expect(roundTrip('Some **bold** here')).toContain('**bold**');
253
+ });
254
+
255
+ it('preserves italic text', () => {
256
+ expect(roundTrip('Some *italic* here')).toContain('*italic*');
257
+ });
258
+
259
+ it('preserves inline code', () => {
260
+ expect(roundTrip('Use `code` here')).toContain('`code`');
261
+ });
262
+
263
+ it('preserves code blocks', () => {
264
+ const md = '```js\nconst x = 1;\n```';
265
+ const result = roundTrip(md);
266
+ expect(result).toContain('```js');
267
+ expect(result).toContain('const x = 1;');
268
+ });
269
+
270
+ it('preserves links', () => {
271
+ const result = roundTrip('Click [here](https://example.com)');
272
+ expect(result).toContain('[here](https://example.com)');
273
+ });
274
+
275
+ it('preserves blockquotes', () => {
276
+ expect(roundTrip('> Important note')).toContain('> Important note');
277
+ });
278
+
279
+ it('preserves unordered lists', () => {
280
+ const result = roundTrip('- Alpha\n- Beta');
281
+ expect(result).toContain('- Alpha');
282
+ expect(result).toContain('- Beta');
283
+ });
284
+
285
+ it('preserves ordered lists', () => {
286
+ const result = roundTrip('1. First\n2. Second');
287
+ expect(result).toContain('1. First');
288
+ expect(result).toContain('2. Second');
289
+ });
290
+ });
package/src/index.ts CHANGED
@@ -40,6 +40,12 @@ export type { WysiwygEditorProps } from './WysiwygEditor.js';
40
40
 
41
41
  export { PreviewPanel } from './PreviewPanel.js';
42
42
  export type { PreviewPanelProps } from './PreviewPanel.js';
43
+ export {
44
+ PreviewSettingsProvider,
45
+ PreviewToolbarControls,
46
+ usePreviewSettings,
47
+ } from './PreviewControls.js';
48
+ export type { PreviewSettings } from './PreviewControls.js';
43
49
 
44
50
  // Chrome (for custom layouts)
45
51
  export { ViewSwitcher } from './ViewSwitcher.js';
@@ -31,6 +31,8 @@
31
31
 
32
32
  .squisq-view-tab {
33
33
  padding: 6px 16px;
34
+ min-width: 72px;
35
+ text-align: center;
34
36
  border: none;
35
37
  background: transparent;
36
38
  color: #6b7280;
@@ -57,9 +59,10 @@
57
59
  .squisq-toolbar {
58
60
  display: flex;
59
61
  align-items: center;
60
- flex-wrap: wrap;
61
- padding: 4px 12px;
62
+ flex-wrap: nowrap;
63
+ padding: 0 12px 0 0;
62
64
  gap: 2px;
65
+ background: rgba(0, 0, 0, 0.07);
63
66
  }
64
67
 
65
68
  /* ─── View Tabs (inside toolbar) ─────────────────────── */
@@ -67,20 +70,28 @@
67
70
  .squisq-toolbar-view-tabs {
68
71
  display: flex;
69
72
  gap: 0;
70
- margin-right: 2px;
73
+ margin-right: 0;
74
+ padding: 0 16px 0 12px;
75
+ background: #ffffff;
76
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
77
+ align-self: stretch;
78
+ align-items: center;
71
79
  }
72
80
 
73
81
  .squisq-toolbar-actions {
74
82
  display: flex;
75
83
  align-items: center;
76
84
  gap: 2px;
85
+ overflow: hidden;
86
+ flex: 1;
87
+ min-width: 0;
77
88
  }
78
89
 
79
90
  .squisq-toolbar-view-tab {
80
91
  padding: 4px 14px;
81
92
  border: none;
82
93
  background: transparent;
83
- color: #6b7280;
94
+ color: #4b5563;
84
95
  cursor: pointer;
85
96
  font-size: 13px;
86
97
  font-weight: 500;
@@ -90,12 +101,22 @@
90
101
  border-color 0.15s;
91
102
  }
92
103
 
104
+ .squisq-toolbar-view-tab::after {
105
+ content: attr(data-label);
106
+ display: block;
107
+ font-weight: 600;
108
+ height: 0;
109
+ overflow: hidden;
110
+ visibility: hidden;
111
+ }
112
+
93
113
  .squisq-toolbar-view-tab:hover {
94
- color: #1f2937;
114
+ color: #111827;
95
115
  }
96
116
 
97
117
  .squisq-toolbar-view-tab--active {
98
- color: #2563eb;
118
+ color: #1d4ed8;
119
+ font-weight: 600;
99
120
  border-bottom-color: #2563eb;
100
121
  }
101
122
 
@@ -149,6 +170,90 @@
149
170
  color: #1d4ed8;
150
171
  }
151
172
 
173
+ /* ─── Toolbar Overflow Menu ──────────────────────────── */
174
+
175
+ .squisq-toolbar-overflow {
176
+ position: relative;
177
+ flex-shrink: 0;
178
+ margin-left: 2px;
179
+ }
180
+
181
+ .squisq-toolbar-overflow-trigger {
182
+ font-size: 16px;
183
+ letter-spacing: 1px;
184
+ }
185
+
186
+ .squisq-toolbar-overflow-menu {
187
+ position: absolute;
188
+ top: 100%;
189
+ right: 0;
190
+ z-index: 100;
191
+ min-width: 180px;
192
+ padding: 4px 0;
193
+ margin-top: 4px;
194
+ background: #fff;
195
+ border: 1px solid #e5e7eb;
196
+ border-radius: 6px;
197
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
198
+ }
199
+
200
+ .squisq-toolbar-overflow-item {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 8px;
204
+ width: 100%;
205
+ padding: 6px 12px;
206
+ border: none;
207
+ background: transparent;
208
+ color: #374151;
209
+ font-size: 13px;
210
+ cursor: pointer;
211
+ text-align: left;
212
+ white-space: nowrap;
213
+ }
214
+
215
+ .squisq-toolbar-overflow-item:hover {
216
+ background: #f3f4f6;
217
+ }
218
+
219
+ .squisq-toolbar-overflow-item:disabled {
220
+ opacity: 0.4;
221
+ cursor: default;
222
+ }
223
+
224
+ .squisq-toolbar-overflow-item--active {
225
+ color: #2563eb;
226
+ background: #eff6ff;
227
+ }
228
+
229
+ .squisq-toolbar-overflow-icon {
230
+ display: inline-flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ width: 18px;
234
+ font-size: 14px;
235
+ font-weight: 600;
236
+ }
237
+
238
+ .squisq-toolbar-overflow-item--danger {
239
+ color: #dc2626;
240
+ }
241
+
242
+ .squisq-toolbar-overflow-item--danger:hover {
243
+ background: #fef2f2;
244
+ color: #b91c1c;
245
+ }
246
+
247
+ .squisq-toolbar-overflow-template {
248
+ gap: 6px;
249
+ padding: 6px 12px;
250
+ }
251
+
252
+ .squisq-toolbar-overflow-template select {
253
+ flex: 1;
254
+ min-width: 0;
255
+ }
256
+
152
257
  /* ─── Template Picker (toolbar) ──────────────────────── */
153
258
 
154
259
  .squisq-template-picker {
@@ -237,12 +342,18 @@
237
342
 
238
343
  /* ─── WYSIWYG Editor ─────────────────────────────────── */
239
344
 
345
+ .squisq-wysiwyg-container {
346
+ background: #eeecea;
347
+ }
348
+
240
349
  .squisq-wysiwyg-editor {
241
350
  padding: 16px 24px;
242
351
  max-width: 800px;
243
352
  margin: 0 auto;
244
353
  outline: none;
245
354
  min-height: 100%;
355
+ background: #fff;
356
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.06);
246
357
  }
247
358
 
248
359
  .squisq-wysiwyg-editor h1 {
@@ -339,6 +450,81 @@
339
450
  font-weight: 600;
340
451
  }
341
452
 
453
+ /* Tiptap table interaction — resize handles, selection, wrapper */
454
+
455
+ .squisq-wysiwyg-editor th,
456
+ .squisq-wysiwyg-editor td {
457
+ position: relative;
458
+ }
459
+
460
+ .squisq-wysiwyg-editor .tableWrapper {
461
+ overflow-x: auto;
462
+ margin: 1em 0;
463
+ }
464
+
465
+ .squisq-wysiwyg-editor .column-resize-handle {
466
+ position: absolute;
467
+ right: -2px;
468
+ top: 0;
469
+ bottom: 0;
470
+ width: 4px;
471
+ background: #3b82f6;
472
+ pointer-events: none;
473
+ }
474
+
475
+ .squisq-wysiwyg-editor.resize-cursor {
476
+ cursor: col-resize;
477
+ }
478
+
479
+ .squisq-wysiwyg-editor .selectedCell::after {
480
+ content: '';
481
+ position: absolute;
482
+ left: 0;
483
+ right: 0;
484
+ top: 0;
485
+ bottom: 0;
486
+ background: rgba(59, 130, 246, 0.12);
487
+ pointer-events: none;
488
+ z-index: 2;
489
+ }
490
+
491
+ /* ─── Table Controls (toolbar) ───────────────────────── */
492
+
493
+ .squisq-table-controls {
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 2px;
497
+ }
498
+
499
+ .squisq-table-controls-label {
500
+ font-size: 11px;
501
+ color: #6b7280;
502
+ font-weight: 500;
503
+ margin-right: 4px;
504
+ white-space: nowrap;
505
+ }
506
+
507
+ .squisq-table-controls .squisq-toolbar-button {
508
+ display: inline-flex;
509
+ align-items: center;
510
+ justify-content: center;
511
+ padding: 3px 5px;
512
+ }
513
+
514
+ .squisq-table-controls .squisq-toolbar-button svg {
515
+ display: block;
516
+ flex-shrink: 0;
517
+ }
518
+
519
+ .squisq-toolbar-button--danger {
520
+ color: #dc2626;
521
+ }
522
+
523
+ .squisq-toolbar-button--danger:hover {
524
+ background: #fef2f2;
525
+ color: #b91c1c;
526
+ }
527
+
342
528
  .squisq-wysiwyg-editor a {
343
529
  color: #2563eb;
344
530
  text-decoration: underline;
@@ -434,20 +620,25 @@
434
620
 
435
621
  /* Toolbar */
436
622
  .squisq-editor-shell[data-theme='dark'] .squisq-toolbar {
437
- /* no border-top sits directly in header */
623
+ background: rgba(255, 255, 255, 0.08);
624
+ }
625
+
626
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-view-tabs {
627
+ background: #111827;
628
+ border-right-color: rgba(255, 255, 255, 0.15);
438
629
  }
439
630
 
440
631
  /* View tabs (dark) */
441
632
  .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-view-tab {
442
- color: #9ca3af;
633
+ color: #d1d5db;
443
634
  }
444
635
 
445
636
  .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-view-tab:hover {
446
- color: #e5e7eb;
637
+ color: #f9fafb;
447
638
  }
448
639
 
449
640
  .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-view-tab--active {
450
- color: #60a5fa;
641
+ color: #93c5fd;
451
642
  border-bottom-color: #60a5fa;
452
643
  }
453
644
 
@@ -478,6 +669,35 @@
478
669
  background: #4b5563;
479
670
  }
480
671
 
672
+ /* Overflow menu (dark) */
673
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-menu {
674
+ background: #1f2937;
675
+ border-color: #374151;
676
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
677
+ }
678
+
679
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item {
680
+ color: #d1d5db;
681
+ }
682
+
683
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item:hover {
684
+ background: #374151;
685
+ }
686
+
687
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--active {
688
+ color: #60a5fa;
689
+ background: #1e3a5f;
690
+ }
691
+
692
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--danger {
693
+ color: #f87171;
694
+ }
695
+
696
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--danger:hover {
697
+ background: #450a0a;
698
+ color: #fca5a5;
699
+ }
700
+
481
701
  /* Template picker (dark) */
482
702
  .squisq-editor-shell[data-theme='dark'] .squisq-template-picker-label {
483
703
  color: #9ca3af;
@@ -516,8 +736,14 @@
516
736
  }
517
737
 
518
738
  /* WYSIWYG Editor */
739
+ .squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-container {
740
+ background: #0f1219;
741
+ }
742
+
519
743
  .squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor {
520
744
  color: #e5e7eb;
745
+ background: #111827;
746
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
521
747
  }
522
748
 
523
749
  .squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor blockquote {
@@ -552,6 +778,27 @@
552
778
  background: #1f2937;
553
779
  }
554
780
 
781
+ .squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor .column-resize-handle {
782
+ background: #60a5fa;
783
+ }
784
+
785
+ .squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor .selectedCell::after {
786
+ background: rgba(96, 165, 250, 0.15);
787
+ }
788
+
789
+ .squisq-editor-shell[data-theme='dark'] .squisq-table-controls-label {
790
+ color: #9ca3af;
791
+ }
792
+
793
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-button--danger {
794
+ color: #f87171;
795
+ }
796
+
797
+ .squisq-editor-shell[data-theme='dark'] .squisq-toolbar-button--danger:hover {
798
+ background: #450a0a;
799
+ color: #fca5a5;
800
+ }
801
+
555
802
  .squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor a {
556
803
  color: #60a5fa;
557
804
  }
@@ -606,12 +853,6 @@
606
853
  .squisq-toolbar-view-tabs {
607
854
  margin-right: 0;
608
855
  }
609
-
610
- .squisq-toolbar-actions {
611
- flex-basis: 100%;
612
- flex-wrap: wrap;
613
- padding-top: 2px;
614
- }
615
856
  }
616
857
 
617
858
  /* ─── Media Bin ──────────────────────────────────────── */