react-email 3.0.6 → 4.0.0-alpha.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 +6 -0
- package/dist/cli/index.js +768 -763
- package/dist/cli/index.mjs +480 -476
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +14 -12
- package/dist/preview/.next/build-manifest.json +5 -5
- package/dist/preview/.next/cache/.rscinfo +1 -1
- package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
- package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
- package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
- package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
- 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 +1 -1
- package/dist/preview/.next/server/app/_not-found/page.js +1 -1
- package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +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 +6 -6
- 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/273.js +1 -0
- package/dist/preview/.next/server/chunks/594.js +10 -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/server-reference-manifest.js +1 -1
- package/dist/preview/.next/server/server-reference-manifest.json +1 -1
- package/dist/preview/.next/server/webpack-runtime.js +1 -1
- package/dist/preview/.next/static/chunks/18b16e15-6ad9b58e10ff8891.js +1 -0
- package/dist/preview/.next/static/chunks/490-48951f2e19ae3aef.js +1 -0
- package/dist/preview/.next/static/chunks/600-2e2ca4c8bbd97b61.js +1 -0
- package/dist/preview/.next/static/chunks/app/{layout-a2901ed1c2c53661.js → layout-490964e2c3604d33.js} +1 -1
- package/dist/preview/.next/static/chunks/app/page-d2432acd08db8fc0.js +1 -0
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-f4e211e00c026401.js +1 -0
- package/dist/preview/.next/static/chunks/webpack-7bf1ffb05f5540be.js +1 -0
- package/dist/preview/.next/static/css/5e0736cafbb392a9.css +3 -0
- package/dist/preview/.next/trace +21 -21
- package/package.json +12 -7
- package/postcss.config.js +1 -1
- package/src/actions/email-validation/check-links.ts +88 -0
- package/src/actions/email-validation/get-line-and-column-from-index.spec.ts +22 -0
- package/src/actions/email-validation/get-line-and-column-from-index.ts +43 -0
- package/src/actions/email-validation/quick-fetch.ts +12 -0
- package/src/actions/get-email-path-from-slug.ts +7 -4
- package/src/actions/render-email-by-path.tsx +3 -3
- package/src/animated-icons-data/help.json +1082 -0
- package/src/animated-icons-data/link.json +1309 -0
- package/src/animated-icons-data/load.json +443 -0
- package/src/animated-icons-data/mail.json +1320 -0
- package/src/app/globals.css +0 -24
- package/src/app/layout.tsx +7 -3
- package/src/app/page.tsx +9 -10
- package/src/app/preview/[...slug]/page.tsx +3 -2
- package/src/app/preview/[...slug]/preview.tsx +5 -5
- package/src/app/preview/[...slug]/rendering-error.tsx +6 -6
- package/src/components/button.tsx +8 -8
- package/src/components/code-container.tsx +7 -7
- package/src/components/code-snippet.tsx +11 -0
- package/src/components/code.tsx +4 -4
- package/src/components/heading.tsx +1 -1
- package/src/components/icons/icon-button.tsx +1 -1
- package/src/components/icons/icon-circle-check.tsx +21 -0
- package/src/components/icons/icon-circle-close.tsx +17 -0
- package/src/components/icons/icon-circle-warning.tsx +17 -0
- package/src/components/icons/icon-email.tsx +18 -0
- package/src/components/icons/icon-link.tsx +14 -0
- package/src/components/icons/icon-stamp.tsx +14 -0
- package/src/components/send.tsx +9 -9
- package/src/components/shell.tsx +32 -34
- package/src/components/sidebar/{sidebar-directory-children.tsx → file-tree-directory-children.tsx} +22 -18
- package/src/components/sidebar/{sidebar-directory.tsx → file-tree-directory.tsx} +11 -12
- package/src/components/sidebar/file-tree.tsx +31 -0
- package/src/components/sidebar/link-checker.tsx +291 -0
- package/src/components/sidebar/sidebar.tsx +296 -22
- package/src/components/text.tsx +1 -1
- package/src/components/tooltip-content.tsx +3 -3
- package/src/components/tooltip.tsx +1 -1
- package/src/components/topbar.tsx +14 -17
- package/src/hooks/use-email-rendering-result.ts +2 -2
- package/src/hooks/use-icon-animation.ts +44 -0
- package/src/utils/cn.ts +1 -1
- package/src/utils/esbuild/renderring-utilities-exporter.ts +1 -1
- package/src/utils/get-email-component.ts +6 -6
- package/src/utils/get-emails-directory-metadata.spec.ts +0 -1
- package/src/utils/improve-error-with-sourcemap.ts +1 -1
- package/src/utils/static-node-modules-for-vm.ts +6 -6
- package/tsconfig.json +2 -6
- package/.eslintrc.js +0 -52
- package/.prettierignore +0 -3
- package/.prettierrc.js +0 -8
- package/dist/preview/.next/cache/eslint/.cache_1c3sgg +0 -1
- package/dist/preview/.next/server/chunks/391.js +0 -1
- package/dist/preview/.next/server/chunks/720.js +0 -10
- package/dist/preview/.next/static/chunks/12-b9450aa0845e7574.js +0 -1
- package/dist/preview/.next/static/chunks/154-4202f86af36ccff4.js +0 -1
- package/dist/preview/.next/static/chunks/app/page-54a86772095e22e0.js +0 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-2bfad134b65ddd79.js +0 -1
- package/dist/preview/.next/static/chunks/webpack-9255716c9496e606.js +0 -1
- package/dist/preview/.next/static/css/eb0a93282704d7ab.css +0 -3
- /package/dist/preview/.next/static/{Trk1e7GzgKOLunAXBDCy- → fZaiKz58wDr55pxLu9uHa}/_buildManifest.js +0 -0
- /package/dist/preview/.next/static/{Trk1e7GzgKOLunAXBDCy- → fZaiKz58wDr55pxLu9uHa}/_ssgManifest.js +0 -0
|
@@ -1,45 +1,319 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
3
|
+
import * as Tabs from '@radix-ui/react-tabs';
|
|
4
|
+
import { clsx } from 'clsx';
|
|
5
|
+
import { motion } from 'framer-motion';
|
|
6
|
+
import Lottie from 'lottie-react';
|
|
7
|
+
import Link from 'next/link';
|
|
8
|
+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
9
|
+
import type * as React from 'react';
|
|
10
|
+
import animatedHelpIcon from '../../animated-icons-data/help.json';
|
|
11
|
+
import animatedLinkIcon from '../../animated-icons-data/link.json';
|
|
12
|
+
import animatedMailIcon from '../../animated-icons-data/mail.json';
|
|
5
13
|
import { useEmails } from '../../contexts/emails';
|
|
14
|
+
import { useIconAnimation } from '../../hooks/use-icon-animation';
|
|
6
15
|
import { cn } from '../../utils';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
16
|
+
import { Button } from '../button';
|
|
17
|
+
import { Heading } from '../heading';
|
|
18
|
+
import { Tooltip } from '../tooltip';
|
|
19
|
+
import { FileTree } from './file-tree';
|
|
20
|
+
import { LinkChecker } from './link-checker';
|
|
21
|
+
|
|
22
|
+
type SidebarPanelValue = 'file-tree' | 'link-checker' | 'image-checker';
|
|
9
23
|
|
|
10
24
|
interface SidebarProps {
|
|
11
25
|
className?: string;
|
|
12
26
|
currentEmailOpenSlug?: string;
|
|
27
|
+
markup?: string;
|
|
13
28
|
style?: React.CSSProperties;
|
|
14
29
|
}
|
|
15
30
|
|
|
31
|
+
interface NavigationButtonProps {
|
|
32
|
+
children: React.ReactNode;
|
|
33
|
+
className?: string;
|
|
34
|
+
href?: string;
|
|
35
|
+
onMouseEnter?: () => void;
|
|
36
|
+
onMouseLeave?: () => void;
|
|
37
|
+
side?: 'top' | 'bottom' | 'left' | 'right';
|
|
38
|
+
tooltip?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface TabTriggerProps {
|
|
42
|
+
activeTabValue: SidebarPanelValue;
|
|
43
|
+
children?: React.ReactNode;
|
|
44
|
+
className?: string;
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
onMouseEnter?: () => void;
|
|
47
|
+
onMouseLeave?: () => void;
|
|
48
|
+
tabValue: SidebarPanelValue;
|
|
49
|
+
tooltipText: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface PanelProps {
|
|
53
|
+
active: boolean;
|
|
54
|
+
children: React.ReactNode;
|
|
55
|
+
title: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const TAB_ACTION_BASE_CLASSES =
|
|
59
|
+
'group relative aspect-square w-full cursor-pointer text-slate-12 transition-colors duration-150 ease-[cubic-bezier(.36,.66,.6,1)] hover:bg-slate-3 disabled:cursor-not-allowed disabled:bg-slate-2 disabled:text-slate-10';
|
|
60
|
+
|
|
61
|
+
const NavigationButton = ({
|
|
62
|
+
children,
|
|
63
|
+
className,
|
|
64
|
+
href,
|
|
65
|
+
onMouseEnter,
|
|
66
|
+
onMouseLeave,
|
|
67
|
+
side,
|
|
68
|
+
tooltip,
|
|
69
|
+
}: NavigationButtonProps) => (
|
|
70
|
+
<Tooltip.Provider>
|
|
71
|
+
<Tooltip>
|
|
72
|
+
<Tooltip.Trigger asChild>
|
|
73
|
+
<Link
|
|
74
|
+
href={href ?? '#'}
|
|
75
|
+
className={cn(TAB_ACTION_BASE_CLASSES, className)}
|
|
76
|
+
onMouseEnter={onMouseEnter}
|
|
77
|
+
onMouseLeave={onMouseLeave}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</Link>
|
|
81
|
+
</Tooltip.Trigger>
|
|
82
|
+
{tooltip && <Tooltip.Content side={side}>{tooltip}</Tooltip.Content>}
|
|
83
|
+
</Tooltip>
|
|
84
|
+
</Tooltip.Provider>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const TabTrigger = ({
|
|
88
|
+
activeTabValue,
|
|
89
|
+
children,
|
|
90
|
+
className,
|
|
91
|
+
disabled,
|
|
92
|
+
onMouseEnter,
|
|
93
|
+
onMouseLeave,
|
|
94
|
+
tabValue,
|
|
95
|
+
tooltipText,
|
|
96
|
+
}: TabTriggerProps) => {
|
|
97
|
+
const isActive = tabValue === activeTabValue;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Tooltip.Provider>
|
|
101
|
+
<Tooltip>
|
|
102
|
+
<Tooltip.Trigger asChild>
|
|
103
|
+
<Tabs.Trigger
|
|
104
|
+
className={clsx(TAB_ACTION_BASE_CLASSES, className, {
|
|
105
|
+
'bg-slate-6': isActive,
|
|
106
|
+
})}
|
|
107
|
+
data-active={isActive}
|
|
108
|
+
disabled={disabled}
|
|
109
|
+
onMouseEnter={onMouseEnter}
|
|
110
|
+
onMouseLeave={onMouseLeave}
|
|
111
|
+
value={tabValue}
|
|
112
|
+
>
|
|
113
|
+
{isActive && (
|
|
114
|
+
<motion.div
|
|
115
|
+
className="absolute top-0 left-0 h-full w-1 bg-[#0BB9CD] transition-colors duration-300 ease-[bezier(.36,.66,.6,1)]"
|
|
116
|
+
layoutId="sidebar-active-tab"
|
|
117
|
+
transition={{ type: 'spring', bounce: 0.12, duration: 0.6 }}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
<div
|
|
121
|
+
aria-hidden
|
|
122
|
+
className="pointer-events-none absolute inset-0 flex items-center justify-center pl-1 transition-opacity duration-150 ease-in"
|
|
123
|
+
>
|
|
124
|
+
{children}
|
|
125
|
+
</div>
|
|
126
|
+
</Tabs.Trigger>
|
|
127
|
+
</Tooltip.Trigger>
|
|
128
|
+
<Tooltip.Content side="right">{tooltipText}</Tooltip.Content>
|
|
129
|
+
</Tooltip>
|
|
130
|
+
</Tooltip.Provider>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const Panel = ({ title, active, children }: PanelProps) => (
|
|
135
|
+
<>
|
|
136
|
+
<div
|
|
137
|
+
className={clsx(
|
|
138
|
+
'hidden min-h-[3.3125rem] flex-shrink items-center p-3 px-4 lg:flex',
|
|
139
|
+
{
|
|
140
|
+
'bg-slate-3': active,
|
|
141
|
+
},
|
|
142
|
+
)}
|
|
143
|
+
>
|
|
144
|
+
<Heading as="h2" className="truncate" size="2" weight="medium">
|
|
145
|
+
{title}
|
|
146
|
+
</Heading>
|
|
147
|
+
</div>
|
|
148
|
+
<div className="-mt-[.5px] relative h-[calc(100vh-4.375rem)] w-full border-slate-4 border-t px-4 pb-3">
|
|
149
|
+
{children}
|
|
150
|
+
</div>
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const ReactIcon = () => (
|
|
155
|
+
<svg
|
|
156
|
+
fill="none"
|
|
157
|
+
height="32"
|
|
158
|
+
viewBox="0 0 32 32"
|
|
159
|
+
width="32"
|
|
160
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
161
|
+
className="pointer-events-none duration-300 ease-[cubic-bezier(.42,0,.58,1.8)] group-hover:rotate-90"
|
|
162
|
+
>
|
|
163
|
+
<g clipPath="url(#clip0_27_291)">
|
|
164
|
+
<path
|
|
165
|
+
clipRule="evenodd"
|
|
166
|
+
d="M24.4558 24.4853C25.2339 23.7073 25.3805 22.6549 25.2947 21.746C25.2078 20.8254 24.8697 19.8258 24.3896 18.8287C23.957 17.9302 23.3802 16.9745 22.6821 16C23.3802 15.0255 23.957 14.0698 24.3896 13.1713C24.8697 12.1742 25.2078 11.1746 25.2947 10.254C25.3805 9.34508 25.2339 8.29273 24.4558 7.51472C23.6778 6.73671 22.6255 6.59004 21.7165 6.67584C20.796 6.76273 19.7964 7.10086 18.7993 7.58094C17.9007 8.01357 16.945 8.59036 15.9706 9.28842C14.9961 8.59036 14.0404 8.01357 13.1418 7.58094C12.1447 7.10086 11.1451 6.76273 10.2246 6.67584C9.31564 6.59004 8.26329 6.73671 7.48528 7.51472C6.70727 8.29273 6.5606 9.34508 6.6464 10.254C6.7333 11.1746 7.07142 12.1742 7.5515 13.1713C7.98414 14.0698 8.56092 15.0255 9.25898 16C8.56092 16.9745 7.98414 17.9302 7.5515 18.8287C7.07142 19.8258 6.7333 20.8254 6.6464 21.746C6.5606 22.6549 6.70727 23.7073 7.48528 24.4853C8.26329 25.2633 9.31564 25.41 10.2246 25.3242C11.1451 25.2373 12.1447 24.8991 13.1418 24.4191C14.0404 23.9864 14.9961 23.4096 15.9706 22.7116C16.945 23.4096 17.9007 23.9864 18.7993 24.4191C19.7964 24.8991 20.796 25.2373 21.7165 25.3242C22.6255 25.41 23.6778 25.2633 24.4558 24.4853ZM15.9706 20.948C16.8399 20.2684 17.724 19.4874 18.591 18.6205C19.458 17.7535 20.239 16.8693 20.9186 16C20.239 15.1307 19.458 14.2465 18.591 13.3795C17.724 12.5126 16.8399 11.7316 15.9706 11.052C15.1012 11.7316 14.2171 12.5126 13.3501 13.3795C12.4831 14.2465 11.7021 15.1307 11.0225 16C11.7021 16.8693 12.4831 17.7535 13.3501 18.6205C14.2171 19.4874 15.1012 20.2684 15.9706 20.948ZM17.1498 21.8145C17.968 21.1558 18.7885 20.4195 19.5893 19.6187C20.39 18.818 21.1264 17.9974 21.7851 17.1792C23.7187 19.9919 24.4627 22.4819 23.4576 23.487C22.4524 24.4922 19.9625 23.7482 17.1498 21.8145ZM10.156 17.1792C10.8148 17.9974 11.5511 18.818 12.3518 19.6187C13.1526 20.4195 13.9731 21.1558 14.7914 21.8145C11.9786 23.7482 9.48871 24.4922 8.48355 23.487C7.47839 22.4819 8.22238 19.9919 10.156 17.1792ZM10.156 14.8208C10.8148 14.0026 11.5511 13.182 12.3518 12.3813C13.1526 11.5805 13.9731 10.8442 14.7914 10.1855C11.9786 8.25182 9.48871 7.50783 8.48355 8.51299C7.47839 9.51815 8.22238 12.0081 10.156 14.8208ZM17.1498 10.1855C17.968 10.8442 18.7885 11.5805 19.5893 12.3813C20.39 13.182 21.1264 14.0026 21.7851 14.8208C23.7187 12.0081 24.4627 9.51815 23.4576 8.51299C22.4524 7.50783 19.9625 8.25182 17.1498 10.1855Z"
|
|
167
|
+
fill="white"
|
|
168
|
+
fillRule="evenodd"
|
|
169
|
+
stroke="white"
|
|
170
|
+
strokeWidth="0.5"
|
|
171
|
+
/>
|
|
172
|
+
</g>
|
|
173
|
+
</svg>
|
|
174
|
+
);
|
|
175
|
+
|
|
16
176
|
export const Sidebar = ({
|
|
17
177
|
className,
|
|
18
178
|
currentEmailOpenSlug,
|
|
179
|
+
markup: emailMarkup,
|
|
19
180
|
style,
|
|
20
181
|
}: SidebarProps) => {
|
|
182
|
+
const pathname = usePathname();
|
|
183
|
+
const searchParams = useSearchParams();
|
|
184
|
+
const router = useRouter();
|
|
185
|
+
const activePanelValue = (searchParams.get('sidebar-panel') ??
|
|
186
|
+
'file-tree') as SidebarPanelValue;
|
|
21
187
|
const { emailsDirectoryMetadata } = useEmails();
|
|
22
188
|
|
|
189
|
+
const mailAnimation = useIconAnimation();
|
|
190
|
+
const linkAnimation = useIconAnimation();
|
|
191
|
+
const helpAnimation = useIconAnimation();
|
|
192
|
+
|
|
193
|
+
const setActivePanelValue = (newValue: SidebarPanelValue) => {
|
|
194
|
+
const params = new URLSearchParams(searchParams);
|
|
195
|
+
params.set('sidebar-panel', newValue);
|
|
196
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
197
|
+
};
|
|
198
|
+
|
|
23
199
|
return (
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
200
|
+
<Tabs.Root
|
|
201
|
+
asChild
|
|
202
|
+
onValueChange={(v) => setActivePanelValue(v as SidebarPanelValue)}
|
|
203
|
+
orientation="vertical"
|
|
204
|
+
value={activePanelValue}
|
|
27
205
|
>
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
206
|
+
<aside
|
|
207
|
+
className={cn(
|
|
208
|
+
'fixed top-[4.375rem] left-0 z-[9999] grid h-full max-h-[calc(100dvh-4.375rem)] w-screen max-w-full grid-cols-[3.375rem,1fr] overflow-hidden bg-black will-change-auto lg:top-0 lg:z-auto lg:max-h-screen lg:max-w-[20rem]',
|
|
209
|
+
className,
|
|
210
|
+
)}
|
|
211
|
+
style={style}
|
|
212
|
+
>
|
|
213
|
+
<Tabs.List className="flex h-full flex-col border-slate-6 border-r">
|
|
214
|
+
<TabTrigger
|
|
215
|
+
activeTabValue={activePanelValue}
|
|
216
|
+
onMouseEnter={mailAnimation.onMouseEnter}
|
|
217
|
+
onMouseLeave={mailAnimation.onMouseLeave}
|
|
218
|
+
tabValue="file-tree"
|
|
219
|
+
tooltipText="File Explorer"
|
|
220
|
+
>
|
|
221
|
+
<Lottie
|
|
222
|
+
animationData={animatedMailIcon as object}
|
|
223
|
+
autoPlay={false}
|
|
224
|
+
className="h-5 w-5"
|
|
225
|
+
loop={false}
|
|
226
|
+
lottieRef={mailAnimation.ref}
|
|
227
|
+
/>
|
|
228
|
+
</TabTrigger>
|
|
229
|
+
<TabTrigger
|
|
230
|
+
activeTabValue={activePanelValue}
|
|
231
|
+
className="relative"
|
|
232
|
+
onMouseEnter={linkAnimation.onMouseEnter}
|
|
233
|
+
onMouseLeave={linkAnimation.onMouseLeave}
|
|
234
|
+
tabValue="link-checker"
|
|
235
|
+
tooltipText="Link Checker"
|
|
236
|
+
>
|
|
237
|
+
<Lottie
|
|
238
|
+
animationData={animatedLinkIcon as object}
|
|
239
|
+
autoPlay={false}
|
|
240
|
+
className="h-6 w-6"
|
|
241
|
+
loop={false}
|
|
242
|
+
lottieRef={linkAnimation.ref}
|
|
39
243
|
/>
|
|
40
|
-
</
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
244
|
+
</TabTrigger>
|
|
245
|
+
<div className="mt-auto flex flex-col">
|
|
246
|
+
<NavigationButton
|
|
247
|
+
className="flex items-center justify-center"
|
|
248
|
+
href="https://react.email/docs"
|
|
249
|
+
onMouseEnter={helpAnimation.onMouseEnter}
|
|
250
|
+
onMouseLeave={helpAnimation.onMouseLeave}
|
|
251
|
+
side="right"
|
|
252
|
+
tooltip="Documentation"
|
|
253
|
+
>
|
|
254
|
+
<Lottie
|
|
255
|
+
animationData={animatedHelpIcon as object}
|
|
256
|
+
autoPlay={false}
|
|
257
|
+
className="h-5 w-5"
|
|
258
|
+
loop={false}
|
|
259
|
+
lottieRef={helpAnimation.ref}
|
|
260
|
+
/>
|
|
261
|
+
</NavigationButton>
|
|
262
|
+
<NavigationButton
|
|
263
|
+
className="flex items-center justify-center"
|
|
264
|
+
href="https://react.email"
|
|
265
|
+
side="right"
|
|
266
|
+
tooltip="Website"
|
|
267
|
+
>
|
|
268
|
+
<div className="flex h-7 w-7 items-center justify-center">
|
|
269
|
+
<ReactIcon />
|
|
270
|
+
</div>
|
|
271
|
+
</NavigationButton>
|
|
272
|
+
</div>
|
|
273
|
+
</Tabs.List>
|
|
274
|
+
<div className="flex overflow-y-auto overflow-x-hidden">
|
|
275
|
+
<div className="flex w-full flex-col border-slate-6 border-r">
|
|
276
|
+
{activePanelValue === 'link-checker' && (
|
|
277
|
+
<Panel
|
|
278
|
+
title="Link Checker"
|
|
279
|
+
active={activePanelValue === 'link-checker'}
|
|
280
|
+
>
|
|
281
|
+
{currentEmailOpenSlug && emailMarkup ? (
|
|
282
|
+
<LinkChecker
|
|
283
|
+
emailMarkup={emailMarkup}
|
|
284
|
+
emailSlug={currentEmailOpenSlug}
|
|
285
|
+
/>
|
|
286
|
+
) : (
|
|
287
|
+
<div className="mt-4 flex w-full flex-col gap-2 text-pretty text-xs leading-relaxed">
|
|
288
|
+
<div className="flex flex-col gap-1 rounded-lg border border-[#0BB9CD]/50 bg-[#0BB9CD]/20 text-white">
|
|
289
|
+
<span className="mx-2.5 mt-2">
|
|
290
|
+
To use the Link Checker, you need to select a template.
|
|
291
|
+
</span>
|
|
292
|
+
<Button
|
|
293
|
+
className="mx-2 my-2.5 transition-all disabled:border-transparent disabled:bg-slate-11"
|
|
294
|
+
onClick={() => setActivePanelValue('file-tree')}
|
|
295
|
+
>
|
|
296
|
+
Select a template
|
|
297
|
+
</Button>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
</Panel>
|
|
302
|
+
)}
|
|
303
|
+
{activePanelValue === 'file-tree' && (
|
|
304
|
+
<Panel
|
|
305
|
+
title="File Explorer"
|
|
306
|
+
active={activePanelValue === 'file-tree'}
|
|
307
|
+
>
|
|
308
|
+
<FileTree
|
|
309
|
+
currentEmailOpenSlug={currentEmailOpenSlug}
|
|
310
|
+
emailsDirectoryMetadata={emailsDirectoryMetadata}
|
|
311
|
+
/>
|
|
312
|
+
</Panel>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</aside>
|
|
317
|
+
</Tabs.Root>
|
|
44
318
|
);
|
|
45
319
|
};
|
package/src/components/text.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as SlotPrimitive from '@radix-ui/react-slot';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { type As,
|
|
3
|
+
import { type As, cn, unreachable } from '../utils';
|
|
4
4
|
|
|
5
5
|
export type TextSize = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
6
6
|
export type TextColor = 'gray' | 'white';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { cn } from '../utils';
|
|
4
3
|
import { inter } from '../app/inter';
|
|
4
|
+
import { cn } from '../utils';
|
|
5
5
|
|
|
6
|
-
type ContentElement = React.
|
|
6
|
+
type ContentElement = React.ComponentRef<typeof TooltipPrimitive.Content>;
|
|
7
7
|
type ContentProps = React.ComponentPropsWithoutRef<
|
|
8
8
|
typeof TooltipPrimitive.Content
|
|
9
9
|
>;
|
|
@@ -18,7 +18,7 @@ export const TooltipContent = React.forwardRef<
|
|
|
18
18
|
<TooltipPrimitive.Content
|
|
19
19
|
{...props}
|
|
20
20
|
className={cn(
|
|
21
|
-
'
|
|
21
|
+
'z-20 rounded-md border border-slate-6 bg-black px-3 py-2 text-white text-xs',
|
|
22
22
|
`${inter.variable} font-sans`,
|
|
23
23
|
)}
|
|
24
24
|
ref={forwardedRef}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
2
|
-
import * as React from 'react';
|
|
2
|
+
import type * as React from 'react';
|
|
3
3
|
import { TooltipContent } from './tooltip-content';
|
|
4
4
|
|
|
5
5
|
type RootProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
|
3
3
|
import { motion } from 'framer-motion';
|
|
4
|
-
import * as React from 'react';
|
|
4
|
+
import type * as React from 'react';
|
|
5
5
|
import { cn } from '../utils';
|
|
6
6
|
import { tabTransition } from '../utils/constants';
|
|
7
7
|
import { Heading } from './heading';
|
|
@@ -21,21 +21,21 @@ interface TopbarProps {
|
|
|
21
21
|
setActiveView?: (view: string) => void;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export const Topbar
|
|
24
|
+
export const Topbar = ({
|
|
25
25
|
currentEmailOpenSlug,
|
|
26
26
|
pathSeparator,
|
|
27
27
|
markup,
|
|
28
28
|
activeView,
|
|
29
29
|
setActiveView,
|
|
30
30
|
onToggleSidebar,
|
|
31
|
-
}) => {
|
|
31
|
+
}: TopbarProps) => {
|
|
32
32
|
return (
|
|
33
33
|
<Tooltip.Provider>
|
|
34
|
-
<header className="flex
|
|
34
|
+
<header className="relative flex h-[3.3125rem] items-center justify-between border-slate-6 border-b px-3">
|
|
35
35
|
<Tooltip>
|
|
36
36
|
<Tooltip.Trigger asChild>
|
|
37
37
|
<button
|
|
38
|
-
className="hidden
|
|
38
|
+
className="relative hidden rounded-lg px-2 py-2 text-slate-11 transition duration-200 ease-in-out hover:bg-slate-5 hover:text-slate-12 lg:flex"
|
|
39
39
|
onClick={() => {
|
|
40
40
|
if (onToggleSidebar) {
|
|
41
41
|
onToggleSidebar();
|
|
@@ -48,17 +48,15 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
48
48
|
</Tooltip.Trigger>
|
|
49
49
|
<Tooltip.Content>Show/hide sidebar</Tooltip.Content>
|
|
50
50
|
</Tooltip>
|
|
51
|
-
|
|
52
|
-
<div className="items-center overflow-hidden hidden lg:flex text-center absolute left-1/2 transform -translate-x-1/2 top-1/2 -translate-y-1/2">
|
|
51
|
+
<div className="-translate-x-1/2 -translate-y-1/2 absolute top-1/2 left-1/2 hidden transform items-center overflow-hidden text-center lg:flex">
|
|
53
52
|
<Heading as="h2" className="truncate" size="2" weight="medium">
|
|
54
53
|
{currentEmailOpenSlug.split(pathSeparator).pop()}
|
|
55
54
|
</Heading>
|
|
56
55
|
</div>
|
|
57
|
-
|
|
58
|
-
<div className="flex gap-3 justify-between lg:justify-start w-full lg:w-fit">
|
|
56
|
+
<div className="flex w-full justify-between gap-3 lg:w-fit lg:justify-start">
|
|
59
57
|
<ToggleGroup.Root
|
|
60
58
|
aria-label="View mode"
|
|
61
|
-
className="inline-block items-center
|
|
59
|
+
className="inline-block h-[36px] items-center overflow-hidden rounded-md border border-slate-6 bg-slate-2"
|
|
62
60
|
onValueChange={(value) => {
|
|
63
61
|
if (value) setActiveView?.(value);
|
|
64
62
|
}}
|
|
@@ -70,7 +68,7 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
70
68
|
<Tooltip.Trigger asChild>
|
|
71
69
|
<div
|
|
72
70
|
className={cn(
|
|
73
|
-
'px-3 py-2 transition ease-in-out
|
|
71
|
+
'relative px-3 py-2 transition duration-200 ease-in-out hover:text-slate-12',
|
|
74
72
|
{
|
|
75
73
|
'text-slate-11': activeView !== 'desktop',
|
|
76
74
|
'text-slate-12': activeView === 'desktop',
|
|
@@ -80,7 +78,7 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
80
78
|
{activeView === 'desktop' && (
|
|
81
79
|
<motion.span
|
|
82
80
|
animate={{ opacity: 1 }}
|
|
83
|
-
className="absolute
|
|
81
|
+
className="absolute top-0 right-0 bottom-0 left-0 bg-slate-4"
|
|
84
82
|
exit={{ opacity: 0 }}
|
|
85
83
|
initial={{ opacity: 0 }}
|
|
86
84
|
layoutId="topbar-tabs"
|
|
@@ -98,7 +96,7 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
98
96
|
<Tooltip.Trigger asChild>
|
|
99
97
|
<div
|
|
100
98
|
className={cn(
|
|
101
|
-
'px-3 py-2 transition ease-in-out
|
|
99
|
+
'relative px-3 py-2 transition duration-200 ease-in-out hover:text-slate-12',
|
|
102
100
|
{
|
|
103
101
|
'text-slate-11': activeView !== 'mobile',
|
|
104
102
|
'text-slate-12': activeView === 'mobile',
|
|
@@ -108,7 +106,7 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
108
106
|
{activeView === 'mobile' && (
|
|
109
107
|
<motion.span
|
|
110
108
|
animate={{ opacity: 1 }}
|
|
111
|
-
className="absolute
|
|
109
|
+
className="absolute top-0 right-0 bottom-0 left-0 bg-slate-4"
|
|
112
110
|
exit={{ opacity: 0 }}
|
|
113
111
|
initial={{ opacity: 0 }}
|
|
114
112
|
layoutId="topbar-tabs"
|
|
@@ -126,7 +124,7 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
126
124
|
<Tooltip.Trigger asChild>
|
|
127
125
|
<div
|
|
128
126
|
className={cn(
|
|
129
|
-
'px-3 py-2 transition ease-in-out
|
|
127
|
+
'relative px-3 py-2 transition duration-200 ease-in-out hover:text-slate-12',
|
|
130
128
|
{
|
|
131
129
|
'text-slate-11': activeView !== 'source',
|
|
132
130
|
'text-slate-12': activeView === 'source',
|
|
@@ -136,7 +134,7 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
136
134
|
{activeView === 'source' && (
|
|
137
135
|
<motion.span
|
|
138
136
|
animate={{ opacity: 1 }}
|
|
139
|
-
className="absolute
|
|
137
|
+
className="absolute top-0 right-0 bottom-0 left-0 bg-slate-4"
|
|
140
138
|
exit={{ opacity: 0 }}
|
|
141
139
|
initial={{ opacity: 0 }}
|
|
142
140
|
layoutId="topbar-tabs"
|
|
@@ -150,7 +148,6 @@ export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
|
150
148
|
</Tooltip>
|
|
151
149
|
</ToggleGroup.Item>
|
|
152
150
|
</ToggleGroup.Root>
|
|
153
|
-
|
|
154
151
|
{markup ? (
|
|
155
152
|
<div className="flex justify-end">
|
|
156
153
|
<Send markup={markup} />
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
+
import { getEmailPathFromSlug } from '../actions/get-email-path-from-slug';
|
|
2
3
|
import {
|
|
3
|
-
renderEmailByPath,
|
|
4
4
|
type EmailRenderingResult,
|
|
5
|
+
renderEmailByPath,
|
|
5
6
|
} from '../actions/render-email-by-path';
|
|
6
|
-
import { getEmailPathFromSlug } from '../actions/get-email-path-from-slug';
|
|
7
7
|
import { useHotreload } from './use-hot-reload';
|
|
8
8
|
|
|
9
9
|
export const useEmailRenderingResult = (
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LottieRefCurrentProps } from 'lottie-react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
const TIMEOUT = 150;
|
|
5
|
+
const THRESHOLD_ANIMATION = 0.9;
|
|
6
|
+
|
|
7
|
+
export const useIconAnimation = () => {
|
|
8
|
+
const ref = React.useRef<LottieRefCurrentProps>(null);
|
|
9
|
+
const timer = React.useRef<NodeJS.Timeout | null>(null);
|
|
10
|
+
|
|
11
|
+
const onMouseLeave = React.useCallback(() => {
|
|
12
|
+
timer.current && clearTimeout(timer.current);
|
|
13
|
+
}, []);
|
|
14
|
+
|
|
15
|
+
const onMouseEnter = React.useCallback(() => {
|
|
16
|
+
if (!ref.current) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const total = Math.round(ref.current.animationItem?.totalFrames ?? 0);
|
|
21
|
+
const current = Math.round(
|
|
22
|
+
(ref.current.animationItem?.currentFrame ?? 0) + 1,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (current === 1 || current >= total * THRESHOLD_ANIMATION) {
|
|
26
|
+
timer.current = setTimeout(() => {
|
|
27
|
+
if (!ref.current) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ref.current.stop();
|
|
32
|
+
ref.current.setDirection(1);
|
|
33
|
+
ref.current.setSpeed(1);
|
|
34
|
+
ref.current.play();
|
|
35
|
+
}, TIMEOUT);
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
ref,
|
|
41
|
+
onMouseLeave,
|
|
42
|
+
onMouseEnter,
|
|
43
|
+
};
|
|
44
|
+
};
|
package/src/utils/cn.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import vm from 'node:vm';
|
|
4
|
-
import type React from 'react';
|
|
5
|
-
import { type RawSourceMap } from 'source-map-js';
|
|
6
|
-
import { type OutputFile, build, type BuildFailure } from 'esbuild';
|
|
7
4
|
import type { render } from '@react-email/render';
|
|
8
|
-
import type
|
|
9
|
-
import type
|
|
5
|
+
import { type BuildFailure, type OutputFile, build } from 'esbuild';
|
|
6
|
+
import type React from 'react';
|
|
7
|
+
import type { RawSourceMap } from 'source-map-js';
|
|
8
|
+
import { renderingUtilitiesExporter } from './esbuild/renderring-utilities-exporter';
|
|
10
9
|
import { improveErrorWithSourceMap } from './improve-error-with-sourcemap';
|
|
11
10
|
import { staticNodeModulesForVM } from './static-node-modules-for-vm';
|
|
12
|
-
import {
|
|
11
|
+
import type { EmailTemplate as EmailComponent } from './types/email-template';
|
|
12
|
+
import type { ErrorObject } from './types/error-object';
|
|
13
13
|
|
|
14
14
|
export const getEmailComponent = async (
|
|
15
15
|
emailPath: string,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { type RawSourceMap, SourceMapConsumer } from 'source-map-js';
|
|
2
3
|
import * as stackTraceParser from 'stacktrace-parser';
|
|
3
|
-
import { SourceMapConsumer, type RawSourceMap } from 'source-map-js';
|
|
4
4
|
import type { ErrorObject } from './types/error-object';
|
|
5
5
|
|
|
6
6
|
export const improveErrorWithSourceMap = (
|
|
@@ -48,15 +48,15 @@ import zlib from 'node:zlib';
|
|
|
48
48
|
*/
|
|
49
49
|
export const staticNodeModulesForVM = {
|
|
50
50
|
assert,
|
|
51
|
-
|
|
51
|
+
async_hooks: asyncHooks,
|
|
52
52
|
buffer,
|
|
53
|
-
|
|
53
|
+
child_process: childProcess,
|
|
54
54
|
cluster,
|
|
55
55
|
console,
|
|
56
56
|
constants,
|
|
57
57
|
crypto,
|
|
58
58
|
dgram,
|
|
59
|
-
|
|
59
|
+
diagnostics_channel: diagnosticsChannel,
|
|
60
60
|
dns,
|
|
61
61
|
domain,
|
|
62
62
|
events,
|
|
@@ -70,14 +70,14 @@ export const staticNodeModulesForVM = {
|
|
|
70
70
|
net,
|
|
71
71
|
os,
|
|
72
72
|
path,
|
|
73
|
-
|
|
73
|
+
perf_hooks: perfHooks,
|
|
74
74
|
process,
|
|
75
75
|
punycode,
|
|
76
76
|
querystring,
|
|
77
77
|
readline,
|
|
78
78
|
repl,
|
|
79
79
|
stream,
|
|
80
|
-
|
|
80
|
+
string_decoder: stringDecoder,
|
|
81
81
|
timers,
|
|
82
82
|
'timers/promises': timersPromises,
|
|
83
83
|
tls,
|
|
@@ -87,6 +87,6 @@ export const staticNodeModulesForVM = {
|
|
|
87
87
|
'util/types': utilTypes,
|
|
88
88
|
v8,
|
|
89
89
|
vm,
|
|
90
|
-
|
|
90
|
+
worker_threads: workerThreads,
|
|
91
91
|
zlib,
|
|
92
92
|
};
|