Skip to content

Commit 25a28f4

Browse files
committed
Add layout components, allowing the boilerplate HTML of an email to be customized
1 parent 078cd41 commit 25a28f4

File tree

4 files changed

+113
-81
lines changed

4 files changed

+113
-81
lines changed

docs/setup.md

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,30 @@ Install react-inky from npm.
88
npm install react-inky --save
99
```
1010

11-
Because react-inky is a React library, you need to compile your code using Babel, specifically with the `latest` and `react` presets. If you don't have them already, install those presets into your project with:
11+
Because react-inky is a React library, you need to compile your code using Babel, specifically with the `env` and `react` presets. If you don't have them already, install those presets into your project with:
1212

1313
```bash
14-
npm install babel-preset-latest babel-preset-react --save-dev
14+
npm install babel-preset-env babel-preset-react --save-dev
1515
```
1616

1717
Your `.babelrc` should look like this:
1818

1919
```json
2020
{
21-
"presets": ["latest", "react"]
21+
"presets": ["env", "react"]
2222
}
2323
```
2424

2525
[Learn more about setting up Babel](https://babeljs.io/docs/setup/) if you've never done it before.
2626

2727
## Usage
2828

29-
To convert your React-powered email into a plain HTML email, use ReactDOM's `renderToString()` function, which converts a React component into static HTML.
29+
To convert your React-powered email into a plain HTML email, use ReactDOM's `renderToString()` function, which converts a React component into a string of HTML.
3030

3131
In the below example, our email template component includes a prop, making its contents dynamic.
3232

3333
```jsx
34-
import { Container, Row, Column } from 'react-inky';
34+
import Inky, { Container, Row, Column } from 'react-inky';
3535
import { renderToString } from 'react-dom/server';
3636

3737
function EmailTemplate({ name }) {
@@ -44,28 +44,25 @@ function EmailTemplate({ name }) {
4444
);
4545
}
4646

47-
const template = <EmailTemplate name="Inky" />;
48-
renderToString(template); // => <table class="container">...</table>
47+
renderToString((
48+
<Inky>
49+
<Inky.Head>
50+
<link rel="stylesheet" href="style.css" />
51+
</Inky.Head>
52+
<Inky.Body preview="Preview text">
53+
<EmailTemplate name="Inky" />
54+
</Inky.Body>
55+
</Inky>
56+
));
4957
```
5058

51-
This isn't quite enough, however. The `<Container />` component doesn't include the boilerplate required to build a full HTML email: the doctype, `<html>`, `<head>`, `<body>` and so on. JSX can't render an HTML doctype, and since the boilerplate rarely changes anyway, react-inky bundles it into a function.
59+
Note that, while most React projects use the HTML5 doctype, email templates use the XHTML Strict doctype. React doesn't add this automatically when rendering, so you'll need to add it before the final HTML of your email template.
5260

53-
Pass your email template to the `inky()` function *before* passing it to `renderToString()`. The `inky()` function will wrap your email in the Foundation for Emails boilerplate.
61+
The doctype string is included with react-inky:
5462

5563
```jsx
56-
import inky, { Container, Row, Column } from 'react-inky';
64+
import Inky from 'inky';
5765
import { renderToString } from 'react-dom/server';
5866

59-
function EmailTemplate({ name }) {
60-
return (
61-
<Container>
62-
<Row>
63-
<Column>Hello, {name}!</Column>
64-
</Row>
65-
</Container>
66-
);
67-
}
68-
69-
const template = <EmailTemplate name="Inky" />;
70-
renderToString(inky(template)); // => <!DOCTYPE html PUBLIC...
67+
const html = Inky.doctype + renderToString(/* Template */);
7168
```

src/Html.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import Center from './components/Center';
4+
5+
function Inky({children}) {
6+
return (
7+
<html xmlns="http://www.w3.org/1999/xhtml">
8+
{children}
9+
</html>
10+
);
11+
}
12+
13+
Inky.propTypes = {children: PropTypes.node};
14+
Inky.defaultProps = {children: null};
15+
16+
function Head({children}) {
17+
return (
18+
<head>
19+
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8"/>
20+
<meta name="viewport" content="width=device-width"/>
21+
{children}
22+
</head>
23+
);
24+
}
25+
26+
Head.propTypes = {children: PropTypes.node};
27+
Head.defaultProps = {children: null};
28+
29+
function Body({children, preview}) {
30+
return (
31+
<body>
32+
{preview && <span className="preheader">{preview}</span>}
33+
<table className="body" data-made-with-foundation>
34+
<tr>
35+
<td is class="float-center" align="center" valign="top">
36+
<Center>
37+
{children}
38+
</Center>
39+
</td>
40+
</tr>
41+
</table>
42+
{/* Prevent font size manipulation by Gmail on iOS */}
43+
<div style={{display: 'none', whiteSpace: 'nowrap', font: '15px courier', lineHeight: 0}}> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </div>
44+
</body>
45+
);
46+
}
47+
48+
Body.propTypes = {children: PropTypes.node, preview: PropTypes.string};
49+
Body.defaultProps = {children: null, preview: null};
50+
51+
Inky.Head = Head;
52+
Inky.Body = Body;
53+
Inky.doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
54+
55+
export default Inky;

src/__tests__/index.js

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,49 @@
1+
import React from 'react';
12
import chai, {expect} from 'chai';
23
import chaiEnzyme from 'chai-enzyme';
34
import chaiHtml from 'chai-html';
45
import dirtyChai from 'dirty-chai';
5-
import inky from '../';
6+
import {render} from 'enzyme';
7+
import Inky from '../';
68

79
chai.use(chaiEnzyme);
810
chai.use(chaiHtml);
911
chai.use(dirtyChai);
1012

11-
describe('inky()', () => {
12-
const input = '<div></div>';
13-
let output;
13+
describe('Inky', () => {
14+
it('renders the boilerplate of an email', () => {
15+
const wrapper = render((
16+
<Inky>
17+
<Inky.Head>
18+
<link rel="stylesheet" href="style.css"/>
19+
</Inky.Head>
20+
<Inky.Body preview="Preview text">
21+
<div/>
22+
</Inky.Body>
23+
</Inky>
24+
));
1425

15-
before(() => {
16-
output = inky(input);
17-
});
18-
19-
it('returns a string', () => {
20-
expect(output).to.be.a('string');
21-
});
22-
23-
it('contains input HTML', () => {
24-
expect(output).to.contain(input);
26+
expect(wrapper.html()).html.to.equal(`
27+
<html xmlns="http://www.w3.org/1999/xhtml">
28+
<head>
29+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
30+
<meta name="viewport" content="width=device-width" />
31+
<link rel="stylesheet" href="style.css" />
32+
</head>
33+
<body>
34+
<span class="preheader">Preview text</span>
35+
<table class="body" data-made-with-foundation="true">
36+
<tr>
37+
<td is="true" class="float-center" align="center" valign="top">
38+
<center>
39+
<div is="true" align="center" class="float-center" />
40+
</center>
41+
</td>
42+
</tr>
43+
</table>
44+
<div style="display:none;white-space:nowrap;font:15px courier;line-height:0;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </div>
45+
</body>
46+
</html>
47+
`);
2548
});
2649
});

src/index.js

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,11 @@ export {default as Callout} from './components/Callout';
33
export {default as Center} from './components/Center';
44
export {default as Column} from './components/Column';
55
export {default as Container} from './components/Container';
6-
export {default as Inky} from './components/Inky';
6+
export {default as Squid} from './components/Inky';
77
export {default as Item} from './components/Item';
88
export {default as Menu} from './components/Menu';
99
export {default as Row} from './components/Row';
1010
export {default as Spacer} from './components/Spacer';
1111
export {default as Wrapper} from './components/Wrapper';
1212

13-
/**
14-
* Wrap an HTML string in the required boilerplate for Foundation for Emails.
15-
* @prop {String} [elem=''] HTML to insert.
16-
* @returns {String} Full HTML email.
17-
*
18-
* @example
19-
* import inky, { Container } from 'react-inky';
20-
* import { renderToString } from 'react-dom/server';
21-
*
22-
* const email = <Container />;
23-
* inky(renderToString(<Container />));
24-
*/
25-
export default function inky(elem = '') {
26-
return `
27-
<!-- Emails use the XHTML Strict doctype -->
28-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
29-
<html xmlns="http://www.w3.org/1999/xhtml">
30-
<head>
31-
<!-- The character set should be utf-8 -->
32-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
33-
<meta name="viewport" content="width=device-width"/>
34-
<!-- Link to the email's CSS, which will be inlined into the email -->
35-
<link rel="stylesheet" href="assets/css/foundation-emails.css">
36-
<style>
37-
<!-- Your CSS to inline should be added here -->
38-
</style>
39-
</head>
40-
41-
<body>
42-
<!-- Wrapper for the body of the email -->
43-
<table class="body" data-made-with-foundation>
44-
<tr>
45-
<!-- The class, align, and <center> tag center the container -->
46-
<td class="float-center" align="center" valign="top">
47-
<center>
48-
${elem}
49-
</center>
50-
</td>
51-
</tr>
52-
</table>
53-
</body>
54-
</html>
55-
`;
56-
}
13+
export {default} from './Html';

0 commit comments

Comments
 (0)