react-email 4.0.0-alpha.7 → 4.0.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 +2 -48
- package/dist/cli/index.js +24 -17
- package/dist/cli/index.mjs +32 -25
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +10 -10
- package/dist/preview/.next/app-path-routes-manifest.json +1 -1
- package/dist/preview/.next/build-manifest.json +3 -3
- 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 +3 -3
- package/dist/preview/.next/required-server-files.json +3 -3
- 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/favicon.ico/route.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 +115 -11
- 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/app-paths-manifest.json +1 -1
- package/dist/preview/.next/server/chunks/203.js +1 -0
- package/dist/preview/.next/server/chunks/600.js +3 -3
- package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
- package/dist/preview/.next/server/next-font-manifest.js +1 -1
- package/dist/preview/.next/server/next-font-manifest.json +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/static/chunks/683-017aee9270cb8999.js +1 -0
- package/dist/preview/.next/static/chunks/app/layout-311310b665ad8e17.js +1 -0
- package/dist/preview/.next/static/chunks/app/{page-9ea0bd45cd6294b0.js → page-9d038f3c5feb0570.js} +1 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-db4014629693f2b3.js +1 -0
- package/dist/preview/.next/static/chunks/{main-app-256b213b179a95cc.js → main-app-785b93ae096c4901.js} +1 -1
- package/dist/preview/.next/static/css/dda71861895dd2e4.css +3 -0
- package/dist/preview/.next/trace +26 -26
- package/dist/preview/.next/types/app/layout.ts +1 -1
- package/dist/preview/.next/types/app/page.ts +1 -1
- package/dist/preview/.next/types/app/preview/[...slug]/page.ts +1 -1
- package/package.json +1 -1
- package/src/actions/email-validation/check-compatibility.ts +16 -4
- package/src/app/layout.tsx +1 -1
- package/src/app/page.tsx +4 -4
- package/src/app/preview/[...slug]/preview.tsx +35 -26
- package/src/components/code-container.tsx +2 -2
- package/src/components/code.tsx +16 -7
- package/src/components/resizable-wrapper.tsx +3 -3
- package/src/components/shell.tsx +13 -18
- package/src/components/sidebar/file-tree-directory-children.tsx +4 -2
- package/src/components/sidebar/file-tree-directory.tsx +18 -18
- package/src/components/sidebar/file-tree.tsx +2 -2
- package/src/components/sidebar/sidebar.tsx +16 -18
- package/src/components/toolbar/linter.tsx +35 -33
- package/src/components/toolbar/results.tsx +1 -1
- package/src/components/toolbar/spam-assassin.tsx +16 -13
- package/src/components/toolbar/use-cached-state.ts +2 -2
- package/src/components/toolbar.tsx +103 -30
- package/src/components/topbar.tsx +17 -5
- package/src/utils/__snapshots__/get-email-component.spec.ts.snap +1 -1
- package/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap +74 -0
- package/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap +24 -0
- package/src/utils/caniemail/ast/get-object-variables.spec.ts +19 -0
- package/src/utils/caniemail/ast/get-used-style-properties.spec.ts +23 -0
- package/src/utils/caniemail/get-css-property-with-value.ts +2 -2
- package/tailwind.config.ts +8 -0
- package/dist/preview/.next/server/chunks/943.js +0 -1
- package/dist/preview/.next/static/chunks/683-1fb40795502f6e63.js +0 -1
- package/dist/preview/.next/static/chunks/app/layout-ffdee5cc1be30e7b.js +0 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-9e22979a25c836c0.js +0 -1
- package/dist/preview/.next/static/css/eaae8ce545b295f9.css +0 -3
- /package/dist/preview/.next/static/{Pms2orsQgT5xpttCfZfH5 → t4yeIF5ZYqVHaYIHgPxHn}/_buildManifest.js +0 -0
- /package/dist/preview/.next/static/{Pms2orsQgT5xpttCfZfH5 → t4yeIF5ZYqVHaYIHgPxHn}/_ssgManifest.js +0 -0
|
@@ -101,19 +101,22 @@ export const SpamAssassin = ({ result }: SpamAssassinProps) => {
|
|
|
101
101
|
<Results.Column>
|
|
102
102
|
{result.points === 0
|
|
103
103
|
? 'Congratulations! Your email is clean of abuse indicators.'
|
|
104
|
-
: '
|
|
104
|
+
: 'Higher scores are better'}
|
|
105
105
|
</Results.Column>
|
|
106
|
-
<Results.Column
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
<Results.Column className="text-right tracking-tighter font-bold">
|
|
107
|
+
<span
|
|
108
|
+
className={cn(
|
|
109
|
+
'text-3xl',
|
|
110
|
+
result.points === 0 ? 'text-green-400' : null,
|
|
111
|
+
result.points > 0 && result.points <= 1.5 ? null : null,
|
|
112
|
+
result.points > 1.5 ? 'text-yellow-200' : null,
|
|
113
|
+
result.points > 3 ? 'text-orange-400' : null,
|
|
114
|
+
result.points >= 5 ? 'text-red-400' : null,
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
{(10 - result.points).toFixed(1)}
|
|
118
|
+
</span>{' '}
|
|
119
|
+
<span className="text-lg">/ 10</span>
|
|
117
120
|
</Results.Column>
|
|
118
121
|
</Results.Row>
|
|
119
122
|
{toSorted(result.checks, (a, b) => b.points - a.points).map(
|
|
@@ -140,7 +143,7 @@ export const SpamAssassin = ({ result }: SpamAssassinProps) => {
|
|
|
140
143
|
check.points > 3 ? 'text-red-400' : null,
|
|
141
144
|
)}
|
|
142
145
|
>
|
|
143
|
-
{check.points.toFixed(1)}
|
|
146
|
+
-{check.points.toFixed(1)}
|
|
144
147
|
</Results.Column>
|
|
145
148
|
</Results.Row>
|
|
146
149
|
),
|
|
@@ -4,7 +4,7 @@ export const useCachedState = <T>(key: string) => {
|
|
|
4
4
|
let value: T | undefined = undefined;
|
|
5
5
|
if ('localStorage' in global) {
|
|
6
6
|
const storedValue = global.localStorage.getItem(key);
|
|
7
|
-
if (storedValue !== null) {
|
|
7
|
+
if (storedValue !== null && storedValue !== 'undefined') {
|
|
8
8
|
try {
|
|
9
9
|
value = JSON.parse(storedValue) as T;
|
|
10
10
|
} catch (exception) {
|
|
@@ -12,7 +12,7 @@ export const useCachedState = <T>(key: string) => {
|
|
|
12
12
|
'Failed to load stored value for',
|
|
13
13
|
key,
|
|
14
14
|
'with value',
|
|
15
|
-
|
|
15
|
+
storedValue,
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -8,6 +8,7 @@ import { isBuilding } from '../app/env';
|
|
|
8
8
|
import { PreviewContext } from '../contexts/preview';
|
|
9
9
|
import { cn } from '../utils';
|
|
10
10
|
import { IconArrowDown } from './icons/icon-arrow-down';
|
|
11
|
+
import { IconCheck } from './icons/icon-check';
|
|
11
12
|
import { IconInfo } from './icons/icon-info';
|
|
12
13
|
import { IconReload } from './icons/icon-reload';
|
|
13
14
|
import { Compatibility, useCompatibility } from './toolbar/compatibility';
|
|
@@ -22,6 +23,19 @@ import { useCachedState } from './toolbar/use-cached-state';
|
|
|
22
23
|
|
|
23
24
|
export type ToolbarTabValue = 'linter' | 'compatibility' | 'spam-assassin';
|
|
24
25
|
|
|
26
|
+
export const useToolbarState = () => {
|
|
27
|
+
const searchParams = useSearchParams();
|
|
28
|
+
const activeTab = (searchParams.get('toolbar-panel') ?? undefined) as
|
|
29
|
+
| ToolbarTabValue
|
|
30
|
+
| undefined;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
activeTab,
|
|
34
|
+
|
|
35
|
+
toggled: activeTab !== undefined,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
25
39
|
const ToolbarInner = ({
|
|
26
40
|
serverLintingRows,
|
|
27
41
|
serverSpamCheckingResult,
|
|
@@ -43,11 +57,7 @@ const ToolbarInner = ({
|
|
|
43
57
|
const searchParams = useSearchParams();
|
|
44
58
|
const router = useRouter();
|
|
45
59
|
|
|
46
|
-
const activeTab = (
|
|
47
|
-
| ToolbarTabValue
|
|
48
|
-
| undefined;
|
|
49
|
-
|
|
50
|
-
const toggled = activeTab !== undefined;
|
|
60
|
+
const { activeTab, toggled } = useToolbarState();
|
|
51
61
|
|
|
52
62
|
const setActivePanelValue = (newValue: ToolbarTabValue | undefined) => {
|
|
53
63
|
const params = new URLSearchParams(searchParams);
|
|
@@ -114,7 +124,7 @@ const ToolbarInner = ({
|
|
|
114
124
|
className={cn(
|
|
115
125
|
'absolute bottom-0 left-0 right-0',
|
|
116
126
|
'bg-black group/toolbar text-xs text-slate-11 h-52 transition-transform',
|
|
117
|
-
'data-[toggled=false]:translate-y-[
|
|
127
|
+
'data-[toggled=false]:translate-y-[10.625rem]',
|
|
118
128
|
)}
|
|
119
129
|
>
|
|
120
130
|
<Tabs.Root
|
|
@@ -155,13 +165,6 @@ const ToolbarInner = ({
|
|
|
155
165
|
'The Compatibility tab shows how well the HTML/CSS is supported across mail clients like Outlook, Gmail, etc. Powered by Can I Email.') ||
|
|
156
166
|
'Info'
|
|
157
167
|
}
|
|
158
|
-
onClick={() => {
|
|
159
|
-
if (activeTab === undefined) {
|
|
160
|
-
setActivePanelValue('linter');
|
|
161
|
-
} else {
|
|
162
|
-
setActivePanelValue(undefined);
|
|
163
|
-
}
|
|
164
|
-
}}
|
|
165
168
|
>
|
|
166
169
|
<IconInfo size={24} />
|
|
167
170
|
</ToolbarButton>
|
|
@@ -209,32 +212,50 @@ const ToolbarInner = ({
|
|
|
209
212
|
</div>
|
|
210
213
|
</Tabs.List>
|
|
211
214
|
|
|
212
|
-
<div className="flex-grow transition-opacity opacity-100 group-data-[toggled=false]/toolbar:opacity-0 overflow-y-auto
|
|
215
|
+
<div className="flex-grow transition-opacity opacity-100 group-data-[toggled=false]/toolbar:opacity-0 overflow-y-auto pr-3 pl-4 pt-3">
|
|
213
216
|
<Tabs.Content value="linter">
|
|
214
217
|
{lintLoading ? (
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
<LoadingState message="Analyzing your code for linting issues..." />
|
|
219
|
+
) : lintingRows?.length === 0 ? (
|
|
220
|
+
<SuccessWrapper>
|
|
221
|
+
<SuccessIcon />
|
|
222
|
+
<SuccessTitle>All good</SuccessTitle>
|
|
223
|
+
<SuccessDescription>
|
|
224
|
+
No linting issues found.
|
|
225
|
+
</SuccessDescription>
|
|
226
|
+
</SuccessWrapper>
|
|
218
227
|
) : (
|
|
219
|
-
<Linter rows={lintingRows} />
|
|
228
|
+
<Linter rows={lintingRows ?? []} />
|
|
220
229
|
)}
|
|
221
230
|
</Tabs.Content>
|
|
222
|
-
<Tabs.Content value="
|
|
223
|
-
{
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
231
|
+
<Tabs.Content value="compatibility">
|
|
232
|
+
{compatibilityLoading ? (
|
|
233
|
+
<LoadingState message="Checking email compatibility..." />
|
|
234
|
+
) : compatibilityCheckingResults?.length === 0 ? (
|
|
235
|
+
<SuccessWrapper>
|
|
236
|
+
<SuccessIcon />
|
|
237
|
+
<SuccessTitle>Great compatibility</SuccessTitle>
|
|
238
|
+
<SuccessDescription>
|
|
239
|
+
Template should render properly everywhere.
|
|
240
|
+
</SuccessDescription>
|
|
241
|
+
</SuccessWrapper>
|
|
227
242
|
) : (
|
|
228
|
-
<
|
|
243
|
+
<Compatibility results={compatibilityCheckingResults ?? []} />
|
|
229
244
|
)}
|
|
230
245
|
</Tabs.Content>
|
|
231
|
-
<Tabs.Content value="
|
|
232
|
-
{
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
246
|
+
<Tabs.Content value="spam-assassin">
|
|
247
|
+
{spamLoading ? (
|
|
248
|
+
<LoadingState message="Evaluating your email for spam indicators..." />
|
|
249
|
+
) : spamCheckingResult?.isSpam === false ? (
|
|
250
|
+
<SuccessWrapper>
|
|
251
|
+
<SuccessIcon />
|
|
252
|
+
<SuccessTitle>10/10</SuccessTitle>
|
|
253
|
+
<SuccessDescription>
|
|
254
|
+
Your email is clean of abuse indicators.
|
|
255
|
+
</SuccessDescription>
|
|
256
|
+
</SuccessWrapper>
|
|
236
257
|
) : (
|
|
237
|
-
<
|
|
258
|
+
<SpamAssassin result={spamCheckingResult} />
|
|
238
259
|
)}
|
|
239
260
|
</Tabs.Content>
|
|
240
261
|
</div>
|
|
@@ -244,6 +265,58 @@ const ToolbarInner = ({
|
|
|
244
265
|
);
|
|
245
266
|
};
|
|
246
267
|
|
|
268
|
+
const LoadingState = ({ message }: { message: string }) => {
|
|
269
|
+
return (
|
|
270
|
+
<div className="flex flex-col items-center justify-center pt-8">
|
|
271
|
+
<div className="relative mb-8 flex items-center justify-center">
|
|
272
|
+
<div className="h-12 w-12 rounded-full bg-gradient-to-br from-cyan-400/80 to-cyan-600/80 opacity-10 blur-xl absolute m-auto left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 animate-pulse" />
|
|
273
|
+
<div className="h-12 w-12 rounded-full border border-slate-4 absolute m-auto left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
|
274
|
+
<div className="h-10 w-10 rounded-full flex items-center justify-center absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
|
275
|
+
<div className="h-5 w-5 rounded-full border-2 border-white/50 border-t-transparent animate-spin-fast" />
|
|
276
|
+
</div>
|
|
277
|
+
</div>
|
|
278
|
+
<h3 className="text-slate-12 font-medium text-base mb-1">Processing</h3>
|
|
279
|
+
<p className="text-slate-11 text-sm text-center max-w-[320px]">
|
|
280
|
+
{message}
|
|
281
|
+
</p>
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const SuccessWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
287
|
+
return (
|
|
288
|
+
<div className="flex flex-col items-center justify-center pt-8">
|
|
289
|
+
{children}
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const SuccessIcon = () => {
|
|
295
|
+
return (
|
|
296
|
+
<div className="relative mb-8 flex items-center justify-center">
|
|
297
|
+
<div className="h-16 w-16 rounded-full bg-gradient-to-br from-green-300/20 opacity-80 to-emerald-500/30 blur-md absolute m-auto left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
|
298
|
+
<div className="h-12 w-12 rounded-full bg-gradient-to-br from-green-400/80 opacity-10 to-emerald-600/80 absolute m-auto left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 shadow-lg" />
|
|
299
|
+
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-green-400 to-emerald-600 flex items-center justify-center absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 shadow-[inset_0_1px_1px_rgba(255,255,255,0.4)]">
|
|
300
|
+
<IconCheck size={24} className="text-white drop-shadow-sm" />
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const SuccessTitle = ({ children }) => {
|
|
307
|
+
return (
|
|
308
|
+
<h3 className="text-slate-12 font-medium text-base mb-1">{children}</h3>
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const SuccessDescription = ({ children }) => {
|
|
313
|
+
return (
|
|
314
|
+
<p className="text-slate-11 text-sm text-center max-w-[320px]">
|
|
315
|
+
{children}
|
|
316
|
+
</p>
|
|
317
|
+
);
|
|
318
|
+
};
|
|
319
|
+
|
|
247
320
|
interface ToolbarProps {
|
|
248
321
|
serverSpamCheckingResult: SpamCheckingResult | undefined;
|
|
249
322
|
serverLintingRows: LintingRow[] | undefined;
|
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { use } from 'react';
|
|
4
|
+
import { cn } from '../utils';
|
|
4
5
|
import { Heading } from './heading';
|
|
5
6
|
import { IconHideSidebar } from './icons/icon-hide-sidebar';
|
|
6
7
|
import { ShellContext } from './shell';
|
|
7
8
|
import { Tooltip } from './tooltip';
|
|
8
9
|
|
|
9
|
-
interface TopbarProps {
|
|
10
|
+
interface TopbarProps extends React.ComponentProps<'header'> {
|
|
10
11
|
emailTitle: string;
|
|
11
12
|
children: React.ReactNode;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const Topbar = ({
|
|
15
|
+
export const Topbar = ({
|
|
16
|
+
emailTitle,
|
|
17
|
+
children,
|
|
18
|
+
className,
|
|
19
|
+
...props
|
|
20
|
+
}: TopbarProps) => {
|
|
15
21
|
const { toggleSidebar } = use(ShellContext)!;
|
|
16
22
|
|
|
17
23
|
return (
|
|
18
24
|
<Tooltip.Provider>
|
|
19
|
-
<header
|
|
20
|
-
|
|
25
|
+
<header
|
|
26
|
+
{...props}
|
|
27
|
+
className={cn(
|
|
28
|
+
'flex h-14 items-center justify-between gap-3 border-slate-6 border-b px-3 py-2',
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
<div className="flex w-fit items-center gap-3">
|
|
21
33
|
<Tooltip>
|
|
22
34
|
<Tooltip.Trigger asChild>
|
|
23
35
|
<button
|
|
24
|
-
className="
|
|
36
|
+
className="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"
|
|
25
37
|
onClick={() => {
|
|
26
38
|
toggleSidebar();
|
|
27
39
|
}}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`getEmailComponent() > with a demo email template 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><link rel="preload" as="image" href="/static/vercel-logo.png"/><link rel="preload" as="image" href="/static/vercel-user.png"/><link rel="preload" as="image" href="/static/vercel-arrow.png"/><link rel="preload" as="image" href="/static/vercel-team.png"/><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--></head><body style="background-color:rgb(255,255,255);margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";padding-left:0.5rem;padding-right:0.5rem"><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> </div></div><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);border-radius:0.25rem;margin-top:40px;margin-bottom:40px;margin-left:auto;margin-right:auto;padding:20px;max-width:465px"><tbody><tr style="width:100%"><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="margin-top:32px"><tbody><tr><td><img alt="Vercel" height="37" src="/static/vercel-logo.png" style="margin-top:0px;margin-bottom:0px;margin-left:auto;margin-right:auto;display:block;outline:none;border:none;text-decoration:none" width="40"/></td></tr></tbody></table><h1 style="color:rgb(0,0,0);font-size:24px;font-weight:400;text-align:center;padding:0px;margin-top:30px;margin-bottom:30px;margin-left:0px;margin-right:0px">Join <strong>Enigma</strong> on <strong>Vercel</strong></h1><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px">Hello <!-- -->alanturing<!-- -->,</p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px"><strong>Alan</strong> (<a href="mailto:alan.turing@example.com" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">alan.turing@example.com</a>) has invited you to the <strong>Enigma</strong> team on<!-- --> <strong>Vercel</strong>.</p><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody><tr><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody style="width:100%"><tr style="width:100%"><td align="right" data-id="__react-email-column"><img height="64" src="/static/vercel-user.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td><td align="center" data-id="__react-email-column"><img alt="
|
|
3
|
+
exports[`getEmailComponent() > with a demo email template 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><link rel="preload" as="image" href="/static/vercel-logo.png"/><link rel="preload" as="image" href="/static/vercel-user.png"/><link rel="preload" as="image" href="/static/vercel-arrow.png"/><link rel="preload" as="image" href="/static/vercel-team.png"/><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--></head><body style="background-color:rgb(255,255,255);margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";padding-left:0.5rem;padding-right:0.5rem"><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> </div></div><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);border-radius:0.25rem;margin-top:40px;margin-bottom:40px;margin-left:auto;margin-right:auto;padding:20px;max-width:465px"><tbody><tr style="width:100%"><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="margin-top:32px"><tbody><tr><td><img alt="Vercel Logo" height="37" src="/static/vercel-logo.png" style="margin-top:0px;margin-bottom:0px;margin-left:auto;margin-right:auto;display:block;outline:none;border:none;text-decoration:none" width="40"/></td></tr></tbody></table><h1 style="color:rgb(0,0,0);font-size:24px;font-weight:400;text-align:center;padding:0px;margin-top:30px;margin-bottom:30px;margin-left:0px;margin-right:0px">Join <strong>Enigma</strong> on <strong>Vercel</strong></h1><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px">Hello <!-- -->alanturing<!-- -->,</p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px"><strong>Alan</strong> (<a href="mailto:alan.turing@example.com" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">alan.turing@example.com</a>) has invited you to the <strong>Enigma</strong> team on<!-- --> <strong>Vercel</strong>.</p><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody><tr><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody style="width:100%"><tr style="width:100%"><td align="right" data-id="__react-email-column"><img alt="alanturing's profile picture" height="64" src="/static/vercel-user.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td><td align="center" data-id="__react-email-column"><img alt="Arrow indicating invitation" height="9" src="/static/vercel-arrow.png" style="display:block;outline:none;border:none;text-decoration:none" width="12"/></td><td align="left" data-id="__react-email-column"><img alt="Enigma team logo" height="64" src="/static/vercel-team.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td></tr></tbody></table></td></tr></tbody></table><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="text-align:center;margin-top:32px;margin-bottom:32px"><tbody><tr><td><a href="https://vercel.com" style="background-color:rgb(0,0,0);border-radius:0.25rem;color:rgb(255,255,255);font-size:12px;font-weight:600;text-decoration-line:none;text-align:center;padding-left:1.25rem;padding-right:1.25rem;padding-top:0.75rem;padding-bottom:0.75rem;line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:12px 20px 12px 20px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>  </i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">Join the team</span><span><!--[if mso]><i style="mso-font-width:500%" hidden>  ​</i><![endif]--></span></a></td></tr></tbody></table><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px">or copy and paste this URL into your browser:<!-- --> <a href="https://vercel.com" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://vercel.com</a></p><hr style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);margin-top:26px;margin-bottom:26px;margin-left:0px;margin-right:0px;width:100%;border:none;border-top:1px solid #eaeaea"/><p style="color:rgb(102,102,102);font-size:12px;line-height:24px;margin-bottom:16px;margin-top:16px">This invitation was intended for<!-- --> <span style="color:rgb(0,0,0)">alanturing</span>. This invite was sent from <span style="color:rgb(0,0,0)">204.13.186.218</span> <!-- -->located in<!-- --> <span style="color:rgb(0,0,0)">São Paulo, Brazil</span>. If you were not expecting this invitation, you can ignore this email. If you are concerned about your account's safety, please reply to this email to get in touch with us.</p></td></tr></tbody></table><!--/$--></body></html>"`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`getObjectVariables() 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"buttonStyle": [
|
|
6
|
+
Node {
|
|
7
|
+
"computed": false,
|
|
8
|
+
"end": 91,
|
|
9
|
+
"key": Node {
|
|
10
|
+
"end": 84,
|
|
11
|
+
"loc": SourceLocation {
|
|
12
|
+
"end": Position {
|
|
13
|
+
"column": 14,
|
|
14
|
+
"index": 84,
|
|
15
|
+
"line": 5,
|
|
16
|
+
},
|
|
17
|
+
"filename": undefined,
|
|
18
|
+
"identifierName": "borderRadius",
|
|
19
|
+
"start": Position {
|
|
20
|
+
"column": 2,
|
|
21
|
+
"index": 72,
|
|
22
|
+
"line": 5,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
"name": "borderRadius",
|
|
26
|
+
"start": 72,
|
|
27
|
+
"type": "Identifier",
|
|
28
|
+
},
|
|
29
|
+
"loc": SourceLocation {
|
|
30
|
+
"end": Position {
|
|
31
|
+
"column": 21,
|
|
32
|
+
"index": 91,
|
|
33
|
+
"line": 5,
|
|
34
|
+
},
|
|
35
|
+
"filename": undefined,
|
|
36
|
+
"identifierName": undefined,
|
|
37
|
+
"start": Position {
|
|
38
|
+
"column": 2,
|
|
39
|
+
"index": 72,
|
|
40
|
+
"line": 5,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"method": false,
|
|
44
|
+
"shorthand": false,
|
|
45
|
+
"start": 72,
|
|
46
|
+
"type": "ObjectProperty",
|
|
47
|
+
"value": Node {
|
|
48
|
+
"end": 91,
|
|
49
|
+
"extra": {
|
|
50
|
+
"raw": "'5px'",
|
|
51
|
+
"rawValue": "5px",
|
|
52
|
+
},
|
|
53
|
+
"loc": SourceLocation {
|
|
54
|
+
"end": Position {
|
|
55
|
+
"column": 21,
|
|
56
|
+
"index": 91,
|
|
57
|
+
"line": 5,
|
|
58
|
+
},
|
|
59
|
+
"filename": undefined,
|
|
60
|
+
"identifierName": undefined,
|
|
61
|
+
"start": Position {
|
|
62
|
+
"column": 16,
|
|
63
|
+
"index": 86,
|
|
64
|
+
"line": 5,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
"start": 86,
|
|
68
|
+
"type": "StringLiteral",
|
|
69
|
+
"value": "5px",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`getUsedStyleProperties() 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"location": SourceLocation {
|
|
7
|
+
"end": Position {
|
|
8
|
+
"column": 21,
|
|
9
|
+
"index": 91,
|
|
10
|
+
"line": 5,
|
|
11
|
+
},
|
|
12
|
+
"filename": undefined,
|
|
13
|
+
"identifierName": undefined,
|
|
14
|
+
"start": Position {
|
|
15
|
+
"column": 2,
|
|
16
|
+
"index": 72,
|
|
17
|
+
"line": 5,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
"name": "borderRadius",
|
|
21
|
+
"value": "5px",
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import { getObjectVariables } from './get-object-variables';
|
|
3
|
+
|
|
4
|
+
test('getObjectVariables()', () => {
|
|
5
|
+
const reactCode = `
|
|
6
|
+
<Button style={buttonStyle}>Click me</Button>
|
|
7
|
+
|
|
8
|
+
const buttonStyle = {
|
|
9
|
+
borderRadius: '5px',
|
|
10
|
+
};
|
|
11
|
+
`;
|
|
12
|
+
const ast = parse(reactCode, {
|
|
13
|
+
strictMode: false,
|
|
14
|
+
errorRecovery: true,
|
|
15
|
+
sourceType: 'unambiguous',
|
|
16
|
+
plugins: ['jsx', 'typescript', 'decorators'],
|
|
17
|
+
});
|
|
18
|
+
expect(getObjectVariables(ast)).toMatchSnapshot();
|
|
19
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import { getObjectVariables } from './get-object-variables';
|
|
3
|
+
import { getUsedStyleProperties } from './get-used-style-properties';
|
|
4
|
+
|
|
5
|
+
test('getUsedStyleProperties()', async () => {
|
|
6
|
+
const reactCode = `
|
|
7
|
+
<Button style={buttonStyle}>Click me</Button>
|
|
8
|
+
|
|
9
|
+
const buttonStyle = {
|
|
10
|
+
borderRadius: '5px',
|
|
11
|
+
};
|
|
12
|
+
`;
|
|
13
|
+
const ast = parse(reactCode, {
|
|
14
|
+
strictMode: false,
|
|
15
|
+
errorRecovery: true,
|
|
16
|
+
sourceType: 'unambiguous',
|
|
17
|
+
plugins: ['jsx', 'typescript', 'decorators'],
|
|
18
|
+
});
|
|
19
|
+
const objectVariables = getObjectVariables(ast);
|
|
20
|
+
expect(
|
|
21
|
+
await getUsedStyleProperties(ast, reactCode, '', objectVariables),
|
|
22
|
+
).toMatchSnapshot();
|
|
23
|
+
});
|
|
@@ -6,8 +6,8 @@ export const getCssPropertyWithValue = (title: string) => {
|
|
|
6
6
|
if (match) {
|
|
7
7
|
const [_full, propertyName, propertyValue] = match;
|
|
8
8
|
return {
|
|
9
|
-
name: propertyName
|
|
10
|
-
value: propertyValue
|
|
9
|
+
name: propertyName!,
|
|
10
|
+
value: propertyValue!,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
return undefined;
|
package/tailwind.config.ts
CHANGED
|
@@ -82,6 +82,14 @@ const config: Config = {
|
|
|
82
82
|
'0%': { strokeDashoffset: '1000' },
|
|
83
83
|
'100%': { strokeDashoffset: '0' },
|
|
84
84
|
},
|
|
85
|
+
spin: {
|
|
86
|
+
'0%': { transform: 'rotate(0deg)' },
|
|
87
|
+
'100%': { transform: 'rotate(360deg)' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
animation: {
|
|
91
|
+
'spin-slow': 'spin 3s linear infinite',
|
|
92
|
+
'spin-fast': 'spin 1.5s linear infinite',
|
|
85
93
|
},
|
|
86
94
|
},
|
|
87
95
|
},
|