@react-email/preview-server 4.2.11 → 4.3.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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +9 -9
- package/.next/build-manifest.json +2 -2
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/preview/[...slug]/page.js +30 -30
- package/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
- package/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/235.js +1 -1
- package/.next/server/chunks/{597.js → 385.js} +7 -7
- package/.next/server/chunks/630.js +1 -1
- package/.next/server/chunks/727.js +1 -0
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/442-9645091f2b304619.js +1 -0
- package/.next/static/chunks/{615-5d450200bdf8a0cb.js → 615-aa01e647fd9055dc.js} +1 -1
- package/.next/static/chunks/900-d73ea57316faa50d.js +1 -0
- package/.next/static/chunks/app/layout-0337303a89a72f7e.js +1 -0
- package/.next/static/chunks/app/{page-af54466b804e69f7.js → page-80a93dc65160c488.js} +1 -1
- package/.next/static/chunks/app/preview/[...slug]/page-3205284657cb4573.js +1 -0
- package/.next/static/css/7d8cf00703036864.css +3 -0
- package/.next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- package/.next/static/media/21350d82a1f187e9-s.woff2 +0 -0
- package/.next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- package/.next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- package/.next/trace +29 -29
- package/CHANGELOG.md +8 -0
- package/package.json +12 -12
- package/readme.md +3 -4
- package/src/actions/email-validation/check-compatibility.ts +1 -0
- package/src/actions/get-email-path-from-slug.ts +1 -1
- package/src/actions/render-email-by-path.tsx +8 -3
- package/src/app/env.ts +0 -4
- package/src/app/layout.tsx +8 -5
- package/src/app/page.tsx +2 -4
- package/src/app/preview/[...slug]/error-overlay.tsx +1 -0
- package/src/app/preview/[...slug]/page.tsx +3 -5
- package/src/app/preview/[...slug]/preview.tsx +28 -24
- package/src/components/resizable-wrapper.tsx +170 -71
- package/src/components/toolbar.tsx +2 -3
- package/src/components/topbar/active-view-toggle-group.tsx +4 -4
- package/src/components/topbar/view-size-controls.tsx +107 -186
- package/src/contexts/emails.tsx +3 -6
- package/src/contexts/preview.tsx +13 -1
- package/src/hooks/use-hot-reload.ts +2 -2
- package/src/utils/caniemail/get-compatibility-stats-for-entry.ts +0 -1
- package/src/utils/caniemail/tailwind/get-tailwind-config.spec.ts +2 -2
- package/src/utils/contains-email-template.spec.ts +6 -6
- package/src/utils/convert-stack-with-sourcemap.ts +0 -1
- package/src/utils/get-emails-directory-metadata.ts +0 -1
- package/src/utils/js-email-detection.spec.ts +3 -3
- package/src/utils/run-bundled-code.ts +1 -3
- package/src/utils/testing/request-response-email.tsx +0 -1
- package/tailwind-internals.d.ts +0 -2
- package/.next/server/chunks/420.js +0 -1
- package/.next/static/chunks/557-287480320fe241b8.js +0 -1
- package/.next/static/chunks/926-cd84f2c04e4197e1.js +0 -1
- package/.next/static/chunks/app/layout-581001443a6ac38a.js +0 -1
- package/.next/static/chunks/app/preview/[...slug]/page-356d4a373756b232.js +0 -1
- package/.next/static/css/e2f28c91a6a919eb.css +0 -3
- package/.next/static/media/26a46d62cd723877-s.woff2 +0 -0
- package/.next/static/media/55c55f0601d81cf3-s.woff2 +0 -0
- package/.next/static/media/581909926a08bbc8-s.woff2 +0 -0
- package/.next/static/media/97e0cb1ae144a2a9-s.woff2 +0 -0
- package/src/contexts/fragment-identifier.tsx +0 -48
- package/src/hooks/use-icon-animation.ts +0 -41
- /package/.next/static/{OIjazFWdAl7sqkAJKSCpp → bw7nsTv8dL6IXcqslAAMG}/_buildManifest.js +0 -0
- /package/.next/static/{OIjazFWdAl7sqkAJKSCpp → bw7nsTv8dL6IXcqslAAMG}/_ssgManifest.js +0 -0
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-email/preview-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "A live preview of your emails right in your browser.",
|
|
5
5
|
"main": "./index.mjs",
|
|
6
6
|
"dependencies": {
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"@babel/traverse": "7.27.0",
|
|
10
10
|
"@lottiefiles/dotlottie-react": "0.13.3",
|
|
11
11
|
"@radix-ui/colors": "3.0.0",
|
|
12
|
-
"@radix-ui/react-collapsible": "1.1.
|
|
13
|
-
"@radix-ui/react-dropdown-menu": "2.1.
|
|
14
|
-
"@radix-ui/react-popover": "1.1.
|
|
15
|
-
"@radix-ui/react-slot": "1.2.
|
|
16
|
-
"@radix-ui/react-tabs": "1.1.
|
|
17
|
-
"@radix-ui/react-toggle-group": "1.1.
|
|
18
|
-
"@radix-ui/react-tooltip": "1.2.
|
|
12
|
+
"@radix-ui/react-collapsible": "1.1.12",
|
|
13
|
+
"@radix-ui/react-dropdown-menu": "2.1.16",
|
|
14
|
+
"@radix-ui/react-popover": "1.1.15",
|
|
15
|
+
"@radix-ui/react-slot": "1.2.3",
|
|
16
|
+
"@radix-ui/react-tabs": "1.1.13",
|
|
17
|
+
"@radix-ui/react-toggle-group": "1.1.11",
|
|
18
|
+
"@radix-ui/react-tooltip": "1.2.8",
|
|
19
19
|
"@types/node": "22.14.1",
|
|
20
20
|
"@types/normalize-path": "3.0.2",
|
|
21
21
|
"@types/react": "19.0.10",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"@types/webpack": "5.28.5",
|
|
24
24
|
"autoprefixer": "10.4.21",
|
|
25
25
|
"clsx": "2.1.1",
|
|
26
|
-
"esbuild": "0.25.
|
|
27
|
-
"framer-motion": "12.23.
|
|
26
|
+
"esbuild": "0.25.10",
|
|
27
|
+
"framer-motion": "12.23.22",
|
|
28
28
|
"json5": "2.2.3",
|
|
29
29
|
"log-symbols": "4.1.0",
|
|
30
30
|
"module-punycode": "npm:punycode@2.3.1",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"prism-react-renderer": "2.4.1",
|
|
36
36
|
"react": "19.0.0",
|
|
37
37
|
"react-dom": "19.0.0",
|
|
38
|
-
"sharp": "0.34.
|
|
38
|
+
"sharp": "0.34.4",
|
|
39
39
|
"socket.io-client": "4.8.1",
|
|
40
40
|
"sonner": "2.0.3",
|
|
41
41
|
"source-map-js": "1.2.1",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"postcss": "8.5.3",
|
|
61
61
|
"tailwindcss": "3.4.0",
|
|
62
62
|
"typescript": "5.8.3",
|
|
63
|
-
"@react-email/components": "0.5.
|
|
63
|
+
"@react-email/components": "0.5.6"
|
|
64
64
|
},
|
|
65
65
|
"license": "MIT",
|
|
66
66
|
"repository": {
|
package/readme.md
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
<div align="center">A live preview of your emails right in your browser.</div>
|
|
3
3
|
<br />
|
|
4
4
|
<div align="center">
|
|
5
|
-
<a href="https://react.email">Website</a>
|
|
5
|
+
<a href="https://react.email">Website</a>
|
|
6
6
|
<span> · </span>
|
|
7
|
-
<a href="https://github.com/resend/react-email">GitHub</a>
|
|
8
|
-
|
|
9
|
-
<a href="https://react.email/discord">Discord</a>
|
|
7
|
+
<a href="https://github.com/resend/react-email">GitHub</a>
|
|
8
|
+
|
|
10
9
|
</div>
|
|
11
10
|
|
|
12
11
|
This package is used to store the preview server, it is also published and versioned so that it can be installed when the [CLI](../react-email) is being used.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use server';
|
|
2
|
+
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { cache } from 'react';
|
|
5
6
|
import { emailsDirectoryAbsolutePath } from '../app/env';
|
|
6
7
|
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
8
8
|
export const getEmailPathFromSlug = cache(async (slug: string) => {
|
|
9
9
|
if (['.tsx', '.jsx', '.ts', '.js'].includes(path.extname(slug)))
|
|
10
10
|
return path.join(emailsDirectoryAbsolutePath, slug);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use server';
|
|
2
|
+
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { styleText } from 'node:util';
|
|
@@ -40,9 +41,13 @@ export const renderEmailByPath = async (
|
|
|
40
41
|
emailPath: string,
|
|
41
42
|
invalidatingCache = false,
|
|
42
43
|
): Promise<EmailRenderingResult> => {
|
|
43
|
-
if (invalidatingCache)
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if (invalidatingCache) {
|
|
45
|
+
cache.delete(emailPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (cache.has(emailPath)) {
|
|
49
|
+
return cache.get(emailPath)!;
|
|
50
|
+
}
|
|
46
51
|
|
|
47
52
|
const timeBeforeEmailRendered = performance.now();
|
|
48
53
|
|
package/src/app/env.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
-
/** ONLY ACCESSIBLE ON THE SERVER */
|
|
3
|
-
export const emailsDirRelativePath = process.env.EMAILS_DIR_RELATIVE_PATH!;
|
|
4
|
-
|
|
5
1
|
/** ONLY ACCESSIBLE ON THE SERVER */
|
|
6
2
|
export const userProjectLocation = process.env.USER_PROJECT_LOCATION!;
|
|
7
3
|
|
package/src/app/layout.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Metadata } from 'next';
|
|
2
1
|
import './globals.css';
|
|
2
|
+
|
|
3
|
+
import type { Metadata } from 'next';
|
|
3
4
|
import { EmailsProvider } from '../contexts/emails';
|
|
4
5
|
import { getEmailsDirectoryMetadata } from '../utils/get-emails-directory-metadata';
|
|
5
6
|
import { emailsDirectoryAbsolutePath } from './env';
|
|
@@ -11,7 +12,11 @@ export const metadata: Metadata = {
|
|
|
11
12
|
|
|
12
13
|
export const dynamic = 'force-dynamic';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
export default async function RootLayout({
|
|
16
|
+
children,
|
|
17
|
+
}: {
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
}) {
|
|
15
20
|
const emailsDirectoryMetadata = await getEmailsDirectoryMetadata(
|
|
16
21
|
emailsDirectoryAbsolutePath,
|
|
17
22
|
);
|
|
@@ -38,6 +43,4 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
|
|
38
43
|
</body>
|
|
39
44
|
</html>
|
|
40
45
|
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default RootLayout;
|
|
46
|
+
}
|
package/src/app/page.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { Shell } from '../components/shell';
|
|
|
7
7
|
import { emailsDirectoryAbsolutePath } from './env';
|
|
8
8
|
import logo from './logo.png';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
export default function Home() {
|
|
11
11
|
const baseEmailsDirectoryName = path.basename(emailsDirectoryAbsolutePath);
|
|
12
12
|
|
|
13
13
|
return (
|
|
@@ -41,6 +41,4 @@ const Home = () => {
|
|
|
41
41
|
</div>
|
|
42
42
|
</Shell>
|
|
43
43
|
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default Home;
|
|
44
|
+
}
|
|
@@ -26,11 +26,11 @@ export interface PreviewParams {
|
|
|
26
26
|
slug: string[];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
export default async function Page({
|
|
30
30
|
params: paramsPromise,
|
|
31
31
|
}: {
|
|
32
32
|
params: Promise<PreviewParams>;
|
|
33
|
-
})
|
|
33
|
+
}) {
|
|
34
34
|
const params = await paramsPromise;
|
|
35
35
|
// will come in here as segments of a relative path to the email
|
|
36
36
|
// ex: ['authentication', 'verify-password.tsx']
|
|
@@ -141,7 +141,7 @@ This is most likely not an issue with the preview server. Maybe there was a typo
|
|
|
141
141
|
</Shell>
|
|
142
142
|
</PreviewProvider>
|
|
143
143
|
);
|
|
144
|
-
}
|
|
144
|
+
}
|
|
145
145
|
|
|
146
146
|
export async function generateMetadata({
|
|
147
147
|
params,
|
|
@@ -152,5 +152,3 @@ export async function generateMetadata({
|
|
|
152
152
|
|
|
153
153
|
return { title: `${path.basename(slug.join('/'))} — React Email` };
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
export default Page;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
4
|
-
import {
|
|
4
|
+
import { useState } from 'react';
|
|
5
5
|
import { flushSync } from 'react-dom';
|
|
6
6
|
import { Toaster } from 'sonner';
|
|
7
7
|
import { useDebouncedCallback } from 'use-debounce';
|
|
@@ -16,7 +16,7 @@ import { useToolbarState } from '../../../components/toolbar';
|
|
|
16
16
|
import { Tooltip } from '../../../components/tooltip';
|
|
17
17
|
import { ActiveViewToggleGroup } from '../../../components/topbar/active-view-toggle-group';
|
|
18
18
|
import { ViewSizeControls } from '../../../components/topbar/view-size-controls';
|
|
19
|
-
import {
|
|
19
|
+
import { usePreviewContext } from '../../../contexts/preview';
|
|
20
20
|
import { useClampedState } from '../../../hooks/use-clamped-state';
|
|
21
21
|
import { cn } from '../../../utils';
|
|
22
22
|
import { ErrorOverlay } from './error-overlay';
|
|
@@ -26,7 +26,7 @@ interface PreviewProps extends React.ComponentProps<'div'> {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
29
|
-
const { renderingResult, renderedEmailMetadata } =
|
|
29
|
+
const { renderingResult, renderedEmailMetadata } = usePreviewContext();
|
|
30
30
|
|
|
31
31
|
const router = useRouter();
|
|
32
32
|
const pathname = usePathname();
|
|
@@ -56,17 +56,17 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
|
56
56
|
|
|
57
57
|
const [maxWidth, setMaxWidth] = useState(Number.POSITIVE_INFINITY);
|
|
58
58
|
const [maxHeight, setMaxHeight] = useState(Number.POSITIVE_INFINITY);
|
|
59
|
-
const minWidth =
|
|
60
|
-
const minHeight =
|
|
59
|
+
const minWidth = 220;
|
|
60
|
+
const minHeight = minWidth * 1.6;
|
|
61
61
|
const storedWidth = searchParams.get('width');
|
|
62
62
|
const storedHeight = searchParams.get('height');
|
|
63
63
|
const [width, setWidth] = useClampedState(
|
|
64
|
-
storedWidth ? Number.parseInt(storedWidth) :
|
|
64
|
+
storedWidth ? Number.parseInt(storedWidth) : 1024,
|
|
65
65
|
minWidth,
|
|
66
66
|
maxWidth,
|
|
67
67
|
);
|
|
68
68
|
const [height, setHeight] = useClampedState(
|
|
69
|
-
storedHeight ? Number.parseInt(storedHeight) :
|
|
69
|
+
storedHeight ? Number.parseInt(storedHeight) : 600,
|
|
70
70
|
minHeight,
|
|
71
71
|
maxHeight,
|
|
72
72
|
);
|
|
@@ -83,22 +83,26 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
|
83
83
|
return (
|
|
84
84
|
<>
|
|
85
85
|
<Topbar emailTitle={emailTitle}>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
86
|
+
{activeView === 'preview' && (
|
|
87
|
+
<ViewSizeControls
|
|
88
|
+
setViewHeight={(height) => {
|
|
89
|
+
setHeight(height);
|
|
90
|
+
flushSync(() => {
|
|
91
|
+
handleSaveViewSize();
|
|
92
|
+
});
|
|
93
|
+
}}
|
|
94
|
+
setViewWidth={(width) => {
|
|
95
|
+
setWidth(width);
|
|
96
|
+
flushSync(() => {
|
|
97
|
+
handleSaveViewSize();
|
|
98
|
+
});
|
|
99
|
+
}}
|
|
100
|
+
viewHeight={height}
|
|
101
|
+
viewWidth={width}
|
|
102
|
+
minWidth={minWidth}
|
|
103
|
+
minHeight={minHeight}
|
|
104
|
+
/>
|
|
105
|
+
)}
|
|
102
106
|
<ActiveViewToggleGroup
|
|
103
107
|
activeView={activeView}
|
|
104
108
|
setActiveView={handleViewChange}
|
|
@@ -113,7 +117,7 @@ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
|
113
117
|
<div
|
|
114
118
|
{...props}
|
|
115
119
|
className={cn(
|
|
116
|
-
'h-[calc(100%-3.5rem-2.375rem)] will-change-[height] flex p-4 transition-[height] duration-300',
|
|
120
|
+
'h-[calc(100%-3.5rem-2.375rem)] will-change-[height] flex p-4 transition-[height] duration-300 relative',
|
|
117
121
|
activeView === 'preview' && 'bg-gray-200',
|
|
118
122
|
toolbarToggled && 'h-[calc(100%-3.5rem-13rem)]',
|
|
119
123
|
className,
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { Slot } from '@radix-ui/react-slot';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type ComponentProps,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
3
9
|
import { cn } from '../utils';
|
|
10
|
+
import { VIEW_PRESETS } from './topbar/view-size-controls';
|
|
4
11
|
|
|
5
12
|
type Direction = 'north' | 'south' | 'east' | 'west';
|
|
6
13
|
|
|
@@ -56,14 +63,17 @@ export const ResizableWrapper = ({
|
|
|
56
63
|
...rest
|
|
57
64
|
}: ResizableWrapperProps) => {
|
|
58
65
|
const resizableRef = useRef<HTMLElement>(null);
|
|
59
|
-
|
|
66
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
60
67
|
const mouseMoveListener = useRef<(event: MouseEvent) => void>(null);
|
|
68
|
+
const [direction, setDirection] = useState<Direction | null>(null);
|
|
61
69
|
|
|
62
70
|
const handleStopResizing = useCallback(() => {
|
|
63
71
|
if (mouseMoveListener.current) {
|
|
64
72
|
document.removeEventListener('mousemove', mouseMoveListener.current);
|
|
65
73
|
}
|
|
66
74
|
document.removeEventListener('mouseup', handleStopResizing);
|
|
75
|
+
setIsResizing(false);
|
|
76
|
+
setDirection(null);
|
|
67
77
|
onResizeEnd?.();
|
|
68
78
|
}, []);
|
|
69
79
|
|
|
@@ -78,6 +88,38 @@ export const ResizableWrapper = ({
|
|
|
78
88
|
const center = isHorizontal
|
|
79
89
|
? resizableBoundingRect.x + resizableBoundingRect.width / 2
|
|
80
90
|
: resizableBoundingRect.y + resizableBoundingRect.height / 2;
|
|
91
|
+
|
|
92
|
+
const newPosition = Math.abs(mousePosition - center) * 2;
|
|
93
|
+
|
|
94
|
+
setIsResizing(true);
|
|
95
|
+
setDirection(direction);
|
|
96
|
+
|
|
97
|
+
const threshold = 12;
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < VIEW_PRESETS.length; i++) {
|
|
100
|
+
const preset = VIEW_PRESETS[i];
|
|
101
|
+
|
|
102
|
+
if (preset) {
|
|
103
|
+
if (
|
|
104
|
+
isHorizontal &&
|
|
105
|
+
newPosition > preset.dimensions.width - threshold &&
|
|
106
|
+
newPosition < preset.dimensions.width + threshold
|
|
107
|
+
) {
|
|
108
|
+
onResize(preset.dimensions.width, direction);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
!isHorizontal &&
|
|
114
|
+
newPosition > preset.dimensions.height - threshold &&
|
|
115
|
+
newPosition < preset.dimensions.height + threshold
|
|
116
|
+
) {
|
|
117
|
+
onResize(preset.dimensions.height, direction);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
81
123
|
onResize(Math.abs(mousePosition - center) * 2, direction);
|
|
82
124
|
} else {
|
|
83
125
|
handleStopResizing();
|
|
@@ -89,85 +131,142 @@ export const ResizableWrapper = ({
|
|
|
89
131
|
};
|
|
90
132
|
|
|
91
133
|
useEffect(() => {
|
|
92
|
-
if (!window.document)
|
|
134
|
+
if (!window.document) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
93
137
|
|
|
94
138
|
return () => {
|
|
95
139
|
handleStopResizing();
|
|
96
140
|
};
|
|
97
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
98
141
|
}, []);
|
|
99
142
|
|
|
100
143
|
return (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
tabIndex={0}
|
|
134
|
-
>
|
|
135
|
-
<div className="h-8 w-1 rounded-md bg-black/30" />
|
|
136
|
-
</div>
|
|
137
|
-
<div
|
|
138
|
-
aria-label="resize-north"
|
|
139
|
-
aria-valuenow={height}
|
|
140
|
-
aria-valuemin={minHeight}
|
|
141
|
-
aria-valuemax={maxHeight}
|
|
142
|
-
onDragStart={(event) => event.preventDefault()}
|
|
143
|
-
className="-translate-x-1/2 -translate-y-1/2 absolute top-0 left-1/2 cursor-n-resize p-2 [user-drag:none]"
|
|
144
|
-
draggable="false"
|
|
145
|
-
onMouseDown={() => {
|
|
146
|
-
handleStartResizing('north');
|
|
147
|
-
}}
|
|
148
|
-
role="slider"
|
|
149
|
-
tabIndex={0}
|
|
150
|
-
>
|
|
151
|
-
<div className="h-1 w-8 rounded-md bg-black/30" />
|
|
144
|
+
<>
|
|
145
|
+
<div className=" overflow-hidden absolute inset-0">
|
|
146
|
+
<div className="absolute mx-auto box-content -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
|
|
147
|
+
{VIEW_PRESETS.map((preset) => (
|
|
148
|
+
<div
|
|
149
|
+
key={preset.name}
|
|
150
|
+
className="-translate-x-1/2 -translate-y-1/2 absolute pointer-events-none select-none"
|
|
151
|
+
style={{
|
|
152
|
+
width: preset.dimensions.width,
|
|
153
|
+
height: preset.dimensions.height,
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{width === preset.dimensions.width &&
|
|
157
|
+
isResizing &&
|
|
158
|
+
(direction === 'east' || direction === 'west') && (
|
|
159
|
+
<>
|
|
160
|
+
<div className="absolute right-0 -top-[100vw] -bottom-[100vw] border-r-2 border-cyan-5" />
|
|
161
|
+
<div className="absolute left-0 -top-[100vw] -bottom-[100vw] border-l-2 border-cyan-5" />
|
|
162
|
+
</>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{height === preset.dimensions.height &&
|
|
166
|
+
isResizing &&
|
|
167
|
+
(direction === 'north' || direction === 'south') && (
|
|
168
|
+
<>
|
|
169
|
+
<div className="absolute top-0 -left-[100vw] -right-[100vw] border-t-2 border-cyan-5" />
|
|
170
|
+
<div className="absolute bottom-0 -left-[100vw] -right-[100vw] border-b-2 border-cyan-5" />
|
|
171
|
+
</>
|
|
172
|
+
)}
|
|
173
|
+
</div>
|
|
174
|
+
))}
|
|
175
|
+
</div>
|
|
152
176
|
</div>
|
|
177
|
+
|
|
153
178
|
<div
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
aria-valuemin={minHeight}
|
|
157
|
-
aria-valuemax={maxHeight}
|
|
158
|
-
onDragStart={(event) => event.preventDefault()}
|
|
159
|
-
className="-translate-x-1/2 -translate-y-1/2 absolute top-full left-1/2 cursor-s-resize p-2 [user-drag:none]"
|
|
160
|
-
draggable="false"
|
|
161
|
-
onMouseDown={() => {
|
|
162
|
-
handleStartResizing('south');
|
|
163
|
-
}}
|
|
164
|
-
role="slider"
|
|
165
|
-
tabIndex={0}
|
|
179
|
+
{...rest}
|
|
180
|
+
className={cn('relative mx-auto my-auto box-content', rest.className)}
|
|
166
181
|
>
|
|
167
|
-
<div
|
|
168
|
-
|
|
182
|
+
<div
|
|
183
|
+
aria-label="resize-west"
|
|
184
|
+
aria-valuenow={width}
|
|
185
|
+
aria-valuemin={minWidth}
|
|
186
|
+
aria-valuemax={maxWidth}
|
|
187
|
+
className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 -left-2 cursor-w-resize p-2 [user-drag:none]"
|
|
188
|
+
onDragStart={(event) => event.preventDefault()}
|
|
189
|
+
draggable="false"
|
|
190
|
+
onMouseDown={() => {
|
|
191
|
+
handleStartResizing('west');
|
|
192
|
+
}}
|
|
193
|
+
role="slider"
|
|
194
|
+
tabIndex={0}
|
|
195
|
+
>
|
|
196
|
+
<div
|
|
197
|
+
className={cn('h-8 w-1 rounded-md bg-black/50 transition-colors', {
|
|
198
|
+
'bg-black': direction === 'west',
|
|
199
|
+
})}
|
|
200
|
+
/>
|
|
201
|
+
</div>
|
|
202
|
+
<div
|
|
203
|
+
aria-label="resize-east"
|
|
204
|
+
aria-valuenow={width}
|
|
205
|
+
aria-valuemin={minWidth}
|
|
206
|
+
aria-valuemax={maxWidth}
|
|
207
|
+
onDragStart={(event) => event.preventDefault()}
|
|
208
|
+
className="translate-x-1/2 -translate-y-1/2 absolute top-1/2 -right-2 cursor-e-resize p-2 [user-drag:none]"
|
|
209
|
+
draggable="false"
|
|
210
|
+
onMouseDown={() => {
|
|
211
|
+
handleStartResizing('east');
|
|
212
|
+
}}
|
|
213
|
+
role="slider"
|
|
214
|
+
tabIndex={0}
|
|
215
|
+
>
|
|
216
|
+
<div
|
|
217
|
+
className={cn('h-8 w-1 rounded-md bg-black/50 transition-colors', {
|
|
218
|
+
'bg-black': direction === 'east',
|
|
219
|
+
})}
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
<div
|
|
223
|
+
aria-label="resize-north"
|
|
224
|
+
aria-valuenow={height}
|
|
225
|
+
aria-valuemin={minHeight}
|
|
226
|
+
aria-valuemax={maxHeight}
|
|
227
|
+
onDragStart={(event) => event.preventDefault()}
|
|
228
|
+
className="-translate-x-1/2 -translate-y-1/2 absolute -top-2 left-1/2 cursor-n-resize p-2 [user-drag:none]"
|
|
229
|
+
draggable="false"
|
|
230
|
+
onMouseDown={() => {
|
|
231
|
+
handleStartResizing('north');
|
|
232
|
+
}}
|
|
233
|
+
role="slider"
|
|
234
|
+
tabIndex={0}
|
|
235
|
+
>
|
|
236
|
+
<div
|
|
237
|
+
className={cn('h-1 w-8 rounded-md bg-black/50 transition-colors', {
|
|
238
|
+
'bg-black': direction === 'north',
|
|
239
|
+
})}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
<div
|
|
243
|
+
aria-label="resize-south"
|
|
244
|
+
aria-valuenow={height}
|
|
245
|
+
aria-valuemin={minHeight}
|
|
246
|
+
aria-valuemax={maxHeight}
|
|
247
|
+
onDragStart={(event) => event.preventDefault()}
|
|
248
|
+
className="-translate-x-1/2 translate-y-1/2 absolute -bottom-2 left-1/2 cursor-s-resize p-2 [user-drag:none]"
|
|
249
|
+
draggable="false"
|
|
250
|
+
onMouseDown={() => {
|
|
251
|
+
handleStartResizing('south');
|
|
252
|
+
}}
|
|
253
|
+
role="slider"
|
|
254
|
+
tabIndex={0}
|
|
255
|
+
>
|
|
256
|
+
<div
|
|
257
|
+
className={cn('h-1 w-8 rounded-md bg-black/50 transition-colors', {
|
|
258
|
+
'bg-black': direction === 'south',
|
|
259
|
+
})}
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
169
262
|
|
|
170
|
-
|
|
171
|
-
|
|
263
|
+
<Slot
|
|
264
|
+
ref={resizableRef}
|
|
265
|
+
className={isResizing ? 'pointer-events-none select-none' : ''}
|
|
266
|
+
>
|
|
267
|
+
{children}
|
|
268
|
+
</Slot>
|
|
269
|
+
</div>
|
|
270
|
+
</>
|
|
172
271
|
);
|
|
173
272
|
};
|
|
@@ -5,7 +5,7 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
|
5
5
|
import * as React from 'react';
|
|
6
6
|
import type { CompatibilityCheckingResult } from '../actions/email-validation/check-compatibility';
|
|
7
7
|
import { isBuilding } from '../app/env';
|
|
8
|
-
import {
|
|
8
|
+
import { usePreviewContext } from '../contexts/preview';
|
|
9
9
|
import { cn } from '../utils';
|
|
10
10
|
import { IconArrowDown } from './icons/icon-arrow-down';
|
|
11
11
|
import { IconCheck } from './icons/icon-check';
|
|
@@ -332,8 +332,7 @@ export const Toolbar = ({
|
|
|
332
332
|
serverSpamCheckingResult,
|
|
333
333
|
serverCompatibilityResults,
|
|
334
334
|
}: ToolbarProps) => {
|
|
335
|
-
const { emailPath, emailSlug, renderedEmailMetadata } =
|
|
336
|
-
React.use(PreviewContext)!;
|
|
335
|
+
const { emailPath, emailSlug, renderedEmailMetadata } = usePreviewContext();
|
|
337
336
|
|
|
338
337
|
if (renderedEmailMetadata === undefined) return null;
|
|
339
338
|
const { prettyMarkup, plainText, reactMarkup } = renderedEmailMetadata;
|
|
@@ -30,7 +30,7 @@ export const ActiveViewToggleGroup = ({
|
|
|
30
30
|
<Tooltip.Trigger asChild>
|
|
31
31
|
<div
|
|
32
32
|
className={cn(
|
|
33
|
-
'
|
|
33
|
+
'w-9 flex items-center py-2 transition ease-in-out duration-200 relative hover:text-slate-12',
|
|
34
34
|
{
|
|
35
35
|
'text-slate-11': activeView !== 'desktop',
|
|
36
36
|
'text-slate-12': activeView === 'desktop',
|
|
@@ -47,7 +47,7 @@ export const ActiveViewToggleGroup = ({
|
|
|
47
47
|
transition={tabTransition}
|
|
48
48
|
/>
|
|
49
49
|
)}
|
|
50
|
-
<IconMonitor />
|
|
50
|
+
<IconMonitor className="m-auto" />
|
|
51
51
|
</div>
|
|
52
52
|
</Tooltip.Trigger>
|
|
53
53
|
<Tooltip.Content>Preview</Tooltip.Content>
|
|
@@ -58,7 +58,7 @@ export const ActiveViewToggleGroup = ({
|
|
|
58
58
|
<Tooltip.Trigger asChild>
|
|
59
59
|
<div
|
|
60
60
|
className={cn(
|
|
61
|
-
'
|
|
61
|
+
'w-9 flex py-2 transition ease-in-out duration-200 relative hover:text-slate-12',
|
|
62
62
|
{
|
|
63
63
|
'text-slate-11': activeView !== 'source',
|
|
64
64
|
'text-slate-12': activeView === 'source',
|
|
@@ -75,7 +75,7 @@ export const ActiveViewToggleGroup = ({
|
|
|
75
75
|
transition={tabTransition}
|
|
76
76
|
/>
|
|
77
77
|
)}
|
|
78
|
-
<IconSource />
|
|
78
|
+
<IconSource className="m-auto" />
|
|
79
79
|
</div>
|
|
80
80
|
</Tooltip.Trigger>
|
|
81
81
|
<Tooltip.Content>Code</Tooltip.Content>
|