Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/web/src/app/components/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const metadata: Metadata = {
export default async function ComponentsPage() {
return (
<PageWrapper>
<div className="pointer-events-none absolute inset-0 flex justify-center">
<div className="pointer-events-none absolute inset-0 flex justify-center [mask-image:linear-gradient(to_bottom,transparent_0%,black_4%,black_96%,transparent_100%)]">
<div className="hidden h-full w-full max-w-7xl grid-cols-2 gap-4 px-4 lg:grid">
<div className="border-r-slate-3 border-l border-l-slate-4" />
<div className="border-r border-r-slate-4" />
Expand All @@ -41,8 +41,8 @@ export default async function ComponentsPage() {
</p>
</div>
<div className="relative grid grid-cols-1 gap-x-4 px-1 pb-10 md:grid-cols-2 md:px-0 lg:grid-cols-3">
<div className="-translate-x-1/2 absolute top-0 left-1/2 h-px w-[100dvw] border-slate-4 border-t" />
<div className="-translate-x-1/2 absolute bottom-0 left-1/2 h-px w-[100dvw] border-slate-4 border-b" />
<div className="-translate-x-1/2 absolute top-0 left-1/2 h-px w-[100dvw] border-slate-4 border-t xl:[mask-image:linear-gradient(to_right,transparent_0%,black_10%,black_90%,transparent_100%)]" />
<div className="-translate-x-1/2 absolute bottom-0 left-1/2 h-px w-[100dvw] border-slate-4 border-b xl:[mask-image:linear-gradient(to_right,transparent_0%,black_10%,black_90%,transparent_100%)]" />
{componentsStructure.map((category, index) => {
const slug = slugify(category.name);
const Illustration = dynamic(
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Analytics } from '@vercel/analytics/react';
import type { Metadata } from 'next';
import '@/styles/globals.css';
import localFont from 'next/font/local';
import { Topbar } from '@/components/topbar';

const inter = localFont({
display: 'swap',
Expand Down Expand Up @@ -95,6 +96,9 @@ export default function RootLayout({
<script src="/js/web-streams-polyfill.js" />
</head>
<body className="h-screen-ios overflow-x-hidden bg-black font-sans text-slate-11 text-sm selection:bg-cyan-5 selection:text-cyan-12">
<div className="relative mx-auto flex flex-col justify-between px-2 md:max-w-7xl md:px-4">
<Topbar />
</div>
{children}
<Analytics />
</body>
Expand Down
4 changes: 0 additions & 4 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ import PlaygroundSection from '@/components/sections/playground';
import PrimitivesSection from '@/components/sections/primitives';
import TestimonialSection from '@/components/sections/testimonial';
import ToolsSection from '@/components/sections/tools';
import { Topbar } from '@/components/topbar';

const Home = () => (
<main className="max-lg:overflow-x-clip">
<div className="relative mx-auto flex flex-col justify-between px-2 md:max-w-7xl md:px-4">
<Topbar />
</div>
<HeroSection />
<div className="relative mx-auto flex flex-col justify-between px-2 md:max-w-7xl md:px-4">
<PlaygroundSection />
Expand Down
41 changes: 33 additions & 8 deletions apps/web/src/components/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import classnames from 'classnames';
import { MenuIcon } from 'lucide-react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import * as React from 'react';
Expand All @@ -16,6 +15,16 @@ interface MenuItemProps {

const GITHUB_URL = 'https://github.com/resend/react-email';

async function getRepoStarCount() {
const res = await fetch('https://api.github.com/repos/resend/react-email');
const data = await res.json();
const starCount = data.stargazers_count;
if (starCount > 999) {
return `${(starCount / 1000).toFixed(1)}K`;
}
return starCount;
}

function MenuItem({ className, children, href, onClick }: MenuItemProps) {
const pathname = usePathname();
const [, activeItem] = pathname?.split('/') ?? [];
Expand Down Expand Up @@ -67,10 +76,26 @@ function MenuItems({ onItemClick }: { onItemClick: () => void }) {
);
}

function MenuIcon() {
return (
<div className="flex flex-col gap-2">
{Array.from({ length: 2 }).map((_, index) => (
<div key={index} className="w-5 h-px rounded-full bg-slate-11" />
))}
</div>
);
}

function SocialIcons({ onItemClick }: { onItemClick: () => void }) {
const [starCount, setStarCount] = React.useState<string | number>('');

React.useEffect(() => {
getRepoStarCount().then(setStarCount);
}, []);

return (
<MenuItem
className="w-8 justify-center"
className="w-fit gap-1.5 justify-center px-2"
href={GITHUB_URL}
onClick={onItemClick}
>
Expand All @@ -85,6 +110,7 @@ function SocialIcons({ onItemClick }: { onItemClick: () => void }) {
fill="currentColor"
/>
</svg>
{starCount && <span>{starCount}</span>}
</MenuItem>
);
}
Expand All @@ -110,7 +136,8 @@ export function Menu() {
<SocialIcons onItemClick={handleItemClick} />
</ul>
</nav>
<nav className="relative flex items-center gap-2 md:hidden">
<nav className="relative flex items-center gap-1 md:hidden">
<SocialIcons onItemClick={handleItemClick} />
<ul className="flex gap-2">
<Drawer.Root
onOpenChange={setDrawerOpen}
Expand All @@ -121,14 +148,12 @@ export function Menu() {
<MenuIcon />
</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay className="-translate-x-1/2 -translate-y-1/2 fixed top-1/2 left-1/2 z-[2] h-[200dvh] w-[200dvw] bg-slate-800/50" />
<Drawer.Content className="fixed right-0 bottom-0 left-0 z-50 flex h-fit flex-col gap-8 rounded-t-xl bg-black p-8 pt-10">
<Drawer.Overlay className="-translate-x-1/2 -translate-y-1/2 fixed top-1/2 left-1/2 z-50 h-[200dvh] w-[200dvw] bg-black/80" />
<Drawer.Content className="fixed right-0 bottom-0 left-0 z-[51] flex h-fit flex-col gap-8 rounded-t-xl bg-black border-t border-slate-5 p-8 pt-10">
<Drawer.Title className="sr-only">Menu</Drawer.Title>
<ul className="flex w-full flex-col items-start gap-4">
<MenuItems onItemClick={handleItemClick} />
</ul>
<ul className="flex w-fit gap-2">
<SocialIcons onItemClick={handleItemClick} />
</ul>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
Expand Down
2 changes: 0 additions & 2 deletions apps/web/src/components/page-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Footer } from '@/components/footer';
import { Topbar } from '@/components/topbar';

interface PageWrapperProps {
children: React.ReactNode;
Expand All @@ -15,7 +14,6 @@ export function PageWrapper({ children, className = '' }: PageWrapperProps) {
className={`relative mx-auto flex min-h-[100dvh] flex-col justify-between px-2 md:max-w-7xl md:px-4 ${className}`}
vaul-drawer-wrapper=""
>
<Topbar />
{children}
<Footer />
</div>
Expand Down
7 changes: 1 addition & 6 deletions apps/web/src/components/sections/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ import { Text } from '@/components/text';
// Dynamically import Tower component to avoid SSR issues with Three.js
const Tower = dynamic(() => import('@/webgl/tower').then((mod) => mod.Tower), {
ssr: false,
loading: () => (
<div className="w-full h-full flex items-center justify-center">
<div className="text-white/30">Loading...</div>
</div>
),
});

const HeroSection = () => {
Expand Down Expand Up @@ -56,7 +51,7 @@ const HeroSection = () => {
/>
</div>
<Heading
className="!text-white/80 relative mb-8 text-center lg:text-left before:absolute before:top-0 before:left-0 before:w-full before:animate-[shine_2s_ease-in-out] before:bg-[length:200%] before:bg-shine before:bg-clip-text before:text-transparent before:content-['The_next_generation_of_writing_emails'] before:select-none before:pointer-events-none text-balance"
className="text-white/80 relative mb-8 text-center lg:text-left before:absolute before:top-0 before:left-0 before:w-full before:animate-[shine_1.5s_ease-in-out] before:bg-[length:225%] before:bg-shine before:bg-clip-text before:text-transparent before:content-['The_next_generation_of_writing_emails'] before:select-none before:pointer-events-none text-balance"
weight="medium"
size="10"
>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/sections/integration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const IntegrationSection = () => {

return (
<section className="relative pt-12 pb-28 md:pb-80 px-6">
<div className="space-y-16">
<div className="space-y-12 md:space-y-16">
<div className="max-w-full text-center space-y-4">
<Heading
size="8"
Expand Down
9 changes: 8 additions & 1 deletion apps/web/src/components/sections/playground/code-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import * as Tabs from '@radix-ui/react-tabs';
import { render } from '@react-email/render';
import classNames from 'classnames';
import React from 'react';
import { CodeBlock } from '@/components/code-block';
import { CopyCode } from '@/components/copy-code';
import { IconFile } from '@/components/icons/icon-file';
import { useScroll } from '@/utils/use-scroll';
import WelcomeEmail from './code-example';

type Tab = {
Expand Down Expand Up @@ -74,6 +76,8 @@ const CodePreviewHeader = ({
const CodePreviewContent = ({ tabs }: { tabs: Tab[] }) => {
const [emailOutput, setEmailOutput] = React.useState<string | null>(null);

const { isScrolling } = useScroll();

React.useEffect(() => {
const renderEmail = async () => {
const html = await render(<WelcomeEmail />);
Expand All @@ -90,7 +94,10 @@ const CodePreviewContent = ({ tabs }: { tabs: Tab[] }) => {
<Tabs.Content
key={tab.value}
value={tab.value}
className="h-[400px] md:h-[600px] overflow-auto"
className={classNames(
'h-[400px] md:h-[600px] overflow-auto',
isScrolling && 'pointer-events-none',
)}
style={{
maskImage: `
linear-gradient(to bottom, transparent 0%, black 4%, black 96%, transparent 100%),
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/sections/primitives.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Text } from '@/components/text';

const PrimitivesSection = () => {
return (
<section className="relative my-24 md:my-40 md:py-20 space-y-16 max-md:px-6">
<section className="relative md:my-40 md:py-20 space-y-16 max-md:px-6">
<div className="flex flex-col gap-4">
<Heading size="8" weight="medium" className="text-white/80">
Battle-tested Primitives
Expand Down
2 changes: 0 additions & 2 deletions apps/web/src/utils/spam-assassin/parse-pointing-table-rows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ export const parsePointingTableRows = (response: string) => {
}

if (match?.groups === undefined) {
console.log(line);
throw new Error('Could not match the columns in the row', {
cause: {
line,
Expand All @@ -57,7 +56,6 @@ export const parsePointingTableRows = (response: string) => {
}
const parsedPoints = Number.parseFloat(pts);
if (Number.isNaN(parsedPoints)) {
console.log(line);
throw new Error('could not parse points to insert into rows array', {
cause: {
line,
Expand Down
31 changes: 31 additions & 0 deletions apps/web/src/utils/use-scroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';

export const useScroll = () => {
const [isScrolling, setIsScrolling] = React.useState(false);

React.useEffect(() => {
let scrollTimeout: NodeJS.Timeout;

const handleScroll = () => {
document.body.classList.add('scrolling');
setIsScrolling(true);

clearTimeout(scrollTimeout);

scrollTimeout = setTimeout(() => {
document.body.classList.remove('scrolling');
setIsScrolling(false);
}, 150);
};

window.addEventListener('scroll', handleScroll, { passive: true });

return () => {
window.removeEventListener('scroll', handleScroll);
clearTimeout(scrollTimeout);
document.body.classList.remove('scrolling');
};
}, []);

return { isScrolling };
};
4 changes: 2 additions & 2 deletions apps/web/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ module.exports = {
},
keyframes: {
shine: {
'0%': { backgroundPosition: '-100%' },
'100%': { backgroundPosition: '100%' },
'0%': { backgroundPosition: '0%' },
'100%': { backgroundPosition: '110%' },
},
dash: {
'0%': { strokeDashoffset: 1000 },
Expand Down
Loading