react-email 6.9.3 → 6.10.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/CHANGELOG.md +18 -0
- package/dist/cli/index.mjs +6 -10
- package/dist/components/container/container.cjs +18 -3
- package/dist/components/container/container.d.cts +3 -1
- package/dist/components/container/container.d.mts +3 -1
- package/dist/components/container/container.mjs +18 -3
- package/dist/components/element-marker.cjs +8 -2
- package/dist/components/element-marker.d.cts +14 -0
- package/dist/components/element-marker.d.mts +14 -0
- package/dist/components/element-marker.mjs +8 -3
- package/dist/components/index.d.cts +1 -0
- package/dist/components/index.d.mts +1 -0
- package/dist/components/section/section.cjs +18 -3
- package/dist/components/section/section.d.cts +3 -1
- package/dist/components/section/section.d.mts +3 -1
- package/dist/components/section/section.mjs +18 -3
- package/dist/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.cjs +34 -21
- package/dist/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.mjs +34 -21
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/package.json +4 -4
- package/src/components/container/container.tsx +66 -46
- package/src/components/element-marker.ts +25 -2
- package/src/components/index.ts +5 -0
- package/src/components/section/section.tsx +66 -46
- package/src/components/tailwind/tailwind.spec.tsx +13 -0
- package/src/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.ts +46 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# react-email
|
|
2
2
|
|
|
3
|
+
## 6.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e9939f0: Upgrade `chokidar` to v5, which ships with npm provenance, so installs under pnpm's `trustPolicy: no-downgrade` no longer fail. The CLI now needs Node 20.19 or newer.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 1b45ed0: The dev server now refuses to start on Node older than 20.19, which matches the package's declared minimum, and names the installed version in the error.
|
|
12
|
+
|
|
13
|
+
## 6.9.5
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 0250981: fix responsive padding not applying to inner td for Container and Section
|
|
18
|
+
|
|
19
|
+
## 6.9.4
|
|
20
|
+
|
|
3
21
|
## 6.9.3
|
|
4
22
|
|
|
5
23
|
## 6.9.2
|
package/dist/cli/index.mjs
CHANGED
|
@@ -6533,7 +6533,7 @@ const getTracingRootDir = async (usersProjectLocation) => {
|
|
|
6533
6533
|
//#region package.json
|
|
6534
6534
|
var package_default = {
|
|
6535
6535
|
name: "react-email",
|
|
6536
|
-
version: "6.
|
|
6536
|
+
version: "6.10.0",
|
|
6537
6537
|
description: "A live preview of your emails right in your browser.",
|
|
6538
6538
|
bin: { "email": "./dist/cli/index.mjs" },
|
|
6539
6539
|
type: "module",
|
|
@@ -6565,7 +6565,7 @@ var package_default = {
|
|
|
6565
6565
|
"directory": "packages/react-email"
|
|
6566
6566
|
},
|
|
6567
6567
|
keywords: ["react", "email"],
|
|
6568
|
-
engines: { "node": ">=20.
|
|
6568
|
+
engines: { "node": ">=20.19.0" },
|
|
6569
6569
|
peerDependencies: {
|
|
6570
6570
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc",
|
|
6571
6571
|
"react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
|
|
@@ -6574,7 +6574,7 @@ var package_default = {
|
|
|
6574
6574
|
"@babel/parser": "catalog:",
|
|
6575
6575
|
"@babel/traverse": "catalog:",
|
|
6576
6576
|
"@react-email/render": "workspace:>=2.1.0",
|
|
6577
|
-
"chokidar": "^
|
|
6577
|
+
"chokidar": "^5.0.0",
|
|
6578
6578
|
"commander": "catalog:",
|
|
6579
6579
|
"conf": "^15.0.2",
|
|
6580
6580
|
"css-tree": "3.2.1",
|
|
@@ -7229,10 +7229,6 @@ const conf = new Conf({
|
|
|
7229
7229
|
});
|
|
7230
7230
|
//#endregion
|
|
7231
7231
|
//#region src/cli/utils/style-text.ts
|
|
7232
|
-
/**
|
|
7233
|
-
* Centralized fallback for Node versions (<20.12.0) without util.styleText.
|
|
7234
|
-
* Returns the original text when styleText is unavailable.
|
|
7235
|
-
*/
|
|
7236
7232
|
const styleText = nodeUtil.styleText ? nodeUtil.styleText : (_, text) => text;
|
|
7237
7233
|
//#endregion
|
|
7238
7234
|
//#region src/cli/utils/preview/get-env-variables-for-preview-app.ts
|
|
@@ -7299,9 +7295,9 @@ const safeAsyncServerListen = (server, port) => {
|
|
|
7299
7295
|
});
|
|
7300
7296
|
};
|
|
7301
7297
|
const startDevServer = async (emailsDirRelativePath, staticBaseDirRelativePath, port, compatibilityClients) => {
|
|
7302
|
-
const [
|
|
7303
|
-
if (
|
|
7304
|
-
console.error(` ${logSymbols.error} Node ${
|
|
7298
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
7299
|
+
if (major < 20 || major === 20 && minor < 19) {
|
|
7300
|
+
console.error(` ${logSymbols.error} Node ${process.versions.node} is not supported. Please upgrade to Node 20.19 or higher.`);
|
|
7305
7301
|
process.exit(1);
|
|
7306
7302
|
}
|
|
7307
7303
|
const previewServerLocation = await getUiLocation();
|
|
@@ -4,14 +4,14 @@ let react = require("react");
|
|
|
4
4
|
react = require_runtime.__toESM(react);
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
6
|
//#region src/components/container/container.tsx
|
|
7
|
-
const Container = react.forwardRef(({ children, style = {}, ...props }, ref) => {
|
|
7
|
+
const Container = react.forwardRef(({ children, style = {}, tdClassName, ...props }, ref) => {
|
|
8
8
|
const tdStyle = {};
|
|
9
9
|
const tableStyle = {};
|
|
10
10
|
const styleRecord = style;
|
|
11
11
|
for (const key in styleRecord) {
|
|
12
12
|
if (!Object.hasOwn(styleRecord, key)) continue;
|
|
13
13
|
const value = styleRecord[key];
|
|
14
|
-
if (key
|
|
14
|
+
if (key.startsWith("padding")) tdStyle[key] = value;
|
|
15
15
|
else tableStyle[key] = value;
|
|
16
16
|
}
|
|
17
17
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("table", {
|
|
@@ -30,6 +30,7 @@ const Container = react.forwardRef(({ children, style = {}, ...props }, ref) =>
|
|
|
30
30
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
|
|
31
31
|
style: { width: "100%" },
|
|
32
32
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
33
|
+
className: tdClassName,
|
|
33
34
|
style: tdStyle,
|
|
34
35
|
children
|
|
35
36
|
})
|
|
@@ -37,6 +38,20 @@ const Container = react.forwardRef(({ children, style = {}, ...props }, ref) =>
|
|
|
37
38
|
});
|
|
38
39
|
});
|
|
39
40
|
Container.displayName = "Container";
|
|
40
|
-
require_element_marker.markAsElement(Container)
|
|
41
|
+
require_element_marker.markAsElement(Container, { resolveTailwind(props, { style, className, classProperties }) {
|
|
42
|
+
const tableClasses = [];
|
|
43
|
+
const tdClasses = props.tdClassName ? [props.tdClassName] : [];
|
|
44
|
+
for (const name of className?.split(" ") ?? []) {
|
|
45
|
+
const properties = classProperties[name];
|
|
46
|
+
if (properties && properties.length > 0 && properties.every((property) => property.startsWith("padding"))) tdClasses.push(name);
|
|
47
|
+
else tableClasses.push(name);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
...props,
|
|
51
|
+
style,
|
|
52
|
+
className: tableClasses.length > 0 ? tableClasses.join(" ") : void 0,
|
|
53
|
+
tdClassName: tdClasses.length > 0 ? tdClasses.join(" ") : void 0
|
|
54
|
+
};
|
|
55
|
+
} });
|
|
41
56
|
//#endregion
|
|
42
57
|
exports.Container = Container;
|
|
@@ -2,6 +2,8 @@ import * as React$1 from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/components/container/container.d.ts
|
|
4
4
|
type ContainerProps = Readonly<React$1.ComponentPropsWithoutRef<'table'>>;
|
|
5
|
-
declare const Container: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> &
|
|
5
|
+
declare const Container: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> & {
|
|
6
|
+
tdClassName?: string;
|
|
7
|
+
} & React$1.RefAttributes<HTMLTableElement>>;
|
|
6
8
|
//#endregion
|
|
7
9
|
export { Container, ContainerProps };
|
|
@@ -2,6 +2,8 @@ import * as React$1 from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/components/container/container.d.ts
|
|
4
4
|
type ContainerProps = Readonly<React$1.ComponentPropsWithoutRef<'table'>>;
|
|
5
|
-
declare const Container: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> &
|
|
5
|
+
declare const Container: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> & {
|
|
6
|
+
tdClassName?: string;
|
|
7
|
+
} & React$1.RefAttributes<HTMLTableElement>>;
|
|
6
8
|
//#endregion
|
|
7
9
|
export { Container, ContainerProps };
|
|
@@ -2,14 +2,14 @@ import { markAsElement } from "../element-marker.mjs";
|
|
|
2
2
|
import * as React$1 from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/components/container/container.tsx
|
|
5
|
-
const Container = React$1.forwardRef(({ children, style = {}, ...props }, ref) => {
|
|
5
|
+
const Container = React$1.forwardRef(({ children, style = {}, tdClassName, ...props }, ref) => {
|
|
6
6
|
const tdStyle = {};
|
|
7
7
|
const tableStyle = {};
|
|
8
8
|
const styleRecord = style;
|
|
9
9
|
for (const key in styleRecord) {
|
|
10
10
|
if (!Object.hasOwn(styleRecord, key)) continue;
|
|
11
11
|
const value = styleRecord[key];
|
|
12
|
-
if (key
|
|
12
|
+
if (key.startsWith("padding")) tdStyle[key] = value;
|
|
13
13
|
else tableStyle[key] = value;
|
|
14
14
|
}
|
|
15
15
|
return /* @__PURE__ */ jsx("table", {
|
|
@@ -28,6 +28,7 @@ const Container = React$1.forwardRef(({ children, style = {}, ...props }, ref) =
|
|
|
28
28
|
children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", {
|
|
29
29
|
style: { width: "100%" },
|
|
30
30
|
children: /* @__PURE__ */ jsx("td", {
|
|
31
|
+
className: tdClassName,
|
|
31
32
|
style: tdStyle,
|
|
32
33
|
children
|
|
33
34
|
})
|
|
@@ -35,6 +36,20 @@ const Container = React$1.forwardRef(({ children, style = {}, ...props }, ref) =
|
|
|
35
36
|
});
|
|
36
37
|
});
|
|
37
38
|
Container.displayName = "Container";
|
|
38
|
-
markAsElement(Container)
|
|
39
|
+
markAsElement(Container, { resolveTailwind(props, { style, className, classProperties }) {
|
|
40
|
+
const tableClasses = [];
|
|
41
|
+
const tdClasses = props.tdClassName ? [props.tdClassName] : [];
|
|
42
|
+
for (const name of className?.split(" ") ?? []) {
|
|
43
|
+
const properties = classProperties[name];
|
|
44
|
+
if (properties && properties.length > 0 && properties.every((property) => property.startsWith("padding"))) tdClasses.push(name);
|
|
45
|
+
else tableClasses.push(name);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...props,
|
|
49
|
+
style,
|
|
50
|
+
className: tableClasses.length > 0 ? tableClasses.join(" ") : void 0,
|
|
51
|
+
tdClassName: tdClasses.length > 0 ? tdClasses.join(" ") : void 0
|
|
52
|
+
};
|
|
53
|
+
} });
|
|
39
54
|
//#endregion
|
|
40
55
|
export { Container };
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
//#region src/components/element-marker.ts
|
|
2
2
|
const elementMarker = Symbol.for("react-email.element");
|
|
3
|
-
const markAsElement = (component) => {
|
|
4
|
-
component[elementMarker] =
|
|
3
|
+
const markAsElement = (component, options = {}) => {
|
|
4
|
+
component[elementMarker] = options;
|
|
5
|
+
};
|
|
6
|
+
const getElementOptions = (type) => {
|
|
7
|
+
if (typeof type !== "function" && typeof type !== "object") return void 0;
|
|
8
|
+
if (type === null) return void 0;
|
|
9
|
+
return type[elementMarker];
|
|
5
10
|
};
|
|
6
11
|
//#endregion
|
|
7
12
|
exports.elementMarker = elementMarker;
|
|
13
|
+
exports.getElementOptions = getElementOptions;
|
|
8
14
|
exports.markAsElement = markAsElement;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React$1 from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/components/element-marker.d.ts
|
|
4
|
+
interface ResolvedTailwind {
|
|
5
|
+
style: React$1.CSSProperties;
|
|
6
|
+
className: string | undefined;
|
|
7
|
+
classProperties: Record<string, string[]>;
|
|
8
|
+
}
|
|
9
|
+
interface ElementOptions<Props = object> {
|
|
10
|
+
resolveTailwind?: (props: Props, resolved: ResolvedTailwind) => Props;
|
|
11
|
+
}
|
|
12
|
+
declare const markAsElement: <Props>(component: unknown, options?: ElementOptions<Props>) => void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ElementOptions, ResolvedTailwind, markAsElement };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React$1 from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/components/element-marker.d.ts
|
|
4
|
+
interface ResolvedTailwind {
|
|
5
|
+
style: React$1.CSSProperties;
|
|
6
|
+
className: string | undefined;
|
|
7
|
+
classProperties: Record<string, string[]>;
|
|
8
|
+
}
|
|
9
|
+
interface ElementOptions<Props = object> {
|
|
10
|
+
resolveTailwind?: (props: Props, resolved: ResolvedTailwind) => Props;
|
|
11
|
+
}
|
|
12
|
+
declare const markAsElement: <Props>(component: unknown, options?: ElementOptions<Props>) => void;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ElementOptions, ResolvedTailwind, markAsElement };
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
//#region src/components/element-marker.ts
|
|
2
2
|
const elementMarker = Symbol.for("react-email.element");
|
|
3
|
-
const markAsElement = (component) => {
|
|
4
|
-
component[elementMarker] =
|
|
3
|
+
const markAsElement = (component, options = {}) => {
|
|
4
|
+
component[elementMarker] = options;
|
|
5
|
+
};
|
|
6
|
+
const getElementOptions = (type) => {
|
|
7
|
+
if (typeof type !== "function" && typeof type !== "object") return void 0;
|
|
8
|
+
if (type === null) return void 0;
|
|
9
|
+
return type[elementMarker];
|
|
5
10
|
};
|
|
6
11
|
//#endregion
|
|
7
|
-
export { elementMarker, markAsElement };
|
|
12
|
+
export { elementMarker, getElementOptions, markAsElement };
|
|
@@ -6,6 +6,7 @@ import { CodeBlock, CodeBlockProps } from "./code-block/code-block.cjs";
|
|
|
6
6
|
import { CodeInline, CodeInlineProps } from "./code-inline/code-inline.cjs";
|
|
7
7
|
import { Column, ColumnProps } from "./column/column.cjs";
|
|
8
8
|
import { Container, ContainerProps } from "./container/container.cjs";
|
|
9
|
+
import { ElementOptions, ResolvedTailwind, markAsElement } from "./element-marker.cjs";
|
|
9
10
|
import { Font, FontProps } from "./font/font.cjs";
|
|
10
11
|
import { Head, HeadProps } from "./head/head.cjs";
|
|
11
12
|
import { Heading, HeadingAs, HeadingProps } from "./heading/heading.cjs";
|
|
@@ -6,6 +6,7 @@ import { CodeBlock, CodeBlockProps } from "./code-block/code-block.mjs";
|
|
|
6
6
|
import { CodeInline, CodeInlineProps } from "./code-inline/code-inline.mjs";
|
|
7
7
|
import { Column, ColumnProps } from "./column/column.mjs";
|
|
8
8
|
import { Container, ContainerProps } from "./container/container.mjs";
|
|
9
|
+
import { ElementOptions, ResolvedTailwind, markAsElement } from "./element-marker.mjs";
|
|
9
10
|
import { Font, FontProps } from "./font/font.mjs";
|
|
10
11
|
import { Head, HeadProps } from "./head/head.mjs";
|
|
11
12
|
import { Heading, HeadingAs, HeadingProps } from "./heading/heading.mjs";
|
|
@@ -4,14 +4,14 @@ let react = require("react");
|
|
|
4
4
|
react = require_runtime.__toESM(react);
|
|
5
5
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
6
6
|
//#region src/components/section/section.tsx
|
|
7
|
-
const Section = react.forwardRef(({ children, style = {}, ...props }, ref) => {
|
|
7
|
+
const Section = react.forwardRef(({ children, style = {}, tdClassName, ...props }, ref) => {
|
|
8
8
|
const tdStyle = {};
|
|
9
9
|
const tableStyle = {};
|
|
10
10
|
const styleRecord = style;
|
|
11
11
|
for (const key in styleRecord) {
|
|
12
12
|
if (!Object.hasOwn(styleRecord, key)) continue;
|
|
13
13
|
const value = styleRecord[key];
|
|
14
|
-
if (key
|
|
14
|
+
if (key.startsWith("padding")) tdStyle[key] = value;
|
|
15
15
|
else tableStyle[key] = value;
|
|
16
16
|
}
|
|
17
17
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("table", {
|
|
@@ -25,12 +25,27 @@ const Section = react.forwardRef(({ children, style = {}, ...props }, ref) => {
|
|
|
25
25
|
ref,
|
|
26
26
|
style: tableStyle,
|
|
27
27
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
|
|
28
|
+
className: tdClassName,
|
|
28
29
|
style: tdStyle,
|
|
29
30
|
children
|
|
30
31
|
}) }) })
|
|
31
32
|
});
|
|
32
33
|
});
|
|
33
34
|
Section.displayName = "Section";
|
|
34
|
-
require_element_marker.markAsElement(Section)
|
|
35
|
+
require_element_marker.markAsElement(Section, { resolveTailwind(props, { style, className, classProperties }) {
|
|
36
|
+
const tableClasses = [];
|
|
37
|
+
const tdClasses = props.tdClassName ? [props.tdClassName] : [];
|
|
38
|
+
for (const name of className?.split(" ") ?? []) {
|
|
39
|
+
const properties = classProperties[name];
|
|
40
|
+
if (properties && properties.length > 0 && properties.every((property) => property.startsWith("padding"))) tdClasses.push(name);
|
|
41
|
+
else tableClasses.push(name);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
...props,
|
|
45
|
+
style,
|
|
46
|
+
className: tableClasses.length > 0 ? tableClasses.join(" ") : void 0,
|
|
47
|
+
tdClassName: tdClasses.length > 0 ? tdClasses.join(" ") : void 0
|
|
48
|
+
};
|
|
49
|
+
} });
|
|
35
50
|
//#endregion
|
|
36
51
|
exports.Section = Section;
|
|
@@ -2,6 +2,8 @@ import * as React$1 from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/components/section/section.d.ts
|
|
4
4
|
type SectionProps = Readonly<React$1.ComponentPropsWithoutRef<'table'>>;
|
|
5
|
-
declare const Section: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> &
|
|
5
|
+
declare const Section: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> & {
|
|
6
|
+
tdClassName?: string;
|
|
7
|
+
} & React$1.RefAttributes<HTMLTableElement>>;
|
|
6
8
|
//#endregion
|
|
7
9
|
export { Section, SectionProps };
|
|
@@ -2,6 +2,8 @@ import * as React$1 from "react";
|
|
|
2
2
|
|
|
3
3
|
//#region src/components/section/section.d.ts
|
|
4
4
|
type SectionProps = Readonly<React$1.ComponentPropsWithoutRef<'table'>>;
|
|
5
|
-
declare const Section: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> &
|
|
5
|
+
declare const Section: React$1.ForwardRefExoticComponent<Readonly<Omit<React$1.DetailedHTMLProps<React$1.TableHTMLAttributes<HTMLTableElement>, HTMLTableElement>, "ref">> & {
|
|
6
|
+
tdClassName?: string;
|
|
7
|
+
} & React$1.RefAttributes<HTMLTableElement>>;
|
|
6
8
|
//#endregion
|
|
7
9
|
export { Section, SectionProps };
|
|
@@ -2,14 +2,14 @@ import { markAsElement } from "../element-marker.mjs";
|
|
|
2
2
|
import * as React$1 from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/components/section/section.tsx
|
|
5
|
-
const Section = React$1.forwardRef(({ children, style = {}, ...props }, ref) => {
|
|
5
|
+
const Section = React$1.forwardRef(({ children, style = {}, tdClassName, ...props }, ref) => {
|
|
6
6
|
const tdStyle = {};
|
|
7
7
|
const tableStyle = {};
|
|
8
8
|
const styleRecord = style;
|
|
9
9
|
for (const key in styleRecord) {
|
|
10
10
|
if (!Object.hasOwn(styleRecord, key)) continue;
|
|
11
11
|
const value = styleRecord[key];
|
|
12
|
-
if (key
|
|
12
|
+
if (key.startsWith("padding")) tdStyle[key] = value;
|
|
13
13
|
else tableStyle[key] = value;
|
|
14
14
|
}
|
|
15
15
|
return /* @__PURE__ */ jsx("table", {
|
|
@@ -23,12 +23,27 @@ const Section = React$1.forwardRef(({ children, style = {}, ...props }, ref) =>
|
|
|
23
23
|
ref,
|
|
24
24
|
style: tableStyle,
|
|
25
25
|
children: /* @__PURE__ */ jsx("tbody", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
26
|
+
className: tdClassName,
|
|
26
27
|
style: tdStyle,
|
|
27
28
|
children
|
|
28
29
|
}) }) })
|
|
29
30
|
});
|
|
30
31
|
});
|
|
31
32
|
Section.displayName = "Section";
|
|
32
|
-
markAsElement(Section)
|
|
33
|
+
markAsElement(Section, { resolveTailwind(props, { style, className, classProperties }) {
|
|
34
|
+
const tableClasses = [];
|
|
35
|
+
const tdClasses = props.tdClassName ? [props.tdClassName] : [];
|
|
36
|
+
for (const name of className?.split(" ") ?? []) {
|
|
37
|
+
const properties = classProperties[name];
|
|
38
|
+
if (properties && properties.length > 0 && properties.every((property) => property.startsWith("padding"))) tdClasses.push(name);
|
|
39
|
+
else tableClasses.push(name);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...props,
|
|
43
|
+
style,
|
|
44
|
+
className: tableClasses.length > 0 ? tableClasses.join(" ") : void 0,
|
|
45
|
+
tdClassName: tdClasses.length > 0 ? tdClasses.join(" ") : void 0
|
|
46
|
+
};
|
|
47
|
+
} });
|
|
33
48
|
//#endregion
|
|
34
49
|
export { Section };
|
|
@@ -1,35 +1,48 @@
|
|
|
1
1
|
const require_runtime = require("../../../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_element_marker = require("../../../element-marker.cjs");
|
|
2
3
|
const require_make_inline_styles_for = require("../css/make-inline-styles-for.cjs");
|
|
3
4
|
const require_sanitize_class_name = require("../compatibility/sanitize-class-name.cjs");
|
|
4
5
|
const require_is_component = require("../react/is-component.cjs");
|
|
5
6
|
let react = require("react");
|
|
6
7
|
react = require_runtime.__toESM(react, 1);
|
|
8
|
+
let css_tree = require("css-tree");
|
|
7
9
|
//#region src/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.ts
|
|
8
10
|
function cloneElementWithInlinedStyles(element, inlinableRules, nonInlinableRules, customProperties) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
if (!element.props.className || require_is_component.isComponent(element)) return react.default.cloneElement(element, element.props, element.props.children);
|
|
12
|
+
const classes = element.props.className.trim().split(/\s+/);
|
|
13
|
+
const residualClasses = [];
|
|
14
|
+
const classProperties = Object.create(null);
|
|
15
|
+
const rules = [];
|
|
16
|
+
for (const className of classes) {
|
|
17
|
+
const classRules = inlinableRules.get(className);
|
|
18
|
+
if (classRules) rules.push(...classRules);
|
|
19
|
+
const nonInlinable = nonInlinableRules.get(className);
|
|
20
|
+
if (nonInlinable) {
|
|
21
|
+
const sanitized = require_sanitize_class_name.sanitizeClassName(className);
|
|
22
|
+
residualClasses.push(sanitized);
|
|
23
|
+
const properties = [];
|
|
24
|
+
for (const rule of nonInlinable) (0, css_tree.walk)(rule, {
|
|
25
|
+
visit: "Declaration",
|
|
26
|
+
enter(declaration) {
|
|
27
|
+
properties.push(declaration.property);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
classProperties[sanitized] = properties;
|
|
31
|
+
} else if (!classRules) residualClasses.push(className);
|
|
32
|
+
}
|
|
33
|
+
const resolved = {
|
|
34
|
+
style: {
|
|
21
35
|
...require_make_inline_styles_for.makeInlineStylesFor(rules, customProperties),
|
|
22
36
|
...element.props.style
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
const newProps = {
|
|
37
|
+
},
|
|
38
|
+
className: residualClasses.length > 0 ? residualClasses.join(" ") : void 0,
|
|
39
|
+
classProperties
|
|
40
|
+
};
|
|
41
|
+
const resolveTailwind = require_element_marker.getElementOptions(element.type)?.resolveTailwind;
|
|
42
|
+
const newProps = resolveTailwind ? resolveTailwind(element.props, resolved) : {
|
|
31
43
|
...element.props,
|
|
32
|
-
|
|
44
|
+
style: resolved.style,
|
|
45
|
+
className: resolved.className
|
|
33
46
|
};
|
|
34
47
|
return react.default.cloneElement(element, newProps, newProps.children);
|
|
35
48
|
}
|
|
@@ -1,33 +1,46 @@
|
|
|
1
|
+
import { getElementOptions } from "../../../element-marker.mjs";
|
|
1
2
|
import { makeInlineStylesFor } from "../css/make-inline-styles-for.mjs";
|
|
2
3
|
import { sanitizeClassName } from "../compatibility/sanitize-class-name.mjs";
|
|
3
4
|
import { isComponent } from "../react/is-component.mjs";
|
|
4
5
|
import React from "react";
|
|
6
|
+
import { walk } from "css-tree/dist/csstree.esm";
|
|
5
7
|
//#region src/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.ts
|
|
6
8
|
function cloneElementWithInlinedStyles(element, inlinableRules, nonInlinableRules, customProperties) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
if (!element.props.className || isComponent(element)) return React.cloneElement(element, element.props, element.props.children);
|
|
10
|
+
const classes = element.props.className.trim().split(/\s+/);
|
|
11
|
+
const residualClasses = [];
|
|
12
|
+
const classProperties = Object.create(null);
|
|
13
|
+
const rules = [];
|
|
14
|
+
for (const className of classes) {
|
|
15
|
+
const classRules = inlinableRules.get(className);
|
|
16
|
+
if (classRules) rules.push(...classRules);
|
|
17
|
+
const nonInlinable = nonInlinableRules.get(className);
|
|
18
|
+
if (nonInlinable) {
|
|
19
|
+
const sanitized = sanitizeClassName(className);
|
|
20
|
+
residualClasses.push(sanitized);
|
|
21
|
+
const properties = [];
|
|
22
|
+
for (const rule of nonInlinable) walk(rule, {
|
|
23
|
+
visit: "Declaration",
|
|
24
|
+
enter(declaration) {
|
|
25
|
+
properties.push(declaration.property);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
classProperties[sanitized] = properties;
|
|
29
|
+
} else if (!classRules) residualClasses.push(className);
|
|
30
|
+
}
|
|
31
|
+
const resolved = {
|
|
32
|
+
style: {
|
|
19
33
|
...makeInlineStylesFor(rules, customProperties),
|
|
20
34
|
...element.props.style
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
const newProps = {
|
|
35
|
+
},
|
|
36
|
+
className: residualClasses.length > 0 ? residualClasses.join(" ") : void 0,
|
|
37
|
+
classProperties
|
|
38
|
+
};
|
|
39
|
+
const resolveTailwind = getElementOptions(element.type)?.resolveTailwind;
|
|
40
|
+
const newProps = resolveTailwind ? resolveTailwind(element.props, resolved) : {
|
|
29
41
|
...element.props,
|
|
30
|
-
|
|
42
|
+
style: resolved.style,
|
|
43
|
+
className: resolved.className
|
|
31
44
|
};
|
|
32
45
|
return React.cloneElement(element, newProps, newProps.children);
|
|
33
46
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_element_marker = require("./components/element-marker.cjs");
|
|
2
3
|
const require_body = require("./components/body/body.cjs");
|
|
3
4
|
const require_button = require("./components/button/button.cjs");
|
|
4
5
|
const require_code_block = require("./components/code-block/code-block.cjs");
|
|
@@ -64,6 +65,7 @@ exports.hopscotch = require_themes.hopscotch;
|
|
|
64
65
|
exports.inlineStyles = require_inline_styles.inlineStyles;
|
|
65
66
|
exports.laserwave = require_themes.laserwave;
|
|
66
67
|
exports.lucario = require_themes.lucario;
|
|
68
|
+
exports.markAsElement = require_element_marker.markAsElement;
|
|
67
69
|
exports.materialDark = require_themes.materialDark;
|
|
68
70
|
exports.materialLight = require_themes.materialLight;
|
|
69
71
|
exports.materialOceanic = require_themes.materialOceanic;
|
package/dist/index.d.cts
CHANGED
|
@@ -6,6 +6,7 @@ import { CodeBlock, CodeBlockProps } from "./components/code-block/code-block.cj
|
|
|
6
6
|
import { CodeInline, CodeInlineProps } from "./components/code-inline/code-inline.cjs";
|
|
7
7
|
import { Column, ColumnProps } from "./components/column/column.cjs";
|
|
8
8
|
import { Container, ContainerProps } from "./components/container/container.cjs";
|
|
9
|
+
import { ElementOptions, ResolvedTailwind, markAsElement } from "./components/element-marker.cjs";
|
|
9
10
|
import { Font, FontProps } from "./components/font/font.cjs";
|
|
10
11
|
import { Head, HeadProps } from "./components/head/head.cjs";
|
|
11
12
|
import { Heading, HeadingAs, HeadingProps } from "./components/heading/heading.cjs";
|
|
@@ -23,4 +24,4 @@ import { EmailElementProps, Tailwind, TailwindConfig, TailwindProps, pixelBasedP
|
|
|
23
24
|
import { TailwindSetup, setupTailwind } from "./components/tailwind/utils/tailwindcss/setup-tailwind.cjs";
|
|
24
25
|
import { Text, TextProps } from "./components/text/text.cjs";
|
|
25
26
|
export * from "@react-email/render";
|
|
26
|
-
export { Body, BodyProps, Button, ButtonProps, CodeBlock, CodeBlockProps, CodeInline, CodeInlineProps, Column, ColumnProps, Container, ContainerProps, EmailElementProps, Font, FontProps, Head, HeadProps, Heading, HeadingAs, HeadingProps, Hr, HrProps, Html, HtmlProps, Img, ImgProps, Link, LinkProps, Markdown, MarkdownProps, Preview, PreviewProps, PrismLanguage, Row, RowProps, Section, SectionProps, Tailwind, TailwindConfig, TailwindProps, TailwindSetup, Text, TextProps, Theme, a11yDark, atomDark, baseAteliersulphurpoolLight, cb, coldarkCold, coldarkDark, coyWithoutShadows, darcula, dracula, duotoneDark, duotoneEarth, duotoneForest, duotoneLight, duotoneSea, duotoneSpace, ghcolors, gruvboxDark, gruvboxLight, holiTheme, hopscotch, inlineStyles, laserwave, lucario, materialDark, materialLight, materialOceanic, nightOwl, nord, oneDark, oneLight, pixelBasedPreset, pojoaque, renderWhiteSpace, sanitizeStyleSheet, setupTailwind, shadesOfPurple, solarizedDarkAtom, synthwave84, vesper, vs, vscDarkPlus, xonokai, zTouch };
|
|
27
|
+
export { Body, BodyProps, Button, ButtonProps, CodeBlock, CodeBlockProps, CodeInline, CodeInlineProps, Column, ColumnProps, Container, ContainerProps, ElementOptions, EmailElementProps, Font, FontProps, Head, HeadProps, Heading, HeadingAs, HeadingProps, Hr, HrProps, Html, HtmlProps, Img, ImgProps, Link, LinkProps, Markdown, MarkdownProps, Preview, PreviewProps, PrismLanguage, ResolvedTailwind, Row, RowProps, Section, SectionProps, Tailwind, TailwindConfig, TailwindProps, TailwindSetup, Text, TextProps, Theme, a11yDark, atomDark, baseAteliersulphurpoolLight, cb, coldarkCold, coldarkDark, coyWithoutShadows, darcula, dracula, duotoneDark, duotoneEarth, duotoneForest, duotoneLight, duotoneSea, duotoneSpace, ghcolors, gruvboxDark, gruvboxLight, holiTheme, hopscotch, inlineStyles, laserwave, lucario, markAsElement, materialDark, materialLight, materialOceanic, nightOwl, nord, oneDark, oneLight, pixelBasedPreset, pojoaque, renderWhiteSpace, sanitizeStyleSheet, setupTailwind, shadesOfPurple, solarizedDarkAtom, synthwave84, vesper, vs, vscDarkPlus, xonokai, zTouch };
|
package/dist/index.d.mts
CHANGED
|
@@ -6,6 +6,7 @@ import { CodeBlock, CodeBlockProps } from "./components/code-block/code-block.mj
|
|
|
6
6
|
import { CodeInline, CodeInlineProps } from "./components/code-inline/code-inline.mjs";
|
|
7
7
|
import { Column, ColumnProps } from "./components/column/column.mjs";
|
|
8
8
|
import { Container, ContainerProps } from "./components/container/container.mjs";
|
|
9
|
+
import { ElementOptions, ResolvedTailwind, markAsElement } from "./components/element-marker.mjs";
|
|
9
10
|
import { Font, FontProps } from "./components/font/font.mjs";
|
|
10
11
|
import { Head, HeadProps } from "./components/head/head.mjs";
|
|
11
12
|
import { Heading, HeadingAs, HeadingProps } from "./components/heading/heading.mjs";
|
|
@@ -23,4 +24,4 @@ import { EmailElementProps, Tailwind, TailwindConfig, TailwindProps, pixelBasedP
|
|
|
23
24
|
import { TailwindSetup, setupTailwind } from "./components/tailwind/utils/tailwindcss/setup-tailwind.mjs";
|
|
24
25
|
import { Text, TextProps } from "./components/text/text.mjs";
|
|
25
26
|
export * from "@react-email/render";
|
|
26
|
-
export { Body, BodyProps, Button, ButtonProps, CodeBlock, CodeBlockProps, CodeInline, CodeInlineProps, Column, ColumnProps, Container, ContainerProps, EmailElementProps, Font, FontProps, Head, HeadProps, Heading, HeadingAs, HeadingProps, Hr, HrProps, Html, HtmlProps, Img, ImgProps, Link, LinkProps, Markdown, MarkdownProps, Preview, PreviewProps, PrismLanguage, Row, RowProps, Section, SectionProps, Tailwind, TailwindConfig, TailwindProps, TailwindSetup, Text, TextProps, Theme, a11yDark, atomDark, baseAteliersulphurpoolLight, cb, coldarkCold, coldarkDark, coyWithoutShadows, darcula, dracula, duotoneDark, duotoneEarth, duotoneForest, duotoneLight, duotoneSea, duotoneSpace, ghcolors, gruvboxDark, gruvboxLight, holiTheme, hopscotch, inlineStyles, laserwave, lucario, materialDark, materialLight, materialOceanic, nightOwl, nord, oneDark, oneLight, pixelBasedPreset, pojoaque, renderWhiteSpace, sanitizeStyleSheet, setupTailwind, shadesOfPurple, solarizedDarkAtom, synthwave84, vesper, vs, vscDarkPlus, xonokai, zTouch };
|
|
27
|
+
export { Body, BodyProps, Button, ButtonProps, CodeBlock, CodeBlockProps, CodeInline, CodeInlineProps, Column, ColumnProps, Container, ContainerProps, ElementOptions, EmailElementProps, Font, FontProps, Head, HeadProps, Heading, HeadingAs, HeadingProps, Hr, HrProps, Html, HtmlProps, Img, ImgProps, Link, LinkProps, Markdown, MarkdownProps, Preview, PreviewProps, PrismLanguage, ResolvedTailwind, Row, RowProps, Section, SectionProps, Tailwind, TailwindConfig, TailwindProps, TailwindSetup, Text, TextProps, Theme, a11yDark, atomDark, baseAteliersulphurpoolLight, cb, coldarkCold, coldarkDark, coyWithoutShadows, darcula, dracula, duotoneDark, duotoneEarth, duotoneForest, duotoneLight, duotoneSea, duotoneSpace, ghcolors, gruvboxDark, gruvboxLight, holiTheme, hopscotch, inlineStyles, laserwave, lucario, markAsElement, materialDark, materialLight, materialOceanic, nightOwl, nord, oneDark, oneLight, pixelBasedPreset, pojoaque, renderWhiteSpace, sanitizeStyleSheet, setupTailwind, shadesOfPurple, solarizedDarkAtom, synthwave84, vesper, vs, vscDarkPlus, xonokai, zTouch };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { markAsElement } from "./components/element-marker.mjs";
|
|
1
2
|
import { Body } from "./components/body/body.mjs";
|
|
2
3
|
import { Button } from "./components/button/button.mjs";
|
|
3
4
|
import { CodeBlock } from "./components/code-block/code-block.mjs";
|
|
@@ -22,4 +23,4 @@ import { setupTailwind } from "./components/tailwind/utils/tailwindcss/setup-tai
|
|
|
22
23
|
import { Tailwind, pixelBasedPreset } from "./components/tailwind/tailwind.mjs";
|
|
23
24
|
import { Text } from "./components/text/text.mjs";
|
|
24
25
|
export * from "@react-email/render";
|
|
25
|
-
export { Body, Button, CodeBlock, CodeInline, Column, Container, Font, Head, Heading, Hr, Html, Img, Link, Markdown, Preview, Row, Section, Tailwind, Text, a11yDark, atomDark, baseAteliersulphurpoolLight, cb, coldarkCold, coldarkDark, coyWithoutShadows, darcula, dracula, duotoneDark, duotoneEarth, duotoneForest, duotoneLight, duotoneSea, duotoneSpace, ghcolors, gruvboxDark, gruvboxLight, holiTheme, hopscotch, inlineStyles, laserwave, lucario, materialDark, materialLight, materialOceanic, nightOwl, nord, oneDark, oneLight, pixelBasedPreset, pojoaque, renderWhiteSpace, sanitizeStyleSheet, setupTailwind, shadesOfPurple, solarizedDarkAtom, synthwave84, vesper, vs, vscDarkPlus, xonokai, zTouch };
|
|
26
|
+
export { Body, Button, CodeBlock, CodeInline, Column, Container, Font, Head, Heading, Hr, Html, Img, Link, Markdown, Preview, Row, Section, Tailwind, Text, a11yDark, atomDark, baseAteliersulphurpoolLight, cb, coldarkCold, coldarkDark, coyWithoutShadows, darcula, dracula, duotoneDark, duotoneEarth, duotoneForest, duotoneLight, duotoneSea, duotoneSpace, ghcolors, gruvboxDark, gruvboxLight, holiTheme, hopscotch, inlineStyles, laserwave, lucario, markAsElement, materialDark, materialLight, materialOceanic, nightOwl, nord, oneDark, oneLight, pixelBasedPreset, pojoaque, renderWhiteSpace, sanitizeStyleSheet, setupTailwind, shadesOfPurple, solarizedDarkAtom, synthwave84, vesper, vs, vscDarkPlus, xonokai, zTouch };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.10.0",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/cli/index.mjs"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"email"
|
|
30
30
|
],
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": ">=20.
|
|
32
|
+
"node": ">=20.19.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.0 || ^19.0 || ^19.0.0-rc",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@babel/parser": "7.29.2",
|
|
40
40
|
"@babel/traverse": "7.29.0",
|
|
41
41
|
"@react-email/render": ">=2.1.0",
|
|
42
|
-
"chokidar": "^
|
|
42
|
+
"chokidar": "^5.0.0",
|
|
43
43
|
"commander": "^13.0.0",
|
|
44
44
|
"conf": "^15.0.2",
|
|
45
45
|
"css-tree": "3.2.1",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@types/mime-types": "2.1.4",
|
|
68
68
|
"@types/prompts": "2.4.9",
|
|
69
69
|
"@types/shelljs": "0.10.0",
|
|
70
|
-
"next": "16.3.
|
|
70
|
+
"next": "16.3.3",
|
|
71
71
|
"react": "19.2.4",
|
|
72
72
|
"react-dom": "19.2.4",
|
|
73
73
|
"shelljs": "0.10.0",
|
|
@@ -3,57 +3,77 @@ import { markAsElement } from '../element-marker.js';
|
|
|
3
3
|
|
|
4
4
|
export type ContainerProps = Readonly<React.ComponentPropsWithoutRef<'table'>>;
|
|
5
5
|
|
|
6
|
-
export const Container = React.forwardRef<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
export const Container = React.forwardRef<
|
|
7
|
+
HTMLTableElement,
|
|
8
|
+
ContainerProps & { tdClassName?: string }
|
|
9
|
+
>(({ children, style = {}, tdClassName, ...props }, ref) => {
|
|
10
|
+
// Split padding styles to improve compatibility with Klaviyo and Outlook,
|
|
11
|
+
// while preserving user-provided style property order without allocating
|
|
12
|
+
// entry arrays on each render.
|
|
13
|
+
const tdStyle: React.CSSProperties = {};
|
|
14
|
+
const tableStyle: React.CSSProperties = {};
|
|
15
|
+
|
|
16
|
+
const styleRecord = style as Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
for (const key in styleRecord) {
|
|
19
|
+
if (!Object.hasOwn(styleRecord, key)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const value = styleRecord[key];
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
if (key.startsWith('padding')) {
|
|
26
|
+
(tdStyle as Record<string, unknown>)[key] = value;
|
|
27
|
+
} else {
|
|
28
|
+
(tableStyle as Record<string, unknown>)[key] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
22
31
|
|
|
32
|
+
return (
|
|
33
|
+
<table
|
|
34
|
+
align="center"
|
|
35
|
+
width="100%"
|
|
36
|
+
{...props}
|
|
37
|
+
border={0}
|
|
38
|
+
cellPadding="0"
|
|
39
|
+
cellSpacing="0"
|
|
40
|
+
ref={ref}
|
|
41
|
+
role="presentation"
|
|
42
|
+
style={{ maxWidth: '37.5em', ...tableStyle }}
|
|
43
|
+
>
|
|
44
|
+
<tbody>
|
|
45
|
+
<tr style={{ width: '100%' }}>
|
|
46
|
+
<td className={tdClassName} style={tdStyle}>
|
|
47
|
+
{children}
|
|
48
|
+
</td>
|
|
49
|
+
</tr>
|
|
50
|
+
</tbody>
|
|
51
|
+
</table>
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
Container.displayName = 'Container';
|
|
56
|
+
markAsElement<ContainerProps & { tdClassName?: string }>(Container, {
|
|
57
|
+
resolveTailwind(props, { style, className, classProperties }) {
|
|
58
|
+
const tableClasses: string[] = [];
|
|
59
|
+
const tdClasses = props.tdClassName ? [props.tdClassName] : [];
|
|
60
|
+
for (const name of className?.split(' ') ?? []) {
|
|
61
|
+
const properties = classProperties[name];
|
|
23
62
|
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
key === 'paddingBottom' ||
|
|
28
|
-
key === 'paddingLeft'
|
|
63
|
+
properties &&
|
|
64
|
+
properties.length > 0 &&
|
|
65
|
+
properties.every((property) => property.startsWith('padding'))
|
|
29
66
|
) {
|
|
30
|
-
(
|
|
67
|
+
tdClasses.push(name);
|
|
31
68
|
} else {
|
|
32
|
-
(
|
|
69
|
+
tableClasses.push(name);
|
|
33
70
|
}
|
|
34
71
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
border={0}
|
|
42
|
-
cellPadding="0"
|
|
43
|
-
cellSpacing="0"
|
|
44
|
-
ref={ref}
|
|
45
|
-
role="presentation"
|
|
46
|
-
style={{ maxWidth: '37.5em', ...tableStyle }}
|
|
47
|
-
>
|
|
48
|
-
<tbody>
|
|
49
|
-
<tr style={{ width: '100%' }}>
|
|
50
|
-
<td style={tdStyle}>{children}</td>
|
|
51
|
-
</tr>
|
|
52
|
-
</tbody>
|
|
53
|
-
</table>
|
|
54
|
-
);
|
|
72
|
+
return {
|
|
73
|
+
...props,
|
|
74
|
+
style,
|
|
75
|
+
className: tableClasses.length > 0 ? tableClasses.join(' ') : undefined,
|
|
76
|
+
tdClassName: tdClasses.length > 0 ? tdClasses.join(' ') : undefined,
|
|
77
|
+
};
|
|
55
78
|
},
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
Container.displayName = 'Container';
|
|
59
|
-
markAsElement(Container);
|
|
79
|
+
});
|
|
@@ -1,5 +1,28 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
|
|
1
3
|
export const elementMarker = Symbol.for('react-email.element');
|
|
2
4
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
+
export interface ResolvedTailwind {
|
|
6
|
+
style: React.CSSProperties;
|
|
7
|
+
className: string | undefined;
|
|
8
|
+
classProperties: Record<string, string[]>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ElementOptions<Props = object> {
|
|
12
|
+
resolveTailwind?: (props: Props, resolved: ResolvedTailwind) => Props;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const markAsElement = <Props>(
|
|
16
|
+
component: unknown,
|
|
17
|
+
options: ElementOptions<Props> = {},
|
|
18
|
+
) => {
|
|
19
|
+
(component as Record<symbol, ElementOptions<Props>>)[elementMarker] = options;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const getElementOptions = (
|
|
23
|
+
type: unknown,
|
|
24
|
+
): ElementOptions | undefined => {
|
|
25
|
+
if (typeof type !== 'function' && typeof type !== 'object') return undefined;
|
|
26
|
+
if (type === null) return undefined;
|
|
27
|
+
return (type as Record<symbol, ElementOptions | undefined>)[elementMarker];
|
|
5
28
|
};
|
package/src/components/index.ts
CHANGED
|
@@ -4,6 +4,11 @@ export * from './code-block/index.js';
|
|
|
4
4
|
export * from './code-inline/index.js';
|
|
5
5
|
export * from './column/index.js';
|
|
6
6
|
export * from './container/index.js';
|
|
7
|
+
export {
|
|
8
|
+
type ElementOptions,
|
|
9
|
+
markAsElement,
|
|
10
|
+
type ResolvedTailwind,
|
|
11
|
+
} from './element-marker.js';
|
|
7
12
|
export * from './font/index.js';
|
|
8
13
|
export * from './head/index.js';
|
|
9
14
|
export * from './heading/index.js';
|
|
@@ -3,57 +3,77 @@ import { markAsElement } from '../element-marker.js';
|
|
|
3
3
|
|
|
4
4
|
export type SectionProps = Readonly<React.ComponentPropsWithoutRef<'table'>>;
|
|
5
5
|
|
|
6
|
-
export const Section = React.forwardRef<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
export const Section = React.forwardRef<
|
|
7
|
+
HTMLTableElement,
|
|
8
|
+
SectionProps & { tdClassName?: string }
|
|
9
|
+
>(({ children, style = {}, tdClassName, ...props }, ref) => {
|
|
10
|
+
// Split padding styles to improve compatibility with Klaviyo and Outlook,
|
|
11
|
+
// while preserving user-provided style property order without allocating
|
|
12
|
+
// entry arrays on each render.
|
|
13
|
+
const tdStyle: React.CSSProperties = {};
|
|
14
|
+
const tableStyle: React.CSSProperties = {};
|
|
15
|
+
|
|
16
|
+
const styleRecord = style as Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
for (const key in styleRecord) {
|
|
19
|
+
if (!Object.hasOwn(styleRecord, key)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const value = styleRecord[key];
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
if (key.startsWith('padding')) {
|
|
26
|
+
(tdStyle as Record<string, unknown>)[key] = value;
|
|
27
|
+
} else {
|
|
28
|
+
(tableStyle as Record<string, unknown>)[key] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
22
31
|
|
|
32
|
+
return (
|
|
33
|
+
<table
|
|
34
|
+
align="center"
|
|
35
|
+
width="100%"
|
|
36
|
+
border={0}
|
|
37
|
+
cellPadding="0"
|
|
38
|
+
cellSpacing="0"
|
|
39
|
+
role="presentation"
|
|
40
|
+
{...props}
|
|
41
|
+
ref={ref}
|
|
42
|
+
style={tableStyle}
|
|
43
|
+
>
|
|
44
|
+
<tbody>
|
|
45
|
+
<tr>
|
|
46
|
+
<td className={tdClassName} style={tdStyle}>
|
|
47
|
+
{children}
|
|
48
|
+
</td>
|
|
49
|
+
</tr>
|
|
50
|
+
</tbody>
|
|
51
|
+
</table>
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
Section.displayName = 'Section';
|
|
56
|
+
markAsElement<SectionProps & { tdClassName?: string }>(Section, {
|
|
57
|
+
resolveTailwind(props, { style, className, classProperties }) {
|
|
58
|
+
const tableClasses: string[] = [];
|
|
59
|
+
const tdClasses = props.tdClassName ? [props.tdClassName] : [];
|
|
60
|
+
for (const name of className?.split(' ') ?? []) {
|
|
61
|
+
const properties = classProperties[name];
|
|
23
62
|
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
key === 'paddingBottom' ||
|
|
28
|
-
key === 'paddingLeft'
|
|
63
|
+
properties &&
|
|
64
|
+
properties.length > 0 &&
|
|
65
|
+
properties.every((property) => property.startsWith('padding'))
|
|
29
66
|
) {
|
|
30
|
-
(
|
|
67
|
+
tdClasses.push(name);
|
|
31
68
|
} else {
|
|
32
|
-
(
|
|
69
|
+
tableClasses.push(name);
|
|
33
70
|
}
|
|
34
71
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
cellPadding="0"
|
|
42
|
-
cellSpacing="0"
|
|
43
|
-
role="presentation"
|
|
44
|
-
{...props}
|
|
45
|
-
ref={ref}
|
|
46
|
-
style={tableStyle}
|
|
47
|
-
>
|
|
48
|
-
<tbody>
|
|
49
|
-
<tr>
|
|
50
|
-
<td style={tdStyle}>{children}</td>
|
|
51
|
-
</tr>
|
|
52
|
-
</tbody>
|
|
53
|
-
</table>
|
|
54
|
-
);
|
|
72
|
+
return {
|
|
73
|
+
...props,
|
|
74
|
+
style,
|
|
75
|
+
className: tableClasses.length > 0 ? tableClasses.join(' ') : undefined,
|
|
76
|
+
tdClassName: tdClasses.length > 0 ? tdClasses.join(' ') : undefined,
|
|
77
|
+
};
|
|
55
78
|
},
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
Section.displayName = 'Section';
|
|
59
|
-
markAsElement(Section);
|
|
79
|
+
});
|
|
@@ -275,6 +275,19 @@ describe('Tailwind component', () => {
|
|
|
275
275
|
expect(html).not.toMatch(/<table[^>]*style="[^"]*padding:1rem/);
|
|
276
276
|
});
|
|
277
277
|
|
|
278
|
+
it('routes responsive padding classes on <Section> to the inner <td>', async () => {
|
|
279
|
+
const html = await render(
|
|
280
|
+
<Tailwind>
|
|
281
|
+
<Head />
|
|
282
|
+
<Section className="max-sm:px-5 max-sm:bg-red-500 px-9">x</Section>
|
|
283
|
+
</Tailwind>,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(html).toMatchInlineSnapshot(
|
|
287
|
+
`"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><style>@media (max-width:40rem){.max-sm_bg-red-500{background-color:rgb(251,44,54)!important}}@media (max-width:40rem){.max-sm_px-5{padding-right:1.25rem!important;padding-left:1.25rem!important}}</style></head><!--$--><!--head--><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" class="max-sm_bg-red-500"><tbody><tr><td class="max-sm_px-5" style="padding-right:2.25rem;padding-left:2.25rem">x</td></tr></tbody></table><!--/$-->"`,
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
278
291
|
it('inlines Tailwind classes on <Column> onto its <td>', async () => {
|
|
279
292
|
const html = await render(
|
|
280
293
|
<Tailwind>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Rule, walk } from 'css-tree';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { getElementOptions } from '../../../element-marker.js';
|
|
3
4
|
import type { EmailElementProps } from '../../tailwind.js';
|
|
4
5
|
import { sanitizeClassName } from '../compatibility/sanitize-class-name.js';
|
|
5
6
|
import type { CustomProperties } from '../css/get-custom-properties.js';
|
|
@@ -12,50 +13,58 @@ export function cloneElementWithInlinedStyles(
|
|
|
12
13
|
nonInlinableRules: Map<string, Rule[]>,
|
|
13
14
|
customProperties: CustomProperties,
|
|
14
15
|
) {
|
|
15
|
-
|
|
16
|
+
if (!element.props.className || isComponent(element)) {
|
|
17
|
+
return React.cloneElement(element, element.props, element.props.children);
|
|
18
|
+
}
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
const classes = element.props.className.trim().split(/\s+/);
|
|
20
|
+
const classes = element.props.className.trim().split(/\s+/);
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
const residualClasses: string[] = [];
|
|
23
|
+
const classProperties: Record<string, string[]> = Object.create(null);
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
if (nonInlinableRules.has(className)) {
|
|
29
|
-
residualClasses.push(className);
|
|
30
|
-
} else if (!classRules) {
|
|
31
|
-
residualClasses.push(className);
|
|
32
|
-
}
|
|
25
|
+
const rules: Rule[] = [];
|
|
26
|
+
for (const className of classes) {
|
|
27
|
+
const classRules = inlinableRules.get(className);
|
|
28
|
+
if (classRules) {
|
|
29
|
+
rules.push(...classRules);
|
|
33
30
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} else {
|
|
51
|
-
propsToOverwrite.className = undefined;
|
|
31
|
+
const nonInlinable = nonInlinableRules.get(className);
|
|
32
|
+
if (nonInlinable) {
|
|
33
|
+
const sanitized = sanitizeClassName(className);
|
|
34
|
+
residualClasses.push(sanitized);
|
|
35
|
+
const properties: string[] = [];
|
|
36
|
+
for (const rule of nonInlinable) {
|
|
37
|
+
walk(rule, {
|
|
38
|
+
visit: 'Declaration',
|
|
39
|
+
enter(declaration) {
|
|
40
|
+
properties.push(declaration.property);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
classProperties[sanitized] = properties;
|
|
45
|
+
} else if (!classRules) {
|
|
46
|
+
residualClasses.push(className);
|
|
52
47
|
}
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
const resolved = {
|
|
51
|
+
style: {
|
|
52
|
+
...makeInlineStylesFor(rules, customProperties),
|
|
53
|
+
...element.props.style,
|
|
54
|
+
},
|
|
55
|
+
className:
|
|
56
|
+
residualClasses.length > 0 ? residualClasses.join(' ') : undefined,
|
|
57
|
+
classProperties,
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
const resolveTailwind = getElementOptions(element.type)?.resolveTailwind;
|
|
61
|
+
const newProps = resolveTailwind
|
|
62
|
+
? (resolveTailwind(element.props, resolved) as EmailElementProps)
|
|
63
|
+
: {
|
|
64
|
+
...element.props,
|
|
65
|
+
style: resolved.style,
|
|
66
|
+
className: resolved.className,
|
|
67
|
+
};
|
|
68
|
+
|
|
60
69
|
return React.cloneElement(element, newProps, newProps.children);
|
|
61
70
|
}
|