@unlayer/react-elements 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/README.md ADDED
@@ -0,0 +1,432 @@
1
+ # @unlayer/react-elements
2
+
3
+ React components for building emails, pages, and documents with Unlayer Elements. Full SSR support — works with `renderToHtml`, `renderToString`, Next.js, Remix, and any server-side framework.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @unlayer/react-elements
9
+ # or
10
+ pnpm add @unlayer/react-elements
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ import { Email, Row, Column, ColumnLayouts, Heading, Paragraph, Button } from '@unlayer/react-elements';
17
+
18
+ function WelcomeEmail() {
19
+ return (
20
+ <Email backgroundColor="#f0f0f0" contentWidth="600px">
21
+ <Row layout={ColumnLayouts.TwoEqual} backgroundColor="#ffffff" padding="20px">
22
+ <Column>
23
+ <Heading
24
+ text="Hello World"
25
+ fontSize="24px"
26
+ fontFamily={{ label: "Arial", value: "arial,helvetica,sans-serif" }}
27
+ />
28
+ </Column>
29
+ <Column>
30
+ <Paragraph text="Welcome to our newsletter!" fontSize="14px" />
31
+ </Column>
32
+ </Row>
33
+ <Row layout={ColumnLayouts.OneColumn} padding="10px">
34
+ <Column>
35
+ <Button
36
+ text="Click Me"
37
+ href="https://example.com"
38
+ backgroundColor="#0879A1"
39
+ color="#ffffff"
40
+ />
41
+ </Column>
42
+ </Row>
43
+ </Email>
44
+ );
45
+ }
46
+ ```
47
+
48
+ ## Critical Rules
49
+
50
+ These props have non-obvious shapes that **must** be followed exactly:
51
+
52
+ - **fontFamily**: Must be `{ label: string, value: string }`, NOT a plain string.
53
+ ```tsx
54
+ fontFamily={{ label: "Arial", value: "arial, sans-serif" }}
55
+ ```
56
+ - **fontWeight**: Must be a number (`400`, `700`), NOT a string (`"400"`).
57
+ - **Wrapper component**: Use `<Email>`, `<Page>`, or `<Document>` as root — they set the rendering mode automatically.
58
+ - **href**: Can be a plain string URL (auto-wrapped) or `{ name: "web", values: { href, target } }`.
59
+ - **Image src**: Can be a plain string URL (auto-wrapped) or `{ url, width?, autoWidth?, maxWidth? }`.
60
+ - **children**: Text components accept children as shorthand for `text` prop. `<Paragraph>Hello</Paragraph>` = `<Paragraph text="Hello" />`.
61
+
62
+ ## Structure: Email/Page/Document > Row > Column > Items
63
+
64
+ ```
65
+ <Email> ← root wrapper for email-safe HTML (tables)
66
+ <Page> ← root wrapper for responsive web (div + flexbox)
67
+ <Document> ← root wrapper for print/PDF
68
+
69
+ <Row> ← layout container, use layout={ColumnLayouts.X} or cells={[1,1]}
70
+ <Column> ← must match layout column count
71
+ <Button /> ← item components go inside columns
72
+ <Paragraph />
73
+ </Column>
74
+ </Row>
75
+ </Email>
76
+ ```
77
+
78
+ ## Components
79
+
80
+ ### Root Wrappers
81
+
82
+ | Component | Description |
83
+ |-----------|-------------|
84
+ | `<Email>` | Root wrapper for email-safe HTML (tables for Outlook, Gmail, Yahoo). |
85
+ | `<Page>` | Root wrapper for responsive web display (div + flexbox). |
86
+ | `<Document>` | Root wrapper for print-optimized / PDF rendering. |
87
+
88
+ ### Layout
89
+
90
+ | Component | Description |
91
+ |-----------|-------------|
92
+ | `<Row>` | Layout container. Accepts `layout={ColumnLayouts.X}` or `cells={[1, 1]}`. |
93
+ | `<Column>` | Column inside a Row. Number of Columns must match the layout. |
94
+
95
+ ### Content
96
+
97
+ | Component | Description |
98
+ |-----------|-------------|
99
+ | `<Button>` | CTA button with hover states, links, and full styling |
100
+ | `<Paragraph>` | Rich text with `text` (plain) or `html` (formatted) props |
101
+ | `<Heading>` | Heading (h1-h4) with `level` shorthand |
102
+ | `<Image>` | Responsive image with `src` / `alt` shorthands |
103
+ | `<Divider>` | Horizontal rule / separator |
104
+ | `<Social>` | Social media icons with `icons` shorthand array |
105
+ | `<Menu>` | Navigation menu with `items` shorthand array |
106
+ | `<Table>` | Data table with `headers` / `data` shorthands |
107
+ | `<Video>` | Video embed with `videoUrl` shorthand |
108
+ | `<Html>` | Custom HTML passthrough |
109
+
110
+ ## Component Reference
111
+
112
+ ### Email
113
+ Root wrapper for email-safe HTML. Same props as Body (without `mode`).
114
+ - `backgroundColor?: string` — `"#F7F8F9"`
115
+ - `contentWidth?: string` — `"500px"`
116
+ - `contentAlign?: string` — `"center"`
117
+ - `fontFamily?: { label: string, value: string }` — `{ label: "Arial", value: "arial,helvetica,sans-serif" }`
118
+ - `textColor?: string` — `"#000000"`
119
+ - `linkStyle?: { linkColor, linkHoverColor, linkUnderline, linkHoverUnderline }`
120
+ - `previewText?: string` — preview text shown in email client inboxes
121
+
122
+ ### Page
123
+ Root wrapper for responsive web display. Same props as Email.
124
+
125
+ ### Document
126
+ Root wrapper for print/PDF rendering. Same props as Email.
127
+
128
+ ### Row
129
+ Layout container. Must be child of Email/Page/Document/Body.
130
+ - `layout?: ColumnLayout` — use `ColumnLayouts.X`
131
+ - `cells?: number[]` — alternative to layout
132
+ - `backgroundColor?: string`
133
+ - `padding?: string` — `"0px"`
134
+ - `noStackMobile?: boolean` — `false`
135
+
136
+ ### Column
137
+ Must be child of Row. Count must match layout.
138
+ - `padding?: string` — `"10px"`
139
+ - `backgroundColor?: string`
140
+ - `borderRadius?: string`
141
+
142
+ ### Button
143
+ - `text?: string` — `"Button"` (or use children)
144
+ - `href?: string | Href` — plain string auto-wrapped
145
+ - `backgroundColor?: string` — `"#0879A1"`
146
+ - `color?: string` — `"#FFFFFF"`
147
+ - `hoverBackgroundColor?: string`
148
+ - `hoverColor?: string`
149
+ - `fontSize?: string` — `"14px"`
150
+ - `fontWeight?: number` — `400`
151
+ - `fontFamily?: { label: string, value: string }`
152
+ - `padding?: string` — `"10px 20px"`
153
+ - `borderRadius?: string` — `"4px"`
154
+ - `textAlign?: "left" | "center" | "right"` — `"center"`
155
+ - `containerPadding?: string`
156
+
157
+ ### Paragraph
158
+ - `text?: string` — plain text (auto-converted to Lexical JSON internally)
159
+ - `html?: string` — **rich HTML string** with inline formatting: `<b>`, `<i>`, `<u>`, `<s>`, `<a>`, `<code>`
160
+ - `fontSize?: string` — `"14px"`
161
+ - `color?: string` — `"#000000"`
162
+ - `textAlign?: "left" | "center" | "right"` — `"left"`
163
+ - `lineHeight?: string` — `"140%"`
164
+ - `fontFamily?: { label: string, value: string }`
165
+ - `containerPadding?: string`
166
+
167
+ Priority: `html` > `text` > children. Use `html` for formatted text, `text` for plain text.
168
+
169
+ ```tsx
170
+ <Paragraph text="Plain text paragraph" fontSize="14px" />
171
+ <Paragraph html="Hello <b>bold</b> and <a href='#'>link</a>" fontSize="14px" />
172
+ ```
173
+
174
+ ### Heading
175
+ - `text?: string` — `"Heading"` (or use children)
176
+ - `headingType?: "h1" | "h2" | "h3" | "h4"` — `"h1"`
177
+ - `level?: "h1" | "h2" | "h3" | "h4"` — shorthand for headingType
178
+ - `fontSize?: string` — `"22px"`
179
+ - `fontWeight?: number` — `400`
180
+ - `fontFamily?: { label: string, value: string }`
181
+ - `color?: string` — `"#000000"`
182
+ - `textAlign?: "left" | "center" | "right"` — `"left"`
183
+ - `lineHeight?: string` — `"110%"`
184
+ - `containerPadding?: string`
185
+
186
+ ### Divider
187
+ - `borderTopWidth?: string` — `"1px"`
188
+ - `borderTopColor?: string` — `"#BBBBBB"`
189
+ - `borderTopStyle?: string` — `"solid"`
190
+ - `textAlign?: "left" | "center" | "right"` — `"center"`
191
+ - `containerPadding?: string`
192
+
193
+ ### Image
194
+ - `src?: string | { url, width?, autoWidth?, maxWidth? }` — string URLs auto-wrapped
195
+ - `altText?: string` — alt text for accessibility
196
+ - `textAlign?: "left" | "center" | "right"` — `"center"`
197
+ - `action?: { name: "web", values: { href, target } }`
198
+ - `containerPadding?: string`
199
+
200
+ ### Video
201
+ - `videoUrl?: string` — YouTube/Vimeo URL, auto-parsed
202
+ - `video?: { type: "youtube" | "vimeo", videoId, thumbnail }` — manual control
203
+ - `containerPadding?: string`
204
+
205
+ ### Html
206
+ - `html?: string` — `"<p>Custom HTML content</p>"`
207
+ - `containerPadding?: string`
208
+
209
+ ### Table
210
+ - `headers?: string[]` — shorthand for column headers
211
+ - `data?: string[][]` — shorthand for row data
212
+ - `columns?: number` — `3`
213
+ - `rows?: number` — `3`
214
+ - `enableHeader?: boolean` — `true`
215
+ - `containerPadding?: string`
216
+
217
+ ### Social
218
+ - `icons?: { name: string, url: string }[]` — shorthand
219
+ - `iconType?: "circle" | "rounded" | "squared"` — `"circle"`
220
+ - `iconSize?: number` — `32`
221
+ - `spacing?: number` — `10`
222
+ - `align?: "left" | "center" | "right"` — `"center"`
223
+ - `containerPadding?: string`
224
+
225
+ ### Menu
226
+ - `items?: { text: string, href: string, target?: string }[]` — shorthand
227
+ - `layout?: "horizontal" | "vertical"` — `"horizontal"`
228
+ - `separator?: string` — `"|"`
229
+ - `align?: "left" | "center" | "right"` — `"center"`
230
+ - `containerPadding?: string`
231
+
232
+ ## Column Layouts
233
+
234
+ Pre-built layouts for common column configurations:
235
+
236
+ ```tsx
237
+ import { Row, Column, ColumnLayouts } from '@unlayer/react-elements';
238
+
239
+ <Row layout={ColumnLayouts.OneColumn}> {/* [1] → 100% */}
240
+ <Row layout={ColumnLayouts.TwoEqual}> {/* [1,1] → 50% + 50% */}
241
+ <Row layout={ColumnLayouts.TwoWideNarrow}> {/* [2,1] → 67% + 33% */}
242
+ <Row layout={ColumnLayouts.TwoNarrowWide}> {/* [1,2] → 33% + 67% */}
243
+ <Row layout={ColumnLayouts.ThreeEqual}> {/* [1,1,1] → 33% each */}
244
+ <Row layout={ColumnLayouts.ThreeNarrowWideNarrow}> {/* [1,2,1] → 25% + 50% + 25% */}
245
+ <Row layout={ColumnLayouts.FourEqual}> {/* [1,1,1,1] → 25% each */}
246
+ <Row layout={ColumnLayouts.FiveEqual}> {/* [1,1,1,1,1] → 20% each */}
247
+ <Row cells={[3, 1]}> {/* Custom ratio */}
248
+ ```
249
+
250
+ Number of `<Column>` children must match the layout.
251
+
252
+ ## Rendering Modes
253
+
254
+ Use the semantic wrapper component that matches your target:
255
+
256
+ ```tsx
257
+ <Email>...</Email> // Email-client safe (tables for Outlook, Gmail, Yahoo)
258
+ <Page>...</Page> // Responsive web (div + flexbox)
259
+ <Document>...</Document> // Print/PDF optimized
260
+ ```
261
+
262
+ Each wrapper threads the correct mode to all children automatically.
263
+
264
+ ## renderToHtml
265
+
266
+ Render any element tree to a clean HTML string — no React hydration markers, perfect for email sending and PDF generation:
267
+
268
+ ```tsx
269
+ import { renderToHtml, Email, Row, Column, ColumnLayouts, Paragraph, Button } from '@unlayer/react-elements';
270
+
271
+ const html = renderToHtml(
272
+ <Email backgroundColor="#f4f4f4">
273
+ <Row layout={ColumnLayouts.OneColumn}>
274
+ <Column>
275
+ <Paragraph text="Hello World" fontSize="14px" />
276
+ <Button
277
+ text="Click me"
278
+ backgroundColor="#3b82f6"
279
+ color="#ffffff"
280
+ />
281
+ </Column>
282
+ </Row>
283
+ </Email>
284
+ );
285
+ ```
286
+
287
+ ## UnlayerProvider
288
+
289
+ Configure global settings like CDN base URL, merge tags, text direction, and rendering mode:
290
+
291
+ ```tsx
292
+ import { UnlayerProvider, Email, Row, Column, Social, Menu } from '@unlayer/react-elements';
293
+
294
+ function App() {
295
+ return (
296
+ <UnlayerProvider config={{
297
+ cdnBaseUrl: "https://my-cdn.example.com",
298
+ mergeTagState: { firstName: "Jane", company: "Acme" },
299
+ textDirection: "ltr"
300
+ }}>
301
+ <Email>
302
+ <Row layout={ColumnLayouts.OneColumn}>
303
+ <Column>
304
+ <Social icons={[{ name: "Facebook", url: "https://facebook.com/acme" }]} />
305
+ <Menu items={[{ text: "Home", href: "/" }, { text: "About", href: "/about" }]} />
306
+ </Column>
307
+ </Row>
308
+ </Email>
309
+ </UnlayerProvider>
310
+ );
311
+ }
312
+ ```
313
+
314
+ **Important:** The root wrapper (`Email`/`Page`/`Document`) bridges the provider context to child components. Components inside `UnlayerProvider` but without a root wrapper won't receive the config.
315
+
316
+ ## Types
317
+
318
+ All types are exported and sourced from `@unlayer/types`:
319
+
320
+ ```tsx
321
+ import type {
322
+ ButtonValues, SocialValues, TableValues,
323
+ Href, Icons, TextAlign, LinkStyle,
324
+ SocialIcon, MenuItem,
325
+ ButtonProps, MenuProps, ImageProps,
326
+ } from '@unlayer/react-elements';
327
+ ```
328
+
329
+ ## Common Font Stacks
330
+
331
+ fontFamily must always be an object. Here are ready-to-use stacks:
332
+
333
+ ```tsx
334
+ const sansFont = { label: "Sans Serif", value: "system-ui, -apple-system, BlinkMacSystemFont, sans-serif" };
335
+ const serifFont = { label: "Georgia", value: "Georgia, 'Times New Roman', Times, serif" };
336
+ const monoFont = { label: "Monospace", value: "'SF Mono', 'Fira Code', 'Roboto Mono', monospace" };
337
+ ```
338
+
339
+ ## Common Design Patterns
340
+
341
+ ### Header with Logo
342
+
343
+ ```tsx
344
+ <Row layout={ColumnLayouts.OneColumn} backgroundColor="#ffffff" padding="24px 40px">
345
+ <Column>
346
+ <Image src="https://example.com/logo.png" altText="Logo" textAlign="left" />
347
+ </Column>
348
+ </Row>
349
+ ```
350
+
351
+ ### Accent Bar
352
+
353
+ ```tsx
354
+ <Row layout={ColumnLayouts.OneColumn} backgroundColor="#4f46e5" padding="0">
355
+ <Column>
356
+ <Divider borderTopWidth="3px" borderTopColor="#4f46e5" borderTopStyle="solid" containerPadding="0" />
357
+ </Column>
358
+ </Row>
359
+ ```
360
+
361
+ ### Feature Grid (2×2)
362
+
363
+ ```tsx
364
+ <Row layout={ColumnLayouts.TwoEqual} backgroundColor="#ffffff" padding="24px 40px">
365
+ <Column>
366
+ <Heading text="Feature 1" headingType="h3" fontSize="16px" fontWeight={600} color="#1a1a1a" />
367
+ <Paragraph text="Description of the feature." fontSize="13px" color="#71717a" />
368
+ </Column>
369
+ <Column>
370
+ <Heading text="Feature 2" headingType="h3" fontSize="16px" fontWeight={600} color="#1a1a1a" />
371
+ <Paragraph text="Description of the feature." fontSize="13px" color="#71717a" />
372
+ </Column>
373
+ </Row>
374
+ ```
375
+
376
+ ### Metric Cards (3-column)
377
+
378
+ ```tsx
379
+ <Row layout={ColumnLayouts.ThreeEqual} backgroundColor="#ffffff" padding="0 40px">
380
+ <Column>
381
+ <Heading text="1.2M" headingType="h2" fontSize="28px" fontWeight={700} color="#0f172a" textAlign="center" />
382
+ <Paragraph text="API Calls" fontSize="12px" color="#94a3b8" textAlign="center" />
383
+ </Column>
384
+ {/* Repeat for each metric */}
385
+ </Row>
386
+ ```
387
+
388
+ ### Product Card (image + details)
389
+
390
+ ```tsx
391
+ <Row layout={ColumnLayouts.TwoNarrowWide} backgroundColor="#ffffff" padding="20px 40px">
392
+ <Column>
393
+ <Image src="https://example.com/product.jpg" altText="Product" />
394
+ </Column>
395
+ <Column>
396
+ <Heading text="Product Name" headingType="h3" fontSize="16px" fontWeight={600} color="#1a1a1a" />
397
+ <Paragraph text="Matte White · Medium" fontSize="13px" color="#a1a1aa" />
398
+ <Heading text="$89.00" headingType="h3" fontSize="16px" fontWeight={700} color="#1a1a1a" />
399
+ </Column>
400
+ </Row>
401
+ ```
402
+
403
+ ### Footer
404
+
405
+ ```tsx
406
+ <Row layout={ColumnLayouts.OneColumn} padding="20px 40px 40px 40px">
407
+ <Column>
408
+ <Paragraph text="Company Name · City, State" fontSize="12px" color="#a1a1aa" textAlign="center" />
409
+ </Column>
410
+ </Row>
411
+ ```
412
+
413
+ ## Common Mistakes
414
+
415
+ 1. **fontFamily as string** — `fontFamily="Arial"` → Must be `fontFamily={{ label: "Arial", value: "arial, sans-serif" }}`
416
+ 2. **fontWeight as string** — `fontWeight="700"` → Must be `fontWeight={700}`
417
+ 3. **Column count mismatch** — `TwoEqual` layout requires exactly 2 `<Column>` children
418
+ 4. **Missing Column** — Items must be inside `<Column>`, never directly in `<Row>`
419
+ 5. **Missing Row** — Columns must be inside `<Row>`, never directly in `<Email>`/`<Page>`/`<Document>`
420
+ 6. **containerPadding vs padding** — `padding` is on Row/Column containers; `containerPadding` is per-item internal spacing
421
+
422
+ ## Development
423
+
424
+ ```bash
425
+ pnpm build # Build the package
426
+ pnpm test # Run tests
427
+ pnpm storybook # Launch Storybook
428
+ ```
429
+
430
+ ## License
431
+
432
+ MIT