@react-email/tailwind 0.0.6 → 0.0.8
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/dist/index.js +51 -33
- package/dist/index.mjs +53 -35
- package/package.json +11 -6
- package/readme.md +3 -3
package/dist/index.js
CHANGED
|
@@ -42,18 +42,19 @@ var Tailwind = ({ children, config }) => {
|
|
|
42
42
|
});
|
|
43
43
|
const newChildren = React.Children.toArray(children);
|
|
44
44
|
const fullHTML = (0, import_server.renderToStaticMarkup)(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: newChildren }));
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
45
|
+
const tailwindCss = twi(fullHTML, {
|
|
46
|
+
merge: false,
|
|
47
|
+
ignoreMediaQueries: false
|
|
48
|
+
});
|
|
49
|
+
const css = cleanCss(tailwindCss);
|
|
50
|
+
const cssMap = makeCssMap(css);
|
|
51
|
+
const headStyle = getMediaQueryCss(css);
|
|
51
52
|
const hasResponsiveStyles = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm.test(
|
|
52
53
|
headStyle
|
|
53
54
|
);
|
|
54
55
|
const hasHTML = /<html[^>]*>/gm.test(fullHTML);
|
|
55
56
|
const hasHead = /<head[^>]*>/gm.test(fullHTML);
|
|
56
|
-
if (hasResponsiveStyles && !hasHTML
|
|
57
|
+
if (hasResponsiveStyles && (!hasHTML || !hasHead)) {
|
|
57
58
|
throw new Error(
|
|
58
59
|
"Tailwind: To use responsive styles you must have a <html> and <head> element in your template."
|
|
59
60
|
);
|
|
@@ -69,26 +70,25 @@ var Tailwind = ({ children, config }) => {
|
|
|
69
70
|
if (hasResponsiveStyles && hasHead && domNode.name === "head") {
|
|
70
71
|
let newDomNode = null;
|
|
71
72
|
if (domNode.children) {
|
|
72
|
-
const style = domNode.children.find(
|
|
73
|
-
(child2) => child2 instanceof import_html_react_parser.Element && child2.name === "style"
|
|
74
|
-
);
|
|
75
73
|
const props = (0, import_html_react_parser.attributesToProps)(domNode.attribs);
|
|
76
|
-
newDomNode = /* @__PURE__ */ (0, import_jsx_runtime.
|
|
74
|
+
newDomNode = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("head", { ...props, children: [
|
|
75
|
+
(0, import_html_react_parser.domToReact)(domNode.children),
|
|
76
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: headStyle })
|
|
77
|
+
] });
|
|
77
78
|
}
|
|
78
79
|
return newDomNode;
|
|
79
80
|
}
|
|
80
81
|
if ((_a = domNode.attribs) == null ? void 0 : _a.class) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
const cleanRegex = /[:#\!\-[\]\/\.%]+/g;
|
|
83
|
+
const cleanTailwindClasses = domNode.attribs.class.replace(cleanRegex, "_");
|
|
84
|
+
const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
|
|
85
|
+
const tailwindStyles = cleanTailwindClasses.split(" ").map((className) => {
|
|
86
|
+
return cssMap[`.${className}`];
|
|
87
|
+
}).join(";");
|
|
88
|
+
domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
|
|
89
|
+
domNode.attribs.class = domNode.attribs.class.split(" ").filter((className) => className.search(/^.{2}:/) !== -1).join(" ").replace(cleanRegex, "_");
|
|
90
|
+
if (domNode.attribs.class === "")
|
|
90
91
|
delete domNode.attribs.class;
|
|
91
|
-
}
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -98,27 +98,45 @@ var Tailwind = ({ children, config }) => {
|
|
|
98
98
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: reactHTML });
|
|
99
99
|
};
|
|
100
100
|
Tailwind.displayName = "Tailwind";
|
|
101
|
-
function
|
|
101
|
+
function cleanCss(css) {
|
|
102
|
+
let newCss = css.replace(/\\/g, "").replace(/[.\!\#\w\d\\:\-\[\]\/\.%\(\))]+(?=\s*?{[^{]*?\})\s*?{/g, (m) => {
|
|
103
|
+
return m.replace(/(?<=.)[:#\!\-[\\\]\/\.%]+/g, "_");
|
|
104
|
+
}).replace(/font-family(?<value>[^;\r\n]+)/g, (m, value) => {
|
|
105
|
+
return `font-family${value.replace(/['"]+/g, "")}`;
|
|
106
|
+
});
|
|
107
|
+
return newCss;
|
|
108
|
+
}
|
|
109
|
+
function getMediaQueryCss(css) {
|
|
110
|
+
var _a;
|
|
102
111
|
const mediaQueryRegex = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm;
|
|
103
|
-
|
|
112
|
+
return ((_a = css.replace(mediaQueryRegex, (m) => {
|
|
104
113
|
return m.replace(
|
|
105
114
|
/([^{]+\{)([\s\S]+?)(\}\s*\})/gm,
|
|
106
115
|
(_, start, content, end) => {
|
|
107
|
-
const
|
|
108
|
-
/(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[
|
|
116
|
+
const newContent = content.replace(
|
|
117
|
+
/(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[^};\r\n]+)/gm,
|
|
109
118
|
(_2, prop, value) => {
|
|
110
|
-
return `${prop}: ${value} !important
|
|
119
|
+
return `${prop}: ${value} !important;`;
|
|
111
120
|
}
|
|
112
121
|
);
|
|
113
|
-
return `${start}${
|
|
122
|
+
return `${start}${newContent}${end}`;
|
|
114
123
|
}
|
|
115
124
|
);
|
|
116
|
-
}).
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
}).match(/@media\s*([^{]+)\{([^{}]*\{[^{}]*\})*[^{}]*\}/g)) == null ? void 0 : _a.join("")) ?? "";
|
|
126
|
+
}
|
|
127
|
+
function makeCssMap(css) {
|
|
128
|
+
const cssNoMedia = css.replace(
|
|
129
|
+
/@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm,
|
|
130
|
+
""
|
|
131
|
+
);
|
|
132
|
+
const cssMap = cssNoMedia.split("}").reduce((acc, cur) => {
|
|
133
|
+
const [key, value] = cur.split("{");
|
|
134
|
+
if (key && value) {
|
|
135
|
+
acc[key] = value;
|
|
136
|
+
}
|
|
137
|
+
return acc;
|
|
138
|
+
}, {});
|
|
139
|
+
return cssMap;
|
|
122
140
|
}
|
|
123
141
|
// Annotate the CommonJS export names for ESM import in node:
|
|
124
142
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
// src/tailwind.tsx
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
-
import htmlParser, { attributesToProps, Element } from "html-react-parser";
|
|
4
|
+
import htmlParser, { attributesToProps, domToReact, Element } from "html-react-parser";
|
|
5
5
|
import { tailwindToCSS } from "tw-to-css";
|
|
6
|
-
import { Fragment, jsx } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
var Tailwind = ({ children, config }) => {
|
|
8
8
|
const { twi } = tailwindToCSS({
|
|
9
9
|
config
|
|
10
10
|
});
|
|
11
11
|
const newChildren = React.Children.toArray(children);
|
|
12
12
|
const fullHTML = renderToStaticMarkup(/* @__PURE__ */ jsx(Fragment, { children: newChildren }));
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
13
|
+
const tailwindCss = twi(fullHTML, {
|
|
14
|
+
merge: false,
|
|
15
|
+
ignoreMediaQueries: false
|
|
16
|
+
});
|
|
17
|
+
const css = cleanCss(tailwindCss);
|
|
18
|
+
const cssMap = makeCssMap(css);
|
|
19
|
+
const headStyle = getMediaQueryCss(css);
|
|
19
20
|
const hasResponsiveStyles = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm.test(
|
|
20
21
|
headStyle
|
|
21
22
|
);
|
|
22
23
|
const hasHTML = /<html[^>]*>/gm.test(fullHTML);
|
|
23
24
|
const hasHead = /<head[^>]*>/gm.test(fullHTML);
|
|
24
|
-
if (hasResponsiveStyles && !hasHTML
|
|
25
|
+
if (hasResponsiveStyles && (!hasHTML || !hasHead)) {
|
|
25
26
|
throw new Error(
|
|
26
27
|
"Tailwind: To use responsive styles you must have a <html> and <head> element in your template."
|
|
27
28
|
);
|
|
@@ -37,26 +38,25 @@ var Tailwind = ({ children, config }) => {
|
|
|
37
38
|
if (hasResponsiveStyles && hasHead && domNode.name === "head") {
|
|
38
39
|
let newDomNode = null;
|
|
39
40
|
if (domNode.children) {
|
|
40
|
-
const style = domNode.children.find(
|
|
41
|
-
(child2) => child2 instanceof Element && child2.name === "style"
|
|
42
|
-
);
|
|
43
41
|
const props = attributesToProps(domNode.attribs);
|
|
44
|
-
newDomNode = /* @__PURE__ */
|
|
42
|
+
newDomNode = /* @__PURE__ */ jsxs("head", { ...props, children: [
|
|
43
|
+
domToReact(domNode.children),
|
|
44
|
+
/* @__PURE__ */ jsx("style", { children: headStyle })
|
|
45
|
+
] });
|
|
45
46
|
}
|
|
46
47
|
return newDomNode;
|
|
47
48
|
}
|
|
48
49
|
if ((_a = domNode.attribs) == null ? void 0 : _a.class) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
const cleanRegex = /[:#\!\-[\]\/\.%]+/g;
|
|
51
|
+
const cleanTailwindClasses = domNode.attribs.class.replace(cleanRegex, "_");
|
|
52
|
+
const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
|
|
53
|
+
const tailwindStyles = cleanTailwindClasses.split(" ").map((className) => {
|
|
54
|
+
return cssMap[`.${className}`];
|
|
55
|
+
}).join(";");
|
|
56
|
+
domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
|
|
57
|
+
domNode.attribs.class = domNode.attribs.class.split(" ").filter((className) => className.search(/^.{2}:/) !== -1).join(" ").replace(cleanRegex, "_");
|
|
58
|
+
if (domNode.attribs.class === "")
|
|
58
59
|
delete domNode.attribs.class;
|
|
59
|
-
}
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -66,27 +66,45 @@ var Tailwind = ({ children, config }) => {
|
|
|
66
66
|
return /* @__PURE__ */ jsx(Fragment, { children: reactHTML });
|
|
67
67
|
};
|
|
68
68
|
Tailwind.displayName = "Tailwind";
|
|
69
|
-
function
|
|
69
|
+
function cleanCss(css) {
|
|
70
|
+
let newCss = css.replace(/\\/g, "").replace(/[.\!\#\w\d\\:\-\[\]\/\.%\(\))]+(?=\s*?{[^{]*?\})\s*?{/g, (m) => {
|
|
71
|
+
return m.replace(/(?<=.)[:#\!\-[\\\]\/\.%]+/g, "_");
|
|
72
|
+
}).replace(/font-family(?<value>[^;\r\n]+)/g, (m, value) => {
|
|
73
|
+
return `font-family${value.replace(/['"]+/g, "")}`;
|
|
74
|
+
});
|
|
75
|
+
return newCss;
|
|
76
|
+
}
|
|
77
|
+
function getMediaQueryCss(css) {
|
|
78
|
+
var _a;
|
|
70
79
|
const mediaQueryRegex = /@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm;
|
|
71
|
-
|
|
80
|
+
return ((_a = css.replace(mediaQueryRegex, (m) => {
|
|
72
81
|
return m.replace(
|
|
73
82
|
/([^{]+\{)([\s\S]+?)(\}\s*\})/gm,
|
|
74
83
|
(_, start, content, end) => {
|
|
75
|
-
const
|
|
76
|
-
/(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[
|
|
84
|
+
const newContent = content.replace(
|
|
85
|
+
/(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[^};\r\n]+)/gm,
|
|
77
86
|
(_2, prop, value) => {
|
|
78
|
-
return `${prop}: ${value} !important
|
|
87
|
+
return `${prop}: ${value} !important;`;
|
|
79
88
|
}
|
|
80
89
|
);
|
|
81
|
-
return `${start}${
|
|
90
|
+
return `${start}${newContent}${end}`;
|
|
82
91
|
}
|
|
83
92
|
);
|
|
84
|
-
}).
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
}).match(/@media\s*([^{]+)\{([^{}]*\{[^{}]*\})*[^{}]*\}/g)) == null ? void 0 : _a.join("")) ?? "";
|
|
94
|
+
}
|
|
95
|
+
function makeCssMap(css) {
|
|
96
|
+
const cssNoMedia = css.replace(
|
|
97
|
+
/@media[^{]+\{(?<content>[\s\S]+?)\}\s*\}/gm,
|
|
98
|
+
""
|
|
99
|
+
);
|
|
100
|
+
const cssMap = cssNoMedia.split("}").reduce((acc, cur) => {
|
|
101
|
+
const [key, value] = cur.split("{");
|
|
102
|
+
if (key && value) {
|
|
103
|
+
acc[key] = value;
|
|
104
|
+
}
|
|
105
|
+
return acc;
|
|
106
|
+
}, {});
|
|
107
|
+
return cssMap;
|
|
90
108
|
}
|
|
91
109
|
export {
|
|
92
110
|
Tailwind
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-email/tailwind",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "A React component to wrap emails with Tailwind CSS",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"clean": "rm -rf dist",
|
|
18
18
|
"test": "jest",
|
|
19
19
|
"test:watch": "jest --watch",
|
|
20
|
-
"format:check": "prettier --
|
|
21
|
-
"format": "prettier --
|
|
20
|
+
"format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
|
|
21
|
+
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
@@ -37,16 +37,21 @@
|
|
|
37
37
|
"html-react-parser": "3.0.9",
|
|
38
38
|
"react": "18.2.0",
|
|
39
39
|
"react-dom": "18.2.0",
|
|
40
|
-
"tw-to-css": "0.0.
|
|
40
|
+
"tw-to-css": "0.0.11"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
+
"@babel/preset-react": "7.18.6",
|
|
44
|
+
"@react-email/button": "0.0.7",
|
|
43
45
|
"@testing-library/react": "14.0.0",
|
|
46
|
+
"@types/jest": "29.5.0",
|
|
44
47
|
"@types/react": "18.0.20",
|
|
45
48
|
"@types/react-dom": "18.0.6",
|
|
49
|
+
"babel-jest": "29.5.0",
|
|
46
50
|
"eslint": "8.23.1",
|
|
47
|
-
"
|
|
51
|
+
"jest": "29.5.0",
|
|
52
|
+
"prettier": "2.8.4",
|
|
48
53
|
"react": "18.2.0",
|
|
49
|
-
"
|
|
54
|
+
"ts-jest": "29.0.5",
|
|
50
55
|
"tsup": "6.2.3",
|
|
51
56
|
"typescript": "4.8.3"
|
|
52
57
|
},
|
package/readme.md
CHANGED
|
@@ -32,8 +32,8 @@ npm install @react-email/tailwind -E
|
|
|
32
32
|
Add the component around your email body content.
|
|
33
33
|
|
|
34
34
|
```jsx
|
|
35
|
-
import { Button } from
|
|
36
|
-
import { Tailwind } from
|
|
35
|
+
import { Button } from "@react-email/button";
|
|
36
|
+
import { Tailwind } from "@react-email/tailwind";
|
|
37
37
|
|
|
38
38
|
const Email = () => {
|
|
39
39
|
return (
|
|
@@ -42,7 +42,7 @@ const Email = () => {
|
|
|
42
42
|
theme: {
|
|
43
43
|
extend: {
|
|
44
44
|
colors: {
|
|
45
|
-
|
|
45
|
+
"custom-color": "#ff0000",
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
},
|