react-email 4.0.0-alpha.4 → 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 (170) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cli/index.js +5 -4
  3. package/dist/cli/index.mjs +9 -5
  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/app-path-routes-manifest.json +1 -1
  7. package/dist/preview/.next/build-manifest.json +14 -14
  8. package/dist/preview/.next/cache/.rscinfo +1 -1
  9. package/dist/preview/.next/cache/images/TcyzHbFXGFjrOu3wEMvDoSmqCh3qP3iiNqJf0QbED9Y/60.1741728556140.cQ5qicbpvoXZ7leVmWqG2ElLwXB1ynYeSv8MBSA-QeM.Vy8iMWM3MGUtMTk1ODcxYmIyNzMi.webp +0 -0
  10. package/dist/preview/.next/cache/webpack/client-development/0.pack.gz +0 -0
  11. package/dist/preview/.next/cache/webpack/client-development/1.pack.gz +0 -0
  12. package/dist/preview/.next/cache/webpack/client-development/10.pack.gz +0 -0
  13. package/dist/preview/.next/cache/webpack/client-development/11.pack.gz +0 -0
  14. package/dist/preview/.next/cache/webpack/client-development/12.pack.gz +0 -0
  15. package/dist/preview/.next/cache/webpack/client-development/13.pack.gz +0 -0
  16. package/dist/preview/.next/cache/webpack/client-development/2.pack.gz +0 -0
  17. package/dist/preview/.next/cache/webpack/client-development/3.pack.gz +0 -0
  18. package/dist/preview/.next/cache/webpack/client-development/4.pack.gz +0 -0
  19. package/dist/preview/.next/cache/webpack/client-development/5.pack.gz +0 -0
  20. package/dist/preview/.next/cache/webpack/client-development/6.pack.gz +0 -0
  21. package/dist/preview/.next/cache/webpack/client-development/7.pack.gz +0 -0
  22. package/dist/preview/.next/cache/webpack/client-development/8.pack.gz +0 -0
  23. package/dist/preview/.next/cache/webpack/client-development/9.pack.gz +0 -0
  24. package/dist/preview/.next/cache/webpack/client-development/index.pack.gz +0 -0
  25. package/dist/preview/.next/cache/webpack/client-development/index.pack.gz.old +0 -0
  26. package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
  27. package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
  28. package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
  29. package/dist/preview/.next/cache/webpack/server-development/0.pack.gz +0 -0
  30. package/dist/preview/.next/cache/webpack/server-development/1.pack.gz +0 -0
  31. package/dist/preview/.next/cache/webpack/server-development/2.pack.gz +0 -0
  32. package/dist/preview/.next/cache/webpack/server-development/3.pack.gz +0 -0
  33. package/dist/preview/.next/cache/webpack/server-development/4.pack.gz +0 -0
  34. package/dist/preview/.next/cache/webpack/server-development/5.pack.gz +0 -0
  35. package/dist/preview/.next/cache/webpack/server-development/6.pack.gz +0 -0
  36. package/dist/preview/.next/cache/webpack/server-development/7.pack.gz +0 -0
  37. package/dist/preview/.next/cache/webpack/server-development/8.pack.gz +0 -0
  38. package/dist/preview/.next/cache/webpack/server-development/9.pack.gz +0 -0
  39. package/dist/preview/.next/cache/webpack/server-development/index.pack.gz +0 -0
  40. package/dist/preview/.next/cache/webpack/server-development/index.pack.gz.old +0 -0
  41. package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
  42. package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
  43. package/dist/preview/.next/diagnostics/framework.json +1 -1
  44. package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
  45. package/dist/preview/.next/next-server.js.nft.json +1 -1
  46. package/dist/preview/.next/prerender-manifest.json +1 -1
  47. package/dist/preview/.next/required-server-files.json +1 -1
  48. package/dist/preview/.next/server/app/_not-found/page.js +1 -1
  49. package/dist/preview/.next/server/app/_not-found/page.js.nft.json +1 -1
  50. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  51. package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
  52. package/dist/preview/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  53. package/dist/preview/.next/server/app/page.js +1 -1
  54. package/dist/preview/.next/server/app/page.js.nft.json +1 -1
  55. package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/dist/preview/.next/server/app/preview/[...slug]/page.js +9 -8
  57. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  58. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  59. package/dist/preview/.next/server/app-paths-manifest.json +1 -1
  60. package/dist/preview/.next/server/chunks/143.js +6 -0
  61. package/dist/preview/.next/server/chunks/409.js +5 -0
  62. package/dist/preview/.next/server/chunks/46.js +1 -0
  63. package/dist/preview/.next/server/chunks/478.js +14 -0
  64. package/dist/preview/.next/server/chunks/707.js +13 -0
  65. package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
  66. package/dist/preview/.next/server/next-font-manifest.js +1 -1
  67. package/dist/preview/.next/server/next-font-manifest.json +1 -1
  68. package/dist/preview/.next/server/pages/500.html +1 -1
  69. package/dist/preview/.next/server/pages/_app.js +1 -1
  70. package/dist/preview/.next/server/pages/_app.js.nft.json +1 -1
  71. package/dist/preview/.next/server/pages/_document.js +1 -1
  72. package/dist/preview/.next/server/pages/_document.js.nft.json +1 -1
  73. package/dist/preview/.next/server/pages/_error.js +1 -1
  74. package/dist/preview/.next/server/pages/_error.js.nft.json +1 -1
  75. package/dist/preview/.next/server/server-reference-manifest.js +1 -1
  76. package/dist/preview/.next/server/server-reference-manifest.json +1 -1
  77. package/dist/preview/.next/static/{Pt6wqIrWnQxbiyqaKNFOx → B4EYZiVzdylEG9lAIl-aO}/_buildManifest.js +1 -1
  78. package/dist/preview/.next/static/chunks/575-bc52750855c25df4.js +2 -0
  79. package/dist/preview/.next/static/chunks/684-0f1ef7361c499798.js +1 -0
  80. package/dist/preview/.next/static/chunks/684c6b30-0c65da32762fc4ee.js +1 -0
  81. package/dist/preview/.next/static/chunks/81-e7539b08d9d3fb4d.js +1 -0
  82. package/dist/preview/.next/static/chunks/883-70c8267c50bc4133.js +1 -0
  83. package/dist/preview/.next/static/chunks/921-d1dc8c63f49e85d6.js +1 -0
  84. package/dist/preview/.next/static/chunks/{afa401a5-9ebf2515b1397993.js → afa401a5-a600c227dacf3ab4.js} +1 -1
  85. package/dist/preview/.next/static/chunks/app/_not-found/{page-96d3eac723be3ee2.js → page-03ce767859c36d4e.js} +1 -1
  86. package/dist/preview/.next/static/chunks/app/layout-7cf14e28880544f1.js +1 -0
  87. package/dist/preview/.next/static/chunks/app/page-065cb49b0a078541.js +1 -0
  88. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-656510fd180c803c.js +1 -0
  89. package/dist/preview/.next/static/chunks/{framework-e7cae9cecd5c9ba2.js → framework-2a724981073c3a29.js} +1 -1
  90. package/dist/preview/.next/static/chunks/main-552b9719bbc3a274.js +1 -0
  91. package/dist/preview/.next/static/chunks/main-app-914a73336fd45af5.js +1 -0
  92. package/dist/preview/.next/static/chunks/pages/_app-77ca34bce25ac75c.js +1 -0
  93. package/dist/preview/.next/static/chunks/pages/_error-73f611c46abbb495.js +1 -0
  94. package/dist/preview/.next/static/chunks/{webpack-9255716c9496e606.js → webpack-2eb145a20ee6cb77.js} +1 -1
  95. package/dist/preview/.next/static/css/2df96d9ee014e8de.css +3 -0
  96. package/dist/preview/.next/static/media/05613964ce6c782e-s.p.otf +0 -0
  97. package/dist/preview/.next/static/media/11c6126b9369e85e-s.p.otf +0 -0
  98. package/dist/preview/.next/static/media/26cb97734d8cb717-s.p.otf +0 -0
  99. package/dist/preview/.next/static/media/bb6462617151f6b7-s.p.otf +0 -0
  100. package/dist/preview/.next/static/media/cf6daef822ab0142-s.p.otf +0 -0
  101. package/dist/preview/.next/static/media/e4051546b3043204-s.p.otf +0 -0
  102. package/dist/preview/.next/trace +22 -22
  103. package/package.json +5 -4
  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 -32
  124. package/src/components/icons/icon-base.tsx +4 -2
  125. package/src/components/icons/icon-reload.tsx +19 -0
  126. package/src/components/icons/icon-scanner.tsx +19 -0
  127. package/src/components/icons/icon-scissors.tsx +19 -0
  128. package/src/components/icons/icon-warning.tsx +31 -0
  129. package/src/components/send.tsx +1 -2
  130. package/src/components/shell.tsx +52 -88
  131. package/src/components/sidebar/file-tree-directory-children.tsx +1 -1
  132. package/src/components/sidebar/file-tree.tsx +1 -1
  133. package/src/components/sidebar/sidebar.tsx +23 -378
  134. package/src/components/toolbar/linter.tsx +167 -0
  135. package/src/components/toolbar/results-table.tsx +0 -0
  136. package/src/components/toolbar/results.tsx +48 -0
  137. package/src/components/toolbar/spam-assassin.tsx +155 -0
  138. package/src/components/toolbar.tsx +189 -0
  139. package/src/components/tooltip-content.tsx +1 -2
  140. package/src/components/topbar.tsx +28 -41
  141. package/tailwind.config.ts +1 -0
  142. package/dist/preview/.next/server/chunks/196.js +0 -5
  143. package/dist/preview/.next/server/chunks/300.js +0 -13
  144. package/dist/preview/.next/server/chunks/631.js +0 -6
  145. package/dist/preview/.next/server/chunks/644.js +0 -1
  146. package/dist/preview/.next/server/chunks/734.js +0 -15
  147. package/dist/preview/.next/static/chunks/285-dbf6306a0d45c33d.js +0 -1
  148. package/dist/preview/.next/static/chunks/447-886131c35ca42b91.js +0 -1
  149. package/dist/preview/.next/static/chunks/490-d5745684930d49e0.js +0 -1
  150. package/dist/preview/.next/static/chunks/5fec7a0a-5179023f3f5a9421.js +0 -1
  151. package/dist/preview/.next/static/chunks/603-36207c8905355e23.js +0 -1
  152. package/dist/preview/.next/static/chunks/797-46f6c20952f0a280.js +0 -2
  153. package/dist/preview/.next/static/chunks/app/layout-d06046b8a368df3b.js +0 -1
  154. package/dist/preview/.next/static/chunks/app/page-ef1c23b954fbd0b5.js +0 -1
  155. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-ea8e1ae2b5a4a0ec.js +0 -1
  156. package/dist/preview/.next/static/chunks/main-app-9f2fb5ea26e2765b.js +0 -1
  157. package/dist/preview/.next/static/chunks/main-df761fde212f9cda.js +0 -1
  158. package/dist/preview/.next/static/chunks/pages/_app-203a61b355820ccf.js +0 -1
  159. package/dist/preview/.next/static/chunks/pages/_error-1764ca54938748c8.js +0 -1
  160. package/dist/preview/.next/static/css/e4822d5ba3082a95.css +0 -3
  161. package/dist/preview/.next/static/css/ec5d7e66bd3b6cb8.css +0 -1
  162. package/src/app/inter.ts +0 -7
  163. package/src/components/icons/icon-circle-check.tsx +0 -21
  164. package/src/components/icons/icon-circle-close.tsx +0 -17
  165. package/src/components/icons/icon-circle-warning.tsx +0 -17
  166. package/src/components/sidebar/image-checker.tsx +0 -162
  167. package/src/components/sidebar/link-checker.tsx +0 -151
  168. package/src/components/sidebar/spam-assassin.tsx +0 -158
  169. /package/dist/preview/.next/static/{Pt6wqIrWnQxbiyqaKNFOx → B4EYZiVzdylEG9lAIl-aO}/_ssgManifest.js +0 -0
  170. /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>
@@ -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: {