@react-email/editor 0.0.0-experimental.14 → 0.0.0-experimental.15

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.
@@ -6,16 +6,23 @@
6
6
  [data-re-bubble-menu] {
7
7
  display: flex;
8
8
  align-items: center;
9
+ gap: 0.125rem;
9
10
  }
10
11
 
11
12
  [data-re-bubble-menu-group] {
12
13
  display: flex;
13
14
  align-items: center;
15
+ gap: 0.125rem;
16
+ padding: 0 0.125rem;
17
+ border: none;
18
+ margin: 0;
19
+ min-width: 0;
14
20
  }
15
21
 
16
22
  [data-re-bubble-menu-separator] {
17
23
  align-self: stretch;
18
24
  width: 1px;
25
+ margin: 0.25rem 0;
19
26
  }
20
27
 
21
28
  [data-re-bubble-menu-item] {
@@ -25,12 +32,12 @@
25
32
  cursor: pointer;
26
33
  border: none;
27
34
  background: none;
28
- padding: 0.5rem;
35
+ padding: 0.375rem;
29
36
  }
30
37
 
31
38
  [data-re-bubble-menu-item] svg {
32
- width: 1rem;
33
- height: 1rem;
39
+ width: 0.875rem;
40
+ height: 0.875rem;
34
41
  }
35
42
 
36
43
  [data-re-node-selector] {
@@ -45,13 +52,14 @@
45
52
  border: none;
46
53
  background: none;
47
54
  white-space: nowrap;
48
- font-size: 0.875rem;
49
- padding: 0.5rem;
55
+ font-size: 0.8125rem;
56
+ padding: 0.375rem 0.5rem;
50
57
  }
51
58
 
52
59
  [data-re-node-selector-trigger] svg {
53
- width: 1rem;
54
- height: 1rem;
60
+ width: 0.75rem;
61
+ height: 0.75rem;
62
+ opacity: 0.5;
55
63
  }
56
64
 
57
65
  [data-re-node-selector-content] {
@@ -67,15 +75,15 @@
67
75
  cursor: pointer;
68
76
  border: none;
69
77
  background: none;
70
- padding: 0.375rem 0.75rem;
71
- font-size: 0.875rem;
78
+ padding: 0.375rem 0.5rem;
79
+ font-size: 0.8125rem;
72
80
  width: 100%;
73
81
  text-align: left;
74
82
  }
75
83
 
76
84
  [data-re-node-selector-item] svg {
77
- width: 0.75rem;
78
- height: 0.75rem;
85
+ width: 0.875rem;
86
+ height: 0.875rem;
79
87
  }
80
88
 
81
89
  [data-re-link-selector] {
@@ -90,25 +98,32 @@
90
98
  cursor: pointer;
91
99
  border: none;
92
100
  background: none;
93
- padding: 0.5rem;
101
+ padding: 0.375rem;
94
102
  }
95
103
 
96
104
  [data-re-link-selector-trigger] svg {
97
- width: 1rem;
98
- height: 1rem;
105
+ width: 0.875rem;
106
+ height: 0.875rem;
99
107
  }
100
108
 
101
109
  [data-re-link-selector-form] {
102
110
  display: flex;
103
111
  align-items: center;
104
112
  gap: 0.25rem;
113
+ position: absolute;
114
+ top: 100%;
115
+ left: 0;
116
+ margin-top: 0.25rem;
117
+ width: max-content;
118
+ min-width: 16rem;
119
+ padding: 0.25rem;
105
120
  }
106
121
 
107
122
  [data-re-link-selector-input] {
108
123
  flex: 1;
109
124
  border: none;
110
125
  outline: none;
111
- font-size: 0.875rem;
126
+ font-size: 0.8125rem;
112
127
  padding: 0.25rem;
113
128
  background: transparent;
114
129
  }
@@ -126,6 +141,6 @@
126
141
 
127
142
  [data-re-link-selector-apply] svg,
128
143
  [data-re-link-selector-unlink] svg {
129
- width: 1rem;
130
- height: 1rem;
144
+ width: 0.875rem;
145
+ height: 0.875rem;
131
146
  }
@@ -0,0 +1,44 @@
1
+ /* Minimal functional styles for SlashCommand components.
2
+ * Layout and positioning only - no visual design.
3
+ * Import optionally: import '@react-email/editor/styles/slash-command.css';
4
+ */
5
+
6
+ [data-re-slash-command] {
7
+ max-height: 330px;
8
+ overflow-y: auto;
9
+ width: 256px;
10
+ padding: 0.25rem;
11
+ }
12
+
13
+ [data-re-slash-command-item] {
14
+ display: flex;
15
+ align-items: center;
16
+ gap: 0.5rem;
17
+ width: 100%;
18
+ padding: 0.375rem 0.5rem;
19
+ border: none;
20
+ border-radius: 0.375rem;
21
+ background: none;
22
+ cursor: pointer;
23
+ font-size: 0.875rem;
24
+ line-height: 1.25rem;
25
+ text-align: left;
26
+ }
27
+
28
+ [data-re-slash-command-item] svg {
29
+ flex-shrink: 0;
30
+ }
31
+
32
+ [data-re-slash-command-category] {
33
+ font-size: 0.6875rem;
34
+ font-weight: 600;
35
+ text-transform: uppercase;
36
+ letter-spacing: 0.05em;
37
+ padding: 0.5rem 0.5rem 0.25rem;
38
+ }
39
+
40
+ [data-re-slash-command-empty] {
41
+ padding: 0.75rem 0.5rem;
42
+ font-size: 0.875rem;
43
+ text-align: center;
44
+ }
@@ -16,16 +16,23 @@
16
16
  [data-re-bubble-menu] {
17
17
  display: flex;
18
18
  align-items: center;
19
+ gap: 0.125rem;
19
20
  }
20
21
 
21
22
  [data-re-bubble-menu-group] {
22
23
  display: flex;
23
24
  align-items: center;
25
+ gap: 0.125rem;
26
+ padding: 0 0.125rem;
27
+ border: none;
28
+ margin: 0;
29
+ min-width: 0;
24
30
  }
25
31
 
26
32
  [data-re-bubble-menu-separator] {
27
33
  align-self: stretch;
28
34
  width: 1px;
35
+ margin: 0.25rem 0;
29
36
  }
30
37
 
31
38
  [data-re-bubble-menu-item] {
@@ -35,12 +42,12 @@
35
42
  cursor: pointer;
36
43
  border: none;
37
44
  background: none;
38
- padding: 0.5rem;
45
+ padding: 0.375rem;
39
46
  }
40
47
 
41
48
  [data-re-bubble-menu-item] svg {
42
- width: 1rem;
43
- height: 1rem;
49
+ width: 0.875rem;
50
+ height: 0.875rem;
44
51
  }
45
52
 
46
53
  [data-re-node-selector] {
@@ -55,13 +62,14 @@
55
62
  border: none;
56
63
  background: none;
57
64
  white-space: nowrap;
58
- font-size: 0.875rem;
59
- padding: 0.5rem;
65
+ font-size: 0.8125rem;
66
+ padding: 0.375rem 0.5rem;
60
67
  }
61
68
 
62
69
  [data-re-node-selector-trigger] svg {
63
- width: 1rem;
64
- height: 1rem;
70
+ width: 0.75rem;
71
+ height: 0.75rem;
72
+ opacity: 0.5;
65
73
  }
66
74
 
67
75
  [data-re-node-selector-content] {
@@ -77,15 +85,15 @@
77
85
  cursor: pointer;
78
86
  border: none;
79
87
  background: none;
80
- padding: 0.375rem 0.75rem;
81
- font-size: 0.875rem;
88
+ padding: 0.375rem 0.5rem;
89
+ font-size: 0.8125rem;
82
90
  width: 100%;
83
91
  text-align: left;
84
92
  }
85
93
 
86
94
  [data-re-node-selector-item] svg {
87
- width: 0.75rem;
88
- height: 0.75rem;
95
+ width: 0.875rem;
96
+ height: 0.875rem;
89
97
  }
90
98
 
91
99
  [data-re-link-selector] {
@@ -100,25 +108,32 @@
100
108
  cursor: pointer;
101
109
  border: none;
102
110
  background: none;
103
- padding: 0.5rem;
111
+ padding: 0.375rem;
104
112
  }
105
113
 
106
114
  [data-re-link-selector-trigger] svg {
107
- width: 1rem;
108
- height: 1rem;
115
+ width: 0.875rem;
116
+ height: 0.875rem;
109
117
  }
110
118
 
111
119
  [data-re-link-selector-form] {
112
120
  display: flex;
113
121
  align-items: center;
114
122
  gap: 0.25rem;
123
+ position: absolute;
124
+ top: 100%;
125
+ left: 0;
126
+ margin-top: 0.25rem;
127
+ width: max-content;
128
+ min-width: 16rem;
129
+ padding: 0.25rem;
115
130
  }
116
131
 
117
132
  [data-re-link-selector-input] {
118
133
  flex: 1;
119
134
  border: none;
120
135
  outline: none;
121
- font-size: 0.875rem;
136
+ font-size: 0.8125rem;
122
137
  padding: 0.25rem;
123
138
  background: transparent;
124
139
  }
@@ -136,8 +151,8 @@
136
151
 
137
152
  [data-re-link-selector-apply] svg,
138
153
  [data-re-link-selector-unlink] svg {
139
- width: 1rem;
140
- height: 1rem;
154
+ width: 0.875rem;
155
+ height: 0.875rem;
141
156
  }
142
157
 
143
158
  /* Minimal functional styles for LinkBubbleMenu compound components.
@@ -267,6 +282,51 @@ a[data-re-link-bm-item] {
267
282
  height: 1rem;
268
283
  }
269
284
 
285
+ /* Minimal functional styles for SlashCommand components.
286
+ * Layout and positioning only - no visual design.
287
+ * Import optionally: import '@react-email/editor/styles/slash-command.css';
288
+ */
289
+
290
+ [data-re-slash-command] {
291
+ max-height: 330px;
292
+ overflow-y: auto;
293
+ width: 256px;
294
+ padding: 0.25rem;
295
+ }
296
+
297
+ [data-re-slash-command-item] {
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 0.5rem;
301
+ width: 100%;
302
+ padding: 0.375rem 0.5rem;
303
+ border: none;
304
+ border-radius: 0.375rem;
305
+ background: none;
306
+ cursor: pointer;
307
+ font-size: 0.875rem;
308
+ line-height: 1.25rem;
309
+ text-align: left;
310
+ }
311
+
312
+ [data-re-slash-command-item] svg {
313
+ flex-shrink: 0;
314
+ }
315
+
316
+ [data-re-slash-command-category] {
317
+ font-size: 0.6875rem;
318
+ font-weight: 600;
319
+ text-transform: uppercase;
320
+ letter-spacing: 0.05em;
321
+ padding: 0.5rem 0.5rem 0.25rem;
322
+ }
323
+
324
+ [data-re-slash-command-empty] {
325
+ padding: 0.75rem 0.5rem;
326
+ font-size: 0.875rem;
327
+ text-align: center;
328
+ }
329
+
270
330
  /* ----------------------------------------------------------------
271
331
  * CSS custom properties — light defaults
272
332
  * ---------------------------------------------------------------- */
@@ -340,7 +400,11 @@ a[data-re-link-bm-item] {
340
400
  border: 1px solid var(--re-border);
341
401
  border-radius: var(--re-radius);
342
402
  box-shadow: var(--re-shadow);
343
- overflow: hidden;
403
+ z-index: 50;
404
+ padding: 0.125rem;
405
+ font-family: system-ui, -apple-system, sans-serif;
406
+ font-size: 0.8125rem;
407
+ line-height: 1;
344
408
  }
345
409
 
346
410
  /* ----------------------------------------------------------------
@@ -353,14 +417,6 @@ a[data-re-link-bm-item] {
353
417
  border-left: 1px solid var(--re-border);
354
418
  }
355
419
 
356
- /* ----------------------------------------------------------------
357
- * Item groups (text bubble menu)
358
- * ---------------------------------------------------------------- */
359
-
360
- [data-re-bubble-menu-group] + [data-re-bubble-menu-group] {
361
- border-left: 1px solid var(--re-border);
362
- }
363
-
364
420
  /* ----------------------------------------------------------------
365
421
  * Item buttons (all bubble menus)
366
422
  * ---------------------------------------------------------------- */
@@ -369,8 +425,11 @@ a[data-re-link-bm-item] {
369
425
  [data-re-link-bm-item],
370
426
  [data-re-btn-bm-item],
371
427
  [data-re-img-bm-item] {
372
- color: var(--re-text);
373
- transition: background-color 0.15s;
428
+ color: var(--re-text-muted);
429
+ border-radius: var(--re-radius-sm);
430
+ transition:
431
+ background-color 0.15s,
432
+ color 0.15s;
374
433
  }
375
434
 
376
435
  [data-re-bubble-menu-item]:hover,
@@ -378,6 +437,7 @@ a[data-re-link-bm-item] {
378
437
  [data-re-btn-bm-item]:hover,
379
438
  [data-re-img-bm-item]:hover {
380
439
  background: var(--re-hover);
440
+ color: var(--re-text);
381
441
  }
382
442
 
383
443
  [data-re-bubble-menu-item]:active,
@@ -409,7 +469,9 @@ a[data-re-link-bm-item] {
409
469
 
410
470
  [data-re-node-selector-trigger] {
411
471
  color: var(--re-text);
472
+ border-radius: var(--re-radius-sm);
412
473
  transition: background-color 0.15s;
474
+ font-weight: 500;
413
475
  }
414
476
 
415
477
  [data-re-node-selector-trigger]:hover {
@@ -425,8 +487,9 @@ a[data-re-link-bm-item] {
425
487
  border: 1px solid var(--re-border);
426
488
  border-radius: var(--re-radius);
427
489
  box-shadow: var(--re-shadow);
428
- padding: 0.25rem 0;
490
+ padding: 0.25rem;
429
491
  margin-top: 0.25rem;
492
+ z-index: 50;
430
493
  }
431
494
 
432
495
  [data-re-node-selector-item] {
@@ -451,12 +514,16 @@ a[data-re-link-bm-item] {
451
514
  * ---------------------------------------------------------------- */
452
515
 
453
516
  [data-re-link-selector-trigger] {
454
- color: var(--re-text);
455
- transition: background-color 0.15s;
517
+ color: var(--re-text-muted);
518
+ border-radius: var(--re-radius-sm);
519
+ transition:
520
+ background-color 0.15s,
521
+ color 0.15s;
456
522
  }
457
523
 
458
524
  [data-re-link-selector-trigger]:hover {
459
525
  background: var(--re-hover);
526
+ color: var(--re-text);
460
527
  }
461
528
 
462
529
  [data-re-link-selector-trigger][aria-pressed="true"] {
@@ -538,3 +605,146 @@ a[data-re-link-bm-item] {
538
605
  [data-re-link-bm-unlink]:hover {
539
606
  background: var(--re-danger-hover);
540
607
  }
608
+
609
+ /* ----------------------------------------------------------------
610
+ * Slash Command
611
+ * ---------------------------------------------------------------- */
612
+
613
+ [data-re-slash-command] {
614
+ background: var(--re-bg);
615
+ border: 1px solid var(--re-border);
616
+ border-radius: var(--re-radius);
617
+ box-shadow: var(--re-shadow);
618
+ font-family: system-ui, -apple-system, sans-serif;
619
+ }
620
+
621
+ [data-re-slash-command-item] {
622
+ color: var(--re-text);
623
+ border-radius: var(--re-radius-sm);
624
+ transition: background-color 0.15s;
625
+ }
626
+
627
+ [data-re-slash-command-item]:hover {
628
+ background: var(--re-hover);
629
+ }
630
+
631
+ [data-re-slash-command-item][data-selected] {
632
+ background: var(--re-hover);
633
+ }
634
+
635
+ [data-re-slash-command-item]:active {
636
+ background: var(--re-active);
637
+ }
638
+
639
+ [data-re-slash-command-item] svg {
640
+ color: var(--re-text-muted);
641
+ }
642
+
643
+ [data-re-slash-command-category] {
644
+ color: var(--re-text-muted);
645
+ }
646
+
647
+ [data-re-slash-command-empty] {
648
+ color: var(--re-text-muted);
649
+ }
650
+
651
+ /* ----------------------------------------------------------------
652
+ * Editor content — alignment attribute
653
+ * ---------------------------------------------------------------- */
654
+
655
+ .tiptap [alignment="left"] {
656
+ text-align: left;
657
+ }
658
+
659
+ .tiptap [alignment="center"] {
660
+ text-align: center;
661
+ }
662
+
663
+ .tiptap [alignment="right"] {
664
+ text-align: right;
665
+ }
666
+
667
+ .tiptap [alignment="justify"] {
668
+ text-align: justify;
669
+ }
670
+
671
+ /* ----------------------------------------------------------------
672
+ * Editor content — columns
673
+ * ---------------------------------------------------------------- */
674
+
675
+ .tiptap .node-columns {
676
+ display: flex;
677
+ gap: 0.5rem;
678
+ width: 100%;
679
+ }
680
+
681
+ .tiptap .node-column {
682
+ flex: 1;
683
+ min-width: 0;
684
+ }
685
+
686
+ /* ----------------------------------------------------------------
687
+ * Editor content — base typography
688
+ * ---------------------------------------------------------------- */
689
+
690
+ .tiptap {
691
+ outline: none;
692
+ color: var(--re-text);
693
+ }
694
+
695
+ .tiptap p {
696
+ margin: 0.25em 0;
697
+ }
698
+
699
+ .tiptap h1,
700
+ .tiptap h2,
701
+ .tiptap h3 {
702
+ margin: 0.5em 0 0.25em;
703
+ }
704
+
705
+ .tiptap blockquote {
706
+ border-left: 3px solid var(--re-border);
707
+ margin: 0.5em 0;
708
+ padding-left: 1em;
709
+ color: var(--re-text-muted);
710
+ }
711
+
712
+ .tiptap hr {
713
+ border: none;
714
+ border-top: 1px solid var(--re-border);
715
+ margin: 1em 0;
716
+ }
717
+
718
+ .tiptap code {
719
+ background: var(--re-hover);
720
+ border-radius: 0.25rem;
721
+ padding: 0.125rem 0.375rem;
722
+ font-size: 0.875em;
723
+ }
724
+
725
+ .tiptap pre {
726
+ background: var(--re-hover);
727
+ border-radius: var(--re-radius-sm);
728
+ padding: 0.75rem 1rem;
729
+ overflow-x: auto;
730
+ }
731
+
732
+ .tiptap pre code {
733
+ background: none;
734
+ padding: 0;
735
+ border-radius: 0;
736
+ }
737
+
738
+ .tiptap ul,
739
+ .tiptap ol {
740
+ padding-left: 1.5em;
741
+ margin: 0.25em 0;
742
+ }
743
+
744
+ .tiptap .node-placeholder::before {
745
+ color: var(--re-text-muted);
746
+ content: attr(data-placeholder);
747
+ float: left;
748
+ height: 0;
749
+ pointer-events: none;
750
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-email/editor",
3
- "version": "0.0.0-experimental.14",
3
+ "version": "0.0.0-experimental.15",
4
4
  "description": "",
5
5
  "sideEffects": [
6
6
  "**/*.css"
@@ -26,6 +26,7 @@
26
26
  "./styles/link-bubble-menu.css": "./dist/ui/link-bubble-menu/link-bubble-menu.css",
27
27
  "./styles/button-bubble-menu.css": "./dist/ui/button-bubble-menu/button-bubble-menu.css",
28
28
  "./styles/image-bubble-menu.css": "./dist/ui/image-bubble-menu/image-bubble-menu.css",
29
+ "./styles/slash-command.css": "./dist/ui/slash-command/slash-command.css",
29
30
  "./themes/default.css": "./dist/ui/themes/default.css"
30
31
  },
31
32
  "license": "MIT",
@@ -57,11 +58,14 @@
57
58
  "@tiptap/pm": "^3.17.1",
58
59
  "@tiptap/react": "^3.17.1",
59
60
  "@tiptap/starter-kit": "^3.17.1",
61
+ "@tiptap/suggestion": "^3.17.1",
62
+ "tippy.js": "^6.3.7",
60
63
  "hast-util-from-html": "^2.0.3",
61
64
  "prismjs": "^1.30.0"
62
65
  },
63
66
  "devDependencies": {
64
67
  "@testing-library/react": "^16.0.0",
68
+ "@types/node": "22.19.7",
65
69
  "@types/prismjs": "1.26.5",
66
70
  "postcss": "8.5.6",
67
71
  "postcss-import": "16.1.1",
@@ -77,6 +81,7 @@
77
81
  "build:css": "tsx scripts/copy-css.ts",
78
82
  "build:watch": "tsdown src/index.ts --format esm,cjs --dts --external react --watch",
79
83
  "clean": "rm -rf dist",
84
+ "typecheck": "tsc --noEmit",
80
85
  "test": "vitest run",
81
86
  "test:watch": "vitest"
82
87
  }