-
Notifications
You must be signed in to change notification settings - Fork 665
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ec4b691
commit 37387e3
Showing
37 changed files
with
4,319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/// <reference types="next" /> | ||
/// <reference types="next/image-types/global" /> | ||
|
||
// NOTE: This file should not be edited | ||
// see https://nextjs.org/docs/basic-features/typescript for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** @type {import('next').NextConfig} */ | ||
const nextConfig = { | ||
reactStrictMode: true, | ||
swcMinify: true, | ||
experimental: { | ||
appDir: true, | ||
}, | ||
}; | ||
|
||
module.exports = nextConfig; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
{ | ||
"name": "react-email-client", | ||
"version": "0.0.11", | ||
"description": "The React Email preview application", | ||
"license": "MIT", | ||
"scripts": { | ||
"dev": "next dev", | ||
"build": "next build", | ||
"start": "next start", | ||
"lint": "next lint", | ||
"format:check": "prettier --check \"**/*.{ts,tsx,md}\"", | ||
"format": "prettier --write \"**/*.{ts,tsx,md}\"" | ||
}, | ||
"engines": { | ||
"node": ">=16.0.0" | ||
}, | ||
"dependencies": { | ||
"@radix-ui/colors": "0.1.8", | ||
"@radix-ui/react-collapsible": "1.0.1", | ||
"@radix-ui/react-popover": "1.0.2", | ||
"@radix-ui/react-slot": "1.0.1", | ||
"@radix-ui/react-toggle-group": "1.0.1", | ||
"@radix-ui/react-tooltip": "1.0.2", | ||
"@react-email/render": "0.0.6", | ||
"classnames": "2.3.2", | ||
"framer-motion": "8.4.6", | ||
"next": "13.2.4", | ||
"prism-react-renderer": "1.3.5", | ||
"react": "18.2.0", | ||
"react-dom": "18.2.0" | ||
}, | ||
"devDependencies": { | ||
"@types/classnames": "2.3.1", | ||
"@types/node": "18.11.9", | ||
"@types/react": "18.0.25", | ||
"@types/react-dom": "18.0.9", | ||
"autoprefixer": "10.4.13", | ||
"eslint-config-next": "13.2.4", | ||
"postcss": "8.4.19", | ||
"tailwindcss": "3.2.4", | ||
"typescript": "4.9.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
plugins: { | ||
tailwindcss: {}, | ||
autoprefixer: {}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
'use client'; | ||
|
||
import { Button, Heading, Text } from '../components'; | ||
import { Shell } from '../components/shell'; | ||
import Link from 'next/link'; | ||
|
||
export default function Home({ navItems }) { | ||
return ( | ||
<Shell navItems={navItems}> | ||
<div className="max-w-md border border-slate-6 mx-auto mt-56 rounded-md p-8"> | ||
<Heading as="h2" weight="medium"> | ||
Welcome to the React Email preview! | ||
</Heading> | ||
<Text as="p" className="mt-2 mb-4"> | ||
To start developing your next email template, you can create a{' '} | ||
<code>.jsx</code> or <code>.tsx</code> file under the "emails" folder. | ||
</Text> | ||
|
||
<Button asChild> | ||
<Link href="https://react.email/docs">Check the docs</Link> | ||
</Button> | ||
</div> | ||
</Shell> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import '../styles/globals.css'; | ||
import classnames from 'classnames'; | ||
import { Inter } from "next/font/google"; | ||
|
||
export const inter = Inter({ | ||
subsets: ['latin'], | ||
variable: '--font-inter', | ||
}); | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
return ( | ||
<html lang="en"> | ||
<body className="bg-black text-slate-12 font-sans"> | ||
<div className={classnames(inter.variable, 'font-sans')}> | ||
{children} | ||
</div> | ||
</body> | ||
</html> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import Home from './home'; | ||
import { getEmails } from '../utils/get-emails'; | ||
|
||
export default async function Index() { | ||
const { emails } = await getEmails(); | ||
return <Home navItems={emails} />; | ||
} | ||
|
||
export const metadata = { | ||
title: 'React Email', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { getEmails, CONTENT_DIR } from '../../../utils/get-emails'; | ||
import { promises as fs } from 'fs'; | ||
import { render } from '@react-email/render'; | ||
import Preview from './preview'; | ||
|
||
export const dynamicParams = true; | ||
|
||
export async function generateStaticParams() { | ||
const { emails } = await getEmails(); | ||
|
||
const paths = emails.map((email) => { | ||
return { slug: email }; | ||
}); | ||
|
||
return paths; | ||
} | ||
|
||
export default async function Page({ params }) { | ||
const { emails, filenames } = await getEmails(); | ||
const template = filenames.filter((email) => { | ||
const [fileName] = email.split('.'); | ||
return params.slug === fileName; | ||
}); | ||
|
||
const Email = (await import(`../../../../emails/${params.slug}`)).default; | ||
const markup = render(<Email />, { pretty: true }); | ||
const plainText = render(<Email />, { plainText: true }); | ||
const path = `${process.cwd()}/${CONTENT_DIR}/${template[0]}`; | ||
const reactMarkup = await fs.readFile(path, { | ||
encoding: 'utf-8', | ||
}); | ||
|
||
return ( | ||
<Preview | ||
navItems={emails} | ||
slug={params.slug} | ||
markup={markup} | ||
reactMarkup={reactMarkup} | ||
plainText={plainText} | ||
/> | ||
); | ||
} | ||
|
||
export async function generateMetadata({ params }) { | ||
return { title: `${params.slug} — React Email` }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
'use client'; | ||
|
||
import { Shell } from '../../../components/shell'; | ||
import { useRouter, usePathname, useSearchParams } from 'next/navigation'; | ||
import { CodeContainer } from '../../../components/code-container'; | ||
import React from 'react'; | ||
import { Tooltip } from '../../../components/tooltip'; | ||
|
||
export default function Preview({ | ||
navItems, | ||
slug, | ||
markup, | ||
reactMarkup, | ||
plainText, | ||
}) { | ||
const router = useRouter(); | ||
const pathname = usePathname(); | ||
const searchParams = useSearchParams(); | ||
const [activeView, setActiveView] = React.useState('desktop'); | ||
const [activeLang, setActiveLang] = React.useState('jsx'); | ||
|
||
React.useEffect(() => { | ||
const view = searchParams.get('view'); | ||
const lang = searchParams.get('lang'); | ||
|
||
if (view === 'source' || view === 'desktop') { | ||
setActiveView(view); | ||
} | ||
|
||
if (lang === 'jsx' || lang === 'markup' || lang === 'markdown') { | ||
setActiveLang(lang); | ||
} | ||
}, [searchParams]); | ||
|
||
const handleViewChange = (view: string) => { | ||
setActiveView(view); | ||
router.push(`${pathname}?view=${view}`); | ||
}; | ||
|
||
const handleLangChange = (lang: string) => { | ||
setActiveLang(lang); | ||
router.push(`${pathname}?view=source&lang=${lang}`); | ||
}; | ||
|
||
return ( | ||
<Shell | ||
navItems={navItems} | ||
title={slug} | ||
markup={markup} | ||
activeView={activeView} | ||
setActiveView={handleViewChange} | ||
> | ||
{activeView === 'desktop' ? ( | ||
<iframe srcDoc={markup} className="w-full h-[calc(100vh_-_70px)]" /> | ||
) : ( | ||
<div className="flex gap-6 mx-auto p-6 max-w-3xl"> | ||
<Tooltip.Provider> | ||
<CodeContainer | ||
markups={[ | ||
{ language: 'jsx', content: reactMarkup }, | ||
{ language: 'markup', content: markup }, | ||
{ language: 'markdown', content: plainText }, | ||
]} | ||
activeLang={activeLang} | ||
setActiveLang={handleLangChange} | ||
/> | ||
</Tooltip.Provider> | ||
</div> | ||
)} | ||
</Shell> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import * as React from 'react'; | ||
import classnames from 'classnames'; | ||
import { unreachable } from '../utils'; | ||
import * as SlotPrimitive from '@radix-ui/react-slot'; | ||
|
||
type ButtonElement = React.ElementRef<'button'>; | ||
type RootProps = React.ComponentPropsWithoutRef<'button'>; | ||
|
||
type Appearance = 'white' | 'gradient'; | ||
type Size = '1' | '2' | '3' | '4'; | ||
|
||
interface ButtonProps extends RootProps { | ||
asChild?: boolean; | ||
appearance?: Appearance; | ||
size?: Size; | ||
} | ||
|
||
export const Button = React.forwardRef<ButtonElement, Readonly<ButtonProps>>( | ||
( | ||
{ | ||
asChild, | ||
appearance = 'white', | ||
className, | ||
children, | ||
size = '2', | ||
...props | ||
}, | ||
forwardedRef, | ||
) => { | ||
const classNames = classnames( | ||
getSize(size), | ||
getAppearance(appearance), | ||
'inline-flex items-center justify-center border font-medium', | ||
className, | ||
); | ||
|
||
return asChild ? ( | ||
<SlotPrimitive.Slot ref={forwardedRef} {...props} className={classNames}> | ||
<SlotPrimitive.Slottable>{children}</SlotPrimitive.Slottable> | ||
</SlotPrimitive.Slot> | ||
) : ( | ||
<button ref={forwardedRef} className={classNames} {...props}> | ||
{children} | ||
</button> | ||
); | ||
}, | ||
); | ||
|
||
Button.displayName = 'Button'; | ||
|
||
const getAppearance = (appearance: Appearance | undefined) => { | ||
switch (appearance) { | ||
case undefined: | ||
case 'white': | ||
return [ | ||
'bg-white text-black', | ||
'hover:bg-white/90', | ||
'focus:ring-2 focus:ring-white/20 focus:outline-none focus:bg-white/90', | ||
]; | ||
case 'gradient': | ||
return [ | ||
'bg-gradient backdrop-blur-[20px] border-[#34343A]', | ||
'hover:bg-gradientHover', | ||
'focus:ring-2 focus:ring-white/20 focus:outline-none focus:bg-gradientHover', | ||
]; | ||
default: | ||
unreachable(appearance); | ||
} | ||
}; | ||
|
||
const getSize = (size: Size | undefined) => { | ||
switch (size) { | ||
case '1': | ||
return ''; | ||
case undefined: | ||
case '2': | ||
return 'text-[14px] h-8 px-3 rounded-md gap-2'; | ||
case '3': | ||
return 'text-[14px] h-10 px-4 rounded-md gap-2'; | ||
case '4': | ||
return 'text-base h-11 px-4 rounded-md gap-2'; | ||
default: | ||
unreachable(size); | ||
} | ||
}; |
Oops, something went wrong.