diff --git a/packages/components/package.json b/packages/components/package.json index de81282a4e..59d452461c 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@react-email/components", - "version": "0.5.6", + "version": "1.0.0-tailwindv4.6", "description": "A collection of all components React Email.", "sideEffects": false, "main": "./dist/index.js", @@ -58,7 +58,7 @@ "@react-email/render": "workspace:1.3.2", "@react-email/row": "workspace:0.0.12", "@react-email/section": "workspace:0.0.16", - "@react-email/tailwind": "workspace:1.2.2", + "@react-email/tailwind": "workspace:2.0.0-tailwindv4.4", "@react-email/text": "workspace:0.1.5" }, "peerDependencies": { diff --git a/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap b/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap index a6e818a9d9..3886d5c63b 100644 --- a/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap +++ b/packages/preview-server/src/utils/__snapshots__/get-email-component.spec.ts.snap @@ -12,8 +12,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = ` - + with a demo email template 1`] = `
+ style='margin-right:auto;margin-left:auto;margin-bottom:auto;margin-top:auto;background-color:rgb(255,255,255);padding-right:8px;padding-left:8px;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"'>
@@ -40,7 +39,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = ` cellpadding="0" cellspacing="0" role="presentation" - style="margin-left:auto;margin-right:auto;margin-top:40px;margin-bottom:40px;max-width:465px;border-radius:0.25rem;border-width:1px;border-color:rgb(234,234,234);border-style:solid;padding:20px"> + style="max-width:465px;margin-right:auto;margin-left:auto;margin-bottom:40px;margin-top:40px;border-radius:0.25rem;border-style:solid;border-width:1px;border-color:rgb(234,234,234);padding:20px">
@@ -59,26 +58,26 @@ exports[`getEmailComponent() > with a demo email template 1`] = ` alt="Vercel Logo" height="37" src="/static/vercel-logo.png" - style="margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;display:block;outline:none;border:none;text-decoration:none" + style="display:block;outline:none;border:none;text-decoration:none;margin-right:auto;margin-left:auto;margin-bottom:0;margin-top:0" width="40" />

+ style="margin-right:0;margin-left:0;margin-bottom:30px;margin-top:30px;padding:0;text-align:center;font-weight:400;font-size:24px;color:rgb(0,0,0)"> Join Enigma on Vercel

+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px"> Hello alanturing,

+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px"> Alan (alan.turing@example.com) has invited you to the Enigma team on @@ -110,7 +109,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = ` alt="alanturing's profile picture" height="64" src="/static/vercel-user.png" - style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" + style="display:block;outline:none;border:none;text-decoration:none;border-radius:9999px" width="64" /> with a demo email template 1`] = ` alt="Enigma team logo" height="64" src="/static/vercel-team.png" - style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" + style="display:block;outline:none;border:none;text-decoration:none;border-radius:9999px" width="64" /> @@ -153,7 +152,7 @@ exports[`getEmailComponent() > with a demo email template 1`] = ` with a demo email template 1`] = `

+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px"> or copy and paste this URL into your browser: https://vercel.com


+ style="width:100%;border:none;border-top:1px solid #eaeaea;margin-right:0;margin-left:0;margin-bottom:26px;margin-top:26px;border-style:solid;border-width:1px;border-color:rgb(234,234,234)" />

+ style="font-size:12px;line-height:24px;color:rgb(102,102,102);margin-top:16px;margin-bottom:16px"> This invitation was intended for alanturing. This invite was sent from diff --git a/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap b/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap index 615acfb5ad..7e4c1a61e7 100644 --- a/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap +++ b/packages/react-email/src/commands/testing/__snapshots__/export.spec.ts.snap @@ -10,8 +10,7 @@ exports[`email export 1`] = ` - +
+ style='margin-right:auto;margin-left:auto;margin-bottom:auto;margin-top:auto;background-color:rgb(255,255,255);padding-right:8px;padding-left:8px;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"'>
@@ -38,7 +37,7 @@ exports[`email export 1`] = ` cellpadding="0" cellspacing="0" role="presentation" - style="margin-left:auto;margin-right:auto;margin-top:40px;margin-bottom:40px;max-width:465px;border-radius:0.25rem;border-width:1px;border-color:rgb(234,234,234);border-style:solid;padding:20px"> + style="max-width:465px;margin-right:auto;margin-left:auto;margin-bottom:40px;margin-top:40px;border-radius:0.25rem;border-style:solid;border-width:1px;border-color:rgb(234,234,234);padding:20px">
@@ -57,26 +56,26 @@ exports[`email export 1`] = ` alt="Vercel Logo" height="37" src="/static/vercel-logo.png" - style="margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;display:block;outline:none;border:none;text-decoration:none" + style="display:block;outline:none;border:none;text-decoration:none;margin-right:auto;margin-left:auto;margin-bottom:0;margin-top:0" width="40" />

+ style="margin-right:0;margin-left:0;margin-bottom:30px;margin-top:30px;padding:0;text-align:center;font-weight:400;font-size:24px;color:rgb(0,0,0)"> Join on Vercel

+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px"> Hello ,

+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px"> () has invited you to the team on Vercel. @@ -106,7 +105,7 @@ exports[`email export 1`] = ` undefined's profile picture @@ -147,7 +146,7 @@ exports[`email export 1`] = `

+ style="font-size:14px;line-height:24px;color:rgb(0,0,0);margin-top:16px;margin-bottom:16px"> or copy and paste this URL into your browser:


+ style="width:100%;border:none;border-top:1px solid #eaeaea;margin-right:0;margin-left:0;margin-bottom:26px;margin-top:26px;border-style:solid;border-width:1px;border-color:rgb(234,234,234)" />

+ style="font-size:12px;line-height:24px;color:rgb(102,102,102);margin-top:16px;margin-bottom:16px"> This invitation was intended for . This invite was sent from diff --git a/packages/tailwind/copy-tailwind-types.mjs b/packages/tailwind/copy-tailwind-types.mjs deleted file mode 100644 index 6fe7008614..0000000000 --- a/packages/tailwind/copy-tailwind-types.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { promises as fs } from 'node:fs'; -import path from 'node:path'; -import url from 'node:url'; - -const __dirname = path.dirname(url.fileURLToPath(import.meta.url)); - -await fs.cp( - path.resolve(__dirname, './node_modules/tailwindcss/types'), - path.resolve(__dirname, './dist/tailwindcss'), - { - recursive: true, - }, -); diff --git a/packages/tailwind/package.json b/packages/tailwind/package.json index 43ed1c5e28..b5c1edfdd2 100644 --- a/packages/tailwind/package.json +++ b/packages/tailwind/package.json @@ -1,6 +1,6 @@ { "name": "@react-email/tailwind", - "version": "1.2.2", + "version": "2.0.0-tailwindv4.4", "description": "A React component to wrap emails with Tailwind CSS", "sideEffects": false, "main": "./dist/index.js", @@ -23,8 +23,8 @@ }, "license": "MIT", "scripts": { - "build": "tsc && cross-env NODE_ENV=production vite build --mode production && node ./copy-tailwind-types.mjs", - "build:watch": "vite build --watch", + "build": "tsdown", + "build:watch": "tsdown --watch", "clean": "rm -rf dist", "test": "vitest run", "test:watch": "vitest" @@ -43,7 +43,49 @@ "node": ">=18.0.0" }, "peerDependencies": { - "react": "^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^18.0 || ^19.0 || ^19.0.0-rc", + "@react-email/button": "*", + "@react-email/body": "*", + "@react-email/code-block": "*", + "@react-email/code-inline": "*", + "@react-email/container": "*", + "@react-email/heading": "*", + "@react-email/hr": "*", + "@react-email/img": "*", + "@react-email/link": "*", + "@react-email/preview": "*" + }, + "peerDependenciesMeta": { + "@react-email/button": { + "optional": true + }, + "@react-email/body": { + "optional": true + }, + "@react-email/code-block": { + "optional": true + }, + "@react-email/code-inline": { + "optional": true + }, + "@react-email/container": { + "optional": true + }, + "@react-email/heading": { + "optional": true + }, + "@react-email/hr": { + "optional": true + }, + "@react-email/img": { + "optional": true + }, + "@react-email/link": { + "optional": true + }, + "@react-email/preview": { + "optional": true + } }, "devDependencies": { "@react-email/button": "workspace:^", @@ -54,14 +96,12 @@ "@react-email/link": "workspace:*", "@react-email/render": "workspace:*", "@responsive-email/react-email": "0.0.4", + "@types/css-tree": "2.3.10", "@types/shelljs": "0.8.15", "@vitejs/plugin-react": "4.4.1", - "cross-env": "10.0.0", - "postcss": "8.5.3", - "postcss-selector-parser": "7.1.0", + "css-tree": "3.1.0", "react-dom": "^19", "shelljs": "0.9.2", - "tailwindcss": "3.4.10", "tsconfig": "workspace:*", "typescript": "5.8.3", "vite": "6.3.6", @@ -70,5 +110,8 @@ }, "publishConfig": { "access": "public" + }, + "dependencies": { + "tailwindcss": "4.1.12" } } diff --git a/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap b/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap index 0bdcd1b93d..7ffb5d28fc 100644 --- a/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap +++ b/packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap @@ -1,52 +1,225 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Custom plugins config > uses custom plugins 1`] = `"

"`; +exports[`Tailwind component > - +

I am some text

+ " `; -exports[`Tailwind component > works properly with 'no-underline' 1`] = `"

or copy and paste this URL into your browser: https://react.email

or copy and paste this URL into your browser: https://react.email

"`; +exports[`Tailwind component > works properly with 'no-underline' 1`] = `"

or copy and paste this URL into your browser: https://react.email

or copy and paste this URL into your browser: https://react.email

"`; exports[`Tailwind component > works with Heading component 1`] = `"Hello

My testing heading

friends"`; @@ -57,7 +230,7 @@ exports[`Tailwind component > works with blocklist 1`] = ` @@ -67,63 +240,27 @@ exports[`Tailwind component > works with blocklist 1`] = ` " `; -exports[`Tailwind component > works with calc() with + sign 1`] = `"
something tall
"`; - -exports[`Tailwind component > works with class manipulation done on components 1`] = `"
"`; - -exports[`Tailwind component > works with components that return children 1`] = `"
Hello world

React Email

"`; - -exports[`Tailwind component > works with components that use React.forwardRef 1`] = `"
Hello world

React Email

"`; - -exports[`Tailwind component > works with custom components with fragment at the root 1`] = `"
Hello world

React Email

React Email

"`; - -exports[`non-inlinable styles > adds css to and keeps class names 1`] = `"
"`; - -exports[`non-inlinable styles > does not have duplicate media queries 1`] = ` +exports[`Tailwind component > works with calc() with + sign 1`] = ` " - - - -
- - +
+
something tall
+
+ " `; -exports[`non-inlinable styles > persists existing elements 1`] = `"
"`; - -exports[`non-inlinable styles > throws an error when used without a 1`] = ` -[Error: You are trying to use the following Tailwind classes that cannot be inlined: sm:bg-red-500. -For the media queries to work properly on rendering, they need to be added into a
"`; +exports[`Tailwind component > works with components that return children 1`] = `"
Hello world

React Email

"`; -exports[`non-inlinable styles > works with arbitrarily deep (in the React tree) elements 2`] = `"
"`; +exports[`Tailwind component > works with components that use React.forwardRef 1`] = `"
Hello world

React Email

"`; -exports[`non-inlinable styles > works with relatively complex media query utilities 1`] = `"

I am some text

"`; +exports[`Tailwind component > works with custom components with fragment at the root 1`] = `"
Hello world

React Email

React Email

"`; diff --git a/packages/tailwind/src/hooks/__snapshots__/use-tailwind.spec.ts.snap b/packages/tailwind/src/hooks/__snapshots__/use-tailwind.spec.ts.snap deleted file mode 100644 index baebff2970..0000000000 --- a/packages/tailwind/src/hooks/__snapshots__/use-tailwind.spec.ts.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`useTailwind() 1`] = ` -".text-red-500 { - color: rgb(239 68 68 / 1) -} - @media (min-width: 640px) { - .sm\\:bg-blue-300 { - background-color: rgb(147 197 253 / 1) - } -} - .bg-slate-900 { - background-color: rgb(15 23 42 / 1) -} -" -`; diff --git a/packages/tailwind/src/hooks/use-suspensed-promise.ts b/packages/tailwind/src/hooks/use-suspended-promise.ts similarity index 100% rename from packages/tailwind/src/hooks/use-suspensed-promise.ts rename to packages/tailwind/src/hooks/use-suspended-promise.ts diff --git a/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts b/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts index a43199c247..2063a1ab49 100644 --- a/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts +++ b/packages/tailwind/src/hooks/use-suspensed-promise.spec.ts @@ -1,5 +1,5 @@ /** biome-ignore-all lint/correctness/useHookAtTopLevel: function is not a React hook */ -import { useSuspensedPromise } from './use-suspensed-promise'; +import { useSuspensedPromise } from './use-suspended-promise'; describe('useSuspensedPromise', () => { beforeEach(() => {}); diff --git a/packages/tailwind/src/tailwind.spec.tsx b/packages/tailwind/src/tailwind.spec.tsx index cc05724cbd..8b36f1655a 100644 --- a/packages/tailwind/src/tailwind.spec.tsx +++ b/packages/tailwind/src/tailwind.spec.tsx @@ -4,10 +4,9 @@ import { Heading } from '@react-email/heading'; import { Hr } from '@react-email/hr'; import { Html } from '@react-email/html'; import { Link } from '@react-email/link'; -import { render } from '@react-email/render'; +import { pretty, render } from '@react-email/render'; import { ResponsiveColumn, ResponsiveRow } from '@responsive-email/react-email'; import React from 'react'; -import { vi } from 'vitest'; import type { TailwindConfig } from '.'; import { Tailwind } from '.'; @@ -26,7 +25,11 @@ describe('Tailwind component', () => { it('works with blocklist', async () => { const actualOutput = await render( - + - - , - { pretty: true }, - ); - - expect(spy).toHaveBeenCalled(); - expect(actualOutput).toMatchSnapshot(); - }); - it('works with class manipulation done on components', async () => { const MyComponnt = (props: { className?: string; @@ -66,11 +50,9 @@ describe('Tailwind component', () => { }) => { expect( props.style, - 'Styles should be generated the same for a component', - ).toEqual({ - color: 'rgb(96,165,250)', - padding: '1rem', - }); + 'styles should not be generated for a component', + ).toBeUndefined(); + expect(props.className).toBe('p-4 text-blue-400'); return (
{ expect(actualOutput).toMatchSnapshot(); }); - describe('Inline styles', () => { - it('renders children with inline Tailwind styles', async () => { - const actualOutput = await render( - -
- , - ); + it('renders children with inline Tailwind styles', async () => { + const actualOutput = await render( + +
+ , + ); - expect(actualOutput).not.toBeNull(); - }); + expect(actualOutput).toMatchSnapshot(); }); test('