From 9edd2a963dcc565a5472a84223e0eb4e067102f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 01/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 6a4f59ca3473fd40720c8f8cb6bf329354e205ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 02/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From d6bdc232c2ea527acda36bc5f34ae90c1f798a2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 03/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 42d78e83134ce0269a77555ece2dcfb6bd19043c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 04/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 45be789dc0952b00d46cab792814579003afe62a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 05/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 11e19989324454bdcf999f5747049bfc206ecf9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 06/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 68533fd35a3c9d55eaeda2295e3f3b771257c186 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 07/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From ea193c5691a9c5fe603db1c3700a177a66138dd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:42:13 -0300 Subject: [PATCH 08/78] chore(deps): bump next from 14.2.3 to 15.0.4 (#1810) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From f9e7adee050db3f1489439ff885246838d8b43af Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 3 Dec 2024 12:33:48 -0300 Subject: [PATCH 09/78] small changes to favour not having `react-email` as a devDEpendency --- packages/react-email/tsup.config.ts | 8 ++++---- pnpm-lock.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-email/tsup.config.ts b/packages/react-email/tsup.config.ts index c9f968f497..9b814f1880 100644 --- a/packages/react-email/tsup.config.ts +++ b/packages/react-email/tsup.config.ts @@ -1,8 +1,8 @@ import { defineConfig } from 'tsup'; -export default defineConfig({ - dts: true, +export default defineConfig([{ + dts: false, entry: ['./src/cli/index.ts'], - format: ['esm', 'cjs'], + format: ['cjs'], outDir: 'dist/cli', -}); +}]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82ff55f7ba..56e5ffa52c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3424,7 +3424,7 @@ packages: '@radix-ui/react-slot@1.1.0': resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': From 3ef1edfab6f4dc7c6cbfe387a74765c1568ed3fd Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 3 Dec 2024 13:41:00 -0300 Subject: [PATCH 10/78] export all components from react-email --- packages/react-email/package.json | 38 +++++++++++++++++++++++ packages/react-email/src/package/index.ts | 20 ++++++++++++ packages/react-email/tsup.config.ts | 20 ++++++++---- 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 packages/react-email/src/package/index.ts diff --git a/packages/react-email/package.json b/packages/react-email/package.json index 885331a9d5..27b1e9c55f 100644 --- a/packages/react-email/package.json +++ b/packages/react-email/package.json @@ -5,6 +5,21 @@ "bin": { "email": "./dist/cli/index.js" }, + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, "scripts": { "build": "tsup-node && node build-preview-server.mjs", "dev": "tsup-node --watch", @@ -27,6 +42,26 @@ "node": ">=18.0.0" }, "dependencies": { + "@react-email/body": "workspace:0.0.11-canary.1", + "@react-email/button": "workspace:0.0.19-canary.1", + "@react-email/code-block": "workspace:0.0.11-canary.2", + "@react-email/code-inline": "workspace:0.0.5-canary.1", + "@react-email/column": "workspace:0.0.13-canary.1", + "@react-email/container": "workspace:0.0.15-canary.1", + "@react-email/font": "workspace:0.0.9-canary.1", + "@react-email/head": "workspace:0.0.12-canary.1", + "@react-email/heading": "workspace:0.0.15-canary.1", + "@react-email/hr": "workspace:0.0.11-canary.1", + "@react-email/html": "workspace:0.0.11-canary.1", + "@react-email/img": "workspace:0.0.11-canary.1", + "@react-email/link": "workspace:0.0.12-canary.1", + "@react-email/markdown": "workspace:0.0.13-canary.3", + "@react-email/preview": "workspace:0.0.12-canary.1", + "@react-email/render": "workspace:1.0.3-canary.3", + "@react-email/row": "workspace:0.0.12-canary.1", + "@react-email/section": "workspace:0.0.16-canary.1", + "@react-email/tailwind": "workspace:1.0.3-canary.1", + "@react-email/text": "workspace:0.0.11-canary.1", "@babel/core": "7.24.5", "@babel/parser": "7.24.5", "chalk": "4.1.2", @@ -42,6 +77,9 @@ "ora": "5.4.1", "socket.io": "4.8.0" }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || ^19.0.0-rc" + }, "devDependencies": { "@radix-ui/colors": "1.0.1", "@radix-ui/react-collapsible": "1.1.0", diff --git a/packages/react-email/src/package/index.ts b/packages/react-email/src/package/index.ts new file mode 100644 index 0000000000..8ae88217d0 --- /dev/null +++ b/packages/react-email/src/package/index.ts @@ -0,0 +1,20 @@ +export * from "@react-email/body"; +export * from "@react-email/button"; +export * from "@react-email/code-block"; +export * from "@react-email/code-inline"; +export * from "@react-email/column"; +export * from "@react-email/container"; +export * from "@react-email/font"; +export * from "@react-email/head"; +export * from "@react-email/heading"; +export * from "@react-email/hr"; +export * from "@react-email/html"; +export * from "@react-email/img"; +export * from "@react-email/link"; +export * from "@react-email/markdown"; +export * from "@react-email/preview"; +export * from "@react-email/render"; +export * from "@react-email/row"; +export * from "@react-email/section"; +export * from "@react-email/tailwind"; +export * from "@react-email/text"; diff --git a/packages/react-email/tsup.config.ts b/packages/react-email/tsup.config.ts index 9b814f1880..ece12add53 100644 --- a/packages/react-email/tsup.config.ts +++ b/packages/react-email/tsup.config.ts @@ -1,8 +1,16 @@ import { defineConfig } from 'tsup'; -export default defineConfig([{ - dts: false, - entry: ['./src/cli/index.ts'], - format: ['cjs'], - outDir: 'dist/cli', -}]); +export default defineConfig([ + { + dts: false, + entry: ['./src/cli/index.ts'], + format: ['cjs'], + outDir: 'dist/cli', + }, + { + dts: true, + entry: ['./src/package/index.ts'], + format: ['cjs', 'esm'], + outDir: 'dist', + }, +]); From 345ab445c32134d7d157436642d2494d01151306 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 3 Dec 2024 13:41:06 -0300 Subject: [PATCH 11/78] update demo to use react-email instead of components --- .../emails/magic-links/aws-verify-email.tsx | 3 +- .../magic-links/jobaccepted-magic-link.tsx | 2 +- .../emails/magic-links/linear-login-code.tsx | 3 +- .../emails/magic-links/notion-magic-link.tsx | 3 +- .../magic-links/plaid-verify-identity.tsx | 3 +- .../emails/magic-links/raycast-magic-link.tsx | 3 +- .../demo/emails/magic-links/slack-confirm.tsx | 3 +- .../newsletters/codepen-challengers.tsx | 3 +- .../newsletters/google-play-policy-update.tsx | 3 +- .../newsletters/stack-overflow-tips.tsx | 3 +- .../notifications/github-access-token.tsx | 3 +- .../notifications/vercel-invite-user.tsx | 3 +- .../notifications/yelp-recent-login.tsx | 3 +- apps/demo/emails/receipts/apple-receipt.tsx | 3 +- apps/demo/emails/receipts/nike-receipt.tsx | 3 +- .../reset-password/dropbox-reset-password.tsx | 3 +- .../reset-password/twitch-reset-password.tsx | 3 +- apps/demo/emails/reviews/airbnb-review.tsx | 3 +- apps/demo/emails/reviews/amazon-review.tsx | 3 +- apps/demo/emails/welcome/koala-welcome.tsx | 3 +- apps/demo/emails/welcome/netlify-welcome.tsx | 3 +- apps/demo/emails/welcome/stripe-welcome.tsx | 3 +- packages/react-email/package.json | 40 ++++++------ pnpm-lock.yaml | 63 ++++++++++++++++++- 24 files changed, 102 insertions(+), 66 deletions(-) diff --git a/apps/demo/emails/magic-links/aws-verify-email.tsx b/apps/demo/emails/magic-links/aws-verify-email.tsx index 551f59086d..f38d76a255 100644 --- a/apps/demo/emails/magic-links/aws-verify-email.tsx +++ b/apps/demo/emails/magic-links/aws-verify-email.tsx @@ -10,8 +10,7 @@ import { Preview, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface AWSVerifyEmailProps { verificationCode?: string; diff --git a/apps/demo/emails/magic-links/jobaccepted-magic-link.tsx b/apps/demo/emails/magic-links/jobaccepted-magic-link.tsx index 06ef388250..4b7f8d9836 100644 --- a/apps/demo/emails/magic-links/jobaccepted-magic-link.tsx +++ b/apps/demo/emails/magic-links/jobaccepted-magic-link.tsx @@ -9,7 +9,7 @@ import { Section, Text, Button, -} from "@react-email/components"; +} from "react-email"; const MagicCodeEmail = () => { return ( diff --git a/apps/demo/emails/magic-links/linear-login-code.tsx b/apps/demo/emails/magic-links/linear-login-code.tsx index e4b520526c..f7969996bf 100644 --- a/apps/demo/emails/magic-links/linear-login-code.tsx +++ b/apps/demo/emails/magic-links/linear-login-code.tsx @@ -11,8 +11,7 @@ import { Preview, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface LinearLoginCodeEmailProps { validationCode?: string; diff --git a/apps/demo/emails/magic-links/notion-magic-link.tsx b/apps/demo/emails/magic-links/notion-magic-link.tsx index 7386794972..a7bc2d562f 100644 --- a/apps/demo/emails/magic-links/notion-magic-link.tsx +++ b/apps/demo/emails/magic-links/notion-magic-link.tsx @@ -8,8 +8,7 @@ import { Link, Preview, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface NotionMagicLinkEmailProps { loginCode?: string; diff --git a/apps/demo/emails/magic-links/plaid-verify-identity.tsx b/apps/demo/emails/magic-links/plaid-verify-identity.tsx index 877162d14e..657dcb1d45 100644 --- a/apps/demo/emails/magic-links/plaid-verify-identity.tsx +++ b/apps/demo/emails/magic-links/plaid-verify-identity.tsx @@ -8,8 +8,7 @@ import { Link, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface PlaidVerifyIdentityEmailProps { validationCode?: string; diff --git a/apps/demo/emails/magic-links/raycast-magic-link.tsx b/apps/demo/emails/magic-links/raycast-magic-link.tsx index 3b49182426..408a87e974 100644 --- a/apps/demo/emails/magic-links/raycast-magic-link.tsx +++ b/apps/demo/emails/magic-links/raycast-magic-link.tsx @@ -10,8 +10,7 @@ import { Preview, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface RaycastMagicLinkEmailProps { magicLink?: string; diff --git a/apps/demo/emails/magic-links/slack-confirm.tsx b/apps/demo/emails/magic-links/slack-confirm.tsx index aea983929a..6199bcbd34 100644 --- a/apps/demo/emails/magic-links/slack-confirm.tsx +++ b/apps/demo/emails/magic-links/slack-confirm.tsx @@ -11,8 +11,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface SlackConfirmEmailProps { validationCode?: string; diff --git a/apps/demo/emails/newsletters/codepen-challengers.tsx b/apps/demo/emails/newsletters/codepen-challengers.tsx index 76764a38b1..4a81ca6317 100644 --- a/apps/demo/emails/newsletters/codepen-challengers.tsx +++ b/apps/demo/emails/newsletters/codepen-challengers.tsx @@ -12,8 +12,7 @@ import { Section, Text, Row, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` diff --git a/apps/demo/emails/newsletters/google-play-policy-update.tsx b/apps/demo/emails/newsletters/google-play-policy-update.tsx index a2ed0b1d66..69eb77cfd4 100644 --- a/apps/demo/emails/newsletters/google-play-policy-update.tsx +++ b/apps/demo/emails/newsletters/google-play-policy-update.tsx @@ -11,8 +11,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` diff --git a/apps/demo/emails/newsletters/stack-overflow-tips.tsx b/apps/demo/emails/newsletters/stack-overflow-tips.tsx index 8910f85136..9ff68f6bae 100644 --- a/apps/demo/emails/newsletters/stack-overflow-tips.tsx +++ b/apps/demo/emails/newsletters/stack-overflow-tips.tsx @@ -12,8 +12,7 @@ import { Section, Text, Row, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface StackOverflowTipsEmailProps { tips?: { id: number; description: string }[]; diff --git a/apps/demo/emails/notifications/github-access-token.tsx b/apps/demo/emails/notifications/github-access-token.tsx index 8e14f9a41e..27d4f4c2ce 100644 --- a/apps/demo/emails/notifications/github-access-token.tsx +++ b/apps/demo/emails/notifications/github-access-token.tsx @@ -9,8 +9,7 @@ import { Preview, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface GithubAccessTokenEmailProps { username?: string; diff --git a/apps/demo/emails/notifications/vercel-invite-user.tsx b/apps/demo/emails/notifications/vercel-invite-user.tsx index a7a82a997b..66ef2f0bc3 100644 --- a/apps/demo/emails/notifications/vercel-invite-user.tsx +++ b/apps/demo/emails/notifications/vercel-invite-user.tsx @@ -14,8 +14,7 @@ import { Section, Text, Tailwind, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface VercelInviteUserEmailProps { username?: string; diff --git a/apps/demo/emails/notifications/yelp-recent-login.tsx b/apps/demo/emails/notifications/yelp-recent-login.tsx index 4a3ec1ebae..12ad7665b5 100644 --- a/apps/demo/emails/notifications/yelp-recent-login.tsx +++ b/apps/demo/emails/notifications/yelp-recent-login.tsx @@ -11,8 +11,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface YelpRecentLoginEmailProps { userFirstName?: string; diff --git a/apps/demo/emails/receipts/apple-receipt.tsx b/apps/demo/emails/receipts/apple-receipt.tsx index 5e5d9a20e6..887c10cb57 100644 --- a/apps/demo/emails/receipts/apple-receipt.tsx +++ b/apps/demo/emails/receipts/apple-receipt.tsx @@ -11,8 +11,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` diff --git a/apps/demo/emails/receipts/nike-receipt.tsx b/apps/demo/emails/receipts/nike-receipt.tsx index 2a1f45217c..64f9321a3c 100644 --- a/apps/demo/emails/receipts/nike-receipt.tsx +++ b/apps/demo/emails/receipts/nike-receipt.tsx @@ -12,8 +12,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` diff --git a/apps/demo/emails/reset-password/dropbox-reset-password.tsx b/apps/demo/emails/reset-password/dropbox-reset-password.tsx index 2b8894fe3f..b7fdcc2268 100644 --- a/apps/demo/emails/reset-password/dropbox-reset-password.tsx +++ b/apps/demo/emails/reset-password/dropbox-reset-password.tsx @@ -9,8 +9,7 @@ import { Preview, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface DropboxResetPasswordEmailProps { userFirstname?: string; diff --git a/apps/demo/emails/reset-password/twitch-reset-password.tsx b/apps/demo/emails/reset-password/twitch-reset-password.tsx index ae0e35aa8b..feb458787f 100644 --- a/apps/demo/emails/reset-password/twitch-reset-password.tsx +++ b/apps/demo/emails/reset-password/twitch-reset-password.tsx @@ -10,8 +10,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface TwitchResetPasswordEmailProps { username?: string; diff --git a/apps/demo/emails/reviews/airbnb-review.tsx b/apps/demo/emails/reviews/airbnb-review.tsx index 07386ffa8a..c92d33a0a0 100644 --- a/apps/demo/emails/reviews/airbnb-review.tsx +++ b/apps/demo/emails/reviews/airbnb-review.tsx @@ -11,8 +11,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface AirbnbReviewEmailProps { authorName?: string; diff --git a/apps/demo/emails/reviews/amazon-review.tsx b/apps/demo/emails/reviews/amazon-review.tsx index a5f07194bf..d7760fcc9a 100644 --- a/apps/demo/emails/reviews/amazon-review.tsx +++ b/apps/demo/emails/reviews/amazon-review.tsx @@ -11,8 +11,7 @@ import { Row, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface AmazonReviewEmailProps { titleText?: string; diff --git a/apps/demo/emails/welcome/koala-welcome.tsx b/apps/demo/emails/welcome/koala-welcome.tsx index 44dce55594..43e6469dcb 100644 --- a/apps/demo/emails/welcome/koala-welcome.tsx +++ b/apps/demo/emails/welcome/koala-welcome.tsx @@ -9,8 +9,7 @@ import { Preview, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface KoalaWelcomeEmailProps { userFirstname: string; diff --git a/apps/demo/emails/welcome/netlify-welcome.tsx b/apps/demo/emails/welcome/netlify-welcome.tsx index faa029c440..3dce52fa7c 100644 --- a/apps/demo/emails/welcome/netlify-welcome.tsx +++ b/apps/demo/emails/welcome/netlify-welcome.tsx @@ -13,8 +13,7 @@ import { Section, Text, Tailwind, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; interface NetlifyWelcomeEmailProps { steps?: { diff --git a/apps/demo/emails/welcome/stripe-welcome.tsx b/apps/demo/emails/welcome/stripe-welcome.tsx index 17a41b8230..c458553e44 100644 --- a/apps/demo/emails/welcome/stripe-welcome.tsx +++ b/apps/demo/emails/welcome/stripe-welcome.tsx @@ -10,8 +10,7 @@ import { Preview, Section, Text, -} from "@react-email/components"; -import * as React from "react"; +} from "react-email"; const baseUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` diff --git a/packages/react-email/package.json b/packages/react-email/package.json index 27b1e9c55f..9ce2459288 100644 --- a/packages/react-email/package.json +++ b/packages/react-email/package.json @@ -42,26 +42,26 @@ "node": ">=18.0.0" }, "dependencies": { - "@react-email/body": "workspace:0.0.11-canary.1", - "@react-email/button": "workspace:0.0.19-canary.1", - "@react-email/code-block": "workspace:0.0.11-canary.2", - "@react-email/code-inline": "workspace:0.0.5-canary.1", - "@react-email/column": "workspace:0.0.13-canary.1", - "@react-email/container": "workspace:0.0.15-canary.1", - "@react-email/font": "workspace:0.0.9-canary.1", - "@react-email/head": "workspace:0.0.12-canary.1", - "@react-email/heading": "workspace:0.0.15-canary.1", - "@react-email/hr": "workspace:0.0.11-canary.1", - "@react-email/html": "workspace:0.0.11-canary.1", - "@react-email/img": "workspace:0.0.11-canary.1", - "@react-email/link": "workspace:0.0.12-canary.1", - "@react-email/markdown": "workspace:0.0.13-canary.3", - "@react-email/preview": "workspace:0.0.12-canary.1", - "@react-email/render": "workspace:1.0.3-canary.3", - "@react-email/row": "workspace:0.0.12-canary.1", - "@react-email/section": "workspace:0.0.16-canary.1", - "@react-email/tailwind": "workspace:1.0.3-canary.1", - "@react-email/text": "workspace:0.0.11-canary.1", + "@react-email/body": "workspace:0.0.11", + "@react-email/button": "workspace:0.0.19", + "@react-email/code-block": "workspace:0.0.11", + "@react-email/code-inline": "workspace:0.0.5", + "@react-email/column": "workspace:0.0.13", + "@react-email/container": "workspace:0.0.15", + "@react-email/font": "workspace:0.0.9", + "@react-email/head": "workspace:0.0.12", + "@react-email/heading": "workspace:0.0.15", + "@react-email/hr": "workspace:0.0.11", + "@react-email/html": "workspace:0.0.11", + "@react-email/img": "workspace:0.0.11", + "@react-email/link": "workspace:0.0.12", + "@react-email/markdown": "workspace:0.0.14", + "@react-email/preview": "workspace:0.0.12", + "@react-email/render": "workspace:1.0.3", + "@react-email/row": "workspace:0.0.12", + "@react-email/section": "workspace:0.0.16", + "@react-email/tailwind": "workspace:1.0.4", + "@react-email/text": "workspace:0.0.11", "@babel/core": "7.24.5", "@babel/parser": "7.24.5", "chalk": "4.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56e5ffa52c..b3fd8fed02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,9 +50,6 @@ importers: apps/demo: dependencies: - '@react-email/components': - specifier: workspace:* - version: link:../../packages/components react: specifier: ^19 version: 19.0.0 @@ -960,6 +957,66 @@ importers: '@babel/parser': specifier: 7.24.5 version: 7.24.5 + '@react-email/body': + specifier: workspace:0.0.11-canary.1 + version: link:../body + '@react-email/button': + specifier: workspace:0.0.19-canary.1 + version: link:../button + '@react-email/code-block': + specifier: workspace:0.0.11-canary.2 + version: link:../code-block + '@react-email/code-inline': + specifier: workspace:0.0.5-canary.1 + version: link:../code-inline + '@react-email/column': + specifier: workspace:0.0.13-canary.1 + version: link:../column + '@react-email/container': + specifier: workspace:0.0.15-canary.1 + version: link:../container + '@react-email/font': + specifier: workspace:0.0.9-canary.1 + version: link:../font + '@react-email/head': + specifier: workspace:0.0.12-canary.1 + version: link:../head + '@react-email/heading': + specifier: workspace:0.0.15-canary.1 + version: link:../heading + '@react-email/hr': + specifier: workspace:0.0.11-canary.1 + version: link:../hr + '@react-email/html': + specifier: workspace:0.0.11-canary.1 + version: link:../html + '@react-email/img': + specifier: workspace:0.0.11-canary.1 + version: link:../img + '@react-email/link': + specifier: workspace:0.0.12-canary.1 + version: link:../link + '@react-email/markdown': + specifier: workspace:0.0.13-canary.3 + version: link:../markdown + '@react-email/preview': + specifier: workspace:0.0.12-canary.1 + version: link:../preview + '@react-email/render': + specifier: workspace:1.0.3-canary.3 + version: link:../render + '@react-email/row': + specifier: workspace:0.0.12-canary.1 + version: link:../row + '@react-email/section': + specifier: workspace:0.0.16-canary.1 + version: link:../section + '@react-email/tailwind': + specifier: workspace:1.0.3-canary.1 + version: link:../tailwind + '@react-email/text': + specifier: workspace:0.0.11-canary.1 + version: link:../text chalk: specifier: 4.1.2 version: 4.1.2 From 13e1c45e32f969d45be85dfdface4d45cc4d472f Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 3 Dec 2024 13:42:46 -0300 Subject: [PATCH 12/78] update rendering utilities exporter plugin to import render from react-email --- .../src/utils/esbuild/renderring-utilities-exporter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts b/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts index 4bbac542cd..cc17950a30 100644 --- a/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts +++ b/packages/react-email/src/utils/esbuild/renderring-utilities-exporter.ts @@ -50,8 +50,8 @@ export const renderingUtilitiesExporter = (emailTemplates: string[]) => ({ return result; } - // If @react-email/render does not exist, resolve to @react-email/components - result = await b.resolve('@react-email/components', options); + // If @react-email/render does not exist, resolve to react-email + result = await b.resolve('react-email', options); if (result.errors.length > 0 && result.errors[0]) { result.errors[0].text = "Failed trying to import `render` from either `@react-email/render` or `@react-email/components` to be able to render your email template.\n Maybe you don't have either of them installed?"; From 35569d931616043f3c5ea9bc02fd47fce88ca8d4 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 3 Dec 2024 14:46:27 -0300 Subject: [PATCH 13/78] use react 18.3.1 due to prism-react-renderer not support React 19 --- packages/react-email/package.json | 2 +- .../react-email/src/cli/commands/build.ts | 22 ++++--- pnpm-lock.yaml | 63 ++++++++++--------- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/packages/react-email/package.json b/packages/react-email/package.json index 9ce2459288..851c950702 100644 --- a/packages/react-email/package.json +++ b/packages/react-email/package.json @@ -105,7 +105,7 @@ "eslint-config-turbo": "2.1.0", "framer-motion": "12.0.0-alpha.2", "postcss": "8.4.40", - "prism-react-renderer": "2.1.0", + "prism-react-renderer": "2.4.0", "react": "^19", "react-dom": "^19", "sharp": "0.33.3", diff --git a/packages/react-email/src/cli/commands/build.ts b/packages/react-email/src/cli/commands/build.ts index eff2fd8cd1..b5044535c7 100644 --- a/packages/react-email/src/cli/commands/build.ts +++ b/packages/react-email/src/cli/commands/build.ts @@ -165,6 +165,7 @@ const updatePackageJson = async (builtPreviewAppPath: string) => { ) as { name: string; scripts: Record; + peerDependencies?: Record; dependencies: Record; devDependencies: Record; }; @@ -172,13 +173,20 @@ const updatePackageJson = async (builtPreviewAppPath: string) => { packageJson.scripts.start = 'next start'; packageJson.name = 'preview-server'; - // We remove this one to avoid having resolve issues on our demo build process. - // This is only used in the `export` command so it's irrelevant to have it here. - // - // See `src/actions/render-email-by-path` for more info on how we render the - // email templates without `@react-email/render` being installed. - delete packageJson.devDependencies['@react-email/render']; - delete packageJson.devDependencies['@react-email/components']; + // We remove these dependencies to avoid having resolve issues on our demo's build process. + // The `render` function is only used in the `email export` command so it is irrelevant to have + // it during this build. + for (const key in packageJson.dependencies) { + if (key.startsWith('@react-email')) { + delete packageJson.dependencies[key]; + } + } + for (const key in packageJson.devDependencies) { + if (key.startsWith('@react-email')) { + delete packageJson.devDependencies[key]; + } + } + delete packageJson.peerDependencies; await fs.promises.writeFile( packageJsonPath, JSON.stringify(packageJson), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b3fd8fed02..b96f10ee42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: apps/demo: dependencies: + '@react-email/components': + specifier: workspace:* + version: link:../../packages/components react: specifier: ^19 version: 19.0.0 @@ -958,64 +961,64 @@ importers: specifier: 7.24.5 version: 7.24.5 '@react-email/body': - specifier: workspace:0.0.11-canary.1 + specifier: workspace:0.0.11 version: link:../body '@react-email/button': - specifier: workspace:0.0.19-canary.1 + specifier: workspace:0.0.19 version: link:../button '@react-email/code-block': - specifier: workspace:0.0.11-canary.2 + specifier: workspace:0.0.11 version: link:../code-block '@react-email/code-inline': - specifier: workspace:0.0.5-canary.1 + specifier: workspace:0.0.5 version: link:../code-inline '@react-email/column': - specifier: workspace:0.0.13-canary.1 + specifier: workspace:0.0.13 version: link:../column '@react-email/container': - specifier: workspace:0.0.15-canary.1 + specifier: workspace:0.0.15 version: link:../container '@react-email/font': - specifier: workspace:0.0.9-canary.1 + specifier: workspace:0.0.9 version: link:../font '@react-email/head': - specifier: workspace:0.0.12-canary.1 + specifier: workspace:0.0.12 version: link:../head '@react-email/heading': - specifier: workspace:0.0.15-canary.1 + specifier: workspace:0.0.15 version: link:../heading '@react-email/hr': - specifier: workspace:0.0.11-canary.1 + specifier: workspace:0.0.11 version: link:../hr '@react-email/html': - specifier: workspace:0.0.11-canary.1 + specifier: workspace:0.0.11 version: link:../html '@react-email/img': - specifier: workspace:0.0.11-canary.1 + specifier: workspace:0.0.11 version: link:../img '@react-email/link': - specifier: workspace:0.0.12-canary.1 + specifier: workspace:0.0.12 version: link:../link '@react-email/markdown': - specifier: workspace:0.0.13-canary.3 + specifier: workspace:0.0.14 version: link:../markdown '@react-email/preview': - specifier: workspace:0.0.12-canary.1 + specifier: workspace:0.0.12 version: link:../preview '@react-email/render': - specifier: workspace:1.0.3-canary.3 + specifier: workspace:1.0.3 version: link:../render '@react-email/row': - specifier: workspace:0.0.12-canary.1 + specifier: workspace:0.0.12 version: link:../row '@react-email/section': - specifier: workspace:0.0.16-canary.1 + specifier: workspace:0.0.16 version: link:../section '@react-email/tailwind': - specifier: workspace:1.0.3-canary.1 + specifier: workspace:1.0.4 version: link:../tailwind '@react-email/text': - specifier: workspace:0.0.11-canary.1 + specifier: workspace:0.0.11 version: link:../text chalk: specifier: 4.1.2 @@ -1072,9 +1075,6 @@ importers: '@radix-ui/react-tooltip': specifier: 1.1.2 version: 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@react-email/render': - specifier: workspace:* - version: link:../render '@swc/core': specifier: 1.4.15 version: 1.4.15(@swc/helpers@0.5.15) @@ -1127,8 +1127,8 @@ importers: specifier: 8.4.40 version: 8.4.40 prism-react-renderer: - specifier: 2.1.0 - version: 2.1.0(react@19.0.0) + specifier: 2.4.0 + version: 2.4.0(react@19.0.0) react: specifier: ^19 version: 19.0.0 @@ -3481,7 +3481,7 @@ packages: '@radix-ui/react-slot@1.1.0': resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==} peerDependencies: - '@types/react': npm:types-react@19.0.0-rc.1 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -6889,6 +6889,11 @@ packages: peerDependencies: react: '>=16.0.0' + prism-react-renderer@2.4.0: + resolution: {integrity: sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==} + peerDependencies: + react: '>=16.0.0' + prismjs@1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} @@ -14328,13 +14333,13 @@ snapshots: clsx: 1.2.1 react: 18.3.1 - prism-react-renderer@2.1.0(react@19.0.0): + prism-react-renderer@2.3.1(react@19.0.0): dependencies: '@types/prismjs': 1.26.5 - clsx: 1.2.1 + clsx: 2.1.0 react: 19.0.0 - prism-react-renderer@2.3.1(react@19.0.0): + prism-react-renderer@2.4.0(react@19.0.0): dependencies: '@types/prismjs': 1.26.5 clsx: 2.1.0 From dfa0fbb4579796dd4644c12f1fe63237e90c2e47 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 3 Dec 2024 14:58:43 -0300 Subject: [PATCH 14/78] add achangeset --- .changeset/selfish-dogs-deny.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/selfish-dogs-deny.md diff --git a/.changeset/selfish-dogs-deny.md b/.changeset/selfish-dogs-deny.md new file mode 100644 index 0000000000..67aef4c219 --- /dev/null +++ b/.changeset/selfish-dogs-deny.md @@ -0,0 +1,6 @@ +--- +"react-email": major +--- + +Export all components to substitute @react-email/components + From d37c4076c22bbf188499bad3544235dc1d1db13f Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Tue, 3 Dec 2024 15:03:13 -0300 Subject: [PATCH 15/78] format --- .changeset/selfish-dogs-deny.md | 1 - packages/react-email/src/package/index.ts | 40 +++++++++++------------ 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.changeset/selfish-dogs-deny.md b/.changeset/selfish-dogs-deny.md index 67aef4c219..2f01d405c2 100644 --- a/.changeset/selfish-dogs-deny.md +++ b/.changeset/selfish-dogs-deny.md @@ -3,4 +3,3 @@ --- Export all components to substitute @react-email/components - diff --git a/packages/react-email/src/package/index.ts b/packages/react-email/src/package/index.ts index 8ae88217d0..6c13454481 100644 --- a/packages/react-email/src/package/index.ts +++ b/packages/react-email/src/package/index.ts @@ -1,20 +1,20 @@ -export * from "@react-email/body"; -export * from "@react-email/button"; -export * from "@react-email/code-block"; -export * from "@react-email/code-inline"; -export * from "@react-email/column"; -export * from "@react-email/container"; -export * from "@react-email/font"; -export * from "@react-email/head"; -export * from "@react-email/heading"; -export * from "@react-email/hr"; -export * from "@react-email/html"; -export * from "@react-email/img"; -export * from "@react-email/link"; -export * from "@react-email/markdown"; -export * from "@react-email/preview"; -export * from "@react-email/render"; -export * from "@react-email/row"; -export * from "@react-email/section"; -export * from "@react-email/tailwind"; -export * from "@react-email/text"; +export * from '@react-email/body'; +export * from '@react-email/button'; +export * from '@react-email/code-block'; +export * from '@react-email/code-inline'; +export * from '@react-email/column'; +export * from '@react-email/container'; +export * from '@react-email/font'; +export * from '@react-email/head'; +export * from '@react-email/heading'; +export * from '@react-email/hr'; +export * from '@react-email/html'; +export * from '@react-email/img'; +export * from '@react-email/link'; +export * from '@react-email/markdown'; +export * from '@react-email/preview'; +export * from '@react-email/render'; +export * from '@react-email/row'; +export * from '@react-email/section'; +export * from '@react-email/tailwind'; +export * from '@react-email/text'; From 944cf3d6cc64222a2cf670876100dd61f0eabfe1 Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Wed, 11 Dec 2024 14:20:08 -0300 Subject: [PATCH 16/78] import from specific postcss files for smaller bundle size --- .../src/utils/css/resolve-all-css-variables.ts | 17 ++++++----------- .../src/utils/tailwindcss/setup-tailwind.ts | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/tailwind/src/utils/css/resolve-all-css-variables.ts b/packages/tailwind/src/utils/css/resolve-all-css-variables.ts index c1e1012d3e..0edf24c9e7 100644 --- a/packages/tailwind/src/utils/css/resolve-all-css-variables.ts +++ b/packages/tailwind/src/utils/css/resolve-all-css-variables.ts @@ -1,12 +1,7 @@ -import { - type Root, - type Node, - type Declaration, - Rule, - rule as createRule, - decl as createDeclaration, - AtRule, -} from "postcss"; +import type { Node, Root } from "postcss"; +import Declaration from "postcss/lib/declaration"; +import Rule from "postcss/lib/rule"; +import AtRule from "postcss/lib/at-rule"; import { removeIfEmptyRecursively } from "./remove-if-empty-recursively"; const doNodesMatch = (first: Node | undefined, second: Node | undefined) => { @@ -49,7 +44,7 @@ export const resolveAllCSSVariables = (root: Root) => { ) { const atRule = otherDecl.parent.parent; - const clonedDeclaration = createDeclaration(); + const clonedDeclaration = new Declaration(); clonedDeclaration.prop = decl.prop; clonedDeclaration.value = decl.value.replaceAll( variable, @@ -84,7 +79,7 @@ export const resolveAllCSSVariables = (root: Root) => { } for (const [atRule, declarations] of declarationsForAtRules.entries()) { - const equivalentRule = createRule(); + const equivalentRule = new Rule(); equivalentRule.selector = rule.selector; equivalentRule.append(...declarations); diff --git a/packages/tailwind/src/utils/tailwindcss/setup-tailwind.ts b/packages/tailwind/src/utils/tailwindcss/setup-tailwind.ts index 43612a6ad8..e2a7b7108c 100644 --- a/packages/tailwind/src/utils/tailwindcss/setup-tailwind.ts +++ b/packages/tailwind/src/utils/tailwindcss/setup-tailwind.ts @@ -1,5 +1,5 @@ import type { Rule } from "postcss"; -import { parse } from "postcss"; +import parse from "postcss/lib/parse"; import evaluateTailwindFunctions from "tailwindcss/lib/lib/evaluateTailwindFunctions"; import expandApplyAtRules from "tailwindcss/lib/lib/expandApplyAtRules"; import resolveDefaultsAtRules from "tailwindcss/lib/lib/resolveDefaultsAtRules"; From 77c534d68323ce2ff686065a92e25d0fa0b6f48d Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Wed, 11 Dec 2024 14:20:13 -0300 Subject: [PATCH 17/78] update integration locks --- packages/tailwind/integrations/nextjs/package-lock.json | 2 +- packages/tailwind/integrations/vite/package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tailwind/integrations/nextjs/package-lock.json b/packages/tailwind/integrations/nextjs/package-lock.json index c6bc5c39af..e04f8c4960 100644 --- a/packages/tailwind/integrations/nextjs/package-lock.json +++ b/packages/tailwind/integrations/nextjs/package-lock.json @@ -24,7 +24,7 @@ } }, ".yalc/@react-email/tailwind": { - "version": "1.0.0", + "version": "1.0.4", "license": "MIT", "engines": { "node": ">=18.0.0" diff --git a/packages/tailwind/integrations/vite/package-lock.json b/packages/tailwind/integrations/vite/package-lock.json index 6c29055824..484561fc3d 100644 --- a/packages/tailwind/integrations/vite/package-lock.json +++ b/packages/tailwind/integrations/vite/package-lock.json @@ -29,7 +29,7 @@ } }, ".yalc/@react-email/tailwind": { - "version": "1.0.0", + "version": "1.0.4", "license": "MIT", "engines": { "node": ">=18.0.0" From c878ee59016f99402124a2d991da4a45bf7667be Mon Sep 17 00:00:00 2001 From: gabriel miranda Date: Thu, 12 Dec 2024 17:13:21 -0300 Subject: [PATCH 18/78] initial attempt at using tsup to build tailwind --- package.json | 10 +- .../body/__snapshots__/body.spec.tsx.snap | 3 + .../src/package/body/body.spec.tsx | 26 + .../react-email/src/package/body/body.tsx | 15 + .../button/__snapshots__/button.spec.tsx.snap | 7 + .../src/package/button/button.spec.tsx | 47 + .../react-email/src/package/button/button.tsx | 116 + .../src/package/button/utils/parse-padding.ts | 110 + .../src/package/button/utils/px-to-pt.ts | 2 + .../src/package/button/utils/utils.spec.ts | 113 + .../src/package/code-block/.prettierignore | 1 + .../src/package/code-block/code-block.tsx | 126 + .../src/package/code-block/index.ts | 3 + .../package/code-block/languages-available.ts | 402 + .../src/package/code-block/prism.ts | 15619 ++++++++++++++++ .../src/package/code-block/themes.ts | 4929 +++++ .../src/package/code-inline/code-inline.tsx | 58 + .../src/package/code-inline/index.ts | 1 + .../column/__snapshots__/column.spec.tsx.snap | 3 + .../src/package/column/column.spec.tsx | 26 + .../react-email/src/package/column/column.tsx | 15 + .../__snapshots__/container.spec.tsx.snap | 3 + .../src/package/container/container.spec.tsx | 31 + .../src/package/container/container.tsx | 29 + .../font/__snapshots__/font.spec.tsx.snap | 17 + .../src/package/font/font.spec.tsx | 49 + .../react-email/src/package/font/font.tsx | 76 + .../head/__snapshots__/head.spec.tsx.snap | 9 + .../src/package/head/head.spec.tsx | 28 + .../react-email/src/package/head/head.tsx | 15 + .../__snapshots__/heading.spec.tsx.snap | 3 + .../src/package/heading/heading.spec.tsx | 30 + .../src/package/heading/heading.tsx | 29 + .../src/package/heading/utils/as.ts | 26 + .../src/package/heading/utils/spaces.ts | 48 + .../src/package/heading/utils/utils.spec.ts | 70 + .../package/hr/__snapshots__/hr.spec.tsx.snap | 3 + .../react-email/src/package/hr/hr.spec.tsx | 20 + packages/react-email/src/package/hr/hr.tsx | 20 + .../html/__snapshots__/html.spec.tsx.snap | 3 + .../src/package/html/html.spec.tsx | 24 + .../react-email/src/package/html/html.tsx | 13 + .../img/__snapshots__/img.spec.tsx.snap | 3 + .../react-email/src/package/img/img.spec.tsx | 28 + packages/react-email/src/package/img/img.tsx | 25 + .../react-email/src/package/index.browser.ts | 20 + .../react-email/src/package/index.node.ts | 20 + .../link/__snapshots__/link.spec.tsx.snap | 3 + .../react-email/src/package/link/index.ts | 1 + .../src/package/link/link.spec.tsx | 35 + .../react-email/src/package/link/link.tsx | 22 + .../__snapshots__/markdown.spec.tsx.snap | 50 + .../src/package/markdown/markdown.spec.tsx | 114 + .../src/package/markdown/markdown.tsx | 33 + .../__snapshots__/preview.spec.tsx.snap | 7 + .../src/package/preview/preview.spec.tsx | 45 + .../src/package/preview/preview.tsx | 46 + .../render-async-web.spec.tsx.snap | 3 + .../__snapshots__/render-web.spec.tsx.snap | 3 + .../src/package/render/browser/index.ts | 5 + .../src/package/render/browser/read-stream.ts | 44 + .../render/browser/render-async-web.spec.tsx | 125 + .../package/render/browser/render-async.tsx | 51 + .../render/browser/render-web.spec.tsx | 125 + .../src/package/render/browser/render.tsx | 94 + .../render-async-edge.spec.tsx.snap | 3 + .../render-async-node.spec.tsx.snap | 53 + .../__snapshots__/render-edge.spec.tsx.snap | 3 + .../__snapshots__/render-node.spec.tsx.snap | 53 + .../src/package/render/node/index.ts | 5 + .../src/package/render/node/read-stream.ts | 41 + .../render/node/render-async-edge.spec.tsx | 106 + .../render/node/render-async-node.spec.tsx | 126 + .../src/package/render/node/render-async.tsx | 54 + .../package/render/node/render-edge.spec.tsx | 115 + .../package/render/node/render-node.spec.tsx | 136 + .../src/package/render/node/render.tsx | 51 + .../src/package/render/react-internals.d.ts | 3 + .../src/package/render/shared/options.ts | 19 + .../render/shared/plain-text-selectors.ts | 10 + .../render/shared/react-internals.d.ts | 3 + .../src/package/render/shared/utils/pretty.ts | 17 + .../package/render/shared/utils/preview.tsx | 8 + .../package/render/shared/utils/template.tsx | 13 + .../row/__snapshots__/row.spec.tsx.snap | 3 + .../react-email/src/package/row/row.spec.tsx | 27 + packages/react-email/src/package/row/row.tsx | 31 + .../__snapshots__/section.spec.tsx.snap | 3 + .../src/package/section/section.spec.tsx | 56 + .../src/package/section/section.tsx | 29 + .../__snapshots__/tailwind.spec.tsx.snap | 79 + .../src/package/tailwind/contributing.md | 54 + .../integrations/_tests/nextjs.spec.ts | 8 + .../_tests/utils/run-testing-command.ts | 17 + .../tailwind/integrations/_tests/vite.spec.ts | 8 + .../tailwind/integrations/nextjs/.gitignore | 39 + .../tailwind/integrations/nextjs/README.md | 9 + .../nextjs/emails/vercel-invite-user.tsx | 152 + .../integrations/nextjs/next.config.mjs | 7 + .../integrations/nextjs/package-lock.json | 7705 ++++++++ .../tailwind/integrations/nextjs/package.json | 27 + .../integrations/nextjs/src/app/favicon.ico | Bin 0 -> 25931 bytes .../integrations/nextjs/src/app/layout.tsx | 23 + .../integrations/nextjs/src/app/page.tsx | 7 + .../integrations/nextjs/tsconfig.json | 26 + .../tailwind/integrations/vite/.eslintrc.cjs | 18 + .../tailwind/integrations/vite/.gitignore | 26 + .../tailwind/integrations/vite/README.md | 9 + .../vite/emails/vercel-invite-user.tsx | 151 + .../tailwind/integrations/vite/index.html | 13 + .../integrations/vite/package-lock.json | 8777 +++++++++ .../tailwind/integrations/vite/package.json | 33 + .../integrations/vite/public/vite.svg | 1 + .../tailwind/integrations/vite/src/App.tsx | 10 + .../tailwind/integrations/vite/src/main.tsx | 9 + .../integrations/vite/src/vite-env.d.ts | 1 + .../tailwind/integrations/vite/tsconfig.json | 25 + .../integrations/vite/tsconfig.node.json | 11 + .../tailwind/integrations/vite/vite.config.ts | 7 + .../src/package/tailwind/tailwind.spec.tsx | 655 + .../src/package/tailwind/tailwind.tsx | 117 + .../src/package/tailwind/tsup.config.ts | 22 + .../quick-safe-render-to-string.spec.tsx.snap | 3 + .../convert-css-property-to-react-property.ts | 15 + .../compatibility/escape-class-name.spec.ts | 17 + .../utils/compatibility/escape-class-name.ts | 18 + .../compatibility/sanitize-class-name.spec.ts | 7 + .../compatibility/sanitize-class-name.ts | 36 + .../utils/compatibility/unescape-class.ts | 3 + .../utils/css/make-inline-styles-for.spec.ts | 15 + .../utils/css/make-inline-styles-for.ts | 56 + .../sanitize-media-queries.spec.ts | 88 + .../media-queries/sanitize-media-queries.ts | 64 + .../package/tailwind/utils/css/minify-css.ts | 21 + .../utils/css/remove-if-empty-recursively.ts | 11 + .../css/remove-rule-duplicates-from-root.ts | 16 + .../css/resolve-all-css-variables.spec.ts | 79 + .../utils/css/resolve-all-css-variables.ts | 101 + .../utils/css/sanitize-declarations.ts | 25 + .../tailwind/utils/react/is-component.ts | 9 + .../utils/react/map-react-tree.spec.tsx | 58 + .../tailwind/utils/react/map-react-tree.ts | 57 + .../__snapshots__/setup-tailwind.spec.ts.snap | 16 + .../clone-element-with-inlined-styles.ts | 77 + .../tailwindcss/setup-tailwind-context.ts | 15 + .../utils/tailwindcss/setup-tailwind.spec.ts | 15 + .../utils/tailwindcss/setup-tailwind.ts | 53 + .../utils/tailwindcss/tailwind-internals.d.ts | 133 + .../text/from-dash-case-to-camel-case.ts | 3 + .../text/__snapshots__/text.spec.tsx.snap | 3 + .../src/package/text/text.spec.tsx | 26 + .../react-email/src/package/text/text.tsx | 20 + packages/react-email/vitest.config.ts | 14 + .../integrations/nextjs/package-lock.json | 16 + .../integrations/vite/package-lock.json | 16 + packages/tailwind/package.json | 11 +- packages/tailwind/tsconfig.json | 1 + packages/tailwind/tsup.config.ts | 22 + packages/tailwind/vite.config.ts | 33 - patches/postcss-js@4.0.1.patch | 83 + patches/postcss-nested@6.0.1.patch | 11 + patches/postcss-selector-parser@6.0.16.patch | 152 + patches/postcss@8.4.40.patch | 289 + patches/tailwindcss@3.4.10.patch | 590 + pnpm-lock.yaml | 543 +- 165 files changed, 44450 insertions(+), 485 deletions(-) create mode 100644 packages/react-email/src/package/body/__snapshots__/body.spec.tsx.snap create mode 100644 packages/react-email/src/package/body/body.spec.tsx create mode 100644 packages/react-email/src/package/body/body.tsx create mode 100644 packages/react-email/src/package/button/__snapshots__/button.spec.tsx.snap create mode 100644 packages/react-email/src/package/button/button.spec.tsx create mode 100644 packages/react-email/src/package/button/button.tsx create mode 100644 packages/react-email/src/package/button/utils/parse-padding.ts create mode 100644 packages/react-email/src/package/button/utils/px-to-pt.ts create mode 100644 packages/react-email/src/package/button/utils/utils.spec.ts create mode 100644 packages/react-email/src/package/code-block/.prettierignore create mode 100644 packages/react-email/src/package/code-block/code-block.tsx create mode 100644 packages/react-email/src/package/code-block/index.ts create mode 100644 packages/react-email/src/package/code-block/languages-available.ts create mode 100644 packages/react-email/src/package/code-block/prism.ts create mode 100644 packages/react-email/src/package/code-block/themes.ts create mode 100644 packages/react-email/src/package/code-inline/code-inline.tsx create mode 100644 packages/react-email/src/package/code-inline/index.ts create mode 100644 packages/react-email/src/package/column/__snapshots__/column.spec.tsx.snap create mode 100644 packages/react-email/src/package/column/column.spec.tsx create mode 100644 packages/react-email/src/package/column/column.tsx create mode 100644 packages/react-email/src/package/container/__snapshots__/container.spec.tsx.snap create mode 100644 packages/react-email/src/package/container/container.spec.tsx create mode 100644 packages/react-email/src/package/container/container.tsx create mode 100644 packages/react-email/src/package/font/__snapshots__/font.spec.tsx.snap create mode 100644 packages/react-email/src/package/font/font.spec.tsx create mode 100644 packages/react-email/src/package/font/font.tsx create mode 100644 packages/react-email/src/package/head/__snapshots__/head.spec.tsx.snap create mode 100644 packages/react-email/src/package/head/head.spec.tsx create mode 100644 packages/react-email/src/package/head/head.tsx create mode 100644 packages/react-email/src/package/heading/__snapshots__/heading.spec.tsx.snap create mode 100644 packages/react-email/src/package/heading/heading.spec.tsx create mode 100644 packages/react-email/src/package/heading/heading.tsx create mode 100644 packages/react-email/src/package/heading/utils/as.ts create mode 100644 packages/react-email/src/package/heading/utils/spaces.ts create mode 100644 packages/react-email/src/package/heading/utils/utils.spec.ts create mode 100644 packages/react-email/src/package/hr/__snapshots__/hr.spec.tsx.snap create mode 100644 packages/react-email/src/package/hr/hr.spec.tsx create mode 100644 packages/react-email/src/package/hr/hr.tsx create mode 100644 packages/react-email/src/package/html/__snapshots__/html.spec.tsx.snap create mode 100644 packages/react-email/src/package/html/html.spec.tsx create mode 100644 packages/react-email/src/package/html/html.tsx create mode 100644 packages/react-email/src/package/img/__snapshots__/img.spec.tsx.snap create mode 100644 packages/react-email/src/package/img/img.spec.tsx create mode 100644 packages/react-email/src/package/img/img.tsx create mode 100644 packages/react-email/src/package/index.browser.ts create mode 100644 packages/react-email/src/package/index.node.ts create mode 100644 packages/react-email/src/package/link/__snapshots__/link.spec.tsx.snap create mode 100644 packages/react-email/src/package/link/index.ts create mode 100644 packages/react-email/src/package/link/link.spec.tsx create mode 100644 packages/react-email/src/package/link/link.tsx create mode 100644 packages/react-email/src/package/markdown/__snapshots__/markdown.spec.tsx.snap create mode 100644 packages/react-email/src/package/markdown/markdown.spec.tsx create mode 100644 packages/react-email/src/package/markdown/markdown.tsx create mode 100644 packages/react-email/src/package/preview/__snapshots__/preview.spec.tsx.snap create mode 100644 packages/react-email/src/package/preview/preview.spec.tsx create mode 100644 packages/react-email/src/package/preview/preview.tsx create mode 100644 packages/react-email/src/package/render/browser/__snapshots__/render-async-web.spec.tsx.snap create mode 100644 packages/react-email/src/package/render/browser/__snapshots__/render-web.spec.tsx.snap create mode 100644 packages/react-email/src/package/render/browser/index.ts create mode 100644 packages/react-email/src/package/render/browser/read-stream.ts create mode 100644 packages/react-email/src/package/render/browser/render-async-web.spec.tsx create mode 100644 packages/react-email/src/package/render/browser/render-async.tsx create mode 100644 packages/react-email/src/package/render/browser/render-web.spec.tsx create mode 100644 packages/react-email/src/package/render/browser/render.tsx create mode 100644 packages/react-email/src/package/render/node/__snapshots__/render-async-edge.spec.tsx.snap create mode 100644 packages/react-email/src/package/render/node/__snapshots__/render-async-node.spec.tsx.snap create mode 100644 packages/react-email/src/package/render/node/__snapshots__/render-edge.spec.tsx.snap create mode 100644 packages/react-email/src/package/render/node/__snapshots__/render-node.spec.tsx.snap create mode 100644 packages/react-email/src/package/render/node/index.ts create mode 100644 packages/react-email/src/package/render/node/read-stream.ts create mode 100644 packages/react-email/src/package/render/node/render-async-edge.spec.tsx create mode 100644 packages/react-email/src/package/render/node/render-async-node.spec.tsx create mode 100644 packages/react-email/src/package/render/node/render-async.tsx create mode 100644 packages/react-email/src/package/render/node/render-edge.spec.tsx create mode 100644 packages/react-email/src/package/render/node/render-node.spec.tsx create mode 100644 packages/react-email/src/package/render/node/render.tsx create mode 100644 packages/react-email/src/package/render/react-internals.d.ts create mode 100644 packages/react-email/src/package/render/shared/options.ts create mode 100644 packages/react-email/src/package/render/shared/plain-text-selectors.ts create mode 100644 packages/react-email/src/package/render/shared/react-internals.d.ts create mode 100644 packages/react-email/src/package/render/shared/utils/pretty.ts create mode 100644 packages/react-email/src/package/render/shared/utils/preview.tsx create mode 100644 packages/react-email/src/package/render/shared/utils/template.tsx create mode 100644 packages/react-email/src/package/row/__snapshots__/row.spec.tsx.snap create mode 100644 packages/react-email/src/package/row/row.spec.tsx create mode 100644 packages/react-email/src/package/row/row.tsx create mode 100644 packages/react-email/src/package/section/__snapshots__/section.spec.tsx.snap create mode 100644 packages/react-email/src/package/section/section.spec.tsx create mode 100644 packages/react-email/src/package/section/section.tsx create mode 100644 packages/react-email/src/package/tailwind/__snapshots__/tailwind.spec.tsx.snap create mode 100644 packages/react-email/src/package/tailwind/contributing.md create mode 100644 packages/react-email/src/package/tailwind/integrations/_tests/nextjs.spec.ts create mode 100644 packages/react-email/src/package/tailwind/integrations/_tests/utils/run-testing-command.ts create mode 100644 packages/react-email/src/package/tailwind/integrations/_tests/vite.spec.ts create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/.gitignore create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/README.md create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/emails/vercel-invite-user.tsx create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/next.config.mjs create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/package-lock.json create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/package.json create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/src/app/favicon.ico create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/src/app/layout.tsx create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/src/app/page.tsx create mode 100644 packages/react-email/src/package/tailwind/integrations/nextjs/tsconfig.json create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/.eslintrc.cjs create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/.gitignore create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/README.md create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/emails/vercel-invite-user.tsx create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/index.html create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/package-lock.json create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/package.json create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/public/vite.svg create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/src/App.tsx create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/src/main.tsx create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/src/vite-env.d.ts create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/tsconfig.json create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/tsconfig.node.json create mode 100644 packages/react-email/src/package/tailwind/integrations/vite/vite.config.ts create mode 100644 packages/react-email/src/package/tailwind/tailwind.spec.tsx create mode 100644 packages/react-email/src/package/tailwind/tailwind.tsx create mode 100644 packages/react-email/src/package/tailwind/tsup.config.ts create mode 100644 packages/react-email/src/package/tailwind/utils/__snapshots__/quick-safe-render-to-string.spec.tsx.snap create mode 100644 packages/react-email/src/package/tailwind/utils/compatibility/convert-css-property-to-react-property.ts create mode 100644 packages/react-email/src/package/tailwind/utils/compatibility/escape-class-name.spec.ts create mode 100644 packages/react-email/src/package/tailwind/utils/compatibility/escape-class-name.ts create mode 100644 packages/react-email/src/package/tailwind/utils/compatibility/sanitize-class-name.spec.ts create mode 100644 packages/react-email/src/package/tailwind/utils/compatibility/sanitize-class-name.ts create mode 100644 packages/react-email/src/package/tailwind/utils/compatibility/unescape-class.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/make-inline-styles-for.spec.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/make-inline-styles-for.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/media-queries/sanitize-media-queries.spec.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/media-queries/sanitize-media-queries.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/minify-css.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/remove-if-empty-recursively.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/remove-rule-duplicates-from-root.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/resolve-all-css-variables.spec.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/resolve-all-css-variables.ts create mode 100644 packages/react-email/src/package/tailwind/utils/css/sanitize-declarations.ts create mode 100644 packages/react-email/src/package/tailwind/utils/react/is-component.ts create mode 100644 packages/react-email/src/package/tailwind/utils/react/map-react-tree.spec.tsx create mode 100644 packages/react-email/src/package/tailwind/utils/react/map-react-tree.ts create mode 100644 packages/react-email/src/package/tailwind/utils/tailwindcss/__snapshots__/setup-tailwind.spec.ts.snap create mode 100644 packages/react-email/src/package/tailwind/utils/tailwindcss/clone-element-with-inlined-styles.ts create mode 100644 packages/react-email/src/package/tailwind/utils/tailwindcss/setup-tailwind-context.ts create mode 100644 packages/react-email/src/package/tailwind/utils/tailwindcss/setup-tailwind.spec.ts create mode 100644 packages/react-email/src/package/tailwind/utils/tailwindcss/setup-tailwind.ts create mode 100644 packages/react-email/src/package/tailwind/utils/tailwindcss/tailwind-internals.d.ts create mode 100644 packages/react-email/src/package/tailwind/utils/text/from-dash-case-to-camel-case.ts create mode 100644 packages/react-email/src/package/text/__snapshots__/text.spec.tsx.snap create mode 100644 packages/react-email/src/package/text/text.spec.tsx create mode 100644 packages/react-email/src/package/text/text.tsx create mode 100644 packages/react-email/vitest.config.ts create mode 100644 packages/tailwind/tsup.config.ts delete mode 100644 packages/tailwind/vite.config.ts create mode 100644 patches/postcss-js@4.0.1.patch create mode 100644 patches/postcss-nested@6.0.1.patch create mode 100644 patches/postcss-selector-parser@6.0.16.patch create mode 100644 patches/postcss@8.4.40.patch create mode 100644 patches/tailwindcss@3.4.10.patch diff --git a/package.json b/package.json index df6edd3231..223cc68192 100644 --- a/package.json +++ b/package.json @@ -30,5 +30,13 @@ "vite": "5.4.6", "vitest": "2.0.5" }, - "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c" + "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c", + "pnpm": { + "patchedDependencies": { + "tailwindcss@3.4.10": "patches/tailwindcss@3.4.10.patch", + "postcss-js@4.0.1": "patches/postcss-js@4.0.1.patch", + "postcss-selector-parser@6.0.16": "patches/postcss-selector-parser@6.0.16.patch", + "postcss@8.4.40": "patches/postcss@8.4.40.patch" + } + } } diff --git a/packages/react-email/src/package/body/__snapshots__/body.spec.tsx.snap b/packages/react-email/src/package/body/__snapshots__/body.spec.tsx.snap new file mode 100644 index 0000000000..ca86d77885 --- /dev/null +++ b/packages/react-email/src/package/body/__snapshots__/body.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` component > renders correctly 1`] = `"Lorem ipsum"`; diff --git a/packages/react-email/src/package/body/body.spec.tsx b/packages/react-email/src/package/body/body.spec.tsx new file mode 100644 index 0000000000..cf1779b56f --- /dev/null +++ b/packages/react-email/src/package/body/body.spec.tsx @@ -0,0 +1,26 @@ +import { render } from "../render/node"; +import { Body } from "./body"; + +describe(" component", () => { + it("renders children correctly", async () => { + const testMessage = "Test message"; + const html = await render({testMessage}); + expect(html).toContain(testMessage); + }); + + it("passes style and other props correctly", async () => { + const style = { backgroundColor: "red" }; + const html = await render( + + Test + , + ); + expect(html).toContain('style="background-color:red"'); + expect(html).toContain('data-testid="body-test"'); + }); + + it("renders correctly", async () => { + const actualOutput = await render(Lorem ipsum); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/body/body.tsx b/packages/react-email/src/package/body/body.tsx new file mode 100644 index 0000000000..bfd2871ee1 --- /dev/null +++ b/packages/react-email/src/package/body/body.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; + +export type BodyProps = Readonly>; + +export const Body = React.forwardRef( + ({ children, style, ...props }, ref) => { + return ( + + {children} + + ); + }, +); + +Body.displayName = "Body"; diff --git a/packages/react-email/src/package/button/__snapshots__/button.spec.tsx.snap b/packages/react-email/src/package/button/__snapshots__/button.spec.tsx.snap new file mode 100644 index 0000000000..1b9e7cd7ed --- /dev/null +++ b/packages/react-email/src/package/button/__snapshots__/button.spec.tsx.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`); + expect(html).toContain(testMessage); + }); + + it("passes style and other props correctly", async () => { + const style = { backgroundColor: "red" }; + const html = await render( + , + ); + expect(html).toContain("background-color:red"); + expect(html).toContain('data-testid="button-test"'); + }); + + it("renders correctly with padding values from style prop", async () => { + const actualOutput = await render( + + , + ); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/container/container.tsx b/packages/react-email/src/package/container/container.tsx new file mode 100644 index 0000000000..fcf8aa00ad --- /dev/null +++ b/packages/react-email/src/package/container/container.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; + +export type ContainerProps = Readonly>; + +export const Container = React.forwardRef( + ({ children, style, ...props }, ref) => { + return ( + + + + + + +
{children}
+ ); + }, +); + +Container.displayName = "Container"; diff --git a/packages/react-email/src/package/font/__snapshots__/font.spec.tsx.snap b/packages/react-email/src/package/font/__snapshots__/font.spec.tsx.snap new file mode 100644 index 0000000000..6ce5a111f3 --- /dev/null +++ b/packages/react-email/src/package/font/__snapshots__/font.spec.tsx.snap @@ -0,0 +1,17 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` component > renders correctly 1`] = ` +"" +`; diff --git a/packages/react-email/src/package/font/font.spec.tsx b/packages/react-email/src/package/font/font.spec.tsx new file mode 100644 index 0000000000..c5e8d77ae4 --- /dev/null +++ b/packages/react-email/src/package/font/font.spec.tsx @@ -0,0 +1,49 @@ +import { render } from "../render/node"; +import { Font } from "./font"; + +describe(" component", () => { + it("renders with default props", async () => { + const html = await render( + , + ); + + expect(html).toContain("font-style: normal;"); + expect(html).toContain("font-weight: 400;"); + expect(html).toContain("font-family: 'Arial';"); + }); + + it("renders with webFont prop", async () => { + const webFont = { + url: "example.com/font.woff", + format: "woff", + } as const; + + const html = await render( + , + ); + + expect(html).toContain("font-family: 'Example';"); + expect(html).toContain( + `src: url(${webFont.url}) format('${webFont.format}');`, + ); + }); + + it("renders with multiple fallback fonts", async () => { + const html = await render( + , + ); + + expect(html).toContain("font-family: 'Arial', Helvetica, Verdana;"); + }); + + it("renders correctly", async () => { + const actualOutput = await render( + , + ); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/font/font.tsx b/packages/react-email/src/package/font/font.tsx new file mode 100644 index 0000000000..b52328f021 --- /dev/null +++ b/packages/react-email/src/package/font/font.tsx @@ -0,0 +1,76 @@ +import * as React from "react"; + +type FallbackFont = + | "Arial" + | "Helvetica" + | "Verdana" + | "Georgia" + | "Times New Roman" + | "serif" + | "sans-serif" + | "monospace" + | "cursive" + | "fantasy"; + +type FontFormat = + | "woff" + | "woff2" + | "truetype" + | "opentype" + | "embedded-opentype" + | "svg"; + +type FontWeight = React.CSSProperties["fontWeight"]; +type FontStyle = React.CSSProperties["fontStyle"]; + +export interface FontProps { + /** The font you want to use. NOTE: Do not insert multiple fonts here, use fallbackFontFamily for that */ + fontFamily: string; + /** An array is possible, but the order of the array is the priority order */ + fallbackFontFamily: FallbackFont | FallbackFont[]; + /** Not all clients support web fonts. For support check: https://www.caniemail.com/features/css-at-font-face/ */ + webFont?: { + url: string; + format: FontFormat; + }; + /** Default: 'normal' */ + fontStyle?: FontStyle; + /** Default: 400 */ + fontWeight?: FontWeight; +} + +/** The component MUST be place inside the tag */ +export const Font: React.FC> = ({ + fontFamily, + fallbackFontFamily, + webFont, + fontStyle = "normal", + fontWeight = 400, +}) => { + const src = webFont + ? `src: url(${webFont.url}) format('${webFont.format}');` + : ""; + + const style = ` + @font-face { + font-family: '${fontFamily}'; + font-style: ${fontStyle}; + font-weight: ${fontWeight}; + mso-font-alt: '${ + Array.isArray(fallbackFontFamily) + ? fallbackFontFamily[0] + : fallbackFontFamily + }'; + ${src} + } + + * { + font-family: '${fontFamily}', ${ + Array.isArray(fallbackFontFamily) + ? fallbackFontFamily.join(", ") + : fallbackFontFamily + }; + } + `; + return " +`; diff --git a/packages/react-email/src/package/head/head.spec.tsx b/packages/react-email/src/package/head/head.spec.tsx new file mode 100644 index 0000000000..16d63137d8 --- /dev/null +++ b/packages/react-email/src/package/head/head.spec.tsx @@ -0,0 +1,28 @@ +import { render } from "../render/node"; +import { Head } from "./head"; + +describe(" component", () => { + it("renders children correctly", async () => { + const testMessage = "Test message"; + const html = await render({testMessage}); + expect(html).toContain(testMessage); + }); + + it("renders correctly", async () => { + const actualOutput = await render(); + expect(actualOutput).toMatchSnapshot(); + }); + + it("renders style tags", async () => { + const actualOutput = await render( + + + , + ); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/head/head.tsx b/packages/react-email/src/package/head/head.tsx new file mode 100644 index 0000000000..3560615009 --- /dev/null +++ b/packages/react-email/src/package/head/head.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; + +export type HeadProps = Readonly>; + +export const Head = React.forwardRef( + ({ children, ...props }, ref) => ( + + + + {children} + + ), +); + +Head.displayName = "Head"; diff --git a/packages/react-email/src/package/heading/__snapshots__/heading.spec.tsx.snap b/packages/react-email/src/package/heading/__snapshots__/heading.spec.tsx.snap new file mode 100644 index 0000000000..a790e21f6d --- /dev/null +++ b/packages/react-email/src/package/heading/__snapshots__/heading.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`render > renders the component 1`] = `"

Lorem ipsum

"`; diff --git a/packages/react-email/src/package/heading/heading.spec.tsx b/packages/react-email/src/package/heading/heading.spec.tsx new file mode 100644 index 0000000000..96e1e5894b --- /dev/null +++ b/packages/react-email/src/package/heading/heading.spec.tsx @@ -0,0 +1,30 @@ +import { render } from "../render/node"; +import { Heading } from "./heading"; + +describe("render", () => { + it("renders children correctly", async () => { + const testMessage = "Test message"; + const html = await render({testMessage}); + expect(html).toContain(testMessage); + }); + + it("passes style and other props correctly", async () => { + const style = { backgroundColor: "red" }; + const html = await render( + + Test + , + ); + expect(html).toContain("background-color:red"); + expect(html).toContain('data-testid="heading-test"'); + }); + + it("renders the component", async () => { + const actualOutput = await render( + + Lorem ipsum + , + ); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/heading/heading.tsx b/packages/react-email/src/package/heading/heading.tsx new file mode 100644 index 0000000000..2047f1305f --- /dev/null +++ b/packages/react-email/src/package/heading/heading.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import type { As } from "./utils/as"; +import type { Margin } from "./utils/spaces"; +import { withMargin } from "./utils/spaces"; + +export type HeadingAs = As<"h1", "h2", "h3", "h4", "h5", "h6">; +export type HeadingProps = HeadingAs & Margin; + +export const Heading = React.forwardRef< + HTMLHeadingElement, + Readonly +>( + ( + { as: Tag = "h1", children, style, m, mx, my, mt, mr, mb, ml, ...props }, + ref, + ) => { + return ( + + {children} + + ); + }, +); + +Heading.displayName = "Heading"; diff --git a/packages/react-email/src/package/heading/utils/as.ts b/packages/react-email/src/package/heading/utils/as.ts new file mode 100644 index 0000000000..0452a37899 --- /dev/null +++ b/packages/react-email/src/package/heading/utils/as.ts @@ -0,0 +1,26 @@ +export type As< + DefaultTag extends React.ElementType, + T1 extends React.ElementType, + T2 extends React.ElementType = T1, + T3 extends React.ElementType = T1, + T4 extends React.ElementType = T1, + T5 extends React.ElementType = T1, +> = + | (React.ComponentPropsWithRef & { + as?: DefaultTag; + }) + | (React.ComponentPropsWithRef & { + as: T1; + }) + | (React.ComponentPropsWithRef & { + as: T2; + }) + | (React.ComponentPropsWithRef & { + as: T3; + }) + | (React.ComponentPropsWithRef & { + as: T4; + }) + | (React.ComponentPropsWithRef & { + as: T5; + }); diff --git a/packages/react-email/src/package/heading/utils/spaces.ts b/packages/react-email/src/package/heading/utils/spaces.ts new file mode 100644 index 0000000000..ee35aadcca --- /dev/null +++ b/packages/react-email/src/package/heading/utils/spaces.ts @@ -0,0 +1,48 @@ +import type React from "react"; + +type MarginCSSProperty = React.CSSProperties[ + | "margin" + | "marginLeft" + | "marginRight" + | "marginTop" + | "marginBottom"]; + +export interface Margin { + m?: number | string; + mx?: number | string; + my?: number | string; + mt?: number | string; + mr?: number | string; + mb?: number | string; + ml?: number | string; +} + +export const withMargin = (props: Margin) => { + const nonEmptyStyles = [ + withSpace(props.m, ["margin"]), + withSpace(props.mx, ["marginLeft", "marginRight"]), + withSpace(props.my, ["marginTop", "marginBottom"]), + withSpace(props.mt, ["marginTop"]), + withSpace(props.mr, ["marginRight"]), + withSpace(props.mb, ["marginBottom"]), + withSpace(props.ml, ["marginLeft"]), + ].filter((s) => Object.keys(s).length); + + const mergedStyles = nonEmptyStyles.reduce((acc, style) => { + return { ...acc, ...style }; + }, {}); + return mergedStyles; +}; + +export const withSpace = ( + value: number | string | undefined, + properties: MarginCSSProperty[], +) => { + return properties.reduce((styles, property) => { + // Check to ensure string value is a valid number + if (!isNaN(parseFloat(value as string))) { + return { ...styles, [property as keyof MarginCSSProperty]: `${value}px` }; + } + return styles; + }, {}); +}; diff --git a/packages/react-email/src/package/heading/utils/utils.spec.ts b/packages/react-email/src/package/heading/utils/utils.spec.ts new file mode 100644 index 0000000000..9ba5cc274b --- /dev/null +++ b/packages/react-email/src/package/heading/utils/utils.spec.ts @@ -0,0 +1,70 @@ +import type { Margin } from "./spaces"; +import { withMargin, withSpace } from "./spaces"; + +describe("withMargin", () => { + it("should return an empty object for empty input", () => { + const marginProps: Margin = {}; + const result = withMargin(marginProps); + expect(result).toEqual({}); + }); + + it("should apply margin to the top", () => { + const marginProps: Margin = { mt: "10" }; + const result = withMargin(marginProps); + expect(result).toEqual({ marginTop: "10px" }); + }); + + it("should apply margin to the left and right", () => { + const marginProps: Margin = { mx: "20" }; + const result = withMargin(marginProps); + expect(result).toEqual({ marginLeft: "20px", marginRight: "20px" }); + }); + + it("should apply margin to the top and bottom", () => { + const marginProps: Margin = { my: "15" }; + const result = withMargin(marginProps); + expect(result).toEqual({ + marginBottom: "15px", + marginTop: "15px", + }); + }); + + it("should apply margin to all sides", () => { + const marginProps: Margin = { m: "25" }; + const result = withMargin(marginProps); + expect(result).toEqual({ + margin: "25px", + }); + }); + + it("should apply margin to specified sides when provided", () => { + const marginProps: Margin = { mt: "5", mr: "10", mb: "15", ml: "20" }; + const result = withMargin(marginProps); + expect(result).toEqual({ + marginBottom: "15px", + marginLeft: "20px", + marginRight: "10px", + marginTop: "5px", + }); + }); + + it("should ignore invalid margin values", () => { + const marginProps: Margin = { m: "invalid", mt: "5", mx: "valid" }; + const result = withMargin(marginProps); + expect(result).toEqual({ + marginTop: "5px", + }); + }); +}); + +describe("withSpace", () => { + it("should return an empty object for undefined value", () => { + const result = withSpace(undefined, ["margin"]); + expect(result).toEqual({}); + }); + + it("should apply space to the specified properties", () => { + const result = withSpace(15, ["marginTop", "marginLeft"]); + expect(result).toEqual({ marginTop: "15px", marginLeft: "15px" }); + }); +}); diff --git a/packages/react-email/src/package/hr/__snapshots__/hr.spec.tsx.snap b/packages/react-email/src/package/hr/__snapshots__/hr.spec.tsx.snap new file mode 100644 index 0000000000..575ce7e212 --- /dev/null +++ b/packages/react-email/src/package/hr/__snapshots__/hr.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`
component > renders correctly 1`] = `"
"`; diff --git a/packages/react-email/src/package/hr/hr.spec.tsx b/packages/react-email/src/package/hr/hr.spec.tsx new file mode 100644 index 0000000000..704de51564 --- /dev/null +++ b/packages/react-email/src/package/hr/hr.spec.tsx @@ -0,0 +1,20 @@ +import { render } from "../render/node"; +import { Hr } from "./hr"; + +describe("
component", () => { + it("passes styles and other props correctly", async () => { + const style = { + width: "50%", + borderColor: "black", + }; + const html = await render(
); + expect(html).toContain("width:50%"); + expect(html).toContain("border-color:black"); + expect(html).toContain('data-testid="hr-test"'); + }); + + it("renders correctly", async () => { + const actualOutput = await render(
); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/hr/hr.tsx b/packages/react-email/src/package/hr/hr.tsx new file mode 100644 index 0000000000..894655712d --- /dev/null +++ b/packages/react-email/src/package/hr/hr.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; + +export type HrProps = Readonly>; + +export const Hr = React.forwardRef( + ({ style, ...props }, ref) => ( +
+ ), +); + +Hr.displayName = "Hr"; diff --git a/packages/react-email/src/package/html/__snapshots__/html.spec.tsx.snap b/packages/react-email/src/package/html/__snapshots__/html.spec.tsx.snap new file mode 100644 index 0000000000..9c90e69afa --- /dev/null +++ b/packages/react-email/src/package/html/__snapshots__/html.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` component > renders correctly 1`] = `""`; diff --git a/packages/react-email/src/package/html/html.spec.tsx b/packages/react-email/src/package/html/html.spec.tsx new file mode 100644 index 0000000000..d9c590f631 --- /dev/null +++ b/packages/react-email/src/package/html/html.spec.tsx @@ -0,0 +1,24 @@ +import { render } from "../render/node"; +import { Html } from "./html"; + +describe(" component", () => { + it("renders children correctly", async () => { + const testMessage = "Test message"; + const html = await render({testMessage}); + expect(html).toContain(testMessage); + }); + + it("passes props correctly", async () => { + const html = await render( + , + ); + expect(html).toContain('lang="fr"'); + expect(html).toContain('dir="rtl"'); + expect(html).toContain('data-testid="html-test"'); + }); + + it("renders correctly", async () => { + const actualOutput = await render(); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/html/html.tsx b/packages/react-email/src/package/html/html.tsx new file mode 100644 index 0000000000..9c7741866b --- /dev/null +++ b/packages/react-email/src/package/html/html.tsx @@ -0,0 +1,13 @@ +import * as React from "react"; + +export type HtmlProps = Readonly>; + +export const Html = React.forwardRef( + ({ children, lang = "en", dir = "ltr", ...props }, ref) => ( + + {children} + + ), +); + +Html.displayName = "Html"; diff --git a/packages/react-email/src/package/img/__snapshots__/img.spec.tsx.snap b/packages/react-email/src/package/img/__snapshots__/img.spec.tsx.snap new file mode 100644 index 0000000000..d832c4a1c4 --- /dev/null +++ b/packages/react-email/src/package/img/__snapshots__/img.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` component > renders correctly 1`] = `"Cat"`; diff --git a/packages/react-email/src/package/img/img.spec.tsx b/packages/react-email/src/package/img/img.spec.tsx new file mode 100644 index 0000000000..c096a25cb7 --- /dev/null +++ b/packages/react-email/src/package/img/img.spec.tsx @@ -0,0 +1,28 @@ +import { render } from "../render/node"; +import { Img } from "./img"; + +describe(" component", () => { + it("passes style and other props correctly", async () => { + const style = { backgroundColor: "red", border: "solid 1px black" }; + const html = await render( + Cat, + ); + expect(html).toContain("background-color:red"); + expect(html).toContain("border:solid 1px black"); + expect(html).toContain('data-testid="img-test"'); + }); + + it("renders correctly", async () => { + const actualOutput = await render( + Cat, + ); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/img/img.tsx b/packages/react-email/src/package/img/img.tsx new file mode 100644 index 0000000000..c9f3fa82be --- /dev/null +++ b/packages/react-email/src/package/img/img.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +export type ImgProps = Readonly>; + +export const Img = React.forwardRef( + ({ alt, src, width, height, style, ...props }, ref) => ( + {alt} + ), +); + +Img.displayName = "Img"; diff --git a/packages/react-email/src/package/index.browser.ts b/packages/react-email/src/package/index.browser.ts new file mode 100644 index 0000000000..9efd40234b --- /dev/null +++ b/packages/react-email/src/package/index.browser.ts @@ -0,0 +1,20 @@ +export * from "./body/body"; +export * from "./button/button"; +export * from "./code-block/code-block"; +export * from "./code-inline/code-inline"; +export * from "./column/column"; +export * from "./container/container"; +export * from "./font/font"; +export * from "./head/head"; +export * from "./heading/heading"; +export * from "./hr/hr"; +export * from "./html/html"; +export * from "./img/img"; +export * from "./link/link"; +export * from "./markdown/markdown"; +export * from "./preview/preview"; +export * from "./render/browser"; +export * from "./row/row"; +export * from "./section/section"; +export * from "./tailwind/tailwind"; +export * from "./text/text"; diff --git a/packages/react-email/src/package/index.node.ts b/packages/react-email/src/package/index.node.ts new file mode 100644 index 0000000000..17ec13e9a7 --- /dev/null +++ b/packages/react-email/src/package/index.node.ts @@ -0,0 +1,20 @@ +export * from "./body/body"; +export * from "./button/button"; +export * from "./code-block/code-block"; +export * from "./code-inline/code-inline"; +export * from "./column/column"; +export * from "./container/container"; +export * from "./font/font"; +export * from "./head/head"; +export * from "./heading/heading"; +export * from "./hr/hr"; +export * from "./html/html"; +export * from "./img/img"; +export * from "./link/link"; +export * from "./markdown/markdown"; +export * from "./preview/preview"; +export * from "./render/node"; +export * from "./row/row"; +export * from "./section/section"; +export * from "./tailwind/tailwind"; +export * from "./text/text"; diff --git a/packages/react-email/src/package/link/__snapshots__/link.spec.tsx.snap b/packages/react-email/src/package/link/__snapshots__/link.spec.tsx.snap new file mode 100644 index 0000000000..b3468b5937 --- /dev/null +++ b/packages/react-email/src/package/link/__snapshots__/link.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` component > renders correctly 1`] = `"Example"`; diff --git a/packages/react-email/src/package/link/index.ts b/packages/react-email/src/package/link/index.ts new file mode 100644 index 0000000000..6fc151afbb --- /dev/null +++ b/packages/react-email/src/package/link/index.ts @@ -0,0 +1 @@ +export * from "./link"; diff --git a/packages/react-email/src/package/link/link.spec.tsx b/packages/react-email/src/package/link/link.spec.tsx new file mode 100644 index 0000000000..1f40a8129d --- /dev/null +++ b/packages/react-email/src/package/link/link.spec.tsx @@ -0,0 +1,35 @@ +import { render } from "../render/node"; +import { Link } from "./link"; + +describe(" component", () => { + it("renders children correctly", async () => { + const testMessage = "Test message"; + const html = await render( + {testMessage}, + ); + expect(html).toContain(testMessage); + }); + + it("passes style and other props correctly", async () => { + const style = { color: "red" }; + const html = await render( + + Test + , + ); + expect(html).toContain("color:red"); + expect(html).toContain('data-testid="link-test"'); + }); + + it("opens in a new tab", async () => { + const html = await render(Test); + expect(html).toContain(`target="_blank"`); + }); + + it("renders correctly", async () => { + const actualOutput = await render( + Example, + ); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/link/link.tsx b/packages/react-email/src/package/link/link.tsx new file mode 100644 index 0000000000..8af7cb0da9 --- /dev/null +++ b/packages/react-email/src/package/link/link.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; + +export type LinkProps = Readonly>; + +export const Link = React.forwardRef( + ({ target = "_blank", style, ...props }, ref) => ( + + {props.children} + + ), +); + +Link.displayName = "Link"; diff --git a/packages/react-email/src/package/markdown/__snapshots__/markdown.spec.tsx.snap b/packages/react-email/src/package/markdown/__snapshots__/markdown.spec.tsx.snap new file mode 100644 index 0000000000..f79914132e --- /dev/null +++ b/packages/react-email/src/package/markdown/__snapshots__/markdown.spec.tsx.snap @@ -0,0 +1,50 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` component renders correctly > renders links in the correct format for browsers 1`] = ` +"

Link to React-email

+
" +`; + +exports[` component renders correctly > renders lists in the correct format for browsers 1`] = ` +"

Below is a list

    +
  • Item One
  • +
  • Item Two
  • +
  • Item Three
  • +
+
" +`; + +exports[` component renders correctly > renders text in the correct format for browsers 1`] = ` +"

This is sample bold text in markdown and this is italic text

+
" +`; + +exports[` component renders correctly > renders the headers in the correct format for browsers 1`] = `"

Heading 1!

Heading 2!

Heading 3!

Heading 4!

Heading 5!
Heading 6!
"`; + +exports[` component renders correctly > renders the markdown in the correct format for browsers 1`] = ` +"

Markdown Test Document

This is a test document to check the capabilities of a Markdown parser.

+

Headings

Third-Level Heading

Fourth-Level Heading

Fifth-Level Heading
Sixth-Level Heading

Text Formatting

This is some bold text and this is some italic text. You can also use strikethrough and inline code.

+

Lists

    +
  1. Ordered List Item 1
  2. +
  3. Ordered List Item 2
  4. +
  5. Ordered List Item 3
  6. +
+
    +
  • Unordered List Item 1
  • +
  • Unordered List Item 2
  • +
  • Unordered List Item 3
  • +
+

Links

Markdown Guide

+

Images

Markdown Logo

+

Blockquotes

+

This is a blockquote.

+
    +
  • Author
  • +
+
+

Code Blocks

function greet(name) {
+console.log(\`Hello, \${name}!\`);
+}
+
+
" +`; diff --git a/packages/react-email/src/package/markdown/markdown.spec.tsx b/packages/react-email/src/package/markdown/markdown.spec.tsx new file mode 100644 index 0000000000..ec27cf7b11 --- /dev/null +++ b/packages/react-email/src/package/markdown/markdown.spec.tsx @@ -0,0 +1,114 @@ +import { render } from "../render/node"; +import { Markdown } from "./markdown"; + +describe(" component renders correctly", () => { + it("renders the markdown in the correct format for browsers", async () => { + const actualOutput = await render( + + {`# Markdown Test Document + +This is a **test document** to check the capabilities of a Markdown parser. + +## Headings + +### Third-Level Heading + +#### Fourth-Level Heading + +##### Fifth-Level Heading + +###### Sixth-Level Heading + +## Text Formatting + +This is some **bold text** and this is some *italic text*. You can also use ~~strikethrough~~ and \`inline code\`. + +## Lists + +1. Ordered List Item 1 +2. Ordered List Item 2 +3. Ordered List Item 3 + +- Unordered List Item 1 +- Unordered List Item 2 +- Unordered List Item 3 + +## Links + +[Markdown Guide](https://www.markdownguide.org) + +## Images + +![Markdown Logo](https://markdown-here.com/img/icon256.png) + +## Blockquotes + +> This is a blockquote. +> - Author + +## Code Blocks + +\`\`\`javascript +function greet(name) { +console.log(\`Hello, $\{name}!\`); +} +\`\`\``} + , + ); + expect(actualOutput).toMatchSnapshot(); + }); + + it("renders the headers in the correct format for browsers", async () => { + const actualOutput = await render( + + {` +# Heading 1! +## Heading 2! +### Heading 3! +#### Heading 4! +##### Heading 5! +###### Heading 6! + `} + , + ); + expect(actualOutput).toMatchSnapshot(); + }); + + it("renders text in the correct format for browsers", async () => { + const actualOutput = await render( + + **This is sample bold text in markdown** and *this is italic text* + , + ); + expect(actualOutput).toMatchSnapshot(); + }); + + it("renders links in the correct format for browsers", async () => { + const actualOutput = await render( + Link to [React-email](https://react.email), + ); + expect(actualOutput).toMatchSnapshot(); + }); + + it("renders lists in the correct format for browsers", async () => { + const actualOutput = await render( + + {` +# Below is a list + +- Item One +- Item Two +- Item Three + `} + , + ); + expect(actualOutput).toMatchSnapshot(); + }); +}); diff --git a/packages/react-email/src/package/markdown/markdown.tsx b/packages/react-email/src/package/markdown/markdown.tsx new file mode 100644 index 0000000000..f48e175b95 --- /dev/null +++ b/packages/react-email/src/package/markdown/markdown.tsx @@ -0,0 +1,33 @@ +import type { StylesType } from "md-to-react-email"; +import { parseMarkdownToJSX } from "md-to-react-email"; +import * as React from "react"; + +export type MarkdownProps = Readonly<{ + children: string; + markdownCustomStyles?: StylesType; + markdownContainerStyles?: React.CSSProperties; +}>; + +export const Markdown = React.forwardRef( + ( + { children, markdownContainerStyles, markdownCustomStyles, ...props }, + ref, + ) => { + const parsedMarkdown = parseMarkdownToJSX({ + markdown: children, + customStyles: markdownCustomStyles, + }); + + return ( +
+ ); + }, +); + +Markdown.displayName = "Markdown"; diff --git a/packages/react-email/src/package/preview/__snapshots__/preview.spec.tsx.snap b/packages/react-email/src/package/preview/__snapshots__/preview.spec.tsx.snap new file mode 100644 index 0000000000..7d97758230 --- /dev/null +++ b/packages/react-email/src/package/preview/__snapshots__/preview.spec.tsx.snap @@ -0,0 +1,7 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` component > renders correctly 1`] = `"
Email preview text
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
"`; + +exports[` component > renders correctly with array text 1`] = `"
Email preview text
 ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏
"`; + +exports[` component > renders correctly with really long text 1`] = `"
really longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally longreally
"`; diff --git a/packages/react-email/src/package/preview/preview.spec.tsx b/packages/react-email/src/package/preview/preview.spec.tsx new file mode 100644 index 0000000000..77aca04c3b --- /dev/null +++ b/packages/react-email/src/package/preview/preview.spec.tsx @@ -0,0 +1,45 @@ +/* eslint-disable no-irregular-whitespace */ +import { render } from "../render/node"; +import { Preview, renderWhiteSpace } from "./preview"; + +describe(" component", () => { + it("renders correctly", async () => { + const actualOutput = await render(Email preview text); + expect(actualOutput).toMatchSnapshot(); + }); + + it("renders correctly with array text", async () => { + const actualOutputArray = await render( + Email preview text, + ); + expect(actualOutputArray).toMatchSnapshot(); + }); + + it("renders correctly with really long text", async () => { + const longText = "really long".repeat(100); + const actualOutputLong = await render({longText}); + expect(actualOutputLong).toMatchSnapshot(); + }); +}); + +describe("renderWhiteSpace", () => { + it("renders null when text length is greater than or equal to PREVIEW_MAX_LENGTH (150)", () => { + const text = + "Lorem ipsum dolor sit amet consectetur adipisicing elit. Tenetur dolore mollitia dignissimos itaque. At excepturi reiciendis iure molestias incidunt. Ab saepe, nostrum dicta dolor maiores tenetur eveniet odio amet ipsum?"; + const html = renderWhiteSpace(text); + expect(html).toBeNull(); + }); + + it("renders white space characters when text length is less than PREVIEW_MAX_LENGTH", () => { + const text = "Short text"; + const whiteSpaceCharacters = "\xa0\u200C\u200B\u200D\u200E\u200F\uFEFF"; + + const html = renderWhiteSpace(text); + expect(html).not.toBeNull(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const actualTextContent = html?.props.children; + const expectedTextContent = whiteSpaceCharacters.repeat(150 - text.length); + expect(actualTextContent).toBe(expectedTextContent); + }); +}); diff --git a/packages/react-email/src/package/preview/preview.tsx b/packages/react-email/src/package/preview/preview.tsx new file mode 100644 index 0000000000..167735ab67 --- /dev/null +++ b/packages/react-email/src/package/preview/preview.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; + +export type PreviewProps = Readonly< + React.ComponentPropsWithoutRef<"div"> & { + children: string | string[]; + } +>; + +const PREVIEW_MAX_LENGTH = 150; + +export const Preview = React.forwardRef( + ({ children = "", ...props }, ref) => { + const text = ( + Array.isArray(children) ? children.join("") : children + ).substring(0, PREVIEW_MAX_LENGTH); + + return ( +
+ {text} + {renderWhiteSpace(text)} +
+ ); + }, +); + +Preview.displayName = "Preview"; + +const whiteSpaceCodes = "\xa0\u200C\u200B\u200D\u200E\u200F\uFEFF"; +export const renderWhiteSpace = (text: string) => { + if (text.length >= PREVIEW_MAX_LENGTH) { + return null; + } + + return
{whiteSpaceCodes.repeat(PREVIEW_MAX_LENGTH - text.length)}
; +}; diff --git a/packages/react-email/src/package/render/browser/__snapshots__/render-async-web.spec.tsx.snap b/packages/react-email/src/package/render/browser/__snapshots__/render-async-web.spec.tsx.snap new file mode 100644 index 0000000000..873fa93880 --- /dev/null +++ b/packages/react-email/src/package/render/browser/__snapshots__/render-async-web.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`renderAsync on the browser environment > should handle characters with a higher byte count gracefully 1`] = `"

Test Normal 情報Ⅰコース担当者様

平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。

今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。

伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。

2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。

また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)

受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム

"`; diff --git a/packages/react-email/src/package/render/browser/__snapshots__/render-web.spec.tsx.snap b/packages/react-email/src/package/render/browser/__snapshots__/render-web.spec.tsx.snap new file mode 100644 index 0000000000..2a39925b95 --- /dev/null +++ b/packages/react-email/src/package/render/browser/__snapshots__/render-web.spec.tsx.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`render on the browser environment > should handle characters with a higher byte count gracefully 1`] = `"

Test Normal 情報Ⅰコース担当者様

平素よりお世話になっております。 情報Ⅰサポートチームです。 情報Ⅰ本講座につきまして仕様変更のためご連絡させていただきました。

今後ジクタス上の講座につきましては、8回分の授業をひとまとまりとしてパート分けされた状態で公開されてまいります。

伴いまして、画面上の表示に一部変更がありますのでお知らせいたします。 ご登録いただいた各生徒の受講ペースに応じて公開パートが増えてまいります。 具体的な表示イメージは下記ページをご確認ください。

2024年8月末時点で情報Ⅰ本講座を受講していたアカウントにつきましては、 今まで公開していた第1~9回までの講座一覧に加え、パート分けされた講座が追加で公開されてまりいます。 第1~9回の表示はそのまま引き継がれますが、Webドリルの進捗表示はパート分けの講座には適用されません。ご了承くださいませ。 仕様変更に伴い、現在教室長もしくは講師に生徒アカウントログインをお願いしておりますが、今後は生徒自身にてログインをしていただいて問題ございません。

また、生徒が自宅等にてログインし復習に取り組むことも問題ございませんので、教室にてご指示いただければと存じます。 (実際にご指示いただくかは教室判断に委ねさせていただきます。)

受講ペースが変更したり、増コマが発生したりする場合などは、公開ペースを本部にて調整いたしますので、下記より必ずご連絡くださいませ。 また本件に関して不明な点がございましたら、同フォームよりお問い合わせください。 以上、引き続きよろしくお願い申し上げます。 情報Ⅰサポートチーム

"`; diff --git a/packages/react-email/src/package/render/browser/index.ts b/packages/react-email/src/package/render/browser/index.ts new file mode 100644 index 0000000000..2d3b9b5105 --- /dev/null +++ b/packages/react-email/src/package/render/browser/index.ts @@ -0,0 +1,5 @@ +export * from "./render"; +export * from "./render-async"; + +export * from "../shared/options"; +export * from "../shared/plain-text-selectors"; diff --git a/packages/react-email/src/package/render/browser/read-stream.ts b/packages/react-email/src/package/render/browser/read-stream.ts new file mode 100644 index 0000000000..fea97fc18a --- /dev/null +++ b/packages/react-email/src/package/render/browser/read-stream.ts @@ -0,0 +1,44 @@ +import { + PipeableStream, + ReactDOMServerReadableStream, +} from "react-dom/server.browser"; + +const decoder = new TextDecoder("utf-8"); + +export const readStream = async ( + stream: PipeableStream | ReactDOMServerReadableStream, +) => { + const chunks: Uint8Array[] = []; + + if ("pipeTo" in stream) { + // means it's a readable stream + const writableStream = new WritableStream({ + write(chunk: Uint8Array) { + chunks.push(chunk); + }, + }); + await stream.pipeTo(writableStream); + } else { + throw new Error( + "For some reason, the Node version of `react-dom/server` has been imported instead of the browser one.", + { + cause: { + stream, + }, + }, + ); + } + + let length = 0; + chunks.forEach((item) => { + length += item.length; + }); + const mergedChunks = new Uint8Array(length); + let offset = 0; + chunks.forEach((item) => { + mergedChunks.set(item, offset); + offset += item.length; + }); + + return decoder.decode(mergedChunks); +}; diff --git a/packages/react-email/src/package/render/browser/render-async-web.spec.tsx b/packages/react-email/src/package/render/browser/render-async-web.spec.tsx new file mode 100644 index 0000000000..53aecebf6f --- /dev/null +++ b/packages/react-email/src/package/render/browser/render-async-web.spec.tsx @@ -0,0 +1,125 @@ +/** + * @vitest-environment jsdom + */ + +import { Template } from "../shared/utils/template"; +import { Preview } from "../shared/utils/preview"; +import { renderAsync } from "./render-async"; + +type Import = typeof import("react-dom/server") & { + default: typeof import("react-dom/server"); +}; + +describe("renderAsync on the browser environment", () => { + beforeEach(() => { + vi.mock( + "react-dom/server", + (_importOriginal) => import("react-dom/server.browser"), + ); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + it("converts a React component into HTML with Next 14 error stubs", async () => { + vi.mock("react-dom/server", async (_importOriginal) => { + const ReactDOMServerBrowser = await vi.importActual( + "react-dom/server.browser", + ); + const ERROR_MESSAGE = + "Internal Error: do not use legacy react-dom/server APIs. If you encountered this error, please open an issue on the Next.js repo."; + + return { + ...ReactDOMServerBrowser, + default: { + ...ReactDOMServerBrowser, + renderToString() { + throw new Error(ERROR_MESSAGE); + }, + renderToStaticMarkup() { + throw new Error(ERROR_MESSAGE); + }, + }, + renderToString() { + throw new Error(ERROR_MESSAGE); + }, + renderToStaticMarkup() { + throw new Error(ERROR_MESSAGE); + }, + }; + }); + + const actualOutput = await renderAsync(