react-email 4.0.5 → 4.1.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/cli/index.js +3 -4
- package/dist/cli/index.mjs +3 -4
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +31 -31
- package/dist/preview/.next/build-manifest.json +14 -14
- package/dist/preview/.next/diagnostics/framework.json +1 -1
- package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
- package/dist/preview/.next/next-server.js.nft.json +1 -1
- package/dist/preview/.next/prerender-manifest.json +3 -3
- package/dist/preview/.next/required-server-files.json +1 -2
- package/dist/preview/.next/server/app/_not-found/page.js +1 -1
- package/dist/preview/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
- package/dist/preview/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/dist/preview/.next/server/app/page.js +1 -1
- package/dist/preview/.next/server/app/page.js.nft.json +1 -1
- package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/app/preview/[...slug]/page.js +139 -139
- package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
- package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/chunks/395.js +1 -1
- package/dist/preview/.next/server/chunks/574.js +6 -0
- package/dist/preview/.next/server/chunks/71.js +1 -0
- package/dist/preview/.next/server/chunks/735.js +13 -0
- package/dist/preview/.next/server/chunks/{414.js → 840.js} +3 -3
- package/dist/preview/.next/server/chunks/886.js +8 -0
- package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
- package/dist/preview/.next/server/pages/500.html +1 -1
- package/dist/preview/.next/server/pages/_app.js +1 -1
- package/dist/preview/.next/server/pages/_app.js.nft.json +1 -1
- package/dist/preview/.next/server/pages/_document.js +1 -1
- package/dist/preview/.next/server/pages/_document.js.nft.json +1 -1
- package/dist/preview/.next/server/pages/_error.js +1 -1
- package/dist/preview/.next/server/pages/_error.js.nft.json +1 -1
- package/dist/preview/.next/server/pages-manifest.json +1 -1
- package/dist/preview/.next/server/server-reference-manifest.js +1 -1
- package/dist/preview/.next/server/server-reference-manifest.json +1 -1
- package/dist/preview/.next/static/{jfvVXR08owHTqinvfzxg5 → B5zsyvs_AWXezvTSi2Eyl}/_buildManifest.js +1 -1
- package/dist/preview/.next/static/chunks/246-e7336e2929971f63.js +1 -0
- package/dist/preview/.next/static/chunks/270-688096d43c717256.js +1 -0
- package/dist/preview/.next/static/chunks/539-6e9405ecdc007bb7.js +1 -0
- package/dist/preview/.next/static/chunks/587-e77ff7eb4b703903.js +1 -0
- package/dist/preview/.next/static/chunks/782947c8-c6cfd05e68542601.js +1 -0
- package/dist/preview/.next/static/chunks/803-db74f262c4755323.js +1 -0
- package/dist/preview/.next/static/chunks/853-a01d49f63a859f3d.js +1 -0
- package/dist/preview/.next/static/chunks/{afa401a5-68e1b62b50f76414.js → afa401a5-55858bf5265319eb.js} +1 -1
- package/dist/preview/.next/static/chunks/app/_not-found/page-85e83b2d4bd569a2.js +1 -0
- package/dist/preview/.next/static/chunks/app/layout-0f9706a93f161c1e.js +1 -0
- package/dist/preview/.next/static/chunks/app/page-0ee3a37f3a3f6f17.js +1 -0
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-5fba95d740305052.js +1 -0
- package/dist/preview/.next/static/chunks/{framework-be649707ef7aa6cf.js → framework-b5f555f62da46ed9.js} +1 -1
- package/dist/preview/.next/static/chunks/main-a839e2fbd78a08fa.js +1 -0
- package/dist/preview/.next/static/chunks/main-app-a6a05ec7ce09e366.js +1 -0
- package/dist/preview/.next/static/chunks/pages/_app-ee26b5d329c4bb79.js +1 -0
- package/dist/preview/.next/static/chunks/pages/_error-90cb3f6367e28bcd.js +1 -0
- package/dist/preview/.next/static/chunks/{webpack-a91b84001c1374de.js → webpack-6755bde10ea8daa8.js} +1 -1
- package/dist/preview/.next/trace +27 -27
- package/package.json +4 -4
- package/src/app/preview/[...slug]/preview.tsx +19 -1
- package/src/components/icons/icon-moon.tsx +16 -0
- package/src/components/icons/icon-sun.tsx +16 -0
- package/src/components/topbar/theme-toggle-group.tsx +87 -0
- package/src/hooks/use-iframe-color-scheme.ts +35 -0
- package/dist/preview/.next/server/chunks/522.js +0 -8
- package/dist/preview/.next/server/chunks/616.js +0 -6
- package/dist/preview/.next/server/chunks/747.js +0 -13
- package/dist/preview/.next/server/chunks/988.js +0 -1
- package/dist/preview/.next/static/chunks/186-be509f142abcb591.js +0 -1
- package/dist/preview/.next/static/chunks/221-dc9af10e9928fdb1.js +0 -1
- package/dist/preview/.next/static/chunks/323-4c9f66d5bbb5efc5.js +0 -1
- package/dist/preview/.next/static/chunks/508-30b1f653385f71d5.js +0 -1
- package/dist/preview/.next/static/chunks/529-776647f7f3b9ca0a.js +0 -1
- package/dist/preview/.next/static/chunks/814-343c45533ab34e2a.js +0 -1
- package/dist/preview/.next/static/chunks/9e3860c5-aa584d592f360aac.js +0 -1
- package/dist/preview/.next/static/chunks/app/_not-found/page-3e1a74ee77aa34e0.js +0 -1
- package/dist/preview/.next/static/chunks/app/layout-a67e0fd96dad04d7.js +0 -1
- package/dist/preview/.next/static/chunks/app/page-80d082b8323795e9.js +0 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-6477d0878c7732eb.js +0 -1
- package/dist/preview/.next/static/chunks/main-1bbd9a2a9e1c1cc0.js +0 -1
- package/dist/preview/.next/static/chunks/main-app-670fa872bbeba501.js +0 -1
- package/dist/preview/.next/static/chunks/pages/_app-300091909fcee354.js +0 -1
- package/dist/preview/.next/static/chunks/pages/_error-f76efb2b82ccd7e7.js +0 -1
- /package/dist/preview/.next/static/{jfvVXR08owHTqinvfzxg5 → B5zsyvs_AWXezvTSi2Eyl}/_ssgManifest.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-email",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.1.0-canary.0",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"email": "./dist/cli/index.js"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"glob": "10.3.4",
|
|
30
30
|
"log-symbols": "4.1.0",
|
|
31
31
|
"mime-types": "2.1.35",
|
|
32
|
-
"next": "15.2.
|
|
32
|
+
"next": "15.2.4",
|
|
33
33
|
"normalize-path": "3.0.0",
|
|
34
34
|
"ora": "5.4.1",
|
|
35
35
|
"socket.io": "4.8.1"
|
|
@@ -82,10 +82,10 @@
|
|
|
82
82
|
"typescript": "5.8.2",
|
|
83
83
|
"use-debounce": "10.0.4",
|
|
84
84
|
"zod": "3.24.2",
|
|
85
|
-
"@react-email/components": "0.0.
|
|
85
|
+
"@react-email/components": "0.0.37-canary.0"
|
|
86
86
|
},
|
|
87
87
|
"scripts": {
|
|
88
|
-
"build": "tsup-node && node ./scripts/build-preview-server.mjs",
|
|
88
|
+
"build": "tsup-node && node ./scripts/build-preview-server.mjs && pnpm install --frozen-lockfile",
|
|
89
89
|
"caniemail:fetch": "node ./scripts/fill-caniemail-data.mjs",
|
|
90
90
|
"clean": "rm -rf dist",
|
|
91
91
|
"dev": "tsup-node --watch",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
4
|
-
import { use, useState } from 'react';
|
|
4
|
+
import { use, useRef, useState } from 'react';
|
|
5
5
|
import { flushSync } from 'react-dom';
|
|
6
6
|
import { Toaster } from 'sonner';
|
|
7
7
|
import { useDebouncedCallback } from 'use-debounce';
|
|
@@ -15,9 +15,11 @@ import { Send } from '../../../components/send';
|
|
|
15
15
|
import { useToolbarState } from '../../../components/toolbar';
|
|
16
16
|
import { Tooltip } from '../../../components/tooltip';
|
|
17
17
|
import { ActiveViewToggleGroup } from '../../../components/topbar/active-view-toggle-group';
|
|
18
|
+
import { ThemeToggleGroup } from '../../../components/topbar/theme-toggle-group';
|
|
18
19
|
import { ViewSizeControls } from '../../../components/topbar/view-size-controls';
|
|
19
20
|
import { PreviewContext } from '../../../contexts/preview';
|
|
20
21
|
import { useClampedState } from '../../../hooks/use-clamped-state';
|
|
22
|
+
import { useIframeColorScheme } from '../../../hooks/use-iframe-color-scheme';
|
|
21
23
|
import { cn } from '../../../utils';
|
|
22
24
|
import { RenderingError } from './rendering-error';
|
|
23
25
|
|
|
@@ -32,9 +34,17 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
|
32
34
|
const pathname = usePathname();
|
|
33
35
|
const searchParams = useSearchParams();
|
|
34
36
|
|
|
37
|
+
const activeTheme: 'dark' | 'light' =
|
|
38
|
+
searchParams.get('theme') === 'dark' ? 'dark' : 'light';
|
|
35
39
|
const activeView = searchParams.get('view') ?? 'preview';
|
|
36
40
|
const activeLang = searchParams.get('lang') ?? 'jsx';
|
|
37
41
|
|
|
42
|
+
const handleThemeChange = (theme: 'dark' | 'light') => {
|
|
43
|
+
const params = new URLSearchParams(searchParams);
|
|
44
|
+
params.set('theme', theme);
|
|
45
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
46
|
+
};
|
|
47
|
+
|
|
38
48
|
const handleViewChange = (view: string) => {
|
|
39
49
|
const params = new URLSearchParams(searchParams);
|
|
40
50
|
params.set('view', view);
|
|
@@ -51,6 +61,9 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
|
51
61
|
);
|
|
52
62
|
};
|
|
53
63
|
|
|
64
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
65
|
+
useIframeColorScheme(iframeRef, activeTheme);
|
|
66
|
+
|
|
54
67
|
const hasRenderingMetadata = typeof renderedEmailMetadata !== 'undefined';
|
|
55
68
|
const hasErrors = 'error' in renderingResult;
|
|
56
69
|
|
|
@@ -99,6 +112,10 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
|
99
112
|
viewHeight={height}
|
|
100
113
|
viewWidth={width}
|
|
101
114
|
/>
|
|
115
|
+
<ThemeToggleGroup
|
|
116
|
+
active={activeTheme}
|
|
117
|
+
onChange={(theme) => handleThemeChange(theme)}
|
|
118
|
+
/>
|
|
102
119
|
<ActiveViewToggleGroup
|
|
103
120
|
activeView={activeView}
|
|
104
121
|
setActiveView={handleViewChange}
|
|
@@ -164,6 +181,7 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
|
164
181
|
<iframe
|
|
165
182
|
className="solid max-h-full rounded-lg bg-white"
|
|
166
183
|
ref={(iframe) => {
|
|
184
|
+
iframeRef.current = iframe;
|
|
167
185
|
if (iframe) {
|
|
168
186
|
return makeIframeDocumentBubbleEvents(iframe);
|
|
169
187
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { IconElement, IconProps } from './icon-base';
|
|
3
|
+
import { IconBase } from './icon-base';
|
|
4
|
+
|
|
5
|
+
export const IconMoon = React.forwardRef<IconElement, Readonly<IconProps>>(
|
|
6
|
+
({ ...props }, forwardedRef) => (
|
|
7
|
+
<IconBase ref={forwardedRef} {...props}>
|
|
8
|
+
<path
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
d="m17.75 4.09l-2.53 1.94l.91 3.06l-2.63-1.81l-2.63 1.81l.91-3.06l-2.53-1.94L12.44 4l1.06-3l1.06 3zm3.5 6.91l-1.64 1.25l.59 1.98l-1.7-1.17l-1.7 1.17l.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85c-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14c.4-.4.82-.76 1.27-1.08c.75-.53 1.93.36 1.85 1.19c-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82c-2.81 3.14-2.7 7.96.31 10.98c3.02 3.01 7.84 3.12 10.98.31"
|
|
11
|
+
/>
|
|
12
|
+
</IconBase>
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
IconMoon.displayName = 'IconMoon';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { IconElement, IconProps } from './icon-base';
|
|
3
|
+
import { IconBase } from './icon-base';
|
|
4
|
+
|
|
5
|
+
export const IconSun = React.forwardRef<IconElement, Readonly<IconProps>>(
|
|
6
|
+
({ ...props }, forwardedRef) => (
|
|
7
|
+
<IconBase ref={forwardedRef} {...props}>
|
|
8
|
+
<path
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
d="m3.55 19.09l1.41 1.41l1.8-1.79l-1.42-1.42M12 6c-3.31 0-6 2.69-6 6s2.69 6 6 6s6-2.69 6-6c0-3.32-2.69-6-6-6m8 7h3v-2h-3m-2.76 7.71l1.8 1.79l1.41-1.41l-1.79-1.8M20.45 5l-1.41-1.4l-1.8 1.79l1.42 1.42M13 1h-2v3h2M6.76 5.39L4.96 3.6L3.55 5l1.79 1.81zM1 13h3v-2H1m12 9h-2v3h2"
|
|
11
|
+
/>
|
|
12
|
+
</IconBase>
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
IconSun.displayName = 'IconSun';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
3
|
+
import { cn } from '../../utils';
|
|
4
|
+
import { tabTransition } from '../../utils/constants';
|
|
5
|
+
import { IconMoon } from '../icons/icon-moon';
|
|
6
|
+
import { IconSun } from '../icons/icon-sun';
|
|
7
|
+
import { Tooltip } from '../tooltip';
|
|
8
|
+
|
|
9
|
+
interface ThemeToggleGroupProps {
|
|
10
|
+
active: 'light' | 'dark';
|
|
11
|
+
onChange: (theme: 'light' | 'dark') => unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const ThemeToggleGroup = ({
|
|
15
|
+
active,
|
|
16
|
+
onChange,
|
|
17
|
+
}: ThemeToggleGroupProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<ToggleGroup.Root
|
|
20
|
+
aria-label="Color Scheme"
|
|
21
|
+
className="inline-block items-center bg-slate-2 border border-slate-6 rounded-md overflow-hidden h-[36px]"
|
|
22
|
+
id="theme-toggle"
|
|
23
|
+
onValueChange={(value) => {
|
|
24
|
+
if (value) onChange(value as 'light' | 'dark');
|
|
25
|
+
}}
|
|
26
|
+
type="single"
|
|
27
|
+
value={active}
|
|
28
|
+
>
|
|
29
|
+
<ToggleGroup.Item value="light">
|
|
30
|
+
<Tooltip>
|
|
31
|
+
<Tooltip.Trigger asChild>
|
|
32
|
+
<div
|
|
33
|
+
className={cn(
|
|
34
|
+
'relative px-3 py-2 transition duration-200 ease-in-out hover:text-slate-12',
|
|
35
|
+
{
|
|
36
|
+
'text-slate-11': active !== 'light',
|
|
37
|
+
'text-slate-12': active === 'light',
|
|
38
|
+
},
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
{active === 'light' && (
|
|
42
|
+
<motion.span
|
|
43
|
+
animate={{ opacity: 1 }}
|
|
44
|
+
className="absolute top-0 right-0 bottom-0 left-0 bg-slate-4"
|
|
45
|
+
exit={{ opacity: 0 }}
|
|
46
|
+
initial={{ opacity: 0 }}
|
|
47
|
+
layoutId="topbar-theme-tabs"
|
|
48
|
+
transition={tabTransition}
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
<IconSun />
|
|
52
|
+
</div>
|
|
53
|
+
</Tooltip.Trigger>
|
|
54
|
+
<Tooltip.Content>Light</Tooltip.Content>
|
|
55
|
+
</Tooltip>
|
|
56
|
+
</ToggleGroup.Item>
|
|
57
|
+
<ToggleGroup.Item value="dark">
|
|
58
|
+
<Tooltip>
|
|
59
|
+
<Tooltip.Trigger asChild>
|
|
60
|
+
<div
|
|
61
|
+
className={cn(
|
|
62
|
+
'relative px-3 py-2 transition duration-200 ease-in-out hover:text-slate-12',
|
|
63
|
+
{
|
|
64
|
+
'text-slate-11': active !== 'dark',
|
|
65
|
+
'text-slate-12': active === 'dark',
|
|
66
|
+
},
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{active === 'dark' && (
|
|
70
|
+
<motion.span
|
|
71
|
+
animate={{ opacity: 1 }}
|
|
72
|
+
className="absolute top-0 right-0 bottom-0 left-0 bg-slate-4"
|
|
73
|
+
exit={{ opacity: 0 }}
|
|
74
|
+
initial={{ opacity: 0 }}
|
|
75
|
+
layoutId="topbar-theme-tabs"
|
|
76
|
+
transition={tabTransition}
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
<IconMoon />
|
|
80
|
+
</div>
|
|
81
|
+
</Tooltip.Trigger>
|
|
82
|
+
<Tooltip.Content>Dark</Tooltip.Content>
|
|
83
|
+
</Tooltip>
|
|
84
|
+
</ToggleGroup.Item>
|
|
85
|
+
</ToggleGroup.Root>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export function useIframeColorScheme(
|
|
4
|
+
iframeRef: React.RefObject<HTMLIFrameElement | null>,
|
|
5
|
+
theme: string,
|
|
6
|
+
) {
|
|
7
|
+
React.useEffect(() => {
|
|
8
|
+
const iframe = iframeRef.current;
|
|
9
|
+
|
|
10
|
+
if (!iframe) return;
|
|
11
|
+
|
|
12
|
+
// Set on iframe element itself
|
|
13
|
+
iframe.style.colorScheme = theme;
|
|
14
|
+
|
|
15
|
+
// Set on iframe's document if available
|
|
16
|
+
if (iframe.contentDocument) {
|
|
17
|
+
iframe.contentDocument.documentElement.style.colorScheme = theme;
|
|
18
|
+
iframe.contentDocument.body.style.colorScheme = theme;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Ensure styles are applied after it loads
|
|
22
|
+
const handleLoad = () => {
|
|
23
|
+
if (iframe.contentDocument) {
|
|
24
|
+
iframe.contentDocument.documentElement.style.colorScheme = theme;
|
|
25
|
+
iframe.contentDocument.body.style.colorScheme = theme;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
iframe.addEventListener('load', handleLoad);
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
iframe.removeEventListener('load', handleLoad);
|
|
33
|
+
};
|
|
34
|
+
}, [theme, iframeRef]);
|
|
35
|
+
}
|