Skip to content

Commit 180b377

Browse files
feat(tailwind): extract pseudo classes to stylesheet (#1864)
Co-authored-by: gabriel miranda <[email protected]>
1 parent d9325a2 commit 180b377

8 files changed

+240
-204
lines changed

.changeset/great-parrots-yell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-email/tailwind": minor
3+
---
4+
5+
Extract tailwind pseudo classes to stylesheet

packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,45 +14,7 @@ exports[`Custom theme config > should be able to use custom spacing 1`] = `"<!DO
1414

1515
exports[`Custom theme config > should be able to use custom text alignment 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="text-align:justify"></div><!--/$-->"`;
1616

17-
exports[`Responsive styles > should add css to <head/> and keep responsive class names 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html lang="en"><head><!--$--><style>@media(min-width:640px){.sm_bg-red-300{background-color:rgb(252,165,165) !important}}@media(min-width:768px){.md_bg-red-400{background-color:rgb(248,113,113) !important}}@media(min-width:1024px){.lg_bg-red-500{background-color:rgb(239,68,68) !important}}</style></head><body><div class="sm_bg-red-300 md_bg-red-400 lg_bg-red-500" style="background-color:rgb(254,202,202)"></div><!--/$--></body></html>"`;
18-
19-
exports[`Responsive styles > should not have duplicate media queries 1`] = `"<!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(min-width:768px){.md_px-64px{padding-left:64px !important;padding-right:64px !important}}</style></head><body class="md_px-64px" style="margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;background-color:rgb(255,255,255);font-family:ui-sans-serif, system-ui, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;"><div class="md_px-64px"></div><!--/$--></body>"`;
20-
21-
exports[`Responsive styles > should persist existing <head/> elements 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html lang="en"><head><!--$--><style></style><link/><style>@media(min-width:640px){.sm_bg-red-500{background-color:rgb(239,68,68) !important}}</style></head><body><div class="sm_bg-red-500" style="background-color:rgb(254,202,202)"></div><!--/$--></body></html>"`;
22-
23-
exports[`Responsive styles > should throw an error when used without a <head/> 1`] = `
24-
[Error: You are trying to use the following Tailwind classes that cannot be inlined: sm:bg-red-500.
25-
For the media queries to work properly on rendering, they need to be added into a <style> tag inside of a <head> tag,
26-
the Tailwind component tried finding a <head> element but just wasn't able to find it.
27-
28-
Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
29-
This can also be our <Head> component.
30-
31-
If you do already have a <head> element at some depth,
32-
please file a bug https://github.com/resend/react-email/issues/new?assignees=&labels=Type%3A+Bug&projects=&template=1.bug_report.yml.]
33-
`;
34-
35-
exports[`Responsive styles > should throw error when used without the head and with media query class names only very deeply nested 1`] = `
36-
[Error: You are trying to use the following Tailwind classes that cannot be inlined: sm:h-10 sm:w-10.
37-
For the media queries to work properly on rendering, they need to be added into a <style> tag inside of a <head> tag,
38-
the Tailwind component tried finding a <head> element but just wasn't able to find it.
39-
40-
Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
41-
This can also be our <Head> component.
42-
43-
If you do already have a <head> element at some depth,
44-
please file a bug https://github.com/resend/react-email/issues/new?assignees=&labels=Type%3A+Bug&projects=&template=1.bug_report.yml.]
45-
`;
46-
47-
exports[`Responsive styles > should work with arbitrarily deep (in the React tree) <head> elements 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html lang="en"><head><!--$--><style>@media(min-width:640px){.sm_bg-red-300{background-color:rgb(252,165,165) !important}}@media(min-width:768px){.md_bg-red-400{background-color:rgb(248,113,113) !important}}@media(min-width:1024px){.lg_bg-red-500{background-color:rgb(239,68,68) !important}}</style></head><body><div class="sm_bg-red-300 md_bg-red-400 lg_bg-red-500" style="background-color:rgb(254,202,202)"></div><!--/$--></body></html>"`;
48-
49-
exports[`Responsive styles > should work with arbitrarily deep (in the React tree) <head> elements 2`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html lang="en"><head><!--$--><style>@media(min-width:640px){.sm_bg-red-300{background-color:rgb(252,165,165) !important}}@media(min-width:768px){.md_bg-red-400{background-color:rgb(248,113,113) !important}}@media(min-width:1024px){.lg_bg-red-500{background-color:rgb(239,68,68) !important}}</style></head><body><div class="sm_bg-red-300 md_bg-red-400 lg_bg-red-500" style="background-color:rgb(254,202,202)"></div><!--/$--></body></html>"`;
50-
51-
exports[`Responsive styles > should work with relatively complex media query utilities 1`] = `"<!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 not all and(min-width:640px){.max-sm_text-red-600{color:rgb(220,38,38) !important}}</style></head><p class="max-sm_text-red-600" style="color:rgb(29,78,216)">I am some text</p><!--/$-->"`;
52-
53-
exports[`Tailwind component > <Button className="px-3 py-2 mt-8 text-sm text-gray-200 bg-blue-600 rounded-md"> 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a style="margin-top:2rem;border-radius:0.375rem;background-color:rgb(37,99,235);padding-left:0.75rem;padding-right:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem;color:rgb(229,231,235);font-size:0.875rem;line-height:1.25rem;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:8px 12px 8px 12px" target="_blank"><span><!--[if mso]><i style="mso-font-width:300%;mso-text-raise:12" hidden>&#8202;&#8202;</i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:6px">Testing button</span><span><!--[if mso]><i style="mso-font-width:300%" hidden>&#8202;&#8202;&#8203;</i><![endif]--></span></a>Testing<!--/$-->"`;
54-
55-
exports[`Tailwind component > it should not generate styles from text 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$-->container bg-red-500 bg-blue-300<!--/$-->"`;
17+
exports[`Tailwind component > it should not generate styles from text 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$-->container bg-red-500 bg-blue-300<!--/$-->"`;D
5618

5719
exports[`Tailwind component > should allow for complex children manipulation 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="text-align:center;font-size:0"><tbody><tr><td style="padding:0px 0px 0px 0px"><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="max-width:300px;display:inline-block;vertical-align:top;font-size:16px;box-sizing:border-box"><tbody><tr><td>This is the first column</td></tr></tbody></table><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="max-width:300px;display:inline-block;vertical-align:top;font-size:16px;box-sizing:border-box"><tbody><tr><td>This is the second column</td></tr></tbody></table></td></tr></tbody></table><!--/$-->"`;
5820

@@ -76,4 +38,3 @@ exports[`Tailwind component > should work with components that return children 1
7638

7739
exports[`Tailwind component > should work with components that use React.forwardRef 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="margin-top:100px;font-size:50px;line-height:1">Hello world</div><div style="padding:20px"><p style="font-weight:700;font-size:50px">React Email</p></div><!--/$-->"`;
7840

79-
exports[`Tailwind component > should work with custom components with fragment at the root 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="margin-top:100px;font-size:50px;line-height:1">Hello world</div><div style="padding:20px"><p style="font-weight:700;font-size:50px">React Email</p></div><div style="padding:20px"><p style="font-weight:700;font-size:50px">React Email</p></div><!--/$-->"`;

packages/tailwind/src/tailwind.spec.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ describe('Tailwind component', () => {
338338
});
339339
});
340340

341-
describe('Responsive styles', () => {
341+
describe('non-inlinable styles', () => {
342342
/*
343343
This test is because of https://github.com/resend/react-email/issues/1112
344344
which was being caused because we required to, either have our <Head> component,
@@ -389,22 +389,22 @@ describe('Responsive styles', () => {
389389
const output = await render(
390390
<Tailwind>
391391
<Head />
392-
<Body className="my-auto mx-auto bg-white font-sans md:px-[64px]">
393-
<div className="md:px-[64px]" />
392+
<Body className="bg-white my-auto mx-auto font-sans md:px-[64px] hover:underline">
393+
<div className="md:px-[64px] hover:underline" />
394394
</Body>
395395
</Tailwind>,
396396
);
397397

398398
expect(output).toMatchSnapshot();
399399
});
400400

401-
it('should add css to <head/> and keep responsive class names', async () => {
401+
it('should add css to <head/> and keep class names', async () => {
402402
const actualOutput = await render(
403403
<html lang="en">
404404
<Tailwind>
405405
<head />
406406
<body>
407-
<div className="bg-red-200 sm:bg-red-300 md:bg-red-400 lg:bg-red-500" />
407+
<div className="bg-red-200 sm:bg-red-300 md:bg-red-400 lg:bg-red-500 hover:bg-red-600 focus:bg-red-700 sm:hover:bg-red-200" />
408408
</body>
409409
</Tailwind>
410410
</html>,

packages/tailwind/src/utils/css/media-queries/sanitize-media-queries.spec.ts

Lines changed: 0 additions & 88 deletions
This file was deleted.

packages/tailwind/src/utils/css/media-queries/sanitize-media-queries.ts

Lines changed: 0 additions & 64 deletions
This file was deleted.
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import postcss, { Root } from 'postcss';
2+
import { sanitizeNonInlinableClasses } from './sanitize-non-inlinable-classes';
3+
4+
test('sanitizeNonInlinableClasses()', async () => {
5+
const { root } = await postcss()
6+
.process(
7+
`
8+
.ring-gray-900\\/5{
9+
--tw-ring-color: rgb(17 24 39 / 0.05)
10+
}
11+
.\\[mask-image\\:linear-gradient\\(180deg\\2c white\\2c rgba\\(255\\2c 255\\2c 255\\2c 0\\)\\)\\]{
12+
-webkit-mask-image: linear-gradient(180deg,white,rgba(255,255,255,0));
13+
mask-image: linear-gradient(180deg,white,rgba(255,255,255,0))
14+
}
15+
16+
.hover\\:text-sky-600:hover{
17+
--tw-text-opacity: 1;
18+
color: rgb(2 132 199 / var(--tw-text-opacity))
19+
}
20+
21+
@media (min-width: 640px){
22+
.sm\\:mx-auto{
23+
margin-left: auto;
24+
margin-right: auto
25+
}
26+
27+
.sm\\:focus\\:outline-none:focus{
28+
outline: 2px solid transparent;
29+
outline-offset: 2px
30+
}
31+
32+
.sm\\:max-w-lg{
33+
max-width: 32rem
34+
}
35+
36+
.sm\\:rounded-lg{
37+
border-radius: 0.5rem
38+
}
39+
}
40+
41+
@media (min-width: 768px){
42+
.md\\:hover\\:bg-gray-100:hover{
43+
--tw-bg-opacity: 1;
44+
background-color: rgb(243 244 246 / var(--tw-bg-opacity))
45+
}
46+
47+
.md\\:px-10{
48+
padding-left: 2.5rem;
49+
padding-right: 2.5rem
50+
}
51+
52+
.md\\:py-12{
53+
padding-top: 3rem;
54+
padding-bottom: 3rem
55+
}
56+
}
57+
58+
@media (min-width: 1024px){
59+
.lg\\:focus\\:underline:focus{
60+
text-decoration-line: underline
61+
}
62+
}
63+
`,
64+
)
65+
.async();
66+
67+
const { nonInlinableClasses, sanitizedRules } = sanitizeNonInlinableClasses(
68+
root as Root,
69+
);
70+
expect(nonInlinableClasses).toEqual([
71+
'sm:mx-auto',
72+
'sm:focus:outline-none',
73+
'sm:max-w-lg',
74+
'sm:rounded-lg',
75+
'md:hover:bg-gray-100',
76+
'md:px-10',
77+
'md:py-12',
78+
'lg:focus:underline',
79+
'hover:text-sky-600',
80+
]);
81+
expect(new Root({ nodes: sanitizedRules }).toString()).toBe(`
82+
83+
@media (min-width: 640px){
84+
.sm_mx-auto{
85+
margin-left: auto !important;
86+
margin-right: auto !important
87+
}
88+
89+
.sm_focus_outline-none:focus{
90+
outline: 2px solid transparent !important;
91+
outline-offset: 2px !important
92+
}
93+
94+
.sm_max-w-lg{
95+
max-width: 32rem !important
96+
}
97+
98+
.sm_rounded-lg{
99+
border-radius: 0.5rem !important
100+
}
101+
}
102+
103+
@media (min-width: 768px){
104+
.md_hover_bg-gray-100:hover{
105+
--tw-bg-opacity: 1 !important;
106+
background-color: rgb(243 244 246 / var(--tw-bg-opacity)) !important
107+
}
108+
109+
.md_px-10{
110+
padding-left: 2.5rem !important;
111+
padding-right: 2.5rem !important
112+
}
113+
114+
.md_py-12{
115+
padding-top: 3rem !important;
116+
padding-bottom: 3rem !important
117+
}
118+
}
119+
120+
@media (min-width: 1024px){
121+
.lg_focus_underline:focus{
122+
text-decoration-line: underline !important
123+
}
124+
}
125+
126+
.hover_text-sky-600:hover{
127+
--tw-text-opacity: 1 !important;
128+
color: rgb(2 132 199 / var(--tw-text-opacity)) !important
129+
}`);
130+
});

0 commit comments

Comments
 (0)