@depup/react-email 5.2.10-depup.1 → 6.9.0-depup.1

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.
Files changed (404) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/README.md +11 -10
  3. package/changes.json +21 -17
  4. package/dev/CHANGELOG.md +2 -0
  5. package/dev/index.js +1 -1
  6. package/dev/package.json +1 -1
  7. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  8. package/dist/cli/index.mjs +7801 -0
  9. package/dist/components/body/body.cjs +39 -0
  10. package/dist/components/body/body.d.cts +7 -0
  11. package/dist/components/body/body.d.mts +7 -0
  12. package/dist/components/body/body.mjs +37 -0
  13. package/dist/components/body/index.d.cts +1 -0
  14. package/dist/components/body/index.d.mts +1 -0
  15. package/dist/components/body/margin-properties.cjs +30 -0
  16. package/dist/components/body/margin-properties.mjs +29 -0
  17. package/dist/components/button/button.cjs +64 -0
  18. package/dist/components/button/button.d.cts +13 -0
  19. package/dist/components/button/button.d.mts +13 -0
  20. package/dist/components/button/button.mjs +62 -0
  21. package/dist/components/button/index.d.cts +1 -0
  22. package/dist/components/button/index.d.mts +1 -0
  23. package/dist/components/button/utils/parse-padding.cjs +90 -0
  24. package/dist/components/button/utils/parse-padding.mjs +90 -0
  25. package/dist/components/button/utils/px-to-pt.cjs +4 -0
  26. package/dist/components/button/utils/px-to-pt.mjs +4 -0
  27. package/dist/components/code-block/code-block.cjs +79 -0
  28. package/dist/components/code-block/code-block.d.cts +20 -0
  29. package/dist/components/code-block/code-block.d.mts +20 -0
  30. package/dist/components/code-block/code-block.mjs +77 -0
  31. package/dist/components/code-block/index.d.cts +3 -0
  32. package/dist/components/code-block/index.d.mts +3 -0
  33. package/dist/components/code-block/languages-available.d.cts +4 -0
  34. package/dist/components/code-block/languages-available.d.mts +4 -0
  35. package/dist/components/code-block/prism.cjs +14564 -0
  36. package/dist/components/code-block/prism.mjs +14562 -0
  37. package/dist/components/code-block/themes.cjs +2578 -0
  38. package/dist/components/code-block/themes.d.cts +4877 -0
  39. package/dist/components/code-block/themes.d.mts +4877 -0
  40. package/dist/components/code-block/themes.mjs +2541 -0
  41. package/dist/components/code-inline/code-inline.cjs +43 -0
  42. package/dist/components/code-inline/code-inline.d.cts +12 -0
  43. package/dist/components/code-inline/code-inline.d.mts +12 -0
  44. package/dist/components/code-inline/code-inline.mjs +41 -0
  45. package/dist/components/code-inline/index.d.cts +1 -0
  46. package/dist/components/code-inline/index.d.mts +1 -0
  47. package/dist/components/column/column.cjs +19 -0
  48. package/dist/components/column/column.d.cts +7 -0
  49. package/dist/components/column/column.d.mts +7 -0
  50. package/dist/components/column/column.mjs +17 -0
  51. package/dist/components/column/index.d.cts +1 -0
  52. package/dist/components/column/index.d.mts +1 -0
  53. package/dist/components/container/container.cjs +42 -0
  54. package/dist/components/container/container.d.cts +7 -0
  55. package/dist/components/container/container.d.mts +7 -0
  56. package/dist/components/container/container.mjs +40 -0
  57. package/dist/components/container/index.d.cts +1 -0
  58. package/dist/components/container/index.d.mts +1 -0
  59. package/dist/components/element-marker.cjs +8 -0
  60. package/dist/components/element-marker.mjs +7 -0
  61. package/dist/components/font/font.cjs +22 -0
  62. package/dist/components/font/font.d.cts +26 -0
  63. package/dist/components/font/font.d.mts +26 -0
  64. package/dist/components/font/font.mjs +21 -0
  65. package/dist/components/font/index.d.cts +1 -0
  66. package/dist/components/font/index.d.mts +1 -0
  67. package/dist/components/head/head.cjs +20 -0
  68. package/dist/components/head/head.d.cts +7 -0
  69. package/dist/components/head/head.d.mts +7 -0
  70. package/dist/components/head/head.mjs +18 -0
  71. package/dist/components/head/index.d.cts +1 -0
  72. package/dist/components/head/index.d.mts +1 -0
  73. package/dist/components/heading/heading.cjs +30 -0
  74. package/dist/components/heading/heading.d.cts +22 -0
  75. package/dist/components/heading/heading.d.mts +22 -0
  76. package/dist/components/heading/heading.mjs +28 -0
  77. package/dist/components/heading/index.d.cts +1 -0
  78. package/dist/components/heading/index.d.mts +1 -0
  79. package/dist/components/heading/utils/as.d.cts +16 -0
  80. package/dist/components/heading/utils/as.d.mts +16 -0
  81. package/dist/components/heading/utils/spaces.cjs +24 -0
  82. package/dist/components/heading/utils/spaces.d.cts +14 -0
  83. package/dist/components/heading/utils/spaces.d.mts +14 -0
  84. package/dist/components/heading/utils/spaces.mjs +24 -0
  85. package/dist/components/hr/hr.cjs +21 -0
  86. package/dist/components/hr/hr.d.cts +7 -0
  87. package/dist/components/hr/hr.d.mts +7 -0
  88. package/dist/components/hr/hr.mjs +19 -0
  89. package/dist/components/hr/index.d.cts +1 -0
  90. package/dist/components/hr/index.d.mts +1 -0
  91. package/dist/components/html/html.cjs +15 -0
  92. package/dist/components/html/html.d.cts +7 -0
  93. package/dist/components/html/html.d.mts +7 -0
  94. package/dist/components/html/html.mjs +13 -0
  95. package/dist/components/html/index.d.cts +1 -0
  96. package/dist/components/html/index.d.mts +1 -0
  97. package/dist/components/img/img.cjs +25 -0
  98. package/dist/components/img/img.d.cts +7 -0
  99. package/dist/components/img/img.d.mts +7 -0
  100. package/dist/components/img/img.mjs +23 -0
  101. package/dist/components/img/index.d.cts +1 -0
  102. package/dist/components/img/index.d.mts +1 -0
  103. package/dist/components/index.d.cts +24 -0
  104. package/dist/components/index.d.mts +24 -0
  105. package/dist/components/link/index.d.cts +1 -0
  106. package/dist/components/link/index.d.mts +1 -0
  107. package/dist/components/link/link.cjs +21 -0
  108. package/dist/components/link/link.d.cts +7 -0
  109. package/dist/components/link/link.d.mts +7 -0
  110. package/dist/components/link/link.mjs +19 -0
  111. package/dist/components/markdown/index.d.cts +1 -0
  112. package/dist/components/markdown/index.d.mts +1 -0
  113. package/dist/components/markdown/markdown.cjs +101 -0
  114. package/dist/components/markdown/markdown.d.cts +16 -0
  115. package/dist/components/markdown/markdown.d.mts +16 -0
  116. package/dist/components/markdown/markdown.mjs +99 -0
  117. package/dist/components/markdown/styles.cjs +92 -0
  118. package/dist/components/markdown/styles.d.cts +31 -0
  119. package/dist/components/markdown/styles.d.mts +31 -0
  120. package/dist/components/markdown/styles.mjs +92 -0
  121. package/dist/components/markdown/utils/parse-css-in-js-to-inline-css.cjs +60 -0
  122. package/dist/components/markdown/utils/parse-css-in-js-to-inline-css.mjs +60 -0
  123. package/dist/components/preview/index.d.cts +1 -0
  124. package/dist/components/preview/index.d.mts +1 -0
  125. package/dist/components/preview/preview.cjs +34 -0
  126. package/dist/components/preview/preview.d.cts +21 -0
  127. package/dist/components/preview/preview.d.mts +21 -0
  128. package/dist/components/preview/preview.mjs +31 -0
  129. package/dist/components/row/index.d.cts +1 -0
  130. package/dist/components/row/index.d.mts +1 -0
  131. package/dist/components/row/row.cjs +30 -0
  132. package/dist/components/row/row.d.cts +11 -0
  133. package/dist/components/row/row.d.mts +11 -0
  134. package/dist/components/row/row.mjs +28 -0
  135. package/dist/components/section/index.d.cts +1 -0
  136. package/dist/components/section/index.d.mts +1 -0
  137. package/dist/components/section/section.cjs +36 -0
  138. package/dist/components/section/section.d.cts +7 -0
  139. package/dist/components/section/section.d.mts +7 -0
  140. package/dist/components/section/section.mjs +34 -0
  141. package/dist/components/tailwind/hooks/use-suspended-promise.cjs +15 -0
  142. package/dist/components/tailwind/hooks/use-suspended-promise.mjs +15 -0
  143. package/dist/components/tailwind/index.d.cts +4 -0
  144. package/dist/components/tailwind/index.d.mts +4 -0
  145. package/dist/components/tailwind/inline-styles.cjs +11 -0
  146. package/dist/components/tailwind/inline-styles.d.cts +6 -0
  147. package/dist/components/tailwind/inline-styles.d.mts +6 -0
  148. package/dist/components/tailwind/inline-styles.mjs +11 -0
  149. package/dist/components/tailwind/sanitize-stylesheet.cjs +11 -0
  150. package/dist/components/tailwind/sanitize-stylesheet.d.cts +6 -0
  151. package/dist/components/tailwind/sanitize-stylesheet.d.mts +6 -0
  152. package/dist/components/tailwind/sanitize-stylesheet.mjs +11 -0
  153. package/dist/components/tailwind/tailwind.cjs +120 -0
  154. package/dist/components/tailwind/tailwind.d.cts +25 -0
  155. package/dist/components/tailwind/tailwind.d.mts +25 -0
  156. package/dist/components/tailwind/tailwind.mjs +117 -0
  157. package/dist/components/tailwind/utils/compatibility/get-react-property.cjs +10 -0
  158. package/dist/components/tailwind/utils/compatibility/get-react-property.mjs +10 -0
  159. package/dist/components/tailwind/utils/compatibility/sanitize-class-name.cjs +26 -0
  160. package/dist/components/tailwind/utils/compatibility/sanitize-class-name.mjs +26 -0
  161. package/dist/components/tailwind/utils/css/downlevel-for-email-clients.cjs +126 -0
  162. package/dist/components/tailwind/utils/css/downlevel-for-email-clients.mjs +125 -0
  163. package/dist/components/tailwind/utils/css/extract-rules-per-class.cjs +45 -0
  164. package/dist/components/tailwind/utils/css/extract-rules-per-class.mjs +44 -0
  165. package/dist/components/tailwind/utils/css/get-custom-properties.cjs +35 -0
  166. package/dist/components/tailwind/utils/css/get-custom-properties.mjs +34 -0
  167. package/dist/components/tailwind/utils/css/is-part-inlinable.cjs +10 -0
  168. package/dist/components/tailwind/utils/css/is-part-inlinable.mjs +9 -0
  169. package/dist/components/tailwind/utils/css/is-rule-inlinable.cjs +10 -0
  170. package/dist/components/tailwind/utils/css/is-rule-inlinable.mjs +9 -0
  171. package/dist/components/tailwind/utils/css/make-inline-styles-for.cjs +52 -0
  172. package/dist/components/tailwind/utils/css/make-inline-styles-for.mjs +51 -0
  173. package/dist/components/tailwind/utils/css/resolve-all-css-variables.cjs +89 -0
  174. package/dist/components/tailwind/utils/css/resolve-all-css-variables.mjs +88 -0
  175. package/dist/components/tailwind/utils/css/resolve-calc-expressions.cjs +92 -0
  176. package/dist/components/tailwind/utils/css/resolve-calc-expressions.mjs +91 -0
  177. package/dist/components/tailwind/utils/css/sanitize-declarations.cjs +310 -0
  178. package/dist/components/tailwind/utils/css/sanitize-declarations.mjs +309 -0
  179. package/dist/components/tailwind/utils/css/sanitize-non-inlinable-rules.cjs +46 -0
  180. package/dist/components/tailwind/utils/css/sanitize-non-inlinable-rules.mjs +45 -0
  181. package/dist/components/tailwind/utils/css/split-mixed-rule.cjs +41 -0
  182. package/dist/components/tailwind/utils/css/split-mixed-rule.mjs +40 -0
  183. package/dist/components/tailwind/utils/css/strip-empty-tailwind-vars.cjs +52 -0
  184. package/dist/components/tailwind/utils/css/strip-empty-tailwind-vars.mjs +51 -0
  185. package/dist/components/tailwind/utils/css/unwrap-value.cjs +7 -0
  186. package/dist/components/tailwind/utils/css/unwrap-value.mjs +7 -0
  187. package/dist/components/tailwind/utils/react/is-component.cjs +7 -0
  188. package/dist/components/tailwind/utils/react/is-component.mjs +7 -0
  189. package/dist/components/tailwind/utils/react/map-react-tree.cjs +32 -0
  190. package/dist/components/tailwind/utils/react/map-react-tree.mjs +30 -0
  191. package/dist/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.cjs +37 -0
  192. package/dist/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.mjs +35 -0
  193. package/dist/components/tailwind/utils/tailwindcss/setup-tailwind.cjs +77 -0
  194. package/dist/components/tailwind/utils/tailwindcss/setup-tailwind.d.cts +19 -0
  195. package/dist/components/tailwind/utils/tailwindcss/setup-tailwind.d.mts +19 -0
  196. package/dist/components/tailwind/utils/tailwindcss/setup-tailwind.mjs +76 -0
  197. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/index.cjs +901 -0
  198. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/index.mjs +901 -0
  199. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/preflight.cjs +398 -0
  200. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/preflight.mjs +398 -0
  201. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/theme.cjs +467 -0
  202. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/theme.mjs +467 -0
  203. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/utilities.cjs +6 -0
  204. package/dist/components/tailwind/utils/tailwindcss/tailwind-stylesheets/utilities.mjs +6 -0
  205. package/dist/components/tailwind/utils/text/from-dash-case-to-camel-case.cjs +6 -0
  206. package/dist/components/tailwind/utils/text/from-dash-case-to-camel-case.mjs +6 -0
  207. package/dist/components/text/index.d.cts +1 -0
  208. package/dist/components/text/index.d.mts +1 -0
  209. package/dist/components/text/text.cjs +43 -0
  210. package/dist/components/text/text.d.cts +7 -0
  211. package/dist/components/text/text.d.mts +7 -0
  212. package/dist/components/text/text.mjs +41 -0
  213. package/dist/components/text/utils/compute-margins.cjs +62 -0
  214. package/dist/components/text/utils/compute-margins.mjs +62 -0
  215. package/dist/index.cjs +95 -0
  216. package/dist/index.d.cts +26 -0
  217. package/dist/index.d.mts +26 -0
  218. package/dist/index.mjs +25 -7326
  219. package/package.json +64 -32
  220. package/src/components/body/__snapshots__/body.spec.tsx.snap +23 -0
  221. package/src/components/body/body.spec.tsx +78 -0
  222. package/src/components/body/body.tsx +62 -0
  223. package/src/components/body/index.ts +1 -0
  224. package/src/components/body/margin-properties.ts +27 -0
  225. package/src/components/button/button.spec.tsx +53 -0
  226. package/src/components/button/button.tsx +114 -0
  227. package/src/components/button/index.ts +1 -0
  228. package/src/components/button/utils/parse-padding.ts +137 -0
  229. package/src/components/button/utils/px-to-pt.ts +4 -0
  230. package/src/components/button/utils/utils.spec.ts +153 -0
  231. package/src/components/code-block/code-block.tsx +136 -0
  232. package/src/components/code-block/index.ts +3 -0
  233. package/src/components/code-block/languages-available.ts +402 -0
  234. package/src/components/code-block/prism.ts +15618 -0
  235. package/src/components/code-block/themes.ts +4929 -0
  236. package/src/components/code-inline/code-inline.tsx +60 -0
  237. package/src/components/code-inline/index.ts +1 -0
  238. package/src/components/column/column.spec.tsx +28 -0
  239. package/src/components/column/column.tsx +17 -0
  240. package/src/components/column/index.ts +1 -0
  241. package/src/components/container/container.spec.tsx +33 -0
  242. package/src/components/container/container.tsx +59 -0
  243. package/src/components/container/index.ts +1 -0
  244. package/src/components/element-marker.ts +5 -0
  245. package/src/components/font/font.spec.tsx +63 -0
  246. package/src/components/font/font.tsx +76 -0
  247. package/src/components/font/index.ts +1 -0
  248. package/src/components/head/head.spec.tsx +34 -0
  249. package/src/components/head/head.tsx +15 -0
  250. package/src/components/head/index.ts +1 -0
  251. package/src/components/heading/heading.spec.tsx +32 -0
  252. package/src/components/heading/heading.tsx +31 -0
  253. package/src/components/heading/index.ts +1 -0
  254. package/src/components/heading/utils/as.ts +26 -0
  255. package/src/components/heading/utils/spaces.ts +64 -0
  256. package/src/components/heading/utils/utils.spec.ts +70 -0
  257. package/src/components/hr/hr.spec.tsx +22 -0
  258. package/src/components/hr/hr.tsx +23 -0
  259. package/src/components/hr/index.ts +1 -0
  260. package/src/components/html/html.spec.tsx +26 -0
  261. package/src/components/html/html.tsx +13 -0
  262. package/src/components/html/index.ts +1 -0
  263. package/src/components/img/img.spec.tsx +30 -0
  264. package/src/components/img/img.tsx +27 -0
  265. package/src/components/img/index.ts +1 -0
  266. package/src/components/index.ts +19 -0
  267. package/src/components/link/index.ts +1 -0
  268. package/src/components/link/link.spec.tsx +37 -0
  269. package/src/components/link/link.tsx +24 -0
  270. package/src/components/markdown/index.ts +1 -0
  271. package/src/components/markdown/markdown.spec.tsx +221 -0
  272. package/src/components/markdown/markdown.tsx +239 -0
  273. package/src/components/markdown/styles.ts +130 -0
  274. package/src/components/markdown/utils/parse-css-in-js-to-inline-css.spec.ts +42 -0
  275. package/src/components/markdown/utils/parse-css-in-js-to-inline-css.ts +73 -0
  276. package/src/components/preview/index.ts +1 -0
  277. package/src/components/preview/preview.spec.tsx +49 -0
  278. package/src/components/preview/preview.tsx +56 -0
  279. package/src/components/row/index.ts +1 -0
  280. package/src/components/row/row.spec.tsx +28 -0
  281. package/src/components/row/row.tsx +33 -0
  282. package/src/components/section/index.ts +1 -0
  283. package/src/components/section/section.spec.tsx +58 -0
  284. package/src/components/section/section.tsx +59 -0
  285. package/src/components/tailwind/e2e/integrations.spec.ts +44 -0
  286. package/src/components/tailwind/e2e/nextjs/README.md +9 -0
  287. package/src/components/tailwind/e2e/nextjs/emails/vercel-invite-user.tsx +151 -0
  288. package/src/components/tailwind/e2e/nextjs/next.config.mjs +6 -0
  289. package/src/components/tailwind/e2e/nextjs/package.json +24 -0
  290. package/src/components/tailwind/e2e/nextjs/src/app/favicon.ico +0 -0
  291. package/src/components/tailwind/e2e/nextjs/src/app/layout.tsx +23 -0
  292. package/src/components/tailwind/e2e/nextjs/src/app/page.tsx +7 -0
  293. package/src/components/tailwind/e2e/nextjs/tsconfig.json +27 -0
  294. package/src/components/tailwind/e2e/vite/README.md +9 -0
  295. package/src/components/tailwind/e2e/vite/emails/vercel-invite-user.tsx +151 -0
  296. package/src/components/tailwind/e2e/vite/index.html +13 -0
  297. package/src/components/tailwind/e2e/vite/package.json +25 -0
  298. package/src/components/tailwind/e2e/vite/public/vite.svg +1 -0
  299. package/src/components/tailwind/e2e/vite/src/App.tsx +10 -0
  300. package/src/components/tailwind/e2e/vite/src/main.tsx +9 -0
  301. package/src/components/tailwind/e2e/vite/src/vite-env.d.ts +1 -0
  302. package/src/components/tailwind/e2e/vite/tsconfig.json +25 -0
  303. package/src/components/tailwind/e2e/vite/tsconfig.node.json +11 -0
  304. package/src/components/tailwind/e2e/vite/vite.config.ts +7 -0
  305. package/src/components/tailwind/hooks/use-suspended-promise.ts +34 -0
  306. package/src/components/tailwind/hooks/use-suspensed-promise.spec.ts +263 -0
  307. package/src/components/tailwind/index.ts +4 -0
  308. package/src/components/tailwind/inline-styles.spec.ts +49 -0
  309. package/src/components/tailwind/inline-styles.ts +21 -0
  310. package/src/components/tailwind/sanitize-stylesheet.ts +10 -0
  311. package/src/components/tailwind/tailwind.spec.tsx +1390 -0
  312. package/src/components/tailwind/tailwind.tsx +178 -0
  313. package/src/components/tailwind/utils/__snapshots__/quick-safe-render-to-string.spec.tsx.snap +3 -0
  314. package/src/components/tailwind/utils/compatibility/escape-class-name.spec.ts +17 -0
  315. package/src/components/tailwind/utils/compatibility/escape-class-name.ts +18 -0
  316. package/src/components/tailwind/utils/compatibility/get-react-property.ts +15 -0
  317. package/src/components/tailwind/utils/compatibility/sanitize-class-name.spec.ts +7 -0
  318. package/src/components/tailwind/utils/compatibility/sanitize-class-name.ts +36 -0
  319. package/src/components/tailwind/utils/compatibility/unescape-class.ts +3 -0
  320. package/src/components/tailwind/utils/css/__snapshots__/extract-rules-matching-classes.spec.ts.snap +23 -0
  321. package/src/components/tailwind/utils/css/__snapshots__/remove-rule-duplicates-from-root.spec.ts.snap +3 -0
  322. package/src/components/tailwind/utils/css/__snapshots__/sanitize-non-inlinable-classes.spec.ts.snap +7 -0
  323. package/src/components/tailwind/utils/css/downlevel-for-email-clients.spec.ts +119 -0
  324. package/src/components/tailwind/utils/css/downlevel-for-email-clients.ts +244 -0
  325. package/src/components/tailwind/utils/css/extract-rules-per-class.spec.ts +232 -0
  326. package/src/components/tailwind/utils/css/extract-rules-per-class.ts +76 -0
  327. package/src/components/tailwind/utils/css/get-custom-properties.ts +49 -0
  328. package/src/components/tailwind/utils/css/is-part-inlinable.ts +15 -0
  329. package/src/components/tailwind/utils/css/is-rule-inlinable.ts +15 -0
  330. package/src/components/tailwind/utils/css/make-inline-styles-for.spec.ts +124 -0
  331. package/src/components/tailwind/utils/css/make-inline-styles-for.ts +73 -0
  332. package/src/components/tailwind/utils/css/resolve-all-css-variables.spec.ts +296 -0
  333. package/src/components/tailwind/utils/css/resolve-all-css-variables.ts +236 -0
  334. package/src/components/tailwind/utils/css/resolve-calc-expressions.spec.ts +36 -0
  335. package/src/components/tailwind/utils/css/resolve-calc-expressions.ts +147 -0
  336. package/src/components/tailwind/utils/css/sanitize-declarations.spec.ts +491 -0
  337. package/src/components/tailwind/utils/css/sanitize-declarations.ts +443 -0
  338. package/src/components/tailwind/utils/css/sanitize-non-inlinable-rules.spec.ts +72 -0
  339. package/src/components/tailwind/utils/css/sanitize-non-inlinable-rules.ts +48 -0
  340. package/src/components/tailwind/utils/css/split-mixed-rule.ts +68 -0
  341. package/src/components/tailwind/utils/css/strip-empty-tailwind-vars.spec.ts +128 -0
  342. package/src/components/tailwind/utils/css/strip-empty-tailwind-vars.ts +70 -0
  343. package/src/components/tailwind/utils/css/unwrap-value.ts +9 -0
  344. package/src/components/tailwind/utils/react/is-component.ts +12 -0
  345. package/src/components/tailwind/utils/react/map-react-tree.spec.tsx +58 -0
  346. package/src/components/tailwind/utils/react/map-react-tree.ts +58 -0
  347. package/src/components/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.ts +61 -0
  348. package/src/components/tailwind/utils/tailwindcss/setup-tailwind.spec.ts +25 -0
  349. package/src/components/tailwind/utils/tailwindcss/setup-tailwind.ts +121 -0
  350. package/src/components/tailwind/utils/tailwindcss/tailwind-stylesheets/index.ts +900 -0
  351. package/src/components/tailwind/utils/tailwindcss/tailwind-stylesheets/preflight.ts +397 -0
  352. package/src/components/tailwind/utils/tailwindcss/tailwind-stylesheets/theme.ts +466 -0
  353. package/src/components/tailwind/utils/tailwindcss/tailwind-stylesheets/utilities.ts +5 -0
  354. package/src/components/tailwind/utils/text/from-dash-case-to-camel-case.ts +3 -0
  355. package/src/components/text/index.ts +1 -0
  356. package/src/components/text/text.spec.tsx +35 -0
  357. package/src/components/text/text.tsx +50 -0
  358. package/src/components/text/utils/compute-margins.spec.ts +67 -0
  359. package/src/components/text/utils/compute-margins.ts +102 -0
  360. package/src/index.ts +2 -110
  361. package/tsconfig.json +9 -4
  362. package/tsdown.config.ts +51 -6
  363. package/vitest.config.ts +1 -0
  364. package/vitest.e2e.config.ts +17 -0
  365. package/src/commands/build.ts +0 -269
  366. package/src/commands/dev.ts +0 -27
  367. package/src/commands/export.ts +0 -204
  368. package/src/commands/resend/reset.ts +0 -8
  369. package/src/commands/resend/setup.ts +0 -29
  370. package/src/commands/start.ts +0 -38
  371. package/src/utils/conf.ts +0 -9
  372. package/src/utils/esbuild/escape-string-for-regex.ts +0 -3
  373. package/src/utils/esbuild/renderring-utilities-exporter.ts +0 -63
  374. package/src/utils/get-emails-directory-metadata.spec.ts +0 -82
  375. package/src/utils/get-emails-directory-metadata.ts +0 -140
  376. package/src/utils/get-preview-server-location.ts +0 -50
  377. package/src/utils/index.ts +0 -2
  378. package/src/utils/packageJson.ts +0 -4
  379. package/src/utils/preview/get-env-variables-for-preview-app.ts +0 -20
  380. package/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts +0 -226
  381. package/src/utils/preview/hot-reloading/create-dependency-graph.ts +0 -343
  382. package/src/utils/preview/hot-reloading/get-imported-modules.spec.ts +0 -151
  383. package/src/utils/preview/hot-reloading/get-imported-modules.ts +0 -49
  384. package/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts +0 -11
  385. package/src/utils/preview/hot-reloading/resolve-path-aliases.ts +0 -32
  386. package/src/utils/preview/hot-reloading/setup-hot-reloading.ts +0 -121
  387. package/src/utils/preview/hot-reloading/test/dependency-graph/inner/data-to-import.json +0 -1
  388. package/src/utils/preview/hot-reloading/test/dependency-graph/inner/file-a.ts +0 -5
  389. package/src/utils/preview/hot-reloading/test/dependency-graph/inner/file-b.ts +0 -5
  390. package/src/utils/preview/hot-reloading/test/dependency-graph/inner/general-importing-file.ts +0 -9
  391. package/src/utils/preview/hot-reloading/test/dependency-graph/inner/outer-dependency.ts +0 -3
  392. package/src/utils/preview/hot-reloading/test/dependency-graph/inner/path-aliases.ts +0 -1
  393. package/src/utils/preview/hot-reloading/test/dependency-graph/outer.ts +0 -5
  394. package/src/utils/preview/hot-reloading/test/some-file.ts +0 -0
  395. package/src/utils/preview/hot-reloading/test/tsconfig.json +0 -8
  396. package/src/utils/preview/index.ts +0 -2
  397. package/src/utils/preview/serve-static-file.ts +0 -51
  398. package/src/utils/preview/start-dev-server.ts +0 -252
  399. package/src/utils/register-spinner-autostopping.ts +0 -28
  400. package/src/utils/style-text.ts +0 -11
  401. package/src/utils/tree.spec.ts +0 -29
  402. package/src/utils/tree.ts +0 -76
  403. package/src/utils/types/hot-reload-change.ts +0 -6
  404. package/src/utils/types/hot-reload-event.ts +0 -3
@@ -0,0 +1,1390 @@
1
+ import { pretty, render } from '@react-email/render';
2
+ import { ResponsiveColumn, ResponsiveRow } from '@responsive-email/react-email';
3
+ import React from 'react';
4
+ import plugin from 'tailwindcss/plugin';
5
+ import { Body } from '../body/index.js';
6
+ import { Button } from '../button/index.js';
7
+ import { Column } from '../column/index.js';
8
+ import { Head } from '../head/index.js';
9
+ import { Heading } from '../heading/index.js';
10
+ import { Hr } from '../hr/index.js';
11
+ import { Html } from '../html/index.js';
12
+ import { Link } from '../link/index.js';
13
+ import { Row } from '../row/index.js';
14
+ import { Section } from '../section/index.js';
15
+ import type { TailwindConfig } from './tailwind.js';
16
+ import { Tailwind } from './tailwind.js';
17
+
18
+ describe('Tailwind component', () => {
19
+ const headMissingError =
20
+ 'Tailwind: <head> not found inside <Tailwind>.\nMove <Head /> inside <Tailwind>, or remove these classes that require a <head>:';
21
+
22
+ it('allows for complex children manipulation', async () => {
23
+ const actualOutput = await render(
24
+ <Tailwind>
25
+ <ResponsiveRow>
26
+ <ResponsiveColumn>This is the first column</ResponsiveColumn>
27
+ <ResponsiveColumn>This is the second column</ResponsiveColumn>
28
+ </ResponsiveRow>
29
+ </Tailwind>,
30
+ );
31
+ expect(actualOutput).toMatchInlineSnapshot(
32
+ `"<!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><!--/$-->"`,
33
+ );
34
+ });
35
+
36
+ it('works with blocklist', async () => {
37
+ const actualOutput = await render(
38
+ <Tailwind
39
+ config={{
40
+ blocklist: ['bg-blue-600'],
41
+ }}
42
+ >
43
+ <Head />
44
+ <body>
45
+ <button type="button" className="bg-blue-600 md:p-4">
46
+ Click me
47
+ </button>
48
+ </body>
49
+ </Tailwind>,
50
+ { pretty: true },
51
+ );
52
+
53
+ expect(actualOutput).toMatchInlineSnapshot(`
54
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
55
+ <head>
56
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
57
+ <meta name="x-apple-disable-message-reformatting" />
58
+ <style>
59
+ @media (min-width:48rem){.md_p-4{padding:1rem!important}}
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <!--$--><!--head--><!--body--><button
64
+ type="button"
65
+ class="bg-blue-600 md_p-4">
66
+ Click me</button
67
+ ><!--/$-->
68
+ </body>
69
+ "
70
+ `);
71
+ });
72
+
73
+ it('works with shadows', async () => {
74
+ expect(
75
+ await render(
76
+ <Tailwind>
77
+ <div className="shadow-[#555] shadow">shadow around here</div>
78
+ </Tailwind>,
79
+ ).then(pretty),
80
+ ).toMatchInlineSnapshot(`
81
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
82
+ <!--$-->
83
+ <div
84
+ style="box-shadow:0 0 rgb(0,0,0,0),0 0 rgb(0,0,0,0),0 0 rgb(0,0,0,0),0 0 rgb(0,0,0,0),0 1px 3px 0 rgb(85,85,85,100%),0 1px 2px -1px rgb(85,85,85,100%)">
85
+ shadow around here
86
+ </div>
87
+ <!--/$-->
88
+ "
89
+ `);
90
+ });
91
+
92
+ it('works with class manipulation done on components', async () => {
93
+ const MyComponnt = (props: {
94
+ className?: string;
95
+ style?: React.CSSProperties;
96
+ }) => {
97
+ expect(
98
+ props.style,
99
+ 'styles should not be generated for a component',
100
+ ).toBeUndefined();
101
+ expect(props.className).toBe('p-4 text-blue-400');
102
+ return (
103
+ <div
104
+ className={`${props.className} bg-red-500`}
105
+ style={{ ...props.style, padding: '4px' }}
106
+ />
107
+ );
108
+ };
109
+
110
+ expect(
111
+ await render(
112
+ <Tailwind>
113
+ <MyComponnt className="p-4 text-blue-400" />
114
+ </Tailwind>,
115
+ ),
116
+ ).toMatchInlineSnapshot(
117
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="padding:4px;color:rgb(81,162,255);background-color:rgb(251,44,54)"></div><!--/$-->"`,
118
+ );
119
+ });
120
+
121
+ it("works properly with 'no-underline'", async () => {
122
+ const actualOutput = await render(
123
+ <Html>
124
+ <body>
125
+ <Tailwind>
126
+ <p className="text-[14px] text-black leading-[24px]">
127
+ or copy and paste this URL into your browser:{' '}
128
+ <Link
129
+ className="other text-blue-600 no-underline"
130
+ href="https://react.email"
131
+ >
132
+ https://react.email
133
+ </Link>
134
+ </p>
135
+ <p className="text-[14px] text-black leading-[24px]">
136
+ or copy and paste this URL into your browser:{' '}
137
+ <Link
138
+ className="text-blue-600 no-underline"
139
+ href="https://react.email"
140
+ >
141
+ https://react.email
142
+ </Link>
143
+ </p>
144
+ </Tailwind>
145
+ </body>
146
+ </Html>,
147
+ );
148
+ expect(actualOutput).toMatchInlineSnapshot(
149
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head></head><body><!--$--><!--html--><!--body--><p style="font-size:14px;color:rgb(0,0,0);line-height:24px">or copy and paste this URL into your browser:<!-- --> <a class="other" href="https://react.email" style="color:rgb(21,93,252);text-decoration-line:none" target="_blank">https://react.email</a></p><p style="font-size:14px;color:rgb(0,0,0);line-height:24px">or copy and paste this URL into your browser:<!-- --> <a href="https://react.email" style="color:rgb(21,93,252);text-decoration-line:none" target="_blank">https://react.email</a></p><!--/$--></body></html>"`,
150
+ );
151
+ });
152
+
153
+ it('renders children with inline Tailwind styles', async () => {
154
+ const actualOutput = await render(
155
+ <Tailwind>
156
+ <div className="bg-white" />
157
+ </Tailwind>,
158
+ );
159
+
160
+ expect(actualOutput).toMatchInlineSnapshot(
161
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="background-color:rgb(255,255,255)"></div><!--/$-->"`,
162
+ );
163
+ });
164
+
165
+ test('<Button className="px-3 py-2 mt-8 text-sm text-gray-200 bg-blue-600 rounded-md">', async () => {
166
+ const actualOutput = await render(
167
+ <Tailwind>
168
+ <Button className="mt-8 rounded-md bg-blue-600 px-3 py-2 text-gray-200 text-sm">
169
+ Testing button
170
+ </Button>
171
+ Testing
172
+ </Tailwind>,
173
+ );
174
+
175
+ expect(actualOutput).toMatchInlineSnapshot(
176
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a style="line-height:1.4285714285714286;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;margin-top:2rem;border-radius:0.375rem;background-color:rgb(21,93,252);padding-right:12px;padding-left:12px;padding-bottom:8px;padding-top:8px;color:rgb(229,231,235);font-size:0.875rem" target="_blank"><span><!--[if mso]><i style="mso-font-width:300%;mso-text-raise:12px" 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<!--/$-->"`,
177
+ );
178
+ });
179
+
180
+ it('works with custom components with fragment at the root', async () => {
181
+ const Wrapper = (props: { children: React.ReactNode }) => {
182
+ return <Tailwind>{props.children}</Tailwind>;
183
+ };
184
+
185
+ const Brand = () => {
186
+ return (
187
+ <>
188
+ <div className="p-[20px]">
189
+ <p className="font-bold text-[50px]">React Email</p>
190
+ </div>
191
+ <div className="p-[20px]">
192
+ <p className="font-bold text-[50px]">React Email</p>
193
+ </div>
194
+ </>
195
+ );
196
+ };
197
+
198
+ const EmailTemplate = () => {
199
+ return (
200
+ <Wrapper>
201
+ <div className="mt-[100px] text-[50px] leading-[1]">Hello world</div>
202
+ <Brand />
203
+ </Wrapper>
204
+ );
205
+ };
206
+
207
+ const actualOutput = await render(EmailTemplate());
208
+
209
+ expect(actualOutput).toMatchInlineSnapshot(
210
+ `"<!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><!--/$-->"`,
211
+ );
212
+ });
213
+
214
+ it("doesn't generate styles from text", async () => {
215
+ expect(
216
+ await render(<Tailwind>container bg-red-500 bg-blue-300</Tailwind>),
217
+ ).toMatchInlineSnapshot(
218
+ `"<!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<!--/$-->"`,
219
+ );
220
+ });
221
+
222
+ it('works with components that return children', async () => {
223
+ const Wrapper = (props: { children: React.ReactNode }) => {
224
+ return <Tailwind>{props.children}</Tailwind>;
225
+ };
226
+
227
+ const Brand = () => {
228
+ return (
229
+ <div className="p-[20px]">
230
+ <p className="font-bold text-[50px]">React Email</p>
231
+ </div>
232
+ );
233
+ };
234
+
235
+ const EmailTemplate = () => {
236
+ return (
237
+ <Wrapper>
238
+ <div className="mt-[100px] text-[50px] leading-[1]">Hello world</div>
239
+ <Brand />
240
+ </Wrapper>
241
+ );
242
+ };
243
+
244
+ const actualOutput = await render(EmailTemplate());
245
+
246
+ expect(actualOutput).toMatchInlineSnapshot(
247
+ `"<!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><!--/$-->"`,
248
+ );
249
+ });
250
+
251
+ it('works with Heading component', async () => {
252
+ const EmailTemplate = () => {
253
+ return (
254
+ <Tailwind>
255
+ Hello
256
+ <Heading>My testing heading</Heading>
257
+ friends
258
+ </Tailwind>
259
+ );
260
+ };
261
+
262
+ expect(await render(<EmailTemplate />)).toMatchInlineSnapshot(
263
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$-->Hello<h1>My testing heading</h1>friends<!--/$-->"`,
264
+ );
265
+ });
266
+
267
+ it('routes Tailwind padding on <Section> to the inner <td>', async () => {
268
+ const html = await render(
269
+ <Tailwind>
270
+ <Section className="bg-white p-4">x</Section>
271
+ </Tailwind>,
272
+ );
273
+
274
+ expect(html).toContain('<td style="padding:1rem">');
275
+ expect(html).not.toMatch(/<table[^>]*style="[^"]*padding:1rem/);
276
+ });
277
+
278
+ it('inlines Tailwind classes on <Column> onto its <td>', async () => {
279
+ const html = await render(
280
+ <Tailwind>
281
+ <Row>
282
+ <Column className="bg-white p-4">x</Column>
283
+ </Row>
284
+ </Tailwind>,
285
+ );
286
+
287
+ expect(html).toMatch(
288
+ /<td[^>]*data-id="__react-email-column"[^>]*style="[^"]*padding:1rem/,
289
+ );
290
+ });
291
+
292
+ it('inlines Tailwind classes on <Row> onto its <table>', async () => {
293
+ const html = await render(
294
+ <Tailwind>
295
+ <Row className="bg-white p-4">
296
+ <Column>x</Column>
297
+ </Row>
298
+ </Tailwind>,
299
+ );
300
+
301
+ expect(html).toMatch(
302
+ /<table[^>]*role="presentation"[^>]*style="[^"]*padding:1rem/,
303
+ );
304
+ });
305
+
306
+ it('works with components that use React.forwardRef', async () => {
307
+ const Wrapper = (props: { children: React.ReactNode }) => {
308
+ return <Tailwind>{props.children}</Tailwind>;
309
+ };
310
+
311
+ const Brand = React.forwardRef<HTMLDivElement>((ref, props) => {
312
+ return (
313
+ <div
314
+ className="p-[20px]"
315
+ ref={ref as React.LegacyRef<HTMLDivElement>}
316
+ {...props}
317
+ >
318
+ <p className="font-bold text-[50px]">React Email</p>
319
+ </div>
320
+ );
321
+ });
322
+ Brand.displayName = 'Brand';
323
+
324
+ const EmailTemplate = () => {
325
+ return (
326
+ <Wrapper>
327
+ <div className="mt-[100px] text-[50px] leading-[1]">Hello world</div>
328
+ <Brand />
329
+ </Wrapper>
330
+ );
331
+ };
332
+
333
+ const actualOutput = await render(EmailTemplate());
334
+
335
+ expect(actualOutput).toMatchInlineSnapshot(
336
+ `"<!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><!--/$-->"`,
337
+ );
338
+ });
339
+
340
+ it('uses background image', async () => {
341
+ const actualOutput = await render(
342
+ <Tailwind>
343
+ <div className="bg-[url(https://example.com/image.png)]" />
344
+ </Tailwind>,
345
+ );
346
+
347
+ expect(actualOutput).toMatchInlineSnapshot(
348
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="background-image:url(https://example.com/image.png)"></div><!--/$-->"`,
349
+ );
350
+ });
351
+
352
+ it('does not override inline styles with Tailwind styles', async () => {
353
+ const actualOutput = await render(
354
+ <Tailwind>
355
+ <div
356
+ className="bg-black text-[16px]"
357
+ style={{ backgroundColor: 'red', fontSize: '12px' }}
358
+ />
359
+ </Tailwind>,
360
+ );
361
+
362
+ expect(actualOutput).toMatchInlineSnapshot(
363
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="background-color:red;font-size:12px"></div><!--/$-->"`,
364
+ );
365
+ });
366
+
367
+ it('overrides component styles with Tailwind styles', async () => {
368
+ const actualOutput = await render(
369
+ <Tailwind>
370
+ <Hr className="w-12" />
371
+ </Tailwind>,
372
+ );
373
+
374
+ expect(actualOutput).toMatchInlineSnapshot(
375
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><hr style="width:3rem;border:none;border-color:transparent;border-top:1px solid #eaeaea"/><!--/$-->"`,
376
+ );
377
+ });
378
+
379
+ it('preserves mso styles', async () => {
380
+ const actualOutput = await render(
381
+ <Html>
382
+ <Tailwind>
383
+ <Head />
384
+ <span
385
+ dangerouslySetInnerHTML={{
386
+ __html: `<!--[if mso]><i style="letter-spacing: 10px;mso-font-width:-100%;" hidden>&nbsp;</i><![endif]-->`,
387
+ }}
388
+ />
389
+ <div className="custom-class bg-white sm:bg-red-50 sm:text-sm md:text-lg" />
390
+ </Tailwind>
391
+ </Html>,
392
+ ).then(pretty);
393
+
394
+ expect(actualOutput).toMatchInlineSnapshot(`
395
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
396
+ <html dir="ltr" lang="en">
397
+ <head>
398
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
399
+ <meta name="x-apple-disable-message-reformatting" />
400
+ <style>
401
+ @media (min-width:40rem){.sm_bg-red-50{background-color:rgb(254,242,242)!important}}@media (min-width:40rem){.sm_text-sm{font-size:0.875rem!important;line-height:1.4285714285714286!important}}@media (min-width:48rem){.md_text-lg{font-size:1.125rem!important;line-height:1.5555555555555556!important}}
402
+ </style></head
403
+ ><!--$--><!--html--><!--head--><span
404
+ ><!--[if mso]><i style="letter-spacing: 10px;mso-font-width:-100%;" hidden>&nbsp;</i><![endif]--></span
405
+ >
406
+ <div
407
+ class="custom-class sm_bg-red-50 sm_text-sm md_text-lg"
408
+ style="background-color:rgb(255,255,255)"></div>
409
+ <!--/$-->
410
+ </html>
411
+ "
412
+ `);
413
+ });
414
+
415
+ // See https://github.com/resend/react-email/issues/2388
416
+ it('properly does not inline custom utilities', async () => {
417
+ const actualOutput = await render(
418
+ <Tailwind
419
+ config={{
420
+ plugins: [
421
+ plugin(({ addUtilities }) => {
422
+ addUtilities({
423
+ '.text-body': {
424
+ '@apply text-[green] dark:text-[orange]': {},
425
+ },
426
+ });
427
+ }),
428
+ ],
429
+ }}
430
+ >
431
+ <Html>
432
+ <Head />
433
+ <Body className="text-body">this is the body</Body>
434
+ </Html>
435
+ </Tailwind>,
436
+ );
437
+
438
+ expect(actualOutput).toMatchInlineSnapshot(
439
+ `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><style>@media (prefers-color-scheme:dark){.text-body{color:orange!important}}</style></head><body class="text-body" dir="ltr" lang="en"><!--$--><!--html--><!--head--><!--body--><table border="0" width="100%" cellPadding="0" cellSpacing="0" role="presentation" align="center"><tbody><tr><td dir="ltr" lang="en" style="color:green">this is the body</td></tr></tbody></table><!--/$--></body></html>"`,
440
+ );
441
+ });
442
+
443
+ // See https://github.com/resend/react-email/pull/3594
444
+ it('does not leak a bare nested group/peer rule into the <head> <style>', async () => {
445
+ const html = await render(
446
+ <Html>
447
+ <Tailwind>
448
+ <Head />
449
+ <div className="group">
450
+ <a className="group-hover:underline" href="https://react.email">
451
+ link
452
+ </a>
453
+ </div>
454
+ </Tailwind>
455
+ </Html>,
456
+ ).then(pretty);
457
+
458
+ expect(html).toMatchInlineSnapshot(`
459
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
460
+ <html dir="ltr" lang="en">
461
+ <head>
462
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
463
+ <meta name="x-apple-disable-message-reformatting" />
464
+ <style>
465
+ .group-hover_underline{@media (hover:hover){&:is(:where(.group):hover *){text-decoration-line:underline!important}}}
466
+ </style></head
467
+ ><!--$--><!--html--><!--head-->
468
+ <div class="group">
469
+ <a class="group-hover_underline" href="https://react.email">link</a>
470
+ </div>
471
+ <!--/$-->
472
+ </html>
473
+ "
474
+ `);
475
+ });
476
+
477
+ it('recognizes custom responsive screen', async () => {
478
+ const actualOutput = await render(
479
+ <Html>
480
+ <Tailwind
481
+ config={{
482
+ theme: {
483
+ screens: {
484
+ sm: { min: '640px' },
485
+ md: { min: '768px' },
486
+ lg: { min: '1024px' },
487
+ xl: { min: '1280px' },
488
+ '2xl': { min: '1536px' },
489
+ },
490
+ },
491
+ }}
492
+ >
493
+ <Head />
494
+ <div className="bg-red-100 xl:bg-green-500">Test</div>
495
+ <div className="2xl:bg-blue-500">Test</div>
496
+ </Tailwind>
497
+ </Html>,
498
+ ).then(pretty);
499
+
500
+ expect(actualOutput).toMatchInlineSnapshot(`
501
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
502
+ <html dir="ltr" lang="en">
503
+ <head>
504
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
505
+ <meta name="x-apple-disable-message-reformatting" />
506
+ <style>
507
+ @media (min-width:1280px){.xl_bg-green-500{background-color:rgb(0,201,80)!important}}@media (min-width:1536px){.twoxl_bg-blue-500{background-color:rgb(43,127,255)!important}}
508
+ </style></head
509
+ ><!--$--><!--html--><!--head-->
510
+ <div class="xl_bg-green-500" style="background-color:rgb(255,226,226)">
511
+ Test
512
+ </div>
513
+ <div class="twoxl_bg-blue-500">Test</div>
514
+ <!--/$-->
515
+ </html>
516
+ "
517
+ `);
518
+ });
519
+
520
+ it('works with calc() with + sign', async () => {
521
+ const actualOutput = await render(
522
+ <Tailwind>
523
+ <head />
524
+ <div className="max-h-[calc(50px+3rem)] bg-red-100 lg:max-h-[calc(50px+5rem)]">
525
+ <div className="h-[200px]">something tall</div>
526
+ </div>
527
+ </Tailwind>,
528
+ ).then(pretty);
529
+
530
+ expect(actualOutput).toMatchInlineSnapshot(`
531
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
532
+ <head>
533
+ <style>
534
+ @media (min-width:64rem){.lg_max-h-calc50pxplus5rem{max-height:calc(50px + 5rem)!important}}
535
+ </style></head
536
+ ><!--$--><!--head-->
537
+ <div
538
+ class="lg_max-h-calc50pxplus5rem"
539
+ style="max-height:calc(50px + 3rem);background-color:rgb(255,226,226)">
540
+ <div style="height:200px">something tall</div>
541
+ </div>
542
+ <!--/$-->
543
+ "
544
+ `);
545
+ });
546
+
547
+ describe('with non-inlinable styles', () => {
548
+ /*
549
+ This test is because of https://github.com/resend/react-email/issues/1112
550
+ which was being caused because we required to, either have our <Head> component,
551
+ or a <head> element directly inside the <Tailwind> component for media queries to be applied
552
+ onto. The problem with this approach was that the check to see if an element was an instance of
553
+ the <Head> component fails after minification as we did it by the function name.
554
+
555
+ The best solution is to check for the Head element on arbitrarily deep levels of the React tree
556
+ and apply the styles there. This also fixes the issue where it would not be allowed to use
557
+ Tailwind classes on the <html> element as the <head> would be required directly bellow Tailwind.
558
+ */
559
+ it('works with arbitrarily deep (in the React tree) <head> elements', async () => {
560
+ expect(
561
+ await render(
562
+ <Tailwind>
563
+ <html lang="en">
564
+ <head />
565
+ <body>
566
+ <div className="bg-red-200 sm:bg-red-300 md:bg-red-400 lg:bg-red-500" />
567
+ </body>
568
+ </html>
569
+ </Tailwind>,
570
+ ).then(pretty),
571
+ ).toMatchInlineSnapshot(`
572
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
573
+ <html lang="en">
574
+ <head>
575
+ <style>
576
+ @media (min-width:40rem){.sm_bg-red-300{background-color:rgb(255,162,162)!important}}@media (min-width:48rem){.md_bg-red-400{background-color:rgb(255,100,103)!important}}@media (min-width:64rem){.lg_bg-red-500{background-color:rgb(251,44,54)!important}}
577
+ </style>
578
+ </head>
579
+ <body>
580
+ <!--$--><!--html--><!--head--><!--body-->
581
+ <div
582
+ class="sm_bg-red-300 md_bg-red-400 lg_bg-red-500"
583
+ style="background-color:rgb(255,201,201)"></div>
584
+ <!--/$-->
585
+ </body>
586
+ </html>
587
+ "
588
+ `);
589
+
590
+ const MyHead = (props: Record<string, any>) => {
591
+ return <head {...props} />;
592
+ };
593
+
594
+ expect(
595
+ await render(
596
+ <Tailwind>
597
+ <html lang="en">
598
+ <MyHead />
599
+ <body>
600
+ <div className="bg-red-200 sm:bg-red-300 md:bg-red-400 lg:bg-red-500" />
601
+ </body>
602
+ </html>
603
+ </Tailwind>,
604
+ ),
605
+ ).toMatchInlineSnapshot(
606
+ `"<!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:40rem){.sm_bg-red-300{background-color:rgb(255,162,162)!important}}@media (min-width:48rem){.md_bg-red-400{background-color:rgb(255,100,103)!important}}@media (min-width:64rem){.lg_bg-red-500{background-color:rgb(251,44,54)!important}}</style></head><body><!--$--><!--html--><!--head--><!--body--><div class="sm_bg-red-300 md_bg-red-400 lg_bg-red-500" style="background-color:rgb(255,201,201)"></div><!--/$--></body></html>"`,
607
+ );
608
+ });
609
+
610
+ it('handles non-inlinable styles in custom utilities', async () => {
611
+ const actualOutput = await render(
612
+ <html lang="en">
613
+ <Tailwind
614
+ config={{
615
+ plugins: [
616
+ {
617
+ handler: (api) => {
618
+ api.addUtilities({
619
+ '.text-body': {
620
+ '@apply text-[green] sm:text-[darkgreen]': {},
621
+ },
622
+ });
623
+ },
624
+ },
625
+ ],
626
+ }}
627
+ >
628
+ <head />
629
+ <body>
630
+ <div className="text-body" />
631
+ </body>
632
+ </Tailwind>
633
+ </html>,
634
+ ).then(pretty);
635
+ expect(actualOutput).toMatchInlineSnapshot(`
636
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
637
+ <html lang="en">
638
+ <head>
639
+ <style>
640
+ @media (min-width:40rem){.text-body{color:darkgreen!important}}
641
+ </style>
642
+ </head>
643
+ <body>
644
+ <!--$--><!--html--><!--head--><!--body-->
645
+ <div class="text-body" style="color:green"></div>
646
+ <!--/$-->
647
+ </body>
648
+ </html>
649
+ "
650
+ `);
651
+ });
652
+
653
+ it('adds css to <head/> and keep class names', async () => {
654
+ const actualOutput = await render(
655
+ <html lang="en">
656
+ <Tailwind>
657
+ <head />
658
+ <body>
659
+ <div className="bg-red-200 hover:bg-red-600 focus:bg-red-700 sm:bg-red-300 sm:hover:bg-red-200 md:bg-red-400 lg:bg-red-500" />
660
+ </body>
661
+ </Tailwind>
662
+ </html>,
663
+ ).then(pretty);
664
+
665
+ expect(actualOutput).toMatchInlineSnapshot(`
666
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
667
+ <html lang="en">
668
+ <head>
669
+ <style>
670
+ .hover_bg-red-600{@media (hover:hover){&:hover{background-color:rgb(231,0,11)!important}}}.focus_bg-red-700{&:focus{background-color:rgb(193,0,7)!important}}@media (min-width:40rem){.sm_bg-red-300{background-color:rgb(255,162,162)!important}}@media (min-width:40rem){.sm_hover_bg-red-200{@media (hover:hover){&:hover{background-color:rgb(255,201,201)!important}}}}@media (min-width:48rem){.md_bg-red-400{background-color:rgb(255,100,103)!important}}@media (min-width:64rem){.lg_bg-red-500{background-color:rgb(251,44,54)!important}}
671
+ </style>
672
+ </head>
673
+ <body>
674
+ <!--$--><!--html--><!--head--><!--body-->
675
+ <div
676
+ class="hover_bg-red-600 focus_bg-red-700 sm_bg-red-300 sm_hover_bg-red-200 md_bg-red-400 lg_bg-red-500"
677
+ style="background-color:rgb(255,201,201)"></div>
678
+ <!--/$-->
679
+ </body>
680
+ </html>
681
+ "
682
+ `);
683
+ });
684
+
685
+ it('throws error when used without the head and with media query class names very deeply nested', async () => {
686
+ const Component1 = (props: Record<string, any>) => {
687
+ return (
688
+ <div {...props} className="h-30 w-40 sm:h-10 sm:w-10">
689
+ {props.children}
690
+ </div>
691
+ );
692
+ };
693
+ const Component2 = (props: Record<string, any>) => {
694
+ return (
695
+ <div {...props}>
696
+ <Component1>{props.children}</Component1>
697
+ </div>
698
+ );
699
+ };
700
+ const Component3 = (props: Record<string, any>) => {
701
+ return (
702
+ <div {...props}>
703
+ <Component2>{props.children}</Component2>
704
+ </div>
705
+ );
706
+ };
707
+
708
+ function renderComplexEmailWithoutHead() {
709
+ return render(
710
+ <Tailwind>
711
+ <div className="bg-red-300">
712
+ <Component3 className="random-classname w-full">
713
+ <div className="w-50">Testing</div>
714
+ </Component3>
715
+ </div>
716
+ </Tailwind>,
717
+ );
718
+ }
719
+
720
+ await expect(renderComplexEmailWithoutHead).rejects.toThrow(
721
+ `${headMissingError} sm:h-10 sm:w-10.`,
722
+ );
723
+ });
724
+
725
+ it('works with relatively complex media query utilities', async () => {
726
+ const Email = () => {
727
+ return (
728
+ <Tailwind>
729
+ <Head />
730
+ <p className="text-blue-700 max-sm:text-red-600">I am some text</p>
731
+ </Tailwind>
732
+ );
733
+ };
734
+
735
+ expect(await render(<Email />).then(pretty)).toMatchInlineSnapshot(`
736
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
737
+ <head>
738
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
739
+ <meta name="x-apple-disable-message-reformatting" />
740
+ <style>
741
+ @media (max-width:40rem){.max-sm_text-red-600{color:rgb(231,0,11)!important}}
742
+ </style></head
743
+ ><!--$--><!--head-->
744
+ <p class="max-sm_text-red-600" style="color:rgb(20,71,230)">I am some text</p>
745
+ <!--/$-->
746
+ "
747
+ `);
748
+ });
749
+
750
+ it('throws a clear error when <Head> is outside <Tailwind> and dark: classes are used', async () => {
751
+ function renderEmailWithHeadOutsideTailwind() {
752
+ return render(
753
+ <Html>
754
+ <Head />
755
+ <Tailwind>
756
+ <Body className="dark:bg-white dark:text-gray-100">
757
+ this is the body
758
+ </Body>
759
+ </Tailwind>
760
+ </Html>,
761
+ );
762
+ }
763
+
764
+ await expect(renderEmailWithHeadOutsideTailwind).rejects.toThrow(
765
+ `${headMissingError} dark:bg-white dark:text-gray-100.`,
766
+ );
767
+ });
768
+
769
+ it('throws an error when used without a <head/>', async () => {
770
+ function noHead() {
771
+ return render(
772
+ <Tailwind>
773
+ <html lang="en">
774
+ {/* <Head></Head> */}
775
+ <div className="bg-red-200 sm:bg-red-500" />
776
+ </html>
777
+ </Tailwind>,
778
+ ).then(pretty);
779
+ }
780
+ await expect(noHead).rejects.toThrow(
781
+ `${headMissingError} sm:bg-red-500.`,
782
+ );
783
+ });
784
+
785
+ it('persists existing <head/> elements', async () => {
786
+ const actualOutput = await render(
787
+ <html lang="en">
788
+ <Tailwind>
789
+ <head>
790
+ <style />
791
+ <link />
792
+ </head>
793
+ <body>
794
+ <div className="bg-red-200 sm:bg-red-500" />
795
+ </body>
796
+ </Tailwind>
797
+ </html>,
798
+ ).then(pretty);
799
+
800
+ expect(actualOutput).toMatchInlineSnapshot(`
801
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
802
+ <html lang="en">
803
+ <head>
804
+ <style>
805
+ @media (min-width:40rem){.sm_bg-red-500{background-color:rgb(251,44,54)!important}}
806
+ </style>
807
+ <style></style>
808
+ <link />
809
+ </head>
810
+ <body>
811
+ <!--$--><!--html--><!--head--><!--body-->
812
+ <div class="sm_bg-red-500" style="background-color:rgb(255,201,201)"></div>
813
+ <!--/$-->
814
+ </body>
815
+ </html>
816
+ "
817
+ `);
818
+ });
819
+ });
820
+
821
+ describe('with duplicate classes across presets and plugins', () => {
822
+ it('merges declarations from a preset and a child override for the same class', async () => {
823
+ const base: TailwindConfig = {
824
+ plugins: [
825
+ plugin(({ addComponents }) => {
826
+ addComponents({ '.box': { '@apply rounded-lg bg-white p-4': {} } });
827
+ }),
828
+ ],
829
+ };
830
+ const config: TailwindConfig = {
831
+ presets: [base],
832
+ plugins: [
833
+ plugin(({ addComponents }) => {
834
+ addComponents({ '.box': { '@apply bg-red-500': {} } });
835
+ }),
836
+ ],
837
+ };
838
+
839
+ const actualOutput = await render(
840
+ <Tailwind config={config}>
841
+ <div className="box">hi</div>
842
+ </Tailwind>,
843
+ ).then(pretty);
844
+
845
+ expect(actualOutput).toMatchInlineSnapshot(`
846
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
847
+ <!--$-->
848
+ <div style="border-radius:0.5rem;background-color:rgb(251,44,54);padding:1rem">
849
+ hi
850
+ </div>
851
+ <!--/$-->
852
+ "
853
+ `);
854
+ });
855
+ });
856
+
857
+ describe('with custom theme config', () => {
858
+ it('supports custom colors', async () => {
859
+ const config: TailwindConfig = {
860
+ theme: {
861
+ extend: {
862
+ colors: {
863
+ custom: '#1fb6ff',
864
+ },
865
+ },
866
+ },
867
+ };
868
+
869
+ const actualOutput = await render(
870
+ <Tailwind config={config}>
871
+ <div className="bg-custom text-custom" />
872
+ </Tailwind>,
873
+ ).then(pretty);
874
+
875
+ expect(actualOutput).toMatchInlineSnapshot(`
876
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
877
+ <!--$-->
878
+ <div style="background-color:rgb(31,182,255);color:rgb(31,182,255)"></div>
879
+ <!--/$-->
880
+ "
881
+ `);
882
+ });
883
+
884
+ it('supports custom camelCased colors', async () => {
885
+ const config: TailwindConfig = {
886
+ theme: {
887
+ extend: {
888
+ colors: {
889
+ customColor: '#1fb6ff',
890
+ },
891
+ },
892
+ },
893
+ };
894
+
895
+ const actualOutput = await render(
896
+ <Tailwind config={config}>
897
+ <div className="bg-customColor text-customColor" />
898
+ </Tailwind>,
899
+ ).then(pretty);
900
+
901
+ expect(actualOutput).toMatchInlineSnapshot(`
902
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
903
+ <!--$-->
904
+ <div style="background-color:rgb(31,182,255);color:rgb(31,182,255)"></div>
905
+ <!--/$-->
906
+ "
907
+ `);
908
+ });
909
+
910
+ it('supports custom fonts', async () => {
911
+ const config: TailwindConfig = {
912
+ theme: {
913
+ extend: {
914
+ fontFamily: {
915
+ sans: ['Graphik', 'sans-serif'],
916
+ serif: ['Merriweather', 'serif'],
917
+ },
918
+ },
919
+ },
920
+ };
921
+
922
+ const actualOutput = await render(
923
+ <Tailwind config={config}>
924
+ <div className="font-sans" />
925
+ <div className="font-serif" />
926
+ </Tailwind>,
927
+ ).then(pretty);
928
+
929
+ expect(actualOutput).toMatchInlineSnapshot(`
930
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
931
+ <!--$-->
932
+ <div style="font-family:Graphik,sans-serif"></div>
933
+ <div style="font-family:Merriweather,serif"></div>
934
+ <!--/$-->
935
+ "
936
+ `);
937
+ });
938
+
939
+ it('supports custom spacing', async () => {
940
+ const config: TailwindConfig = {
941
+ theme: {
942
+ extend: {
943
+ spacing: {
944
+ '8xl': '96rem',
945
+ },
946
+ },
947
+ },
948
+ };
949
+ const actualOutput = await render(
950
+ <Tailwind config={config}>
951
+ <div className="m-8xl" />
952
+ </Tailwind>,
953
+ ).then(pretty);
954
+ expect(actualOutput).toMatchInlineSnapshot(`
955
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
956
+ <!--$-->
957
+ <div style="margin:96rem"></div>
958
+ <!--/$-->
959
+ "
960
+ `);
961
+ });
962
+
963
+ it('supports custom border radius', async () => {
964
+ const config: TailwindConfig = {
965
+ theme: {
966
+ extend: {
967
+ borderRadius: {
968
+ '4xl': '2rem',
969
+ },
970
+ },
971
+ },
972
+ };
973
+ const actualOutput = await render(
974
+ <Tailwind config={config}>
975
+ <div className="rounded-4xl" />
976
+ </Tailwind>,
977
+ ).then(pretty);
978
+ expect(actualOutput).toMatchInlineSnapshot(`
979
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
980
+ <!--$-->
981
+ <div style="border-radius:2rem"></div>
982
+ <!--/$-->
983
+ "
984
+ `);
985
+ });
986
+
987
+ it('supports custom text alignment', async () => {
988
+ const config: TailwindConfig = {
989
+ theme: {
990
+ extend: {
991
+ textAlign: {
992
+ justify: 'justify',
993
+ },
994
+ },
995
+ },
996
+ };
997
+
998
+ const actualOutput = await render(
999
+ <Tailwind config={config}>
1000
+ <div className="text-justify" />
1001
+ </Tailwind>,
1002
+ ).then(pretty);
1003
+
1004
+ expect(actualOutput).toMatchInlineSnapshot(`
1005
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1006
+ <!--$-->
1007
+ <div style="text-align:justify"></div>
1008
+ <!--/$-->
1009
+ "
1010
+ `);
1011
+ });
1012
+ });
1013
+
1014
+ describe('with css configuration', () => {
1015
+ it('handles empty theme string', async () => {
1016
+ const actualOutput = await render(
1017
+ <Tailwind theme="">
1018
+ <div className="bg-red-500 text-white">Default utilities</div>
1019
+ </Tailwind>,
1020
+ ).then(pretty);
1021
+
1022
+ expect(actualOutput).toMatchInlineSnapshot(`
1023
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1024
+ <!--$-->
1025
+ <div style="background-color:rgb(251,44,54);color:rgb(255,255,255)">
1026
+ Default utilities
1027
+ </div>
1028
+ <!--/$-->
1029
+ "
1030
+ `);
1031
+ });
1032
+
1033
+ it('supports custom colors', async () => {
1034
+ const theme = `
1035
+ @theme {
1036
+ --color-custom: #1fb6ff;
1037
+ }
1038
+ `;
1039
+
1040
+ const actualOutput = await render(
1041
+ <Tailwind theme={theme}>
1042
+ <div className="bg-custom text-custom" />
1043
+ </Tailwind>,
1044
+ ).then(pretty);
1045
+
1046
+ expect(actualOutput).toMatchInlineSnapshot(`
1047
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1048
+ <!--$-->
1049
+ <div style="background-color:rgb(31,182,255);color:rgb(31,182,255)"></div>
1050
+ <!--/$-->
1051
+ "
1052
+ `);
1053
+ });
1054
+
1055
+ it('supports custom fonts', async () => {
1056
+ const theme = `
1057
+ @theme {
1058
+ --font-sans: "Graphik", sans-serif;
1059
+ --font-serif: "Merriweather", serif;
1060
+ }
1061
+ `;
1062
+
1063
+ const actualOutput = await render(
1064
+ <Tailwind theme={theme}>
1065
+ <div className="font-sans" />
1066
+ <div className="font-serif" />
1067
+ </Tailwind>,
1068
+ ).then(pretty);
1069
+
1070
+ expect(actualOutput).toMatchInlineSnapshot(`
1071
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1072
+ <!--$-->
1073
+ <div style='font-family:"Graphik",sans-serif'></div>
1074
+ <div style='font-family:"Merriweather",serif'></div>
1075
+ <!--/$-->
1076
+ "
1077
+ `);
1078
+ });
1079
+
1080
+ it('supports custom spacing', async () => {
1081
+ const theme = `
1082
+ @theme {
1083
+ --spacing-8xl: 96rem;
1084
+ }
1085
+ `;
1086
+
1087
+ const actualOutput = await render(
1088
+ <Tailwind theme={theme}>
1089
+ <div className="m-8xl" />
1090
+ </Tailwind>,
1091
+ ).then(pretty);
1092
+ expect(actualOutput).toMatchInlineSnapshot(`
1093
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1094
+ <!--$-->
1095
+ <div style="margin:96rem"></div>
1096
+ <!--/$-->
1097
+ "
1098
+ `);
1099
+ });
1100
+
1101
+ it('supports custom border radius', async () => {
1102
+ const theme = `
1103
+ @theme {
1104
+ --border-radius-4xl: 2rem;
1105
+ }
1106
+ `;
1107
+
1108
+ const actualOutput = await render(
1109
+ <Tailwind theme={theme}>
1110
+ <div className="rounded-4xl" />
1111
+ </Tailwind>,
1112
+ ).then(pretty);
1113
+ expect(actualOutput).toMatchInlineSnapshot(`
1114
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1115
+ <!--$-->
1116
+ <div style="border-radius:2rem"></div>
1117
+ <!--/$-->
1118
+ "
1119
+ `);
1120
+ });
1121
+
1122
+ it('supports custom text alignment', async () => {
1123
+ const theme = `
1124
+ @theme {
1125
+ --text-align-justify: justify;
1126
+ }
1127
+ `;
1128
+
1129
+ const actualOutput = await render(
1130
+ <Tailwind theme={theme}>
1131
+ <div className="text-justify" />
1132
+ </Tailwind>,
1133
+ ).then(pretty);
1134
+
1135
+ expect(actualOutput).toMatchInlineSnapshot(`
1136
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1137
+ <!--$-->
1138
+ <div style="text-align:justify"></div>
1139
+ <!--/$-->
1140
+ "
1141
+ `);
1142
+ });
1143
+
1144
+ it('supports both config and theme props together', async () => {
1145
+ const customConfig = {
1146
+ theme: {
1147
+ extend: {
1148
+ colors: {
1149
+ primary: '#ff0000',
1150
+ },
1151
+ },
1152
+ },
1153
+ } satisfies TailwindConfig;
1154
+
1155
+ const customTheme = `
1156
+ @theme {
1157
+ --color-secondary: #00ff00;
1158
+ }
1159
+ `;
1160
+
1161
+ const actualOutput = await render(
1162
+ <Tailwind config={customConfig} theme={customTheme}>
1163
+ <div className="bg-primary text-secondary">Both config and theme</div>
1164
+ </Tailwind>,
1165
+ ).then(pretty);
1166
+
1167
+ expect(actualOutput).toMatchInlineSnapshot(`
1168
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1169
+ <!--$-->
1170
+ <div style="background-color:rgb(255,0,0);color:rgb(0,255,0)">
1171
+ Both config and theme
1172
+ </div>
1173
+ <!--/$-->
1174
+ "
1175
+ `);
1176
+ });
1177
+ });
1178
+
1179
+ describe('with utilities', () => {
1180
+ it('handles empty utilities string', async () => {
1181
+ const actualOutput = await render(
1182
+ <Tailwind utility="">
1183
+ <div className="bg-red-500 text-white">Default utilities</div>
1184
+ </Tailwind>,
1185
+ ).then(pretty);
1186
+
1187
+ expect(actualOutput).toMatchInlineSnapshot(`
1188
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1189
+ <!--$-->
1190
+ <div style="background-color:rgb(251,44,54);color:rgb(255,255,255)">
1191
+ Default utilities
1192
+ </div>
1193
+ <!--/$-->
1194
+ "
1195
+ `);
1196
+ });
1197
+
1198
+ it('supports custom utilities', async () => {
1199
+ const utilities = `
1200
+ .custom-shadow {
1201
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
1202
+ border-radius: 8px;
1203
+ padding: 16px;
1204
+ }
1205
+ `;
1206
+
1207
+ const actualOutput = await render(
1208
+ <Tailwind utility={utilities}>
1209
+ <div className="custom-shadow" />
1210
+ </Tailwind>,
1211
+ ).then(pretty);
1212
+
1213
+ expect(actualOutput).toMatchInlineSnapshot(`
1214
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1215
+ <!--$-->
1216
+ <div
1217
+ style="box-shadow:0 4px 6px rgb(0,0,0,0.1);border-radius:8px;padding:16px"></div>
1218
+ <!--/$-->
1219
+ "
1220
+ `);
1221
+ });
1222
+
1223
+ it('supports animations', async () => {
1224
+ const utilities = `
1225
+ .pulse-animation {
1226
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
1227
+ }
1228
+ `;
1229
+
1230
+ const actualOutput = await render(
1231
+ <Tailwind utility={utilities}>
1232
+ <div className="pulse-animation" />
1233
+ </Tailwind>,
1234
+ ).then(pretty);
1235
+
1236
+ expect(actualOutput).toMatchInlineSnapshot(`
1237
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1238
+ <!--$-->
1239
+ <div style="animation:pulse 2s cubic-bezier(0.4,0,0.6,1) infinite"></div>
1240
+ <!--/$-->
1241
+ "
1242
+ `);
1243
+ });
1244
+
1245
+ it('supports both config and utilities props together', async () => {
1246
+ const customConfig = {
1247
+ theme: {
1248
+ extend: {
1249
+ colors: {
1250
+ primary: '#ff0000',
1251
+ },
1252
+ },
1253
+ },
1254
+ } satisfies TailwindConfig;
1255
+
1256
+ const customUtilities = `
1257
+ .card-base {
1258
+ border: 1px solid #e5e7eb;
1259
+ padding: 20px;
1260
+ }
1261
+ `;
1262
+
1263
+ const actualOutput = await render(
1264
+ <Tailwind config={customConfig} utility={customUtilities}>
1265
+ <div className="bg-primary card-base">Config and utilities</div>
1266
+ </Tailwind>,
1267
+ ).then(pretty);
1268
+
1269
+ expect(actualOutput).toMatchInlineSnapshot(`
1270
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1271
+ <!--$-->
1272
+ <div
1273
+ style="background-color:rgb(255,0,0);border:1px solid rgb(229,231,235);padding:20px">
1274
+ Config and utilities
1275
+ </div>
1276
+ <!--/$-->
1277
+ "
1278
+ `);
1279
+ });
1280
+
1281
+ it('supports config, theme, and utilities together', async () => {
1282
+ const customConfig = {
1283
+ theme: {
1284
+ extend: {
1285
+ colors: {
1286
+ primary: '#ff0000',
1287
+ },
1288
+ },
1289
+ },
1290
+ } satisfies TailwindConfig;
1291
+
1292
+ const customTheme = `
1293
+ @theme {
1294
+ --color-secondary: #00ff00;
1295
+ }
1296
+ `;
1297
+
1298
+ const customUtilities = `
1299
+ .special-border {
1300
+ border: 2px dashed #0000ff;
1301
+ }
1302
+ `;
1303
+
1304
+ const actualOutput = await render(
1305
+ <Tailwind
1306
+ config={customConfig}
1307
+ theme={customTheme}
1308
+ utility={customUtilities}
1309
+ >
1310
+ <div className="bg-primary text-secondary special-border">
1311
+ All three props
1312
+ </div>
1313
+ </Tailwind>,
1314
+ ).then(pretty);
1315
+
1316
+ expect(actualOutput).toMatchInlineSnapshot(`
1317
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1318
+ <!--$-->
1319
+ <div
1320
+ style="background-color:rgb(255,0,0);color:rgb(0,255,0);border:2px dashed rgb(0,0,255)">
1321
+ All three props
1322
+ </div>
1323
+ <!--/$-->
1324
+ "
1325
+ `);
1326
+ });
1327
+ });
1328
+
1329
+ describe('with custom plugins config', () => {
1330
+ const config = {
1331
+ plugins: [
1332
+ {
1333
+ handler: (api) => {
1334
+ api.addUtilities({
1335
+ '.border-custom': {
1336
+ border: '2px solid',
1337
+ },
1338
+ });
1339
+ },
1340
+ },
1341
+ ],
1342
+ } satisfies TailwindConfig;
1343
+
1344
+ it('supports custom plugins', async () => {
1345
+ const actualOutput = await render(
1346
+ <Tailwind config={config}>
1347
+ <div className="border-custom" />
1348
+ </Tailwind>,
1349
+ ).then(pretty);
1350
+
1351
+ expect(actualOutput).toMatchInlineSnapshot(`
1352
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1353
+ <!--$-->
1354
+ <div style="border:2px solid"></div>
1355
+ <!--/$-->
1356
+ "
1357
+ `);
1358
+ });
1359
+
1360
+ it('supports custom plugins with responsive styles', async () => {
1361
+ const actualOutput = await render(
1362
+ <html lang="en">
1363
+ <Tailwind config={config}>
1364
+ <head />
1365
+ <body>
1366
+ <div className="border-custom sm:border-custom" />
1367
+ </body>
1368
+ </Tailwind>
1369
+ </html>,
1370
+ ).then(pretty);
1371
+
1372
+ expect(actualOutput).toMatchInlineSnapshot(`
1373
+ "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1374
+ <html lang="en">
1375
+ <head>
1376
+ <style>
1377
+ @media (min-width:40rem){.sm_border-custom{border:2px solid!important}}
1378
+ </style>
1379
+ </head>
1380
+ <body>
1381
+ <!--$--><!--html--><!--head--><!--body-->
1382
+ <div class="sm_border-custom" style="border:2px solid"></div>
1383
+ <!--/$-->
1384
+ </body>
1385
+ </html>
1386
+ "
1387
+ `);
1388
+ });
1389
+ });
1390
+ });