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
@@ -6,13 +6,18 @@ import { flushSync } from 'react-dom';
6
6
  import { Toaster } from 'sonner';
7
7
  import { useDebouncedCallback } from 'use-debounce';
8
8
  import type { EmailRenderingResult } from '../../../actions/render-email-by-path';
9
+ import { Topbar } from '../../../components';
9
10
  import { CodeContainer } from '../../../components/code-container';
10
11
  import {
11
12
  ResizableWarpper,
12
13
  makeIframeDocumentBubbleEvents,
13
14
  } from '../../../components/resizable-wrapper';
14
- import { Shell } from '../../../components/shell';
15
+ import { Send } from '../../../components/send';
16
+ import { Shell, ShellContent } from '../../../components/shell';
17
+ import { Toolbar } from '../../../components/toolbar';
15
18
  import { Tooltip } from '../../../components/tooltip';
19
+ import { ActiveViewToggleGroup } from '../../../components/topbar/active-view-toggle-group';
20
+ import { ViewSizeControls } from '../../../components/topbar/view-size-controls';
16
21
  import { useClampedState } from '../../../hooks/use-clamped-state';
17
22
  import { useEmailRenderingResult } from '../../../hooks/use-email-rendering-result';
18
23
  import { useHotreload } from '../../../hooks/use-hot-reload';
@@ -80,7 +85,8 @@ const Preview = ({
80
85
  router.push(`${pathname}?${params.toString()}`);
81
86
  };
82
87
 
83
- const hasNoErrors = typeof renderedEmailMetadata !== 'undefined';
88
+ const hasRenderingMetadata = typeof renderedEmailMetadata !== 'undefined';
89
+ const hasErrors = 'error' in renderingResult;
84
90
 
85
91
  const [maxWidth, setMaxWidth] = useState(Number.POSITIVE_INFINITY);
86
92
  const [maxHeight, setMaxHeight] = useState(Number.POSITIVE_INFINITY);
@@ -107,30 +113,37 @@ const Preview = ({
107
113
  }, 300);
108
114
 
109
115
  return (
110
- <Shell
111
- activeView={activeView}
112
- currentEmailOpenSlug={slug}
113
- markup={renderedEmailMetadata?.markup}
114
- pathSeparator={pathSeparator}
115
- setActiveView={handleViewChange}
116
- setViewHeight={(height) => {
117
- setHeight(height);
118
- flushSync(() => {
119
- handleSaveViewSize();
120
- });
121
- }}
122
- setViewWidth={(width) => {
123
- setWidth(width);
124
- flushSync(() => {
125
- handleSaveViewSize();
126
- });
127
- }}
128
- viewHeight={height}
129
- viewWidth={width}
130
- >
131
- {/* This relative is so that when there is any error the user can still switch between emails */}
132
- <div
133
- className="relative flex h-full bg-gray-200 pb-8"
116
+ <Shell currentEmailOpenSlug={slug}>
117
+ <Topbar pathSeparator={pathSeparator} currentEmailOpenSlug={slug}>
118
+ <ViewSizeControls
119
+ setViewHeight={(height) => {
120
+ setHeight(height);
121
+ flushSync(() => {
122
+ handleSaveViewSize();
123
+ });
124
+ }}
125
+ setViewWidth={(width) => {
126
+ setWidth(width);
127
+ flushSync(() => {
128
+ handleSaveViewSize();
129
+ });
130
+ }}
131
+ viewHeight={height}
132
+ viewWidth={width}
133
+ />
134
+ <ActiveViewToggleGroup
135
+ activeView={activeView}
136
+ setActiveView={handleViewChange}
137
+ />
138
+ {hasRenderingMetadata ? (
139
+ <div className="flex justify-end">
140
+ <Send markup={renderedEmailMetadata.markup} />
141
+ </div>
142
+ ) : null}
143
+ </Topbar>
144
+
145
+ <ShellContent
146
+ className="relative flex bg-gray-200"
134
147
  ref={(element) => {
135
148
  const observer = new ResizeObserver((entry) => {
136
149
  const [elementEntry] = entry;
@@ -149,11 +162,9 @@ const Preview = ({
149
162
  };
150
163
  }}
151
164
  >
152
- {'error' in renderingResult ? (
153
- <RenderingError error={renderingResult.error} />
154
- ) : null}
165
+ {hasErrors ? <RenderingError error={renderingResult.error} /> : null}
155
166
 
156
- {hasNoErrors ? (
167
+ {hasRenderingMetadata ? (
157
168
  <>
158
169
  {activeView === 'preview' && (
159
170
  <ResizableWarpper
@@ -223,7 +234,15 @@ const Preview = ({
223
234
  ) : null}
224
235
 
225
236
  <Toaster />
226
- </div>
237
+ </ShellContent>
238
+
239
+ {!hasErrors && hasRenderingMetadata ? (
240
+ <Toolbar
241
+ emailSlug={slug}
242
+ markup={renderedEmailMetadata.markup}
243
+ plainText={renderedEmailMetadata.plainText}
244
+ />
245
+ ) : undefined}
227
246
  </Shell>
228
247
  );
229
248
  };
@@ -8,7 +8,7 @@ export interface IconProps extends RootProps {
8
8
  }
9
9
 
10
10
  export const IconBase = React.forwardRef<IconElement, Readonly<IconProps>>(
11
- ({ size = 20, ...props }, forwardedRef) => (
11
+ ({ size = 20, children, ...props }, forwardedRef) => (
12
12
  <svg
13
13
  fill="none"
14
14
  height={size}
@@ -17,7 +17,9 @@ export const IconBase = React.forwardRef<IconElement, Readonly<IconProps>>(
17
17
  width={size}
18
18
  xmlns="http://www.w3.org/2000/svg"
19
19
  {...props}
20
- />
20
+ >
21
+ {children}
22
+ </svg>
21
23
  ),
22
24
  );
23
25
 
@@ -0,0 +1,19 @@
1
+ import { forwardRef } from 'react';
2
+ import type { IconElement, IconProps } from './icon-base';
3
+ import { IconBase } from './icon-base';
4
+
5
+ export const IconBug = forwardRef<IconElement, IconProps>((props, ref) => (
6
+ <IconBase {...props} ref={ref}>
7
+ <g
8
+ fill="none"
9
+ stroke="currentColor"
10
+ strokeLinecap="round"
11
+ strokeLinejoin="round"
12
+ strokeWidth="2"
13
+ >
14
+ <path d="m8 2l1.88 1.88m4.24 0L16 2M9 7.13v-1a3.003 3.003 0 1 1 6 0v1" />
15
+ <path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6m0 0v-9" />
16
+ <path d="M6.53 9C4.6 8.8 3 7.1 3 5m3 8H2m1 8c0-2.1 1.7-3.9 3.8-4M20.97 5c0 2.1-1.6 3.8-3.5 4M22 13h-4m-.8 4c2.1.1 3.8 1.9 3.8 4" />
17
+ </g>
18
+ </IconBase>
19
+ ));
@@ -0,0 +1,19 @@
1
+ export const IconReload = (props: React.ComponentProps<'svg'>) => {
2
+ return (
3
+ <svg
4
+ width="12"
5
+ height="12"
6
+ viewBox="0 0 12 12"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ {...props}
10
+ >
11
+ <path
12
+ fillRule="evenodd"
13
+ clipRule="evenodd"
14
+ d="M10.52 6C10.52 3.73168 8.75221 1.48 6.00006 1.48C3.77741 1.48 2.67886 3.1251 2.21074 3.99999H3.60005C3.82096 3.99999 4.00005 4.17908 4.00005 4.39999C4.00005 4.6209 3.82096 4.79999 3.60005 4.79999H1.20005C0.979137 4.79999 0.800049 4.6209 0.800049 4.39999V1.99999C0.800049 1.77908 0.979137 1.59999 1.20005 1.59999C1.42096 1.59999 1.60005 1.77908 1.60005 1.99999V3.45056C2.16367 2.45702 3.4673 0.679993 6.00006 0.679993C9.25029 0.679993 11.32 3.34831 11.32 6C11.32 8.65169 9.25029 11.32 6.00006 11.32C4.44499 11.32 3.15027 10.7047 2.22843 9.76673C1.73486 9.26449 1.34939 8.67121 1.08658 8.03257C1.0025 7.8283 1.09995 7.59453 1.30424 7.51046C1.50853 7.42638 1.7423 7.52384 1.82637 7.72812C2.05104 8.27401 2.38001 8.77961 2.79901 9.20593C3.57646 9.99705 4.66802 10.52 6.00006 10.52C8.75221 10.52 10.52 8.26833 10.52 6Z"
15
+ fill="currentColor"
16
+ />
17
+ </svg>
18
+ );
19
+ };
@@ -0,0 +1,19 @@
1
+ export const IconScanner = (props: React.ComponentProps<'svg'>) => {
2
+ return (
3
+ <svg
4
+ width="13"
5
+ height="12"
6
+ viewBox="0 0 13 12"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ {...props}
10
+ >
11
+ <path
12
+ fillRule="evenodd"
13
+ clipRule="evenodd"
14
+ d="M1.5 1.5C1.22386 1.5 1 1.72386 1 2V8C1 8.27614 1.22386 8.5 1.5 8.5H11.5C11.7761 8.5 12 8.27614 12 8V2C12 1.72386 11.7761 1.5 11.5 1.5H1.5ZM0 8C0 8.6531 0.4174 9.2087 1 9.4146V10C1 10.8284 1.67157 11.5 2.5 11.5H10.5C11.3284 11.5 12 10.8284 12 10V9.4146C12.5826 9.2087 13 8.6531 13 8V2C13 1.17157 12.3284 0.5 11.5 0.5H1.5C0.67157 0.5 0 1.17157 0 2V8ZM11 10V9.5H2V10C2 10.2761 2.22386 10.5 2.5 10.5H10.5C10.7761 10.5 11 10.2761 11 10ZM4.5 4.5C4.22386 4.5 4 4.72386 4 5C4 5.27614 4.22386 5.5 4.5 5.5H8.5C8.77614 5.5 9 5.27614 9 5C9 4.72386 8.77614 4.5 8.5 4.5H4.5Z"
15
+ fill="currentColor"
16
+ />
17
+ </svg>
18
+ );
19
+ };
@@ -0,0 +1,19 @@
1
+ export const IconScissors = (props: React.ComponentProps<'svg'>) => {
2
+ return (
3
+ <svg
4
+ width="12"
5
+ height="10"
6
+ viewBox="0 0 12 10"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ {...props}
10
+ >
11
+ <path
12
+ fillRule="evenodd"
13
+ clipRule="evenodd"
14
+ d="M0.760039 2.58762C0.760039 3.27246 1.31521 3.82762 2.00004 3.82762C2.68487 3.82762 3.24004 3.27246 3.24004 2.58762C3.24004 1.90278 2.68487 1.34762 2.00004 1.34762C1.31521 1.34762 0.760039 1.90278 0.760039 2.58762ZM2.00004 4.54762C0.917561 4.54762 0.0400391 3.6701 0.0400391 2.58762C0.0400391 1.50514 0.917561 0.627625 2.00004 0.627625C3.08252 0.627625 3.96004 1.50514 3.96004 2.58762C3.96004 2.84649 3.90986 3.09364 3.81868 3.31986L4.58929 3.83529C4.58359 3.85323 4.5782 3.8713 4.57313 3.88949L4.45035 4.32947L4.16809 4.51817L3.37248 3.98691C3.0189 4.33374 2.53445 4.54762 2.00004 4.54762ZM0.760042 7.39998C0.760042 6.71514 1.31521 6.15997 2.00004 6.15997C2.68487 6.15997 3.24004 6.71514 3.24004 7.39998C3.24004 8.08478 2.68487 8.63999 2.00004 8.63999C1.31521 8.63999 0.760042 8.08478 0.760042 7.39998ZM2.00004 5.43997C0.917561 5.43997 0.0400415 6.31749 0.0400415 7.39998C0.0400415 8.48246 0.917561 9.35998 2.00004 9.35998C3.08252 9.35998 3.96004 8.48246 3.96004 7.39998C3.96004 7.14422 3.91106 6.89996 3.82199 6.67598L12 1.20588L11.2389 1.28588C10.1662 1.39862 9.12733 1.72714 8.18485 2.25167L5.72522 3.6205C5.5385 3.72441 5.40112 3.8987 5.34369 4.10451L5.14127 4.82988L3.37954 6.00764C3.02538 5.65671 2.53802 5.43997 2.00004 5.43997ZM5.71553 6.3719L5.72522 6.37739L8.18485 7.74622C9.12733 8.27071 10.1662 8.59927 11.2389 8.71199L12 8.79198L7.04863 5.48022L5.71553 6.3719Z"
15
+ fill="currentColor"
16
+ />
17
+ </svg>
18
+ );
19
+ };
@@ -0,0 +1,31 @@
1
+ export const IconWarning = (props: React.ComponentProps<'svg'>) => (
2
+ <svg
3
+ width="13"
4
+ height="12"
5
+ viewBox="0 0 13 12"
6
+ fill="none"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ {...props}
9
+ >
10
+ <path
11
+ d="M10.8777 8.99999L6.87766 1.99999C6.79044 1.84609 6.66396 1.71808 6.51112 1.62902C6.35828 1.53997 6.18455 1.49304 6.00766 1.49304C5.83077 1.49304 5.65704 1.53997 5.5042 1.62902C5.35136 1.71808 5.22488 1.84609 5.13766 1.99999L1.13766 8.99999C1.0495 9.15267 1.00327 9.32594 1.00366 9.50224C1.00405 9.67855 1.05105 9.85161 1.13988 10.0039C1.22872 10.1562 1.35623 10.2823 1.50951 10.3694C1.66278 10.4565 1.83636 10.5016 2.01266 10.5H10.0127C10.1881 10.4998 10.3604 10.4535 10.5123 10.3656C10.6642 10.2778 10.7903 10.1515 10.8779 9.99955C10.9656 9.84756 11.0117 9.67518 11.0116 9.49973C11.0116 9.32428 10.9654 9.15193 10.8777 8.99999Z"
12
+ stroke="currentColor"
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ />
16
+ <path
17
+ d="M6.0127 4.5V6.5"
18
+ stroke="currentColor"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ />
22
+ <path
23
+ d="M6.0127 8.5H6.01853"
24
+ stroke="currentColor"
25
+ strokeLinecap="round"
26
+ strokeLinejoin="round"
27
+ />
28
+ </svg>
29
+ );
30
+
31
+ IconWarning.displayName = 'IconCircleWarning';
@@ -1,7 +1,6 @@
1
1
  import * as Popover from '@radix-ui/react-popover';
2
2
  import * as React from 'react';
3
3
  import { toast } from 'sonner';
4
- import { inter } from '../app/inter';
5
4
  import { Button } from './button';
6
5
  import { Text } from './text';
7
6
 
@@ -64,7 +63,7 @@ export const Send = ({ markup }: { markup: string }) => {
64
63
  <Popover.Portal>
65
64
  <Popover.Content
66
65
  align="end"
67
- className={`-mt-10 w-80 rounded-lg border border-slate-6 bg-black/70 p-3 font-sans text-slate-11 shadow-md backdrop-blur-lg ${inter.variable}`}
66
+ className="-mt-10 w-80 rounded-lg border border-slate-6 bg-black/70 p-3 font-sans text-slate-11 shadow-md backdrop-blur-lg font-sans"
68
67
  sideOffset={48}
69
68
  >
70
69
  <form className="mt-1" onSubmit={(e) => void onFormSubmit(e)}>
@@ -4,42 +4,36 @@ import * as React from 'react';
4
4
  import { cn } from '../utils';
5
5
  import { Logo } from './logo';
6
6
  import { Sidebar } from './sidebar';
7
- import { Topbar } from './topbar';
8
7
 
9
- type RootProps = React.ComponentPropsWithoutRef<'div'>;
10
-
11
- interface ShellProps extends RootProps {
12
- markup?: string;
8
+ interface ShellProps {
9
+ children: React.ReactNode;
13
10
  currentEmailOpenSlug?: string;
14
- pathSeparator?: string;
15
-
16
- activeView?: string;
17
- setActiveView?: (view: string) => void;
11
+ }
18
12
 
19
- viewWidth?: number;
20
- setViewWidth?: (width: number) => void;
21
- viewHeight?: number;
22
- setViewHeight?: (height: number) => void;
13
+ interface ShellContextValue {
14
+ sidebarToggled: boolean;
15
+ toggleSidebar: () => void;
23
16
  }
24
17
 
25
- export const Shell = ({
26
- currentEmailOpenSlug,
27
- children,
28
- pathSeparator,
29
- markup,
30
- activeView,
31
- setActiveView,
32
- viewHeight,
33
- viewWidth,
34
- setViewHeight,
35
- setViewWidth,
36
- }: ShellProps) => {
37
- const [sidebarToggled, setSidebarToggled] = React.useState(false);
38
- const [triggerTransition, setTriggerTransition] = React.useState(false);
18
+ export const ShellContext = React.createContext<ShellContextValue | undefined>(
19
+ undefined,
20
+ );
21
+
22
+ export const Shell = ({ children, currentEmailOpenSlug }: ShellProps) => {
23
+ const [sidebarToggled, setSidebarToggled] = React.useState(true);
39
24
 
40
25
  return (
41
- <>
42
- <div className="flex h-[4.375rem] items-center justify-between border-slate-6 border-b px-6 lg:hidden">
26
+ <ShellContext.Provider
27
+ value={{
28
+ toggleSidebar: () => setSidebarToggled((v) => !v),
29
+ sidebarToggled,
30
+ }}
31
+ >
32
+ <div
33
+ className={
34
+ 'flex h-[4.375rem] items-center justify-between border-slate-6 border-b px-6 lg:hidden'
35
+ }
36
+ >
43
37
  <div className="flex h-[4.375rem] items-center">
44
38
  <Logo />
45
39
  </div>
@@ -68,63 +62,36 @@ export const Shell = ({
68
62
  </svg>
69
63
  </button>
70
64
  </div>
71
- <React.Suspense>
72
- <Sidebar
73
- className={cn({
74
- 'lg:-translate-x-full translate-x-0': sidebarToggled,
75
- '-translate-x-full lg:translate-x-0': !sidebarToggled,
76
- })}
77
- currentEmailOpenSlug={currentEmailOpenSlug}
78
- markup={markup}
79
- style={{
80
- transition: triggerTransition ? 'transform 0.2s ease-in-out' : '',
81
- }}
82
- />
83
- </React.Suspense>
84
- <main
85
- className={cn(
86
- 'relative h-full max-h-full min-h-screen w-[100vw] overflow-hidden will-change-width sm:mt-[4.375rem] md:absolute md:right-0 lg:mt-0',
87
- {
88
- 'lg:w-[calc(100dvw)] lg:translate-x-0': sidebarToggled,
89
- 'lg:w-[calc(100dvw-20rem)] lg:translate-x-0': !sidebarToggled,
90
- },
91
- )}
92
- style={{
93
- transition: triggerTransition
94
- ? 'width 0.2s ease-in-out, transform 0.2s ease-in-out'
95
- : '',
96
- }}
97
- >
98
- <div className="relative h-full w-full">
99
- {currentEmailOpenSlug && pathSeparator ? (
100
- <Topbar
101
- activeView={activeView}
102
- currentEmailOpenSlug={currentEmailOpenSlug}
103
- markup={markup}
104
- onToggleSidebar={() => {
105
- setTriggerTransition(true);
65
+ <div className="flex w-[100dvw] h-[100dvh] flex-row">
66
+ <React.Suspense>
67
+ <Sidebar
68
+ className={cn('shrink [transition:width_0.2s_ease-in-out]', {
69
+ '-translate-x-full lg:translate-x-0': sidebarToggled,
70
+ 'lg:w-0': !sidebarToggled,
71
+ })}
72
+ currentEmailOpenSlug={currentEmailOpenSlug}
73
+ />
74
+ </React.Suspense>
75
+ <main
76
+ className={cn(
77
+ 'h-full max-h-full min-h-full overflow-hidden will-change-width lg:mt-0',
78
+ 'grow',
79
+ '[transition:width_0.2s_ease-in-out,_transform_0.2s_ease-in-out]',
80
+ )}
81
+ >
82
+ <div className="relative flex h-full w-full flex-col">{children}</div>
83
+ </main>
84
+ </div>
85
+ </ShellContext.Provider>
86
+ );
87
+ };
106
88
 
107
- requestAnimationFrame(() => {
108
- setSidebarToggled((v) => !v);
109
- });
89
+ type ShellContentRootProps = React.ComponentProps<'div'>;
110
90
 
111
- setTimeout(() => {
112
- setTriggerTransition(false);
113
- }, 300);
114
- }}
115
- pathSeparator={pathSeparator}
116
- setActiveView={setActiveView}
117
- setViewHeight={setViewHeight}
118
- setViewWidth={setViewWidth}
119
- viewHeight={viewHeight}
120
- viewWidth={viewWidth}
121
- />
122
- ) : null}
123
- <div className="relative mx-auto h-[calc(100dvh-3.3125rem)] grow md:h-full">
124
- {children}
125
- </div>
126
- </div>
127
- </main>
128
- </>
91
+ export const ShellContent = ({ children, ...rest }: ShellContentRootProps) => {
92
+ return (
93
+ <div {...rest} className={cn('relative grow', rest.className)}>
94
+ {children}
95
+ </div>
129
96
  );
130
97
  };
@@ -76,7 +76,8 @@ export const FileTreeDirectoryChildren = (props: {
76
76
  <motion.span
77
77
  animate={{ x: 0, opacity: 1 }}
78
78
  className={cn(
79
- 'relative flex h-8 max-w-full items-center rounded-md pl-3 align-middle text-slate-11 text-sm transition-colors duration-100 ease-[cubic-bezier(.6,.12,.34,.96)]',
79
+ 'relative flex h-8 max-w-full items-center gap-2 rounded-md align-middle text-slate-11 text-sm transition-colors duration-100 ease-[cubic-bezier(.6,.12,.34,.96)]',
80
+ props.isRoot ? undefined : 'pl-3',
80
81
  {
81
82
  'text-cyan-11': isCurrentPage,
82
83
  'hover:text-slate-12':
@@ -96,23 +97,25 @@ export const FileTreeDirectoryChildren = (props: {
96
97
  exit={{ opacity: 0 }}
97
98
  initial={{ opacity: 0 }}
98
99
  >
99
- <motion.div
100
- className="absolute top-1 left-[.625rem] h-6 w-px rounded-sm bg-cyan-11"
101
- layoutId="active-file"
102
- transition={{
103
- type: 'spring',
104
- bounce: 0.2,
105
- duration: 0.6,
106
- }}
107
- />
100
+ {props.isRoot ? null : (
101
+ <motion.div
102
+ className="absolute top-1 left-[0.4rem] inset-0 h-6 w-px rounded-sm bg-cyan-11"
103
+ layoutId="active-file"
104
+ transition={{
105
+ type: 'spring',
106
+ bounce: 0.2,
107
+ duration: 0.6,
108
+ }}
109
+ />
110
+ )}
108
111
  </motion.span>
109
112
  ) : null}
110
113
  <IconFile
111
- className="absolute left-4 h-5 w-5"
114
+ className="h-5 w-5"
112
115
  height="20"
113
116
  width="20"
114
117
  />
115
- <span className="truncate pl-8">{emailFilename}</span>
118
+ <span className="truncate">{emailFilename}</span>
116
119
  </motion.span>
117
120
  </Link>
118
121
  );
@@ -13,7 +13,7 @@ export const FileTree = ({
13
13
  emailsDirectoryMetadata,
14
14
  }: FileTreeProps) => {
15
15
  return (
16
- <div className="flex h-full w-full flex-col overflow-hidden lg:w-full lg:min-w-[14.5rem] lg:max-w-[14.5rem]">
16
+ <div className="flex h-full w-full flex-col overflow-hidden lg:w-full lg:min-w-[14.5rem]">
17
17
  <nav className="flex w-full flex-grow flex-col overflow-y-auto p-4 pr-0 pl-0">
18
18
  <Collapsible.Root open>
19
19
  <React.Suspense>