@react-email/tailwind 0.0.7 → 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 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 headStyle = getMediaQueryCSS(
46
- twi(fullHTML, {
47
- merge: false,
48
- ignoreMediaQueries: false
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 && !hasHead) {
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.jsx)("head", { ...props, children: style && style instanceof import_html_react_parser.Element ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `${style.children} ${headStyle}` }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: headStyle }) });
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
- if (hasResponsiveStyles) {
82
- domNode.attribs.class = domNode.attribs.class.replace(
83
- /[:#\!\-[\]\/\.%]+/g,
84
- "_"
85
- );
86
- } else {
87
- const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
88
- const tailwindStyles = twi(domNode.attribs.class);
89
- domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
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 getMediaQueryCSS(css) {
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
- let newCss = css.replace(mediaQueryRegex, (m) => {
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 newcontent = content.replace(
108
- /(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[^;\r\n]+)/gm,
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}${newcontent}${end}`;
122
+ return `${start}${newContent}${end}`;
114
123
  }
115
124
  );
116
- }).replace(/[.\!\#\w\d\\:\-\[\]\/\.%]+\s*?{/g, (m) => {
117
- return m.replace(/(?<=.)[:#\!\-[\\\]\/\.%]+/g, "_");
118
- }).replace(/font-family(?<value>[^;\r\n]+)/g, (m, value) => {
119
- return `font-family${value.replace(/['"]+/g, "")}`;
120
- });
121
- return newCss;
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 headStyle = getMediaQueryCSS(
14
- twi(fullHTML, {
15
- merge: false,
16
- ignoreMediaQueries: false
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 && !hasHead) {
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__ */ jsx("head", { ...props, children: style && style instanceof Element ? /* @__PURE__ */ jsx("style", { children: `${style.children} ${headStyle}` }) : /* @__PURE__ */ jsx("style", { children: headStyle }) });
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
- if (hasResponsiveStyles) {
50
- domNode.attribs.class = domNode.attribs.class.replace(
51
- /[:#\!\-[\]\/\.%]+/g,
52
- "_"
53
- );
54
- } else {
55
- const currentStyles = domNode.attribs.style ? `${domNode.attribs.style};` : "";
56
- const tailwindStyles = twi(domNode.attribs.class);
57
- domNode.attribs.style = `${currentStyles} ${tailwindStyles}`;
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 getMediaQueryCSS(css) {
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
- let newCss = css.replace(mediaQueryRegex, (m) => {
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 newcontent = content.replace(
76
- /(?:[\s\r\n]*)?(?<prop>[\w-]+)\s*:\s*(?<value>[^;\r\n]+)/gm,
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}${newcontent}${end}`;
90
+ return `${start}${newContent}${end}`;
82
91
  }
83
92
  );
84
- }).replace(/[.\!\#\w\d\\:\-\[\]\/\.%]+\s*?{/g, (m) => {
85
- return m.replace(/(?<=.)[:#\!\-[\\\]\/\.%]+/g, "_");
86
- }).replace(/font-family(?<value>[^;\r\n]+)/g, (m, value) => {
87
- return `font-family${value.replace(/['"]+/g, "")}`;
88
- });
89
- return newCss;
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.7",
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 --ignore-path ./../../.prettierignore --check \"**/*.{ts,tsx,md}\"",
21
- "format": "prettier --ignore-path ./../../.prettierignore --write \"**/*.{ts,tsx,md}\""
20
+ "format:check": "prettier --check \"**/*.{ts,tsx,md}\"",
21
+ "format": "prettier --write \"**/*.{ts,tsx,md}\""
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
@@ -40,13 +40,18 @@
40
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
- "eslint-config-custom": "*",
51
+ "jest": "29.5.0",
52
+ "prettier": "2.8.4",
48
53
  "react": "18.2.0",
49
- "tsconfig": "*",
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 '@react-email/button';
36
- import { Tailwind } from '@react-email/tailwind';
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
- 'custom-color': '#ff0000',
45
+ "custom-color": "#ff0000",
46
46
  },
47
47
  },
48
48
  },