react-email 4.0.0-alpha.8 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +2 -55
  2. package/dist/cli/index.js +9 -3
  3. package/dist/cli/index.mjs +9 -3
  4. package/dist/preview/.next/BUILD_ID +1 -1
  5. package/dist/preview/.next/app-build-manifest.json +10 -10
  6. package/dist/preview/.next/build-manifest.json +3 -3
  7. package/dist/preview/.next/cache/.rscinfo +1 -1
  8. package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
  10. package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
  11. package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
  14. package/dist/preview/.next/next-server.js.nft.json +1 -1
  15. package/dist/preview/.next/prerender-manifest.json +3 -3
  16. package/dist/preview/.next/required-server-files.json +3 -3
  17. package/dist/preview/.next/server/app/_not-found/page.js +1 -1
  18. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
  20. package/dist/preview/.next/server/app/page.js +1 -1
  21. package/dist/preview/.next/server/app/page.js.nft.json +1 -1
  22. package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
  23. package/dist/preview/.next/server/app/preview/[...slug]/page.js +9 -9
  24. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
  25. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
  26. package/dist/preview/.next/server/chunks/203.js +1 -0
  27. package/dist/preview/.next/server/chunks/600.js +3 -3
  28. package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
  29. package/dist/preview/.next/server/next-font-manifest.js +1 -1
  30. package/dist/preview/.next/server/next-font-manifest.json +1 -1
  31. package/dist/preview/.next/server/pages/500.html +1 -1
  32. package/dist/preview/.next/server/server-reference-manifest.js +1 -1
  33. package/dist/preview/.next/server/server-reference-manifest.json +1 -1
  34. package/dist/preview/.next/static/chunks/683-017aee9270cb8999.js +1 -0
  35. package/dist/preview/.next/static/chunks/app/layout-311310b665ad8e17.js +1 -0
  36. package/dist/preview/.next/static/chunks/app/{page-9ea0bd45cd6294b0.js → page-9d038f3c5feb0570.js} +1 -1
  37. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-db4014629693f2b3.js +1 -0
  38. package/dist/preview/.next/static/chunks/{main-app-256b213b179a95cc.js → main-app-785b93ae096c4901.js} +1 -1
  39. package/dist/preview/.next/static/css/dda71861895dd2e4.css +3 -0
  40. package/dist/preview/.next/trace +26 -26
  41. package/dist/preview/.next/types/app/layout.ts +1 -1
  42. package/dist/preview/.next/types/app/page.ts +1 -1
  43. package/dist/preview/.next/types/app/preview/[...slug]/page.ts +1 -1
  44. package/package.json +1 -1
  45. package/src/app/layout.tsx +1 -1
  46. package/src/app/page.tsx +4 -4
  47. package/src/app/preview/[...slug]/preview.tsx +35 -26
  48. package/src/components/code-container.tsx +2 -2
  49. package/src/components/code.tsx +16 -7
  50. package/src/components/resizable-wrapper.tsx +3 -3
  51. package/src/components/shell.tsx +13 -18
  52. package/src/components/sidebar/file-tree-directory-children.tsx +3 -11
  53. package/src/components/sidebar/file-tree-directory.tsx +9 -17
  54. package/src/components/sidebar/sidebar.tsx +1 -1
  55. package/src/components/toolbar/linter.tsx +35 -33
  56. package/src/components/toolbar/results.tsx +1 -1
  57. package/src/components/toolbar.tsx +53 -24
  58. package/src/components/topbar.tsx +17 -5
  59. package/tailwind.config.ts +8 -0
  60. package/dist/preview/.next/server/chunks/42.js +0 -1
  61. package/dist/preview/.next/static/chunks/683-b769e5d91bdf9a82.js +0 -1
  62. package/dist/preview/.next/static/chunks/app/layout-7dee682873546401.js +0 -1
  63. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-a610d641c64448cc.js +0 -1
  64. package/dist/preview/.next/static/css/e68ebc9bb8f7b3f4.css +0 -3
  65. /package/dist/preview/.next/static/{SoPVDfPAp9R983pBBriVn → t4yeIF5ZYqVHaYIHgPxHn}/_buildManifest.js +0 -0
  66. /package/dist/preview/.next/static/{SoPVDfPAp9R983pBBriVn → t4yeIF5ZYqVHaYIHgPxHn}/_ssgManifest.js +0 -0
@@ -1,4 +1,4 @@
1
- // File: /home/gabriel/Projects/Resend/react-email/packages/react-email/src/app/layout.tsx
1
+ // File: /home/gabriel/Projects/Resend/react-email.git/release/packages/react-email/src/app/layout.tsx
2
2
  import * as entry from '../../../src/app/layout.js'
3
3
  import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
4
4
 
@@ -1,4 +1,4 @@
1
- // File: /home/gabriel/Projects/Resend/react-email/packages/react-email/src/app/page.tsx
1
+ // File: /home/gabriel/Projects/Resend/react-email.git/release/packages/react-email/src/app/page.tsx
2
2
  import * as entry from '../../../src/app/page.js'
3
3
  import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
4
4
 
@@ -1,4 +1,4 @@
1
- // File: /home/gabriel/Projects/Resend/react-email/packages/react-email/src/app/preview/[...slug]/page.tsx
1
+ // File: /home/gabriel/Projects/Resend/react-email.git/release/packages/react-email/src/app/preview/[...slug]/page.tsx
2
2
  import * as entry from '../../../../../src/app/preview/[...slug]/page.js'
3
3
  import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-email",
3
- "version": "4.0.0-alpha.8",
3
+ "version": "4.0.0",
4
4
  "description": "A live preview of your emails right in your browser.",
5
5
  "bin": {
6
6
  "email": "./dist/cli/index.js"
@@ -27,7 +27,7 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
27
27
  className={`${inter.variable} ${sfMono.variable} font-sans`}
28
28
  lang="en"
29
29
  >
30
- <body className="relative flex h-screen flex-col overflow-x-hidden bg-black text-slate-11 leading-loose selection:bg-cyan-5 selection:text-cyan-12">
30
+ <body className="relative flex h-screen flex-col bg-black text-slate-11 leading-loose selection:bg-cyan-5 selection:text-cyan-12">
31
31
  <EmailsProvider
32
32
  initialEmailsDirectoryMetadata={emailsDirectoryMetadata}
33
33
  >
package/src/app/page.tsx CHANGED
@@ -3,7 +3,7 @@ import Image from 'next/image';
3
3
  import Link from 'next/link';
4
4
  import { Button, Heading, Text } from '../components';
5
5
  import CodeSnippet from '../components/code-snippet';
6
- import { Shell, ShellContent } from '../components/shell';
6
+ import { Shell } from '../components/shell';
7
7
  import { emailsDirectoryAbsolutePath } from './env';
8
8
  import logo from './logo.png';
9
9
 
@@ -12,8 +12,8 @@ const Home = () => {
12
12
 
13
13
  return (
14
14
  <Shell>
15
- <ShellContent className="mx-auto flex max-w-lg items-center justify-center p-8">
16
- <div className="-mt-10 relative flex flex-col items-center gap-3 text-center">
15
+ <div className="w-full h-full flex items-center justify-center p-8">
16
+ <div className="-mt-10 relative max-w-lg flex flex-col items-center gap-3 text-center">
17
17
  <Image
18
18
  alt="React Email Icon"
19
19
  className="mb-8"
@@ -38,7 +38,7 @@ const Home = () => {
38
38
  <Link href="https://react.email/docs">Check the docs</Link>
39
39
  </Button>
40
40
  </div>
41
- </ShellContent>
41
+ </div>
42
42
  </Shell>
43
43
  );
44
44
  };
@@ -1,30 +1,31 @@
1
1
  'use client';
2
2
 
3
3
  import { usePathname, useRouter, useSearchParams } from 'next/navigation';
4
- import { use, useRef } from 'react';
4
+ import { use, useState } from 'react';
5
5
  import { flushSync } from 'react-dom';
6
6
  import { Toaster } from 'sonner';
7
7
  import { useDebouncedCallback } from 'use-debounce';
8
8
  import { Topbar } from '../../../components';
9
9
  import { CodeContainer } from '../../../components/code-container';
10
10
  import {
11
- ResizableWarpper,
11
+ ResizableWrapper,
12
12
  makeIframeDocumentBubbleEvents,
13
13
  } from '../../../components/resizable-wrapper';
14
14
  import { Send } from '../../../components/send';
15
- import { ShellContent } from '../../../components/shell';
15
+ import { useToolbarState } from '../../../components/toolbar';
16
16
  import { Tooltip } from '../../../components/tooltip';
17
17
  import { ActiveViewToggleGroup } from '../../../components/topbar/active-view-toggle-group';
18
18
  import { ViewSizeControls } from '../../../components/topbar/view-size-controls';
19
19
  import { PreviewContext } from '../../../contexts/preview';
20
20
  import { useClampedState } from '../../../hooks/use-clamped-state';
21
+ import { cn } from '../../../utils';
21
22
  import { RenderingError } from './rendering-error';
22
23
 
23
- interface PreviewProps {
24
+ interface PreviewProps extends React.ComponentProps<'div'> {
24
25
  emailTitle: string;
25
26
  }
26
27
 
27
- const Preview = ({ emailTitle }: PreviewProps) => {
28
+ const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
28
29
  const { renderingResult, renderedEmailMetadata } = use(PreviewContext)!;
29
30
 
30
31
  const router = useRouter();
@@ -53,21 +54,21 @@ const Preview = ({ emailTitle }: PreviewProps) => {
53
54
  const hasRenderingMetadata = typeof renderedEmailMetadata !== 'undefined';
54
55
  const hasErrors = 'error' in renderingResult;
55
56
 
56
- const maxWidthRef = useRef(Number.POSITIVE_INFINITY);
57
- const maxHeightRef = useRef(Number.POSITIVE_INFINITY);
58
- const minWidth = 350;
59
- const minHeight = 600;
57
+ const [maxWidth, setMaxWidth] = useState(Number.POSITIVE_INFINITY);
58
+ const [maxHeight, setMaxHeight] = useState(Number.POSITIVE_INFINITY);
59
+ const minWidth = 100;
60
+ const minHeight = 100;
60
61
  const storedWidth = searchParams.get('width');
61
62
  const storedHeight = searchParams.get('height');
62
63
  const [width, setWidth] = useClampedState(
63
64
  storedWidth ? Number.parseInt(storedWidth) : 600,
64
- 350,
65
- maxWidthRef.current,
65
+ minWidth,
66
+ maxWidth,
66
67
  );
67
68
  const [height, setHeight] = useClampedState(
68
69
  storedHeight ? Number.parseInt(storedHeight) : 1024,
69
- 600,
70
- maxHeightRef.current,
70
+ minHeight,
71
+ maxHeight,
71
72
  );
72
73
 
73
74
  const handleSaveViewSize = useDebouncedCallback(() => {
@@ -77,6 +78,8 @@ const Preview = ({ emailTitle }: PreviewProps) => {
77
78
  router.push(`${pathname}?${params.toString()}${location.hash}`);
78
79
  }, 300);
79
80
 
81
+ const { toggled: toolbarToggled } = useToolbarState();
82
+
80
83
  return (
81
84
  <>
82
85
  <Topbar emailTitle={emailTitle}>
@@ -107,14 +110,20 @@ const Preview = ({ emailTitle }: PreviewProps) => {
107
110
  ) : null}
108
111
  </Topbar>
109
112
 
110
- <ShellContent
111
- className="relative flex bg-gray-200"
113
+ <div
114
+ {...props}
115
+ className={cn(
116
+ 'h-[calc(100%-3.5rem-2.375rem)] will-change-height flex p-4 transition-all duration-300',
117
+ activeView === 'preview' && 'bg-gray-200',
118
+ toolbarToggled && 'h-[calc(100%-3.5rem-13rem)]',
119
+ className,
120
+ )}
112
121
  ref={(element) => {
113
122
  const observer = new ResizeObserver((entry) => {
114
123
  const [elementEntry] = entry;
115
124
  if (elementEntry) {
116
- maxWidthRef.current = elementEntry.contentRect.width - 80;
117
- maxHeightRef.current = elementEntry.contentRect.height - 80;
125
+ setMaxWidth(elementEntry.contentRect.width);
126
+ setMaxHeight(elementEntry.contentRect.height);
118
127
  }
119
128
  });
120
129
 
@@ -132,11 +141,11 @@ const Preview = ({ emailTitle }: PreviewProps) => {
132
141
  {hasRenderingMetadata ? (
133
142
  <>
134
143
  {activeView === 'preview' && (
135
- <ResizableWarpper
144
+ <ResizableWrapper
136
145
  minHeight={minHeight}
137
146
  minWidth={minWidth}
138
- maxHeight={maxHeightRef.current}
139
- maxWidth={maxWidthRef.current}
147
+ maxHeight={maxHeight}
148
+ maxWidth={maxWidth}
140
149
  height={height}
141
150
  onResizeEnd={() => {
142
151
  handleSaveViewSize();
@@ -145,9 +154,9 @@ const Preview = ({ emailTitle }: PreviewProps) => {
145
154
  const isHorizontal =
146
155
  direction === 'east' || direction === 'west';
147
156
  if (isHorizontal) {
148
- setWidth(value);
157
+ setWidth(Math.round(value));
149
158
  } else {
150
- setHeight(value);
159
+ setHeight(Math.round(value));
151
160
  }
152
161
  }}
153
162
  width={width}
@@ -166,12 +175,12 @@ const Preview = ({ emailTitle }: PreviewProps) => {
166
175
  }}
167
176
  title={emailTitle}
168
177
  />
169
- </ResizableWarpper>
178
+ </ResizableWrapper>
170
179
  )}
171
180
 
172
181
  {activeView === 'source' && (
173
- <div className="h-full w-full bg-black">
174
- <div className="m-auto flex max-w-3xl p-6">
182
+ <div className="h-full w-full">
183
+ <div className="m-auto h-full flex max-w-3xl p-6">
175
184
  <Tooltip.Provider>
176
185
  <CodeContainer
177
186
  activeLang={activeLang}
@@ -199,7 +208,7 @@ const Preview = ({ emailTitle }: PreviewProps) => {
199
208
  ) : null}
200
209
 
201
210
  <Toaster />
202
- </ShellContent>
211
+ </div>
203
212
  </>
204
213
  );
205
214
  };
@@ -39,7 +39,7 @@ export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
39
39
 
40
40
  return (
41
41
  <div
42
- className="relative w-full items-center whitespace-pre rounded-md border border-slate-6 text-sm"
42
+ className="relative max-h-[650px] w-full h-full whitespace-pre rounded-md border border-slate-6 text-sm"
43
43
  style={{
44
44
  lineHeight: '130%',
45
45
  background:
@@ -84,7 +84,7 @@ export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
84
84
  filename={`email.${activeMarkup.language}`}
85
85
  />
86
86
  </div>
87
- <div>
87
+ <div className="h-[calc(100%-2.25rem)]">
88
88
  <Code language={activeLang}>{activeMarkup.content}</Code>
89
89
  </div>
90
90
  </div>
@@ -3,7 +3,7 @@ import Link from 'next/link';
3
3
  import { useSearchParams } from 'next/navigation';
4
4
  import type { Language } from 'prism-react-renderer';
5
5
  import { Highlight } from 'prism-react-renderer';
6
- import { Fragment, useEffect } from 'react';
6
+ import { Fragment, useEffect, useRef } from 'react';
7
7
  import { useFragmentIdentifier } from '../hooks/use-fragment-identifier';
8
8
  import { cn } from '../utils';
9
9
 
@@ -73,12 +73,18 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
73
73
  return highlight[0] <= line && highlight[1] >= line;
74
74
  };
75
75
 
76
+ const scrollerRef = useRef<HTMLDivElement>(null);
77
+
76
78
  useEffect(() => {
77
- if (highlight) {
78
- document.getElementById(`L${highlight[0]}`)?.scrollIntoView({
79
- block: 'start',
80
- behavior: 'smooth',
81
- });
79
+ const scroller = scrollerRef.current;
80
+ if (highlight && scroller) {
81
+ const lineElement = scroller.querySelector(`#L${highlight[0]}`);
82
+ if (lineElement instanceof HTMLAnchorElement) {
83
+ scroller.scrollTo({
84
+ top: Math.max(lineElement.offsetTop - 325, 0),
85
+ behavior: 'smooth',
86
+ });
87
+ }
82
88
  }
83
89
  }, [highlight]);
84
90
 
@@ -97,7 +103,10 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
97
103
  'linear-gradient(90deg, rgba(56, 189, 248, 0) 0%, rgba(56, 189, 248, 0) 0%, rgba(232, 232, 232, 0.2) 33.02%, rgba(143, 143, 143, 0.6719) 64.41%, rgba(236, 72, 153, 0) 98.93%)',
98
104
  }}
99
105
  />
100
- <div className="flex h-[650px] p-4 max-h-[calc(100vh-10rem)] after:w-full after:static after:block after:h-4 after:content-[''] overflow-auto">
106
+ <div
107
+ ref={scrollerRef}
108
+ className="flex max-h-[650px] h-full p-4 after:w-full after:static after:block after:h-4 after:content-[''] overflow-auto"
109
+ >
101
110
  <div className="text-[#49494f] text-[13px] font-light font-[MonoLisa,_Menlo,_monospace]">
102
111
  {tokens.map((_, i) => (
103
112
  <Link
@@ -4,7 +4,7 @@ import { cn } from '../utils';
4
4
 
5
5
  type Direction = 'north' | 'south' | 'east' | 'west';
6
6
 
7
- type ResizableWarpperProps = {
7
+ type ResizableWrapperProps = {
8
8
  width: number;
9
9
  height: number;
10
10
 
@@ -41,7 +41,7 @@ export const makeIframeDocumentBubbleEvents = (iframe: HTMLIFrameElement) => {
41
41
  };
42
42
  };
43
43
 
44
- export const ResizableWarpper = ({
44
+ export const ResizableWrapper = ({
45
45
  width,
46
46
  height,
47
47
  onResize,
@@ -54,7 +54,7 @@ export const ResizableWarpper = ({
54
54
  minWidth,
55
55
 
56
56
  ...rest
57
- }: ResizableWarpperProps) => {
57
+ }: ResizableWrapperProps) => {
58
58
  const resizableRef = useRef<HTMLElement>(null);
59
59
 
60
60
  const mouseMoveListener = useRef<(event: MouseEvent) => void>(null);
@@ -62,36 +62,31 @@ export const Shell = ({ children, currentEmailOpenSlug }: ShellProps) => {
62
62
  </svg>
63
63
  </button>
64
64
  </div>
65
- <div className="flex w-[100dvw] h-[100dvh] flex-row">
65
+ <div className="w-[100dvw] flex h-[calc(100dvh-4.375rem)] lg:h-[100dvh]">
66
66
  <React.Suspense>
67
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
- })}
68
+ className={cn(
69
+ 'fixed top-[4.375rem] left-0 z-[9999] h-full max-h-full w-full max-w-full will-change-auto [transition:width_0.2s_ease-in-out]',
70
+ 'lg:static lg:inline-block lg:z-auto lg:max-h-full lg:w-[16rem]',
71
+ {
72
+ '-translate-x-full lg:translate-x-0': sidebarToggled,
73
+ 'lg:w-0': !sidebarToggled,
74
+ },
75
+ )}
72
76
  currentEmailOpenSlug={currentEmailOpenSlug}
73
77
  />
74
78
  </React.Suspense>
75
79
  <main
76
80
  className={cn(
77
- 'h-full max-h-full min-h-full overflow-hidden will-change-width lg:mt-0',
78
- 'grow',
81
+ 'inline-block relative overflow-hidden will-change-width',
82
+ 'w-full h-full',
79
83
  '[transition:width_0.2s_ease-in-out,_transform_0.2s_ease-in-out]',
84
+ sidebarToggled && 'lg:w-[calc(100%-16rem)]',
80
85
  )}
81
86
  >
82
- <div className="relative flex h-full w-full flex-col">{children}</div>
87
+ {children}
83
88
  </main>
84
89
  </div>
85
90
  </ShellContext.Provider>
86
91
  );
87
92
  };
88
-
89
- type ShellContentRootProps = React.ComponentProps<'div'>;
90
-
91
- export const ShellContent = ({ children, ...rest }: ShellContentRootProps) => {
92
- return (
93
- <div {...rest} className={cn('relative grow', rest.className)}>
94
- {children}
95
- </div>
96
- );
97
- };
@@ -5,7 +5,6 @@ import { useSearchParams } from 'next/navigation';
5
5
  import { cn } from '../../utils';
6
6
  import type { EmailsDirectory } from '../../utils/get-emails-directory-metadata';
7
7
  import { IconFile } from '../icons/icon-file';
8
- import { Tooltip } from '../tooltip';
9
8
  import { FileTreeDirectory } from './file-tree-directory';
10
9
 
11
10
  export const FileTreeDirectoryChildren = (props: {
@@ -117,16 +116,9 @@ export const FileTreeDirectoryChildren = (props: {
117
116
  height="20"
118
117
  width="20"
119
118
  />
120
- <Tooltip.Provider>
121
- <Tooltip>
122
- <Tooltip.Trigger asChild>
123
- <span className="truncate w-[calc(100%-1.25rem)]">
124
- {emailFilename}
125
- </span>
126
- </Tooltip.Trigger>
127
- <Tooltip.Content>{emailFilename}</Tooltip.Content>
128
- </Tooltip>
129
- </Tooltip.Provider>
119
+ <span className="truncate w-[calc(100%-1.25rem)]">
120
+ {emailFilename}
121
+ </span>
130
122
  </motion.span>
131
123
  </Link>
132
124
  );
@@ -7,7 +7,6 @@ import { Heading } from '../heading';
7
7
  import { IconArrowDown } from '../icons/icon-arrow-down';
8
8
  import { IconFolder } from '../icons/icon-folder';
9
9
  import { IconFolderOpen } from '../icons/icon-folder-open';
10
- import { Tooltip } from '../tooltip';
11
10
  import { FileTreeDirectoryChildren } from './file-tree-directory-children';
12
11
 
13
12
  interface SidebarDirectoryProps {
@@ -63,22 +62,15 @@ export const FileTreeDirectory = ({
63
62
  ) : (
64
63
  <IconFolder height="20" width="20" />
65
64
  )}
66
- <Tooltip.Provider>
67
- <Tooltip>
68
- <Tooltip.Trigger asChild>
69
- <Heading
70
- as="h3"
71
- className="transition grow w-[calc(100%-40px)] truncate duration-200 ease-in-out hover:text-slate-12"
72
- color="gray"
73
- size="2"
74
- weight="medium"
75
- >
76
- {directoryMetadata.directoryName}
77
- </Heading>
78
- </Tooltip.Trigger>
79
- <Tooltip.Content>{directoryMetadata.directoryName}</Tooltip.Content>
80
- </Tooltip>
81
- </Tooltip.Provider>
65
+ <Heading
66
+ as="h3"
67
+ className="transition grow w-[calc(100%-40px)] truncate duration-200 ease-in-out hover:text-slate-12"
68
+ color="gray"
69
+ size="2"
70
+ weight="medium"
71
+ >
72
+ {directoryMetadata.directoryName}
73
+ </Heading>
82
74
  {!isEmpty ? (
83
75
  <IconArrowDown
84
76
  width="20"
@@ -16,7 +16,7 @@ export const Sidebar = ({ className, currentEmailOpenSlug }: SidebarProps) => {
16
16
  return (
17
17
  <aside
18
18
  className={cn(
19
- 'fixed top-[4.375rem] left-0 z-[9999] h-full max-h-full w-screen max-w-full bg-black will-change-auto',
19
+ 'overflow-hidden bg-black',
20
20
  'lg:static lg:z-auto lg:max-h-screen lg:w-[16rem]',
21
21
  className,
22
22
  )}
@@ -92,6 +92,22 @@ export const Linter = ({ rows }: LinterProps) => {
92
92
  const failingCheck = row.result.checks.find(
93
93
  (check) => check.passed === false,
94
94
  )!;
95
+ const metadata: React.ReactNode[] = [];
96
+ for (const check of row.result.checks) {
97
+ if (
98
+ check.type === 'fetch_attempt' &&
99
+ check.metadata.fetchStatusCode
100
+ ) {
101
+ metadata.push(<>HTTP {check.metadata.fetchStatusCode}</>);
102
+ }
103
+ }
104
+ metadata.push(
105
+ <CodePreviewLineLink
106
+ line={row.result.codeLocation.line}
107
+ column={row.result.codeLocation.column}
108
+ type="html"
109
+ />,
110
+ );
95
111
  return (
96
112
  <Result status={row.result.status} key={i}>
97
113
  <Result.Name>{sanitize(failingCheck.type)}</Result.Name>
@@ -102,20 +118,14 @@ export const Linter = ({ rows }: LinterProps) => {
102
118
  {failingCheck.type === 'fetch_attempt' &&
103
119
  failingCheck.metadata.fetchStatusCode &&
104
120
  failingCheck.metadata.fetchStatusCode >= 300 &&
105
- failingCheck.metadata.fetchStatusCode < 400 ? (
106
- <>
107
- <strong>{failingCheck.metadata.fetchStatusCode}</strong>:
108
- There was a redirect, the content may have been moved
109
- </>
110
- ) : null}
121
+ failingCheck.metadata.fetchStatusCode < 400
122
+ ? 'There was a redirect, the content may have been moved'
123
+ : null}
111
124
  {failingCheck.type === 'fetch_attempt' &&
112
125
  failingCheck.metadata.fetchStatusCode &&
113
- failingCheck.metadata.fetchStatusCode >= 400 ? (
114
- <>
115
- <strong>{failingCheck.metadata.fetchStatusCode}</strong>:
116
- The link is broken
117
- </>
118
- ) : null}
126
+ failingCheck.metadata.fetchStatusCode >= 400
127
+ ? 'The link is broken'
128
+ : null}
119
129
  {failingCheck.type === 'syntax'
120
130
  ? 'The link is broken due to invalid syntax'
121
131
  : null}
@@ -124,15 +134,7 @@ export const Linter = ({ rows }: LinterProps) => {
124
134
  {row.result.link}
125
135
  </span>
126
136
  </Result.Description>
127
- <Result.Metadata>
128
- {[
129
- <CodePreviewLineLink
130
- line={row.result.codeLocation.line}
131
- column={row.result.codeLocation.column}
132
- type="html"
133
- />,
134
- ]}
135
- </Result.Metadata>
137
+ <Result.Metadata>{metadata}</Result.Metadata>
136
138
  </Result>
137
139
  );
138
140
  }
@@ -146,6 +148,12 @@ export const Linter = ({ rows }: LinterProps) => {
146
148
  if (check.type === 'image_size' && check.metadata.byteCount) {
147
149
  metadata.push(prettyBytes(check.metadata.byteCount));
148
150
  }
151
+ if (
152
+ check.type === 'fetch_attempt' &&
153
+ check.metadata.fetchStatusCode
154
+ ) {
155
+ metadata.push(<>HTTP {check.metadata.fetchStatusCode}</>);
156
+ }
149
157
  }
150
158
  metadata.push(
151
159
  <CodePreviewLineLink
@@ -164,20 +172,14 @@ export const Linter = ({ rows }: LinterProps) => {
164
172
  {failingCheck.type === 'fetch_attempt' &&
165
173
  failingCheck.metadata.fetchStatusCode &&
166
174
  failingCheck.metadata.fetchStatusCode >= 300 &&
167
- failingCheck.metadata.fetchStatusCode < 400 ? (
168
- <>
169
- <strong>{failingCheck.metadata.fetchStatusCode}</strong>:
170
- There was a redirect, the image may have been moved
171
- </>
172
- ) : null}
175
+ failingCheck.metadata.fetchStatusCode < 400
176
+ ? 'There was a redirect, the image may have been moved'
177
+ : null}
173
178
  {failingCheck.type === 'fetch_attempt' &&
174
179
  failingCheck.metadata.fetchStatusCode &&
175
- failingCheck.metadata.fetchStatusCode >= 400 ? (
176
- <>
177
- <strong>{failingCheck.metadata.fetchStatusCode}</strong>:
178
- The image is broken
179
- </>
180
- ) : null}
180
+ failingCheck.metadata.fetchStatusCode >= 400
181
+ ? 'The image is broken'
182
+ : null}
181
183
  {failingCheck.type === 'syntax'
182
184
  ? 'The image is broken due to an invalid source'
183
185
  : null}
@@ -25,7 +25,7 @@ Results.Row = ({
25
25
  return (
26
26
  <tr
27
27
  className={cn(
28
- 'border-collapse align-bottom border-slate-6 border-b',
28
+ 'border-collapse align-bottom border-slate-6 border-b last:border-b-0',
29
29
  className,
30
30
  )}
31
31
  {...props}