@depup/react-email 6.9.3-depup.0 → 6.11.0-depup.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 +24 -0
- package/README.md +8 -9
- package/changes.json +7 -11
- package/dist/cli/index.mjs +74 -33
- 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 +16 -20
- package/readme.md +8 -0
- 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
|
@@ -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
|
}
|