react-email 1.0.11 → 1.1.1

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 (151) hide show
  1. package/.eslintrc.js +4 -0
  2. package/.prettierrc.js +3 -0
  3. package/dist/_preview/components.d.ts +4 -0
  4. package/dist/_preview/components.js +41 -0
  5. package/dist/_preview/pages.d.ts +9 -0
  6. package/dist/_preview/pages.js +22 -0
  7. package/dist/_preview/root.d.ts +4 -0
  8. package/dist/_preview/root.js +25 -0
  9. package/dist/_preview/styles.d.ts +4 -0
  10. package/dist/_preview/styles.js +9 -0
  11. package/dist/_preview/utils.d.ts +4 -0
  12. package/dist/_preview/utils.js +17 -0
  13. package/dist/commands/dev.d.ts +1 -0
  14. package/dist/commands/dev.js +135 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +2 -29
  17. package/dist/utils/check-directory-exist.d.ts +1 -0
  18. package/dist/utils/check-directory-exist.js +9 -0
  19. package/dist/utils/check-empty-directory.d.ts +1 -0
  20. package/dist/utils/check-empty-directory.js +12 -0
  21. package/dist/utils/contants.d.ts +11 -0
  22. package/dist/utils/contants.js +24 -0
  23. package/dist/utils/create-directory.d.ts +1 -0
  24. package/dist/utils/create-directory.js +9 -0
  25. package/dist/utils/watcher.d.ts +2 -0
  26. package/dist/utils/watcher.js +22 -0
  27. package/package.json +6 -6
  28. package/preview/package.json +3 -18
  29. package/preview/src/components/layout.tsx +9 -44
  30. package/preview/src/components/sidebar.tsx +70 -66
  31. package/preview/src/components/topbar.tsx +44 -2
  32. package/preview/src/pages/_app.tsx +0 -6
  33. package/preview/src/pages/index.tsx +19 -4
  34. package/preview/src/pages/preview/[slug].tsx +13 -2
  35. package/scripts/prepare-preview.ts +177 -0
  36. package/source/_preview/components.ts +47 -0
  37. package/source/_preview/pages.ts +23 -0
  38. package/source/_preview/root.ts +27 -0
  39. package/source/_preview/styles.ts +6 -0
  40. package/source/_preview/utils.ts +16 -0
  41. package/source/commands/dev.ts +165 -0
  42. package/source/index.ts +16 -0
  43. package/source/utils/check-directory-exist.ts +4 -0
  44. package/source/utils/check-empty-directory.ts +6 -0
  45. package/source/utils/contants.ts +38 -0
  46. package/source/utils/create-directory.ts +4 -0
  47. package/source/utils/watcher.ts +22 -0
  48. package/tsconfig.json +36 -0
  49. package/preview/.next/BUILD_ID +0 -1
  50. package/preview/.next/build-manifest.json +0 -42
  51. package/preview/.next/cache/.tsbuildinfo +0 -1
  52. package/preview/.next/cache/next-server.js.nft.json +0 -290
  53. package/preview/.next/cache/webpack/client-development/0.pack +0 -0
  54. package/preview/.next/cache/webpack/client-development/1.pack +0 -0
  55. package/preview/.next/cache/webpack/client-development/10.pack +0 -0
  56. package/preview/.next/cache/webpack/client-development/11.pack +0 -0
  57. package/preview/.next/cache/webpack/client-development/12.pack +0 -0
  58. package/preview/.next/cache/webpack/client-development/13.pack +0 -0
  59. package/preview/.next/cache/webpack/client-development/14.pack +0 -0
  60. package/preview/.next/cache/webpack/client-development/15.pack +0 -0
  61. package/preview/.next/cache/webpack/client-development/16.pack +0 -0
  62. package/preview/.next/cache/webpack/client-development/2.pack +0 -0
  63. package/preview/.next/cache/webpack/client-development/3.pack +0 -0
  64. package/preview/.next/cache/webpack/client-development/4.pack +0 -0
  65. package/preview/.next/cache/webpack/client-development/5.pack +0 -0
  66. package/preview/.next/cache/webpack/client-development/6.pack +0 -0
  67. package/preview/.next/cache/webpack/client-development/7.pack +0 -0
  68. package/preview/.next/cache/webpack/client-development/8.pack +0 -0
  69. package/preview/.next/cache/webpack/client-development/9.pack +0 -0
  70. package/preview/.next/cache/webpack/client-development/index.pack +0 -0
  71. package/preview/.next/cache/webpack/client-development/index.pack.old +0 -0
  72. package/preview/.next/cache/webpack/client-production/0.pack +0 -0
  73. package/preview/.next/cache/webpack/client-production/index.pack +0 -0
  74. package/preview/.next/cache/webpack/server-development/0.pack +0 -0
  75. package/preview/.next/cache/webpack/server-development/1.pack +0 -0
  76. package/preview/.next/cache/webpack/server-development/10.pack +0 -0
  77. package/preview/.next/cache/webpack/server-development/11.pack +0 -0
  78. package/preview/.next/cache/webpack/server-development/12.pack +0 -0
  79. package/preview/.next/cache/webpack/server-development/13.pack +0 -0
  80. package/preview/.next/cache/webpack/server-development/14.pack +0 -0
  81. package/preview/.next/cache/webpack/server-development/15.pack +0 -0
  82. package/preview/.next/cache/webpack/server-development/16.pack +0 -0
  83. package/preview/.next/cache/webpack/server-development/2.pack +0 -0
  84. package/preview/.next/cache/webpack/server-development/3.pack +0 -0
  85. package/preview/.next/cache/webpack/server-development/4.pack +0 -0
  86. package/preview/.next/cache/webpack/server-development/5.pack +0 -0
  87. package/preview/.next/cache/webpack/server-development/6.pack +0 -0
  88. package/preview/.next/cache/webpack/server-development/7.pack +0 -0
  89. package/preview/.next/cache/webpack/server-development/8.pack +0 -0
  90. package/preview/.next/cache/webpack/server-development/9.pack +0 -0
  91. package/preview/.next/cache/webpack/server-development/index.pack +0 -0
  92. package/preview/.next/cache/webpack/server-development/index.pack.old +0 -0
  93. package/preview/.next/cache/webpack/server-production/0.pack +0 -0
  94. package/preview/.next/cache/webpack/server-production/index.pack +0 -0
  95. package/preview/.next/export-marker.json +0 -6
  96. package/preview/.next/images-manifest.json +0 -22
  97. package/preview/.next/next-server.js.nft.json +0 -290
  98. package/preview/.next/package.json +0 -3
  99. package/preview/.next/prerender-manifest.json +0 -24
  100. package/preview/.next/react-loadable-manifest.json +0 -1
  101. package/preview/.next/required-server-files.json +0 -113
  102. package/preview/.next/routes-manifest.json +0 -48
  103. package/preview/.next/server/chunks/664.js +0 -3836
  104. package/preview/.next/server/chunks/676.js +0 -35
  105. package/preview/.next/server/chunks/759.js +0 -1548
  106. package/preview/.next/server/chunks/font-manifest.json +0 -6
  107. package/preview/.next/server/font-loader-manifest.js +0 -4
  108. package/preview/.next/server/font-loader-manifest.json +0 -6
  109. package/preview/.next/server/font-manifest.json +0 -6
  110. package/preview/.next/server/middleware-build-manifest.js +0 -42
  111. package/preview/.next/server/middleware-manifest.json +0 -6
  112. package/preview/.next/server/middleware-react-loadable-manifest.js +0 -1
  113. package/preview/.next/server/pages/404.html +0 -12
  114. package/preview/.next/server/pages/500.html +0 -12
  115. package/preview/.next/server/pages/_app.js +0 -115
  116. package/preview/.next/server/pages/_app.js.nft.json +0 -29
  117. package/preview/.next/server/pages/_document.js +0 -1335
  118. package/preview/.next/server/pages/_document.js.nft.json +0 -33
  119. package/preview/.next/server/pages/_error.js +0 -195
  120. package/preview/.next/server/pages/_error.js.nft.json +0 -23
  121. package/preview/.next/server/pages/index.html +0 -1
  122. package/preview/.next/server/pages/index.js +0 -310
  123. package/preview/.next/server/pages/index.js.nft.json +0 -132
  124. package/preview/.next/server/pages/index.json +0 -1
  125. package/preview/.next/server/pages/preview/[slug].html +0 -1
  126. package/preview/.next/server/pages/preview/[slug].js +0 -388
  127. package/preview/.next/server/pages/preview/[slug].js.nft.json +0 -179
  128. package/preview/.next/server/pages-manifest.json +0 -8
  129. package/preview/.next/server/webpack-runtime.js +0 -213
  130. package/preview/.next/static/cY9PAzmXyKOoOW2gY4i5N/_buildManifest.js +0 -17
  131. package/preview/.next/static/cY9PAzmXyKOoOW2gY4i5N/_ssgManifest.js +0 -2
  132. package/preview/.next/static/chunks/727-0b09744222e89df8.js +0 -4402
  133. package/preview/.next/static/chunks/759-cdb5c7b41d03d871.js +0 -1135
  134. package/preview/.next/static/chunks/framework-3b5a00d5d7e8d93b.js +0 -9188
  135. package/preview/.next/static/chunks/main-50de763069eba4b2.js +0 -5078
  136. package/preview/.next/static/chunks/pages/_app-76f7305c0a91d681.js +0 -116
  137. package/preview/.next/static/chunks/pages/_error-8353112a01355ec2.js +0 -19
  138. package/preview/.next/static/chunks/pages/index-cd725955236a17bd.js +0 -44
  139. package/preview/.next/static/chunks/pages/preview/[slug]-e24c537be91754b2.js +0 -57
  140. package/preview/.next/static/chunks/polyfills-c67a75d1b6f99dc8.js +0 -6342
  141. package/preview/.next/static/chunks/webpack-0b5d8249fb15f5f3.js +0 -141
  142. package/preview/.next/static/css/8543b02f1dad7784.css +0 -3
  143. package/preview/.next/static/media/2aaf0723e720e8b9.p.woff2 +0 -0
  144. package/preview/.next/static/media/9c4f34569c9b36ca.woff2 +0 -0
  145. package/preview/.next/static/media/ae9ae6716d4f8bf8.woff2 +0 -0
  146. package/preview/.next/static/media/b1db3e28af9ef94a.woff2 +0 -0
  147. package/preview/.next/static/media/b967158bc7d7a9fb.woff2 +0 -0
  148. package/preview/.next/static/media/c0f5ec5bbf5913b7.woff2 +0 -0
  149. package/preview/.next/static/media/d1d9458b69004127.woff2 +0 -0
  150. package/preview/.next/trace +0 -7
  151. package/preview/yarn.lock +0 -1434
@@ -1,8 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { Topbar } from './topbar';
3
3
  import { Sidebar } from './sidebar';
4
- import * as ToggleGroup from '@radix-ui/react-toggle-group';
5
- import classnames from 'classnames';
6
4
 
7
5
  type LayoutElement = React.ElementRef<'div'>;
8
6
  type RootProps = React.ComponentPropsWithoutRef<'div'>;
@@ -23,48 +21,15 @@ export const Layout = React.forwardRef<LayoutElement, Readonly<LayoutProps>>(
23
21
  <div className="flex justify-between h-screen">
24
22
  <Sidebar navItems={navItems} />
25
23
  <main className="w-full bg-slate-2">
26
- {title && <Topbar title={title} />}
27
- <div className="pt-16 relative h-[calc(100vh_-_70px)] overflow-auto">
28
- {setViewMode && (
29
- <ToggleGroup.Root
30
- className="items-center bg-slate-2 p-1.5 flex gap-1 border border-slate-6 rounded-md absolute top-4 right-4"
31
- type="single"
32
- value={viewMode}
33
- aria-label="View mode"
34
- onValueChange={(value) => {
35
- if (!value) {
36
- return setViewMode('desktop');
37
- }
38
- setViewMode(value);
39
- }}
40
- >
41
- <ToggleGroup.Item
42
- className={classnames(
43
- 'text-sm text-slate-11 rounded px-1.5 py-0.5',
44
- {
45
- 'text-slate-12 bg-slate-3 font-medium':
46
- viewMode === 'desktop',
47
- },
48
- )}
49
- value="desktop"
50
- >
51
- Desktop
52
- </ToggleGroup.Item>
53
- <ToggleGroup.Item
54
- className={classnames(
55
- 'text-sm text-slate-11 rounded px-1.5 py-0.5',
56
- {
57
- 'text-slate-12 bg-slate-3 font-medium':
58
- viewMode === 'source',
59
- },
60
- )}
61
- value="source"
62
- >
63
- Source
64
- </ToggleGroup.Item>
65
- </ToggleGroup.Root>
66
- )}
67
- <div className="max-w-[600px] mx-auto">{children}</div>
24
+ {title && (
25
+ <Topbar
26
+ title={title}
27
+ viewMode={viewMode}
28
+ setViewMode={setViewMode}
29
+ />
30
+ )}
31
+ <div className="relative h-[calc(100vh_-_70px)] overflow-auto">
32
+ <div className="mx-auto">{children}</div>
68
33
  </div>
69
34
  </main>
70
35
  </div>
@@ -20,20 +20,20 @@ export const Sidebar = React.forwardRef<SidebarElement, Readonly<SidebarProps>>(
20
20
  return (
21
21
  <aside
22
22
  ref={forwardedRef}
23
- className="px-6 w-[275px] flex flex-col gap-4 border-r border-slate-6"
23
+ className="px-6 min-w-[275px] max-w-[275px] flex flex-col gap-4 border-r border-slate-6"
24
24
  {...props}
25
25
  >
26
26
  <div className="h-[70px] flex items-center">
27
27
  <Logo />
28
28
  </div>
29
29
 
30
- <Heading as="h2" color="gray" size="1" weight="medium">
31
- Email
32
- </Heading>
33
-
34
30
  <nav className="flex flex-col gap-4">
35
31
  <Collapsible.Root defaultOpen>
36
- <Collapsible.Trigger className="flex items-center gap-1 w-full">
32
+ <Collapsible.Trigger
33
+ className={classnames('flex items-center gap-1', {
34
+ 'cursor-default': navItems && navItems.length === 0,
35
+ })}
36
+ >
37
37
  <svg
38
38
  className="text-slate-11"
39
39
  width="24"
@@ -62,71 +62,75 @@ export const Sidebar = React.forwardRef<SidebarElement, Readonly<SidebarProps>>(
62
62
  <Heading as="h3" color="white" size="2" weight="medium">
63
63
  All emails
64
64
  </Heading>
65
- <svg
66
- className="text-slate-11"
67
- width="24"
68
- height="24"
69
- viewBox="0 0 24 24"
70
- fill="none"
71
- xmlns="http://www.w3.org/2000/svg"
72
- >
73
- <path
74
- d="M12 15L8.5359 9.75L15.4641 9.75L12 15Z"
75
- fill="currentColor"
76
- />
77
- </svg>
65
+ {navItems && navItems.length > 0 && (
66
+ <svg
67
+ className="text-slate-11"
68
+ width="24"
69
+ height="24"
70
+ viewBox="0 0 24 24"
71
+ fill="none"
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ >
74
+ <path
75
+ d="M12 15L8.5359 9.75L15.4641 9.75L12 15Z"
76
+ fill="currentColor"
77
+ />
78
+ </svg>
79
+ )}
78
80
  </div>
79
81
  </Collapsible.Trigger>
80
82
 
81
- <Collapsible.Content className="relative mt-3">
82
- <div className="absolute left-2.5 w-px h-full bg-slate-6" />
83
+ {navItems && navItems.length > 0 && (
84
+ <Collapsible.Content className="relative mt-3">
85
+ <div className="absolute left-2.5 w-px h-full bg-slate-6" />
83
86
 
84
- <div className="py-2 flex flex-col gap-1.5">
85
- {navItems &&
86
- navItems.map((item) => (
87
- <Link key={item} href={`/preview/${item}`}>
88
- <span
89
- className={classnames(
90
- 'text-[14px] flex items-center font-medium gap-2 h-8 w-full pl-4 pr-2 rounded-md text-slate-11',
91
- {
92
- 'bg-cyan-3 text-cyan-11': query.slug === item,
93
- 'hover:text-slate-12': query.slug !== item,
94
- },
95
- )}
96
- >
97
- {query.slug === item && (
98
- <div className="h-5 bg-cyan-11 w-px absolute left-2.5" />
99
- )}
100
- <svg
101
- width="24"
102
- height="24"
103
- viewBox="0 0 24 24"
104
- fill="none"
105
- xmlns="http://www.w3.org/2000/svg"
87
+ <div className="py-2 flex flex-col gap-1.5 truncate">
88
+ {navItems &&
89
+ navItems.map((item) => (
90
+ <Link key={item} href={`/preview/${item}`}>
91
+ <span
92
+ className={classnames(
93
+ 'text-[14px] flex items-center font-medium gap-2 h-8 w-full pl-4 pr-2 rounded-md text-slate-11',
94
+ {
95
+ 'bg-cyan-3 text-cyan-11': query.slug === item,
96
+ 'hover:text-slate-12': query.slug !== item,
97
+ },
98
+ )}
106
99
  >
107
- <path
108
- d="M7.75 19.25H16.25C17.3546 19.25 18.25 18.3546 18.25 17.25V9L14 4.75H7.75C6.64543 4.75 5.75 5.64543 5.75 6.75V17.25C5.75 18.3546 6.64543 19.25 7.75 19.25Z"
109
- stroke="currentColor"
110
- strokeOpacity="0.927"
111
- strokeWidth="1.5"
112
- strokeLinecap="round"
113
- strokeLinejoin="round"
114
- />
115
- <path
116
- d="M18 9.25H13.75V5"
117
- stroke="currentColor"
118
- strokeOpacity="0.927"
119
- strokeWidth="1.5"
120
- strokeLinecap="round"
121
- strokeLinejoin="round"
122
- />
123
- </svg>
124
- {item}
125
- </span>
126
- </Link>
127
- ))}
128
- </div>
129
- </Collapsible.Content>
100
+ {query.slug === item && (
101
+ <div className="h-5 bg-cyan-11 w-px absolute left-2.5" />
102
+ )}
103
+ <svg
104
+ width="24"
105
+ height="24"
106
+ viewBox="0 0 24 24"
107
+ fill="none"
108
+ xmlns="http://www.w3.org/2000/svg"
109
+ >
110
+ <path
111
+ d="M7.75 19.25H16.25C17.3546 19.25 18.25 18.3546 18.25 17.25V9L14 4.75H7.75C6.64543 4.75 5.75 5.64543 5.75 6.75V17.25C5.75 18.3546 6.64543 19.25 7.75 19.25Z"
112
+ stroke="currentColor"
113
+ strokeOpacity="0.927"
114
+ strokeWidth="1.5"
115
+ strokeLinecap="round"
116
+ strokeLinejoin="round"
117
+ />
118
+ <path
119
+ d="M18 9.25H13.75V5"
120
+ stroke="currentColor"
121
+ strokeOpacity="0.927"
122
+ strokeWidth="1.5"
123
+ strokeLinecap="round"
124
+ strokeLinejoin="round"
125
+ />
126
+ </svg>
127
+ {item}
128
+ </span>
129
+ </Link>
130
+ ))}
131
+ </div>
132
+ </Collapsible.Content>
133
+ )}
130
134
  </Collapsible.Root>
131
135
  </nav>
132
136
  </aside>
@@ -2,16 +2,19 @@ import * as React from 'react';
2
2
  import classnames from 'classnames';
3
3
  import { Heading } from './heading';
4
4
  import { Text } from './text';
5
+ import * as ToggleGroup from '@radix-ui/react-toggle-group';
5
6
 
6
7
  type TopbarElement = React.ElementRef<'header'>;
7
8
  type RootProps = React.ComponentPropsWithoutRef<'header'>;
8
9
 
9
10
  interface TopbarProps extends RootProps {
10
11
  title: string;
12
+ viewMode?: string;
13
+ setViewMode?: (viewMode: string) => void;
11
14
  }
12
15
 
13
16
  export const Topbar = React.forwardRef<TopbarElement, Readonly<TopbarProps>>(
14
- ({ className, title, ...props }, forwardedRef) => {
17
+ ({ className, title, viewMode, setViewMode, ...props }, forwardedRef) => {
15
18
  return (
16
19
  <header
17
20
  ref={forwardedRef}
@@ -36,7 +39,7 @@ export const Topbar = React.forwardRef<TopbarElement, Readonly<TopbarProps>>(
36
39
  <path
37
40
  d="M10.75 8.75L14.25 12L10.75 15.25"
38
41
  stroke="currentColor"
39
- stroke-opacity="0.615"
42
+ strokeOpacity="0.615"
40
43
  strokeWidth="1.5"
41
44
  strokeLinecap="round"
42
45
  strokeLinejoin="round"
@@ -47,6 +50,45 @@ export const Topbar = React.forwardRef<TopbarElement, Readonly<TopbarProps>>(
47
50
  {title}
48
51
  </Heading>
49
52
  </div>
53
+
54
+ {setViewMode && (
55
+ <ToggleGroup.Root
56
+ className="items-center bg-slate-2 p-1.5 flex gap-1 border border-slate-6 rounded-md absolute top-4 right-4"
57
+ type="single"
58
+ value={viewMode}
59
+ aria-label="View mode"
60
+ onValueChange={(value) => {
61
+ if (!value) {
62
+ return setViewMode('desktop');
63
+ }
64
+ setViewMode(value);
65
+ }}
66
+ >
67
+ <ToggleGroup.Item
68
+ className={classnames(
69
+ 'text-sm text-slate-11 rounded px-1.5 py-0.5',
70
+ {
71
+ 'text-slate-12 bg-slate-3 font-medium':
72
+ viewMode === 'desktop',
73
+ },
74
+ )}
75
+ value="desktop"
76
+ >
77
+ Desktop
78
+ </ToggleGroup.Item>
79
+ <ToggleGroup.Item
80
+ className={classnames(
81
+ 'text-sm text-slate-11 rounded px-1.5 py-0.5',
82
+ {
83
+ 'text-slate-12 bg-slate-3 font-medium': viewMode === 'source',
84
+ },
85
+ )}
86
+ value="source"
87
+ >
88
+ Source
89
+ </ToggleGroup.Item>
90
+ </ToggleGroup.Root>
91
+ )}
50
92
  </header>
51
93
  );
52
94
  },
@@ -14,12 +14,6 @@ function MyApp({ Component, pageProps }: AppProps) {
14
14
  <div className={classnames(inter.variable, 'font-sans')}>
15
15
  <Head>
16
16
  <title>React Email</title>
17
- <link rel="preconnect" href="https://fonts.googleapis.com" />
18
- <link rel="preconnect" href="https://fonts.gstatic.com" />
19
- <link
20
- href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap"
21
- rel="stylesheet"
22
- />
23
17
  </Head>
24
18
  <Component {...pageProps} />
25
19
  </div>
@@ -1,7 +1,8 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
- import { Heading } from '../components';
3
+ import { Button, Heading, Text } from '../components';
4
4
  import { Layout } from '../components/layout';
5
+ import Link from 'next/link';
5
6
 
6
7
  interface HomeProps {}
7
8
 
@@ -18,17 +19,31 @@ const getEmails = async () => {
18
19
  export async function getStaticProps({ params }) {
19
20
  try {
20
21
  const emails = await getEmails();
21
- return emails ? { props: { navItems: emails } } : { notFound: true };
22
+ return emails
23
+ ? { props: { navItems: emails } }
24
+ : { props: { navItems: [] } };
22
25
  } catch (error) {
23
26
  console.error(error);
24
- return { notFound: true };
27
+ return { props: { navItems: [] } };
25
28
  }
26
29
  }
27
30
 
28
31
  const Home: React.FC<Readonly<HomeProps>> = ({ navItems }: any) => {
29
32
  return (
30
33
  <Layout navItems={navItems}>
31
- <Heading>Hi</Heading>
34
+ <div className="max-w-md border border-slate-6 mx-auto mt-56 rounded-md p-8">
35
+ <Heading as="h2" weight="medium">
36
+ Welcome to the React Email preview!
37
+ </Heading>
38
+ <Text as="p" className="mt-2 mb-4">
39
+ To start developing your next email template, you can create a{' '}
40
+ <code>.jsx</code> or <code>.tsx</code> file under the "emails" folder.
41
+ </Text>
42
+
43
+ <Button asChild>
44
+ <Link href="https://react.email/docs">Check the docs</Link>
45
+ </Button>
46
+ </div>
32
47
  </Layout>
33
48
  );
34
49
  };
@@ -5,6 +5,7 @@ import { GetStaticPaths } from 'next';
5
5
  import { Layout } from '../../components/layout';
6
6
  import * as React from 'react';
7
7
  import { Code } from '../../components';
8
+ import Head from 'next/head';
8
9
 
9
10
  interface PreviewProps {}
10
11
 
@@ -48,6 +49,7 @@ const Preview: React.FC<Readonly<PreviewProps>> = ({
48
49
  slug,
49
50
  }: any) => {
50
51
  const [viewMode, setViewMode] = React.useState('desktop');
52
+ const title = `${slug} — React Email`;
51
53
 
52
54
  return (
53
55
  <Layout
@@ -56,10 +58,19 @@ const Preview: React.FC<Readonly<PreviewProps>> = ({
56
58
  viewMode={viewMode}
57
59
  setViewMode={setViewMode}
58
60
  >
61
+ <Head>
62
+ <title>{title}</title>
63
+ </Head>
59
64
  {viewMode === 'desktop' ? (
60
- <iframe srcDoc={markup} width="600" height="800" frameBorder="0" />
65
+ <iframe
66
+ srcDoc={markup}
67
+ frameBorder="0"
68
+ className="w-full h-[calc(100vh_-_70px)]"
69
+ />
61
70
  ) : (
62
- <Code>{markup}</Code>
71
+ <div className="max-w-[864px] mx-auto py-10">
72
+ <Code>{markup}</Code>
73
+ </div>
63
74
  )}
64
75
  </Layout>
65
76
  );
@@ -0,0 +1,177 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ const preparePreview = async () => {
5
+ Promise.all([
6
+ prepareRoot(),
7
+ prepareComponents(),
8
+ prepareStyles(),
9
+ prepareUtils(),
10
+ preparePages(),
11
+ ]);
12
+ };
13
+
14
+ const prepareRoot = async () => {
15
+ try {
16
+ const rootFiles = await fs.promises.readdir(path.join('preview'));
17
+ const files = rootFiles
18
+ .filter(
19
+ (file) => !['.next', 'yarn.lock', 'src', 'node_modules'].includes(file),
20
+ )
21
+ .map(async (rootFile) => {
22
+ return {
23
+ title: rootFile,
24
+ content: await fs.promises.readFile(path.join('preview', rootFile), {
25
+ encoding: 'utf-8',
26
+ }),
27
+ };
28
+ });
29
+
30
+ const componentFiles = await Promise.all(files);
31
+ const data = `export const root = ${JSON.stringify(componentFiles)}`;
32
+ await fs.promises.writeFile(
33
+ path.join('source', '_preview', 'root.ts'),
34
+ data,
35
+ );
36
+ } catch (error) {
37
+ console.error({ error });
38
+ }
39
+ };
40
+
41
+ const prepareComponents = async () => {
42
+ try {
43
+ const components = await fs.promises.readdir(
44
+ path.join('preview', 'src', 'components'),
45
+ );
46
+
47
+ const files = components.map(async (component) => {
48
+ return {
49
+ title: component,
50
+ content: await fs.promises.readFile(
51
+ path.join('preview', 'src', 'components', component),
52
+ { encoding: 'utf-8' },
53
+ ),
54
+ };
55
+ });
56
+
57
+ const componentFiles = await Promise.all(files);
58
+ const data = `export const components = ${JSON.stringify(componentFiles)}`;
59
+
60
+ await fs.promises.writeFile(
61
+ path.join('source', '_preview', 'components.ts'),
62
+ data,
63
+ );
64
+ } catch (error) {
65
+ console.error({ error });
66
+ }
67
+ };
68
+
69
+ const prepareStyles = async () => {
70
+ try {
71
+ const styles = await fs.promises.readdir(
72
+ path.join('preview', 'src', 'styles'),
73
+ );
74
+
75
+ const files = styles.map(async (style) => {
76
+ return {
77
+ title: style,
78
+ content: await fs.promises.readFile(
79
+ path.join('preview', 'src', 'styles', style),
80
+ { encoding: 'utf-8' },
81
+ ),
82
+ };
83
+ });
84
+
85
+ const styleFiles = await Promise.all(files);
86
+ const data = `export const styles = ${JSON.stringify(styleFiles)}`;
87
+
88
+ await fs.promises.writeFile(
89
+ path.join('source', '_preview', 'styles.ts'),
90
+ data,
91
+ );
92
+ } catch (error) {
93
+ console.error({ error });
94
+ }
95
+ };
96
+
97
+ const prepareUtils = async () => {
98
+ try {
99
+ const utils = await fs.promises.readdir(
100
+ path.join('preview', 'src', 'utils'),
101
+ );
102
+
103
+ const files = utils.map(async (util) => {
104
+ return {
105
+ title: util,
106
+ content: await fs.promises.readFile(
107
+ path.join('preview', 'src', 'utils', util),
108
+ { encoding: 'utf-8' },
109
+ ),
110
+ };
111
+ });
112
+
113
+ const utilFiles = await Promise.all(files);
114
+ const data = `export const utils = ${JSON.stringify(utilFiles)}`;
115
+
116
+ await fs.promises.writeFile(
117
+ path.join('source', '_preview', 'utils.ts'),
118
+ data,
119
+ );
120
+ } catch (error) {
121
+ console.error({ error });
122
+ }
123
+ };
124
+
125
+ const preparePages = async () => {
126
+ try {
127
+ const pages = await fs.promises.readdir(
128
+ path.join('preview', 'src', 'pages'),
129
+ );
130
+
131
+ const pageFiles = pages.map(async (page) => {
132
+ const isDirectory =
133
+ fs.existsSync(page) && fs.lstatSync(page).isDirectory();
134
+
135
+ if (isDirectory) {
136
+ const pFiles = await fs.promises.readdir(
137
+ path.join('preview', 'src', 'pages', page),
138
+ );
139
+
140
+ const files = pFiles.map(async (file) => {
141
+ return {
142
+ dir: page,
143
+ title: file,
144
+ content: await fs.promises.readFile(
145
+ path.join('preview', 'src', 'pages', page, file),
146
+ { encoding: 'utf-8' },
147
+ ),
148
+ };
149
+ });
150
+
151
+ const [f] = await Promise.all(files);
152
+
153
+ return f;
154
+ }
155
+
156
+ return {
157
+ title: page,
158
+ content: await fs.promises.readFile(
159
+ path.join('preview', 'src', 'pages', page),
160
+ { encoding: 'utf-8' },
161
+ ),
162
+ };
163
+ });
164
+
165
+ const allPages = await Promise.all(pageFiles);
166
+ const data = `export const pages = ${JSON.stringify(allPages)}`;
167
+
168
+ await fs.promises.writeFile(
169
+ path.join('source', '_preview', 'pages.ts'),
170
+ data,
171
+ );
172
+ } catch (error) {
173
+ console.error({ error });
174
+ }
175
+ };
176
+
177
+ preparePreview();