@parathantl/react-email-editor 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 parathantl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,267 @@
1
+ # @parathantl/react-email-editor
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@parathantl/react-email-editor.svg)](https://www.npmjs.com/package/@parathantl/react-email-editor)
4
+ [![license](https://img.shields.io/npm/l/@parathantl/react-email-editor.svg)](https://github.com/parathantl/react-email-editor/blob/main/LICENSE)
5
+
6
+ A visual drag-and-drop email template editor for React, powered by MJML. Build responsive HTML emails with a rich block-based editor, real-time preview, and full MJML round-trip support.
7
+
8
+ ## Features
9
+
10
+ - 12 block types: Text, Heading, Button, Image, Video, Divider, Spacer, Social, HTML, Countdown, Menu, Hero
11
+ - Drag-and-drop block reordering and section management
12
+ - Rich text editing (TipTap) with formatting toolbar
13
+ - MJML generation, parsing, and HTML compilation
14
+ - Template variable support (`{{ variable }}` syntax)
15
+ - Built-in persistence with localStorage or custom adapters
16
+ - Responsive editor UI with collapsible panels
17
+ - Undo/redo history (50 steps)
18
+ - Export to MJML, HTML, and PDF
19
+ - Extensible via registry pattern (add custom block types)
20
+ - Full TypeScript support with strict mode
21
+ - CSS variables for easy theming
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @parathantl/react-email-editor
27
+ ```
28
+
29
+ **Peer dependencies:** React 18+, React DOM 18+
30
+
31
+ **Optional:** Install `mjml-browser` for HTML compilation (MJML to HTML conversion):
32
+
33
+ ```bash
34
+ npm install mjml-browser
35
+ ```
36
+
37
+ ## Quick Start
38
+
39
+ ```tsx
40
+ import { EmailEditor } from '@parathantl/react-email-editor';
41
+ import '@parathantl/react-email-editor/styles.css';
42
+
43
+ function App() {
44
+ return (
45
+ <div style={{ height: '100vh' }}>
46
+ <EmailEditor
47
+ onChange={(template) => console.log(template)}
48
+ onSave={(mjml, html) => console.log(mjml, html)}
49
+ />
50
+ </div>
51
+ );
52
+ }
53
+ ```
54
+
55
+ ## Persistence
56
+
57
+ Templates auto-save and restore when you provide a `persistenceKey`. Each key stores a separate template, so multiple editor instances can coexist.
58
+
59
+ ### localStorage (default)
60
+
61
+ ```tsx
62
+ <EmailEditor persistenceKey="campaign-123" />
63
+ ```
64
+
65
+ ### Custom Adapter (server, IndexedDB, etc.)
66
+
67
+ ```tsx
68
+ import type { PersistenceAdapter } from '@parathantl/react-email-editor';
69
+
70
+ const serverAdapter: PersistenceAdapter = {
71
+ save(key, template) {
72
+ fetch(`/api/templates/${key}`, {
73
+ method: 'PUT',
74
+ body: JSON.stringify(template),
75
+ headers: { 'Content-Type': 'application/json' },
76
+ });
77
+ },
78
+ load(key) {
79
+ // Must be synchronous — preload data before rendering the editor
80
+ return window.__PRELOADED_TEMPLATES__?.[key] ?? null;
81
+ },
82
+ remove(key) {
83
+ fetch(`/api/templates/${key}`, { method: 'DELETE' });
84
+ },
85
+ };
86
+
87
+ <EmailEditor persistenceKey="campaign-123" persistenceAdapter={serverAdapter} />
88
+ ```
89
+
90
+ **Priority order:** persisted data > `initialTemplate` > `initialMJML`
91
+
92
+ ## Loading MJML from Another Component
93
+
94
+ Use the ref API to control the editor from parent components:
95
+
96
+ ```tsx
97
+ import { useRef } from 'react';
98
+ import { EmailEditor } from '@parathantl/react-email-editor';
99
+ import type { EmailEditorRef } from '@parathantl/react-email-editor';
100
+
101
+ function TemplateDesigner() {
102
+ const editorRef = useRef<EmailEditorRef>(null);
103
+
104
+ const loadFromServer = async () => {
105
+ const res = await fetch('/api/templates/welcome');
106
+ const mjml = await res.text();
107
+ editorRef.current?.loadMJML(mjml);
108
+ };
109
+
110
+ const handleSave = async () => {
111
+ const mjml = editorRef.current?.getMJML();
112
+ const html = await editorRef.current?.getHTML();
113
+ // Send to your API
114
+ };
115
+
116
+ return (
117
+ <div>
118
+ <button onClick={loadFromServer}>Load Template</button>
119
+ <button onClick={handleSave}>Save</button>
120
+ <EmailEditor ref={editorRef} persistenceKey="welcome" />
121
+ </div>
122
+ );
123
+ }
124
+ ```
125
+
126
+ Or pass MJML at init time without a ref:
127
+
128
+ ```tsx
129
+ <EmailEditor initialMJML={mjmlString} />
130
+ ```
131
+
132
+ ## Ref API
133
+
134
+ | Method | Returns | Description |
135
+ |--------|---------|-------------|
136
+ | `getMJML()` | `string` | Get current template as MJML |
137
+ | `getHTML()` | `Promise<string>` | Compile and get HTML output |
138
+ | `getJSON()` | `EmailTemplate` | Get template as JSON object |
139
+ | `loadMJML(source)` | `void` | Parse MJML and load into editor |
140
+ | `loadJSON(template)` | `void` | Load an EmailTemplate object |
141
+ | `insertBlock(type, sectionIdx?)` | `void` | Programmatically add a block |
142
+ | `getVariables()` | `string[]` | Extract `{{ variable }}` keys |
143
+ | `undo()` | `void` | Undo last action |
144
+ | `redo()` | `void` | Redo last undone action |
145
+ | `reset()` | `void` | Clear all content |
146
+ | `clearPersisted()` | `void` | Remove saved data for the current key |
147
+ | `exportPDF()` | `Promise<void>` | Generate PDF via print dialog |
148
+
149
+ ## Props
150
+
151
+ | Prop | Type | Description |
152
+ |------|------|-------------|
153
+ | `initialTemplate` | `EmailTemplate` | Initial template object |
154
+ | `initialMJML` | `string` | Initial MJML string (parsed on mount) |
155
+ | `variables` | `Variable[]` | Template variables for `{{ }}` insertion |
156
+ | `imageUploadAdapter` | `ImageUploadAdapter` | Custom image upload handler |
157
+ | `onChange` | `(template: EmailTemplate) => void` | Called on every template change (debounced 150ms) |
158
+ | `onSave` | `(mjml: string, html: string) => void` | Called on Ctrl+S |
159
+ | `onReady` | `() => void` | Called once after editor mounts |
160
+ | `fontFamilies` | `string[]` | Custom font options for the toolbar |
161
+ | `fontSizes` | `string[]` | Custom font size options |
162
+ | `persistenceKey` | `string` | Key for auto-save/restore (enables persistence) |
163
+ | `persistenceAdapter` | `PersistenceAdapter` | Custom storage adapter (defaults to localStorage) |
164
+ | `className` | `string` | CSS class for the outer wrapper |
165
+ | `style` | `CSSProperties` | Inline styles for the outer wrapper |
166
+
167
+ ## Block Types
168
+
169
+ | Type | Description | MJML Output |
170
+ |------|-------------|-------------|
171
+ | `text` | Rich text with formatting | `<mj-text>` |
172
+ | `heading` | Heading (h1–h4) with level selector | `<mj-text><h2>...</h2></mj-text>` |
173
+ | `button` | Call-to-action button | `<mj-button>` |
174
+ | `image` | Image with optional link | `<mj-image>` |
175
+ | `video` | Video thumbnail with play overlay | `<mj-image>` (linked) |
176
+ | `divider` | Horizontal line | `<mj-divider>` |
177
+ | `spacer` | Vertical spacing | `<mj-spacer>` |
178
+ | `social` | Social media icon links | `<mj-social>` |
179
+ | `html` | Raw HTML content | `<mj-text>` |
180
+ | `countdown` | Live countdown timer with digit boxes | `<mj-text>` (HTML table) |
181
+ | `menu` | Navigation menu links | `<mj-navbar>` |
182
+ | `hero` | Heading + subtext + CTA button | `<mj-text>` (composite HTML) |
183
+
184
+ ## Template Variables
185
+
186
+ Define variables and insert them into text blocks as `{{ variable_name }}`:
187
+
188
+ ```tsx
189
+ <EmailEditor
190
+ variables={[
191
+ { key: 'first_name', sample: 'John', label: 'First Name', group: 'Contact' },
192
+ { key: 'company', sample: 'Acme Inc', label: 'Company', group: 'Contact' },
193
+ { key: 'unsubscribe_url', sample: '#', label: 'Unsubscribe URL', group: 'Links' },
194
+ ]}
195
+ />
196
+ ```
197
+
198
+ Extract used variables programmatically:
199
+
200
+ ```tsx
201
+ const keys = editorRef.current?.getVariables();
202
+ // ['first_name', 'unsubscribe_url']
203
+ ```
204
+
205
+ ## Custom Block Types
206
+
207
+ Extend the editor with your own block types using the registry:
208
+
209
+ ```tsx
210
+ import {
211
+ registerBlockRenderer,
212
+ registerBlockProperties,
213
+ registerBlockGenerator,
214
+ } from '@parathantl/react-email-editor';
215
+
216
+ // Register a canvas renderer
217
+ registerBlockRenderer('my-block', MyBlockComponent);
218
+
219
+ // Register a properties panel
220
+ registerBlockProperties('my-block', MyBlockPropertiesPanel);
221
+
222
+ // Register an MJML generator
223
+ registerBlockGenerator('my-block', (block, indent) => {
224
+ return `${indent}<mj-text>${block.properties.content}</mj-text>`;
225
+ });
226
+ ```
227
+
228
+ ## Theming
229
+
230
+ The editor uses CSS custom properties scoped under `--ee-*`. Override them to match your app:
231
+
232
+ ```css
233
+ :root {
234
+ --ee-color-primary: #8b5cf6;
235
+ --ee-color-primary-hover: #7c3aed;
236
+ --ee-bg-panel: #fafafa;
237
+ --ee-border-radius: 8px;
238
+ --ee-font-family: 'Inter', sans-serif;
239
+ }
240
+ ```
241
+
242
+ See `src/styles/variables.css` for the full list of 70+ customizable tokens.
243
+
244
+ ## Keyboard Shortcuts
245
+
246
+ | Shortcut | Action |
247
+ |----------|--------|
248
+ | `Ctrl/Cmd + Z` | Undo |
249
+ | `Ctrl/Cmd + Shift + Z` / `Ctrl/Cmd + Y` | Redo |
250
+ | `Ctrl/Cmd + S` | Save (triggers `onSave`) |
251
+ | `Escape` | Deselect block/section |
252
+ | `Delete` / `Backspace` | Remove selected block or section |
253
+
254
+ ## Responsive Editor
255
+
256
+ The editor automatically adapts to smaller screens:
257
+
258
+ - **>= 1024px** — Full 3-panel layout (sidebar, canvas, properties)
259
+ - **< 1024px** — Panels collapse into toggleable overlays with toolbar buttons
260
+
261
+ ## Browser Support
262
+
263
+ Supports all modern browsers (Chrome, Firefox, Safari, Edge). Requires React 18+.
264
+
265
+ ## License
266
+
267
+ [MIT](LICENSE)