react-email 6.9.3 → 6.9.5
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 +8 -0
- package/dist/cli/index.mjs +1 -1
- 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 +2 -2
- 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
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.9.
|
|
6536
|
+
version: "6.9.5",
|
|
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",
|
|
@@ -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.9.
|
|
3
|
+
"version": "6.9.5",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/cli/index.mjs"
|
|
@@ -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
|
}
|