react-email 4.0.0-alpha.3 → 4.0.0-alpha.5

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.
Files changed (174) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/index.js +7 -5
  3. package/dist/cli/index.mjs +11 -6
  4. package/dist/preview/.next/BUILD_ID +1 -1
  5. package/dist/preview/.next/app-build-manifest.json +31 -34
  6. package/dist/preview/.next/build-manifest.json +14 -14
  7. package/dist/preview/.next/cache/.rscinfo +1 -1
  8. package/dist/preview/.next/cache/images/TcyzHbFXGFjrOu3wEMvDoSmqCh3qP3iiNqJf0QbED9Y/60.1741728556140.cQ5qicbpvoXZ7leVmWqG2ElLwXB1ynYeSv8MBSA-QeM.Vy8iMWM3MGUtMTk1ODcxYmIyNzMi.webp +0 -0
  9. package/dist/preview/.next/cache/webpack/client-development/0.pack.gz +0 -0
  10. package/dist/preview/.next/cache/webpack/client-development/1.pack.gz +0 -0
  11. package/dist/preview/.next/cache/webpack/client-development/10.pack.gz +0 -0
  12. package/dist/preview/.next/cache/webpack/client-development/11.pack.gz +0 -0
  13. package/dist/preview/.next/cache/webpack/client-development/12.pack.gz +0 -0
  14. package/dist/preview/.next/cache/webpack/client-development/13.pack.gz +0 -0
  15. package/dist/preview/.next/cache/webpack/client-development/2.pack.gz +0 -0
  16. package/dist/preview/.next/cache/webpack/client-development/3.pack.gz +0 -0
  17. package/dist/preview/.next/cache/webpack/client-development/4.pack.gz +0 -0
  18. package/dist/preview/.next/cache/webpack/client-development/5.pack.gz +0 -0
  19. package/dist/preview/.next/cache/webpack/client-development/6.pack.gz +0 -0
  20. package/dist/preview/.next/cache/webpack/client-development/7.pack.gz +0 -0
  21. package/dist/preview/.next/cache/webpack/client-development/8.pack.gz +0 -0
  22. package/dist/preview/.next/cache/webpack/client-development/9.pack.gz +0 -0
  23. package/dist/preview/.next/cache/webpack/client-development/index.pack.gz +0 -0
  24. package/dist/preview/.next/cache/webpack/client-development/index.pack.gz.old +0 -0
  25. package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
  26. package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
  27. package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
  28. package/dist/preview/.next/cache/webpack/server-development/0.pack.gz +0 -0
  29. package/dist/preview/.next/cache/webpack/server-development/1.pack.gz +0 -0
  30. package/dist/preview/.next/cache/webpack/server-development/2.pack.gz +0 -0
  31. package/dist/preview/.next/cache/webpack/server-development/3.pack.gz +0 -0
  32. package/dist/preview/.next/cache/webpack/server-development/4.pack.gz +0 -0
  33. package/dist/preview/.next/cache/webpack/server-development/5.pack.gz +0 -0
  34. package/dist/preview/.next/cache/webpack/server-development/6.pack.gz +0 -0
  35. package/dist/preview/.next/cache/webpack/server-development/7.pack.gz +0 -0
  36. package/dist/preview/.next/cache/webpack/server-development/8.pack.gz +0 -0
  37. package/dist/preview/.next/cache/webpack/server-development/9.pack.gz +0 -0
  38. package/dist/preview/.next/cache/webpack/server-development/index.pack.gz +0 -0
  39. package/dist/preview/.next/cache/webpack/server-development/index.pack.gz.old +0 -0
  40. package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
  41. package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
  42. package/dist/preview/.next/diagnostics/framework.json +1 -1
  43. package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
  44. package/dist/preview/.next/next-server.js.nft.json +1 -1
  45. package/dist/preview/.next/prerender-manifest.json +1 -1
  46. package/dist/preview/.next/required-server-files.json +1 -1
  47. package/dist/preview/.next/server/app/_not-found/page.js +1 -1
  48. package/dist/preview/.next/server/app/_not-found/page.js.nft.json +1 -1
  49. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  50. package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
  51. package/dist/preview/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  52. package/dist/preview/.next/server/app/page.js +1 -1
  53. package/dist/preview/.next/server/app/page.js.nft.json +1 -1
  54. package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
  55. package/dist/preview/.next/server/app/preview/[...slug]/page.js +9 -8
  56. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  57. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  58. package/dist/preview/.next/server/chunks/143.js +6 -0
  59. package/dist/preview/.next/server/chunks/409.js +5 -0
  60. package/dist/preview/.next/server/chunks/46.js +1 -0
  61. package/dist/preview/.next/server/chunks/478.js +14 -0
  62. package/dist/preview/.next/server/chunks/707.js +13 -0
  63. package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
  64. package/dist/preview/.next/server/next-font-manifest.js +1 -1
  65. package/dist/preview/.next/server/next-font-manifest.json +1 -1
  66. package/dist/preview/.next/server/pages/500.html +1 -1
  67. package/dist/preview/.next/server/pages/_app.js +1 -1
  68. package/dist/preview/.next/server/pages/_app.js.nft.json +1 -1
  69. package/dist/preview/.next/server/pages/_document.js +1 -1
  70. package/dist/preview/.next/server/pages/_document.js.nft.json +1 -1
  71. package/dist/preview/.next/server/pages/_error.js +1 -1
  72. package/dist/preview/.next/server/pages/_error.js.nft.json +1 -1
  73. package/dist/preview/.next/server/server-reference-manifest.js +1 -1
  74. package/dist/preview/.next/server/server-reference-manifest.json +1 -1
  75. package/dist/preview/.next/static/{iP6qiNn8FML_AvKcxGPhM → B4EYZiVzdylEG9lAIl-aO}/_buildManifest.js +1 -1
  76. package/dist/preview/.next/static/chunks/575-bc52750855c25df4.js +2 -0
  77. package/dist/preview/.next/static/chunks/684-0f1ef7361c499798.js +1 -0
  78. package/dist/preview/.next/static/chunks/684c6b30-0c65da32762fc4ee.js +1 -0
  79. package/dist/preview/.next/static/chunks/81-e7539b08d9d3fb4d.js +1 -0
  80. package/dist/preview/.next/static/chunks/883-70c8267c50bc4133.js +1 -0
  81. package/dist/preview/.next/static/chunks/921-d1dc8c63f49e85d6.js +1 -0
  82. package/dist/preview/.next/static/chunks/{afa401a5-9ebf2515b1397993.js → afa401a5-a600c227dacf3ab4.js} +1 -1
  83. package/dist/preview/.next/static/chunks/app/_not-found/{page-96d3eac723be3ee2.js → page-03ce767859c36d4e.js} +1 -1
  84. package/dist/preview/.next/static/chunks/app/layout-7cf14e28880544f1.js +1 -0
  85. package/dist/preview/.next/static/chunks/app/page-065cb49b0a078541.js +1 -0
  86. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-656510fd180c803c.js +1 -0
  87. package/dist/preview/.next/static/chunks/{framework-e7cae9cecd5c9ba2.js → framework-2a724981073c3a29.js} +1 -1
  88. package/dist/preview/.next/static/chunks/main-552b9719bbc3a274.js +1 -0
  89. package/dist/preview/.next/static/chunks/main-app-914a73336fd45af5.js +1 -0
  90. package/dist/preview/.next/static/chunks/pages/_app-77ca34bce25ac75c.js +1 -0
  91. package/dist/preview/.next/static/chunks/pages/_error-73f611c46abbb495.js +1 -0
  92. package/dist/preview/.next/static/chunks/{webpack-9255716c9496e606.js → webpack-2eb145a20ee6cb77.js} +1 -1
  93. package/dist/preview/.next/static/css/2df96d9ee014e8de.css +3 -0
  94. package/dist/preview/.next/static/media/05613964ce6c782e-s.p.otf +0 -0
  95. package/dist/preview/.next/static/media/11c6126b9369e85e-s.p.otf +0 -0
  96. package/dist/preview/.next/static/media/26cb97734d8cb717-s.p.otf +0 -0
  97. package/dist/preview/.next/static/media/bb6462617151f6b7-s.p.otf +0 -0
  98. package/dist/preview/.next/static/media/cf6daef822ab0142-s.p.otf +0 -0
  99. package/dist/preview/.next/static/media/e4051546b3043204-s.p.otf +0 -0
  100. package/dist/preview/.next/trace +22 -22
  101. package/dist/preview/.next/types/app/layout.ts +1 -1
  102. package/dist/preview/.next/types/app/preview/[...slug]/page.ts +1 -1
  103. package/package.json +7 -5
  104. package/src/actions/email-validation/check-images.spec.tsx +23 -14
  105. package/src/actions/email-validation/check-images.ts +89 -87
  106. package/src/actions/email-validation/check-links.spec.tsx +27 -17
  107. package/src/actions/email-validation/check-links.ts +60 -57
  108. package/src/app/fonts/SFMono/SFMonoBold.otf +0 -0
  109. package/src/app/fonts/SFMono/SFMonoBoldItalic.otf +0 -0
  110. package/src/app/fonts/SFMono/SFMonoHeavy.otf +0 -0
  111. package/src/app/fonts/SFMono/SFMonoHeavyItalic.otf +0 -0
  112. package/src/app/fonts/SFMono/SFMonoLight.otf +0 -0
  113. package/src/app/fonts/SFMono/SFMonoLightItalic.otf +0 -0
  114. package/src/app/fonts/SFMono/SFMonoMedium.otf +0 -0
  115. package/src/app/fonts/SFMono/SFMonoMediumItalic.otf +0 -0
  116. package/src/app/fonts/SFMono/SFMonoRegular.otf +0 -0
  117. package/src/app/fonts/SFMono/SFMonoRegularItalic.otf +0 -0
  118. package/src/app/fonts/SFMono/SFMonoSemibold.otf +0 -0
  119. package/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf +0 -0
  120. package/src/app/fonts.ts +39 -0
  121. package/src/app/layout.tsx +5 -2
  122. package/src/app/page.tsx +3 -3
  123. package/src/app/preview/[...slug]/preview.tsx +50 -31
  124. package/src/components/icons/icon-base.tsx +4 -2
  125. package/src/components/icons/icon-bug.tsx +19 -0
  126. package/src/components/icons/icon-reload.tsx +19 -0
  127. package/src/components/icons/icon-scanner.tsx +19 -0
  128. package/src/components/icons/icon-scissors.tsx +19 -0
  129. package/src/components/icons/icon-warning.tsx +31 -0
  130. package/src/components/send.tsx +1 -2
  131. package/src/components/shell.tsx +52 -85
  132. package/src/components/sidebar/file-tree-directory-children.tsx +15 -12
  133. package/src/components/sidebar/file-tree.tsx +1 -1
  134. package/src/components/sidebar/sidebar.tsx +23 -344
  135. package/src/components/toolbar/linter.tsx +167 -0
  136. package/src/components/toolbar/results-table.tsx +0 -0
  137. package/src/components/toolbar/results.tsx +48 -0
  138. package/src/components/toolbar/spam-assassin.tsx +155 -0
  139. package/src/components/toolbar.tsx +189 -0
  140. package/src/components/tooltip-content.tsx +1 -2
  141. package/src/components/topbar.tsx +28 -41
  142. package/src/utils/__snapshots__/get-email-component.spec.ts.snap +1 -1
  143. package/tailwind.config.ts +1 -0
  144. package/tsconfig.json +1 -1
  145. package/vitest.config.ts +5 -3
  146. package/dist/preview/.next/server/chunks/196.js +0 -5
  147. package/dist/preview/.next/server/chunks/300.js +0 -13
  148. package/dist/preview/.next/server/chunks/509.js +0 -1
  149. package/dist/preview/.next/server/chunks/631.js +0 -6
  150. package/dist/preview/.next/server/chunks/734.js +0 -15
  151. package/dist/preview/.next/static/chunks/285-dbf6306a0d45c33d.js +0 -1
  152. package/dist/preview/.next/static/chunks/447-886131c35ca42b91.js +0 -1
  153. package/dist/preview/.next/static/chunks/490-0db0db14b377daca.js +0 -1
  154. package/dist/preview/.next/static/chunks/5fec7a0a-5179023f3f5a9421.js +0 -1
  155. package/dist/preview/.next/static/chunks/603-36207c8905355e23.js +0 -1
  156. package/dist/preview/.next/static/chunks/797-46f6c20952f0a280.js +0 -2
  157. package/dist/preview/.next/static/chunks/app/layout-f6f64b817a2cf938.js +0 -1
  158. package/dist/preview/.next/static/chunks/app/page-f5f96bd66526060f.js +0 -1
  159. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-fb2bf0253c2dada4.js +0 -1
  160. package/dist/preview/.next/static/chunks/main-app-d1b0aa870bcfb13e.js +0 -1
  161. package/dist/preview/.next/static/chunks/main-df761fde212f9cda.js +0 -1
  162. package/dist/preview/.next/static/chunks/pages/_app-203a61b355820ccf.js +0 -1
  163. package/dist/preview/.next/static/chunks/pages/_error-1764ca54938748c8.js +0 -1
  164. package/dist/preview/.next/static/css/778d574c88a1db3c.css +0 -3
  165. package/dist/preview/.next/static/css/ec5d7e66bd3b6cb8.css +0 -1
  166. package/src/app/inter.ts +0 -7
  167. package/src/components/icons/icon-circle-check.tsx +0 -21
  168. package/src/components/icons/icon-circle-close.tsx +0 -17
  169. package/src/components/icons/icon-circle-warning.tsx +0 -17
  170. package/src/components/sidebar/image-checker.tsx +0 -161
  171. package/src/components/sidebar/link-checker.tsx +0 -151
  172. package/tsconfig.test.json +0 -8
  173. /package/dist/preview/.next/static/{iP6qiNn8FML_AvKcxGPhM → B4EYZiVzdylEG9lAIl-aO}/_ssgManifest.js +0 -0
  174. /package/src/components/{sidebar → toolbar}/checking-results.tsx +0 -0
@@ -0,0 +1,155 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { toast } from 'sonner';
3
+ import { cn } from '../../utils';
4
+ import { IconWarning } from '../icons/icon-warning';
5
+ import { Results } from './results';
6
+
7
+ interface SpamAssassinProps {
8
+ result: SpamCheckingResult | undefined;
9
+ }
10
+
11
+ interface SpamCheckingResult {
12
+ checks: {
13
+ name: string;
14
+ description: string;
15
+ points: number;
16
+ }[];
17
+ isSpam: boolean;
18
+ points: number;
19
+ }
20
+
21
+ function toSorted<T>(array: T[], sorter: (a: T, b: T) => number): T[] {
22
+ const cloned = [...array];
23
+ cloned.sort(sorter);
24
+ return cloned;
25
+ }
26
+
27
+ export const useSpamAssassin = ({
28
+ slug,
29
+ markup,
30
+ plainText,
31
+ }: {
32
+ slug: string;
33
+ markup: string;
34
+ plainText: string;
35
+ }) => {
36
+ const cacheKey = `spam-assassin-${slug.replaceAll('/', '-')}`;
37
+
38
+ const [result, setResult] = useState<SpamCheckingResult | undefined>();
39
+
40
+ useEffect(() => {
41
+ const cachedValue =
42
+ 'localStorage' in global ? global.localStorage.getItem(cacheKey) : null;
43
+ if (cachedValue) {
44
+ try {
45
+ setResult(JSON.parse(cachedValue));
46
+ } catch (exception) {
47
+ setResult(undefined);
48
+ }
49
+ }
50
+ }, [cacheKey]);
51
+
52
+ const [loading, setLoading] = useState(false);
53
+
54
+ const load = async () => {
55
+ setLoading(true);
56
+
57
+ try {
58
+ const response = await fetch('https://react.email/api/check-spam', {
59
+ method: 'POST',
60
+ headers: { 'Content-Type': 'application/json' },
61
+ body: JSON.stringify({
62
+ html: markup,
63
+ plainText: plainText,
64
+ }),
65
+ });
66
+
67
+ if (response.ok) {
68
+ const responseBody = (await response.json()) as
69
+ | { error: string }
70
+ | SpamCheckingResult;
71
+ if ('error' in responseBody) {
72
+ toast.error(responseBody.error);
73
+ } else {
74
+ setResult(responseBody);
75
+ localStorage.setItem(cacheKey, JSON.stringify(responseBody));
76
+ }
77
+ } else {
78
+ console.error(await response.text());
79
+ toast.error('Something went wrong');
80
+ }
81
+ } catch (exception) {
82
+ console.error(exception);
83
+ toast.error(JSON.stringify(exception));
84
+ } finally {
85
+ setLoading(false);
86
+ }
87
+ };
88
+
89
+ return [result, { loading, load }] as const;
90
+ };
91
+
92
+ export const SpamAssassin = ({ result }: SpamAssassinProps) => {
93
+ return (
94
+ <>
95
+ {result ? (
96
+ <Results>
97
+ <Results.Row className="sticky border-b-2 top-0 bg-black">
98
+ <Results.Column className="uppercase">
99
+ <span className="flex gap-1 items-center">
100
+ <IconWarning
101
+ className={cn(
102
+ result.points > 1.5 ? 'text-yellow-200' : null,
103
+ result.points > 3 ? 'text-orange-300' : null,
104
+ result.points >= 5 ? 'text-red-400' : null,
105
+ )}
106
+ />
107
+ Score
108
+ </span>
109
+ </Results.Column>
110
+ <Results.Column>Lower scores are better</Results.Column>
111
+ <Results.Column
112
+ className={cn(
113
+ 'text-right text-2xl tracking-tighter font-mono',
114
+ result.points > 1.5 ? 'text-yellow-200' : null,
115
+ result.points > 3 ? 'text-orange-300' : null,
116
+ result.points >= 5 ? 'text-red-400' : null,
117
+ )}
118
+ >
119
+ {result.points.toFixed(1)}
120
+ </Results.Column>
121
+ </Results.Row>
122
+ {toSorted(result.checks, (a, b) => b.points - a.points).map(
123
+ (check) => (
124
+ <Results.Row key={check.name}>
125
+ <Results.Column className="uppercase">
126
+ <span className="flex gap-1 items-center">
127
+ <IconWarning
128
+ className={cn(
129
+ check.points > 1 ? 'text-yellow-200' : null,
130
+ check.points > 2 ? 'text-orange-300' : null,
131
+ check.points > 3 ? 'text-red-400' : null,
132
+ )}
133
+ />
134
+ {check.name}
135
+ </span>
136
+ </Results.Column>
137
+ <Results.Column>{check.description}</Results.Column>
138
+ <Results.Column
139
+ className={cn(
140
+ 'text-right font-mono tracking-tighter',
141
+ check.points > 1 ? 'text-yellow-200' : null,
142
+ check.points > 2 ? 'text-orange-300' : null,
143
+ check.points > 3 ? 'text-red-400' : null,
144
+ )}
145
+ >
146
+ {check.points.toFixed(1)}
147
+ </Results.Column>
148
+ </Results.Row>
149
+ ),
150
+ )}
151
+ </Results>
152
+ ) : null}
153
+ </>
154
+ );
155
+ };
@@ -0,0 +1,189 @@
1
+ import * as Tabs from '@radix-ui/react-tabs';
2
+ import { LayoutGroup, motion } from 'framer-motion';
3
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
4
+ import { useEffect } from 'react';
5
+ import { cn } from '../utils';
6
+ import { IconArrowDown } from './icons/icon-arrow-down';
7
+ import { IconReload } from './icons/icon-reload';
8
+ import { IconScanner } from './icons/icon-scanner';
9
+ import { IconScissors } from './icons/icon-scissors';
10
+ import { Linter, useLinter } from './toolbar/linter';
11
+ import { SpamAssassin, useSpamAssassin } from './toolbar/spam-assassin';
12
+ import { Tooltip } from './tooltip';
13
+
14
+ type ToolbarProps = React.ComponentProps<'div'> & {
15
+ emailSlug: string;
16
+ markup: string;
17
+ plainText: string;
18
+ };
19
+
20
+ type ActivePanelValue = 'linter' | 'spam-assassin';
21
+
22
+ interface ToolbarButton extends React.ComponentProps<'button'> {
23
+ children: React.ReactNode;
24
+ active?: boolean;
25
+ tooltip?: React.ReactNode;
26
+ }
27
+
28
+ const ToolbarButton = ({
29
+ children,
30
+ className,
31
+ active,
32
+ tooltip,
33
+ ...props
34
+ }: ToolbarButton) => {
35
+ return (
36
+ <Tooltip.Provider>
37
+ <Tooltip>
38
+ <Tooltip.Trigger asChild>
39
+ <button
40
+ type="button"
41
+ {...props}
42
+ className={cn(
43
+ 'h-full w-fit font-medium flex text-sm text-slate-10 items-center align-middle justify-center px-1 py-2 gap-1 relative',
44
+ 'hover:text-slate-12 transition-colors',
45
+ active && 'data-[state=active]:text-cyan-11',
46
+ className,
47
+ )}
48
+ >
49
+ {children}
50
+ {active ? (
51
+ <motion.span
52
+ className="-bottom-px absolute rounded-sm left-0 w-full bg-cyan-11 h-px"
53
+ layoutId="active-toolbar-button"
54
+ transition={{
55
+ type: 'spring',
56
+ bounce: 0.2,
57
+ duration: 0.6,
58
+ }}
59
+ />
60
+ ) : null}
61
+ </button>
62
+ </Tooltip.Trigger>
63
+ {tooltip ? <Tooltip.Content>{tooltip}</Tooltip.Content> : null}
64
+ </Tooltip>
65
+ </Tooltip.Provider>
66
+ );
67
+ };
68
+
69
+ export const Toolbar = ({
70
+ emailSlug,
71
+ markup,
72
+ plainText,
73
+ className,
74
+ ...rest
75
+ }: ToolbarProps) => {
76
+ const pathname = usePathname();
77
+ const searchParams = useSearchParams();
78
+ const router = useRouter();
79
+
80
+ const activePanelValue = (searchParams.get('toolbar-panel') ?? undefined) as
81
+ | ActivePanelValue
82
+ | undefined;
83
+
84
+ const toggled = activePanelValue !== undefined;
85
+
86
+ const setActivePanelValue = (newValue: ActivePanelValue | undefined) => {
87
+ console.log(newValue);
88
+ const params = new URLSearchParams(searchParams);
89
+ if (newValue === undefined) {
90
+ params.delete('toolbar-panel');
91
+ } else {
92
+ params.set('toolbar-panel', newValue);
93
+ }
94
+ router.push(`${pathname}?${params.toString()}`);
95
+ };
96
+
97
+ const [spamCheckingResult, { load: loadSpamChecking }] = useSpamAssassin({
98
+ slug: emailSlug,
99
+ markup,
100
+ plainText,
101
+ });
102
+
103
+ const [lintingResults, { load: loadLinting }] = useLinter({
104
+ slug: emailSlug,
105
+ markup,
106
+ });
107
+
108
+ useEffect(() => {
109
+ loadLinting();
110
+ loadSpamChecking();
111
+ }, []);
112
+
113
+ return (
114
+ <div
115
+ {...rest}
116
+ data-toggled={toggled}
117
+ className={cn(
118
+ 'bg-black group/toolbar text-xs text-slate-11 h-48 transition-all',
119
+ 'data-[toggled=false]:h-8',
120
+ className,
121
+ )}
122
+ >
123
+ <Tabs.Root
124
+ value={activePanelValue}
125
+ onValueChange={(newValue) => {
126
+ setActivePanelValue(newValue as ActivePanelValue);
127
+ }}
128
+ asChild
129
+ >
130
+ <div className="flex flex-col h-full">
131
+ <Tabs.List className="flex gap-4 px-2 border-b border-solid border-slate-6 h-7 w-full">
132
+ <LayoutGroup id="toolbar">
133
+ <Tabs.Trigger asChild value="spam-assassin">
134
+ <ToolbarButton active={activePanelValue === 'spam-assassin'}>
135
+ <IconScissors />
136
+ Spam Assassin
137
+ </ToolbarButton>
138
+ </Tabs.Trigger>
139
+ <Tabs.Trigger asChild value="linter">
140
+ <ToolbarButton active={activePanelValue === 'linter'}>
141
+ <IconScanner />
142
+ Linter
143
+ </ToolbarButton>
144
+ </Tabs.Trigger>
145
+ </LayoutGroup>
146
+ <div className="flex gap-1 ml-auto">
147
+ <ToolbarButton
148
+ tooltip="Reload"
149
+ onClick={() => {
150
+ if (activePanelValue === 'spam-assassin') {
151
+ void loadSpamChecking();
152
+ } else if (activePanelValue === 'linter') {
153
+ void loadLinting();
154
+ } else {
155
+ setActivePanelValue('linter');
156
+ void loadLinting();
157
+ }
158
+ }}
159
+ >
160
+ <IconReload />
161
+ </ToolbarButton>
162
+ <ToolbarButton
163
+ tooltip="Toggle toolbar"
164
+ onClick={() => {
165
+ if (activePanelValue === undefined) {
166
+ setActivePanelValue('linter');
167
+ } else {
168
+ setActivePanelValue(undefined);
169
+ }
170
+ }}
171
+ >
172
+ <IconArrowDown className="transition-transform group-data-[toggled=false]/toolbar:rotate-180" />
173
+ </ToolbarButton>
174
+ </div>
175
+ </Tabs.List>
176
+
177
+ <div className="flex-grow transition-opacity opacity-100 group-data-[toggled=false]/toolbar:opacity-0 overflow-y-auto px-2">
178
+ <Tabs.Content value="linter">
179
+ <Linter results={lintingResults} />
180
+ </Tabs.Content>
181
+ <Tabs.Content value="spam-assassin">
182
+ <SpamAssassin result={spamCheckingResult} />
183
+ </Tabs.Content>
184
+ </div>
185
+ </div>
186
+ </Tabs.Root>
187
+ </div>
188
+ );
189
+ };
@@ -1,6 +1,5 @@
1
1
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
2
2
  import * as React from 'react';
3
- import { inter } from '../app/inter';
4
3
  import { cn } from '../utils';
5
4
 
6
5
  type ContentElement = React.ComponentRef<typeof TooltipPrimitive.Content>;
@@ -19,7 +18,7 @@ export const TooltipContent = React.forwardRef<
19
18
  {...props}
20
19
  className={cn(
21
20
  'z-20 rounded-md border border-slate-6 bg-black px-3 py-2 text-white text-xs',
22
- `${inter.variable} font-sans`,
21
+ 'font-sans',
23
22
  )}
24
23
  ref={forwardedRef}
25
24
  sideOffset={sideOffset}
@@ -1,37 +1,25 @@
1
1
  'use client';
2
2
 
3
+ import { use } from 'react';
3
4
  import { Heading } from './heading';
4
5
  import { IconHideSidebar } from './icons/icon-hide-sidebar';
5
- import { Send } from './send';
6
+ import { ShellContext } from './shell';
6
7
  import { Tooltip } from './tooltip';
7
- import { ActiveViewToggleGroup } from './topbar/active-view-toggle-group';
8
- import { ViewSizeControls } from './topbar/view-size-controls';
9
8
 
10
9
  interface TopbarProps {
11
10
  currentEmailOpenSlug: string;
12
11
  pathSeparator: string;
13
- markup?: string;
14
- onToggleSidebar?: () => void;
15
- activeView?: string;
16
- setActiveView?: (view: string) => void;
17
- viewWidth?: number;
18
- setViewWidth?: (width: number) => void;
19
- viewHeight?: number;
20
- setViewHeight?: (height: number) => void;
12
+
13
+ children: React.ReactNode;
21
14
  }
22
15
 
23
16
  export const Topbar = ({
24
17
  currentEmailOpenSlug,
25
18
  pathSeparator,
26
- markup,
27
- activeView,
28
- setActiveView,
29
- viewWidth,
30
- setViewWidth,
31
- viewHeight,
32
- setViewHeight,
33
- onToggleSidebar,
19
+ children,
34
20
  }: TopbarProps) => {
21
+ const { toggleSidebar } = use(ShellContext)!;
22
+
35
23
  return (
36
24
  <Tooltip.Provider>
37
25
  <header className="relative flex h-[3.3125rem] items-center justify-between gap-3 border-slate-6 border-b px-3">
@@ -41,9 +29,7 @@ export const Topbar = ({
41
29
  <button
42
30
  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"
43
31
  onClick={() => {
44
- if (onToggleSidebar) {
45
- onToggleSidebar();
46
- }
32
+ toggleSidebar();
47
33
  }}
48
34
  type="button"
49
35
  >
@@ -59,25 +45,26 @@ export const Topbar = ({
59
45
  </div>
60
46
  </div>
61
47
  <div className="flex w-full items-center justify-between gap-3 lg:w-fit lg:justify-start">
62
- {setViewWidth && setViewHeight && viewWidth && viewHeight ? (
63
- <ViewSizeControls
64
- setViewHeight={setViewHeight}
65
- setViewWidth={setViewWidth}
66
- viewHeight={viewHeight}
67
- viewWidth={viewWidth}
68
- />
69
- ) : null}
70
- {activeView && setActiveView ? (
71
- <ActiveViewToggleGroup
72
- activeView={activeView}
73
- setActiveView={setActiveView}
74
- />
75
- ) : null}
76
- {markup ? (
77
- <div className="flex justify-end">
78
- <Send markup={markup} />
79
- </div>
80
- ) : null}
48
+ {children}
49
+ {/* {setViewWidth && setViewHeight && viewWidth && viewHeight ? ( */}
50
+ {/* <ViewSizeControls */}
51
+ {/* setViewHeight={setViewHeight} */}
52
+ {/* setViewWidth={setViewWidth} */}
53
+ {/* viewHeight={viewHeight} */}
54
+ {/* viewWidth={viewWidth} */}
55
+ {/* /> */}
56
+ {/* ) : null} */}
57
+ {/* {activeView && setActiveView ? ( */}
58
+ {/* <ActiveViewToggleGroup */}
59
+ {/* activeView={activeView} */}
60
+ {/* setActiveView={setActiveView} */}
61
+ {/* /> */}
62
+ {/* ) : null} */}
63
+ {/* {markup ? ( */}
64
+ {/* <div className="flex justify-end"> */}
65
+ {/* <Send markup={markup} /> */}
66
+ {/* </div> */}
67
+ {/* ) : null} */}
81
68
  </div>
82
69
  </header>
83
70
  </Tooltip.Provider>
@@ -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><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏ ‌​‍‎‏</div></div><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, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;;padding-left:0.5rem;padding-right:0.5rem"><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:16px 0">Hello <!-- -->alanturing<!-- -->,</p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0"><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="invited you to" 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 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/teams/invite/foo" 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>&#8202;&#8202;</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>&#8202;&#8202;&#8203;</i><![endif]--></span></a></td></tr></tbody></table><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0">or copy and paste this URL into your browser:<!-- --> <a href="https://vercel.com/teams/invite/foo" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://vercel.com/teams/invite/foo</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:16px 0">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&#x27;s safety, please reply to this email to get in touch with us.</p></td></tr></tbody></table><!--/$--></body></html>"`;
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, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;;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:16px 0">Hello <!-- -->alanturing<!-- -->,</p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0"><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="invited you to" 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 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/teams/invite/foo" 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>&#8202;&#8202;</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>&#8202;&#8202;&#8203;</i><![endif]--></span></a></td></tr></tbody></table><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin:16px 0">or copy and paste this URL into your browser:<!-- --> <a href="https://vercel.com/teams/invite/foo" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://vercel.com/teams/invite/foo</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:16px 0">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&#x27;s safety, please reply to this email to get in touch with us.</p></td></tr></tbody></table><!--/$--></body></html>"`;
@@ -71,6 +71,7 @@ const config: Config = {
71
71
  },
72
72
  fontFamily: {
73
73
  sans: ['var(--font-inter)', ...fontFamily.sans],
74
+ mono: ['var(--font-sf-mono)', ...fontFamily.mono],
74
75
  },
75
76
  keyframes: {
76
77
  shine: {
package/tsconfig.json CHANGED
@@ -35,5 +35,5 @@
35
35
  "outDir": "dist"
36
36
  },
37
37
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
38
- "exclude": [".next", "dist", "node_modules", "**/*.spec.ts", "**/*.spec.tsx"]
38
+ "exclude": [".next", "dist", "node_modules"]
39
39
  }
package/vitest.config.ts CHANGED
@@ -1,6 +1,4 @@
1
- import type { TsconfigRaw } from 'esbuild';
2
1
  import { defineConfig } from 'vitest/config';
3
- import tsconfig from './tsconfig.test.json';
4
2
 
5
3
  export default defineConfig({
6
4
  test: {
@@ -8,6 +6,10 @@ export default defineConfig({
8
6
  environment: 'happy-dom',
9
7
  },
10
8
  esbuild: {
11
- tsconfigRaw: tsconfig as TsconfigRaw,
9
+ tsconfigRaw: {
10
+ compilerOptions: {
11
+ jsx: 'react-jsx',
12
+ },
13
+ },
12
14
  },
13
15
  });