refactor: website facelift (#10823)

This commit is contained in:
Noel
2025-04-10 22:02:37 +02:00
committed by GitHub
parent 1fe53c7ca2
commit 2e3bc69602
80 changed files with 6136 additions and 2529 deletions

View File

@@ -0,0 +1,14 @@
import { CmdKNoSRR } from '@/components/CmdK';
import { fetchDependencies } from '@/util/fetchDependencies';
export async function CmdK({
params,
}: {
readonly params: Promise<{ readonly packageName: string; readonly version: string }>;
}) {
const { packageName, version } = await params;
const dependencies = await fetchDependencies({ packageName, version });
return <CmdKNoSRR dependencies={dependencies} />;
}

View File

@@ -1,7 +1,7 @@
/* eslint-disable react/no-unknown-property */
import { ImageResponse } from 'next/og';
import { resolveKind } from '~/util/resolveNodeKind';
import { resolveKind } from '@/util/resolveNodeKind';
export const runtime = 'edge';
@@ -15,14 +15,24 @@ export const contentType = 'image/png';
export default async function Image({
params,
}: {
readonly params: { readonly item: string; readonly packageName: string; readonly version: string };
readonly params: Promise<{ readonly item: string; readonly packageName: string; readonly version: string }>;
}) {
const normalizeItem = params.item.split(encodeURIComponent(':')).join('.').toLowerCase();
const { item, packageName, version } = await params;
const isMainVersion = params.version === 'main';
const [fontDataBold, fontDataBlack] = await Promise.all([
fetch(new URL('../../../../../../assets/Geist-Bold.ttf', import.meta.url), {
next: { revalidate: 604_800 },
}).then(async (res) => res.arrayBuffer()),
fetch(new URL('../../../../../../assets/Geist-Black.ttf', import.meta.url), {
next: { revalidate: 604_800 },
}).then(async (res) => res.arrayBuffer()),
]);
const normalizeItem = item.split(encodeURIComponent(':')).join('.').toLowerCase();
const isMain = version === 'main';
const fileContent = await fetch(
`${process.env.BLOB_STORAGE_URL}/rewrite/${params.packageName}/${params.version}.${normalizeItem}.api.json`,
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.${normalizeItem}.api.json`,
{ next: { revalidate: isMain ? 0 : 604_800 } },
);
const node = await fileContent.json();
@@ -30,7 +40,7 @@ export default async function Image({
(
<div tw="flex bg-[#121212] h-full w-full p-14">
<div tw="flex flex-col mx-auto h-full text-white">
<div tw="flex text-4xl text-gray-400">{params.packageName}</div>
<div tw="flex text-4xl text-gray-400">{packageName}</div>
<div tw="flex flex-col justify-between h-full w-full pt-14">
<div tw="flex items-center max-w-full">
<span tw="mr-6">{resolveKind(node.kind, 94)}</span>
@@ -40,7 +50,7 @@ export default async function Image({
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
tw="text-[5.5rem] font-bold w-full"
tw="text-[5.5rem] font-bold w-full"
>
{node.displayName}
</h2>
@@ -94,6 +104,20 @@ export default async function Image({
),
{
...size,
fonts: [
{
name: 'Geist',
data: fontDataBold,
weight: 700,
style: 'normal',
},
{
name: 'Geist',
data: fontDataBlack,
weight: 900,
style: 'normal',
},
],
},
);
}

View File

@@ -1,38 +1,44 @@
'use cache';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { DocItem } from '~/components/DocItem';
import { fetchNode } from '~/util/fetchNode';
import { DocItem } from '@/components/DocItem';
import { fetchNode } from '@/util/fetchNode';
export async function generateMetadata({
params,
}: {
readonly params: {
readonly params: Promise<{
readonly item: string;
readonly packageName: string;
readonly version: string;
};
}>;
}): Promise<Metadata> {
const normalizeItem = params.item.split(encodeURIComponent(':'))[0];
const { item, packageName, version } = await params;
const normalizeItem = item.split(encodeURIComponent(':'))[0];
return {
title: `${normalizeItem} (${params.packageName} - ${params.version})`,
title: `${normalizeItem} (${packageName} - ${version})`,
};
}
export default async function Page({
params,
}: {
readonly params: { readonly item: string; readonly packageName: string; readonly version: string };
readonly params: Promise<{ readonly item: string; readonly packageName: string; readonly version: string }>;
}) {
const node = await fetchNode({ item: params.item, packageName: params.packageName, version: params.version });
const { item, packageName, version } = await params;
const node = await fetchNode({ item, packageName, version });
if (!node) {
notFound();
}
return (
<main className="flex w-full flex-col gap-8 pb-12 md:pb-0">
<DocItem node={node} packageName={params.packageName} version={params.version} />
<main className="mx-auto flex w-full max-w-screen-xl flex-col gap-8 px-6 py-4">
<DocItem node={node} packageName={packageName} version={version} />
</main>
);
}

View File

@@ -1,24 +1,33 @@
import type { Metadata } from 'next';
import dynamic from 'next/dynamic';
import type { PropsWithChildren } from 'react';
import { Navigation } from '~/components/Navigation';
import { OverlayScrollbarsComponent } from '~/components/OverlayScrollbars';
import { Drawer } from '~/components/ui/Drawer';
import { Footer } from '~/components/ui/Footer';
import { fetchDependencies } from '~/util/fetchDependencies';
'use cache';
// eslint-disable-next-line promise/prefer-await-to-then
const CmdK = dynamic(async () => import('~/components/ui/CmdK').then((mod) => mod.CmdK), { ssr: false });
import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted';
import type { Metadata } from 'next';
import Link from 'next/link';
import { Suspense, type PropsWithChildren } from 'react';
import { Footer } from '@/components/Footer';
import { Navigation } from '@/components/Navigation';
import { Scrollbars } from '@/components/OverlayScrollbars';
import { PackageSelect } from '@/components/PackageSelect';
import { SearchButton } from '@/components/SearchButton';
import { ThemeSwitchNoSRR } from '@/components/ThemeSwitch';
import { VersionSelect } from '@/components/VersionSelect';
import { Sidebar, SidebarContent, SidebarHeader, SidebarInset, SidebarTrigger } from '@/components/ui/Sidebar';
import { buttonStyles } from '@/styles/ui/button';
import { ENV } from '@/util/env';
import { fetchVersions } from '@/util/fetchVersions';
import { CmdK } from './CmdK';
export async function generateMetadata({
params,
}: {
readonly params: { readonly packageName: string; readonly version: string };
readonly params: Promise<{ readonly packageName: string; readonly version: string }>;
}): Promise<Metadata> {
const { packageName, version } = await params;
return {
title: {
template: '%s | discord.js',
default: `${params.packageName} (${params.version})`,
default: `${packageName} (${version})`,
},
};
}
@@ -26,44 +35,74 @@ export async function generateMetadata({
export default async function Layout({
params,
children,
}: PropsWithChildren<{ readonly params: { readonly packageName: string; readonly version: string } }>) {
const dependencies = await fetchDependencies({ packageName: params.packageName, version: params.version });
}: PropsWithChildren<{ readonly params: Promise<{ readonly packageName: string; readonly version: string }> }>) {
const { packageName, version } = await params;
const versions = fetchVersions(packageName);
return (
// eslint-disable-next-line react/no-unknown-property
<div className="mx-auto flex max-w-screen-2xl flex-col gap-12 p-6 md:flex-row" vaul-drawer-wrapper="">
<div className="sticky top-6 hidden flex-shrink-0 self-start md:block">
<OverlayScrollbarsComponent
className="max-h-[calc(100dvh-48px)]"
defer
options={{
overflow: { x: 'hidden' },
scrollbars: {
autoHide: 'scroll',
autoHideDelay: 500,
autoHideSuspend: true,
clickScroll: true,
},
}}
>
<Navigation className="pr-4" packageName={params.packageName} version={params.version} />
</OverlayScrollbarsComponent>
</div>
<div className="pb-12">
{children}
<Footer />
</div>
<div className="fixed bottom-0 left-0 right-0 md:hidden">
<Drawer>
<Navigation
className="max-w-none overflow-auto p-0 lg:max-w-none"
drawer
packageName={params.packageName}
version={params.version}
/>
</Drawer>
</div>
<CmdK dependencies={dependencies} />
</div>
<>
<Sidebar closeButton={false} intent="inset">
<SidebarHeader className="bg-[#f3f3f4] p-4 dark:bg-[#121214]">
<div className="flex flex-col gap-2">
<div className="flex place-content-between place-items-center p-1">
<Link className="text-xl font-bold" href={`/docs/packages/${packageName}/${version}`}>
{packageName}
</Link>
<div className="flex place-items-center gap-2">
<Link
aria-label="GitHub"
className={buttonStyles({ variant: 'filled', size: 'icon-sm' })}
href="https://github.com/discordjs/discord.js"
rel="external noopener noreferrer"
target="_blank"
>
<VscGithubInverted aria-hidden data-slot="icon" size={18} />
</Link>
<ThemeSwitchNoSRR />
</div>
</div>
<PackageSelect />
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
<VersionSelect versionsPromise={versions} />
<SearchButton />
</div>
</SidebarHeader>
<SidebarContent className="bg-[#f3f3f4] p-0 py-4 pl-4 dark:bg-[#121214]">
<Scrollbars>
<Navigation packageName={packageName} version={version} />
</Scrollbars>
</SidebarContent>
</Sidebar>
<SidebarInset>
{ENV.IS_LOCAL_DEV ? (
<div className="sticky top-0 z-10 flex place-content-center place-items-center border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
Local test environment
</div>
) : null}
{ENV.IS_PREVIEW ? (
<div className="sticky top-0 z-10 flex place-content-center place-items-center border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
Preview environment
</div>
) : null}
<div className="bg-[#fbfbfb] pb-12 dark:bg-[#1a1a1e]">
<div className="relative px-6 pt-6 md:hidden">
<div className="fixed top-5 left-6 z-20 md:hidden">
<SidebarTrigger aria-label="Navigation" size="icon" variant="filled" />
</div>
<div className="flex place-content-end">
<Link className="text-xl font-bold" href={`/docs/packages/${packageName}/${version}`}>
{packageName}
</Link>
</div>
</div>
{children}
<Footer />
</div>
</SidebarInset>
<Suspense>
<CmdK params={params} />
</Suspense>
</>
);
}

View File

@@ -1,29 +1,19 @@
'use cache';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
import { MDXRemote } from 'next-mdx-remote-client/rsc';
import remarkGfm from 'remark-gfm';
import { getHighlighterCore } from 'shiki/core';
import getWasm from 'shiki/wasm';
import { getSingletonHighlighter } from '@/util/shiki.bundle';
const highlighter = await getHighlighterCore({
themes: [import('shiki/themes/github-light.mjs'), import('shiki/themes/github-dark-dimmed.mjs')],
langs: [
import('shiki/langs/typescript.mjs'),
import('shiki/langs/javascript.mjs'),
import('shiki/langs/shellscript.mjs'),
],
loadWasm: getWasm,
});
export default async function Page({ params }: { readonly params: Promise<{ readonly packageName: string }> }) {
const { packageName } = await params;
export default async function Page({ params }: { readonly params: { readonly packageName: string } }) {
const fileContent = await readFile(
join(process.cwd(), `src/assets/readme/${params.packageName}/home-README.md`),
'utf8',
);
const fileContent = await readFile(join(process.cwd(), `src/assets/readme/${packageName}/home-README.md`), 'utf8');
return (
<div className="prose prose-neutral mx-auto max-w-screen-xl dark:prose-invert">
<div className="prose prose-neutral dark:prose-invert prose-a:[&>img]:inline-block prose-a:[&>img]:m-0 prose-a:[&>img[height='44']]:h-11 prose-p:my-2 prose-pre:py-3 prose-pre:rounded-sm prose-pre:px-0 prose-pre:border prose-pre:border-[#d4d4d4] dark:prose-pre:border-[#404040] prose-code:font-normal prose-a:text-[#5865F2] prose-a:no-underline prose-a:hover:text-[#3d48c3] dark:prose-a:hover:text-[#7782fa] mx-auto max-w-screen-xl px-6 py-6 [&_code_span:last-of-type:empty]:hidden [&_div[align='center']_p_a+a]:ml-2">
<MDXRemote
options={{
mdxOptions: {
@@ -31,7 +21,10 @@ export default async function Page({ params }: { readonly params: { readonly pac
rehypePlugins: [
[
rehypeShikiFromHighlighter,
highlighter,
await getSingletonHighlighter({
langs: ['typescript', 'javascript', 'shellscript'],
themes: ['github-light', 'github-dark-dimmed'],
}),
{
themes: {
light: 'github-light',

View File

@@ -3,18 +3,17 @@ import { GeistMono } from 'geist/font/mono';
import { GeistSans } from 'geist/font/sans';
import type { Metadata, Viewport } from 'next';
import type { PropsWithChildren } from 'react';
import { LocalizedStringProvider } from 'react-aria-components/i18n';
import { DESCRIPTION } from '~/util/constants';
import { ENV } from '~/util/env';
import { DESCRIPTION } from '@/util/constants';
import { ENV } from '@/util/env';
import { Providers } from './providers';
import '~/styles/main.css';
import '@/styles/base.css';
import 'overlayscrollbars/overlayscrollbars.css';
export const viewport: Viewport = {
themeColor: [
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
{ media: '(prefers-color-scheme: dark)', color: '#121212' },
{ media: '(prefers-color-scheme: light)', color: '#fbfbfb' },
{ media: '(prefers-color-scheme: dark)', color: '#1a1a1e' },
],
colorScheme: 'light dark',
};
@@ -70,28 +69,15 @@ export const metadata: Metadata = {
},
other: {
'msapplication-TileColor': '#121212',
'msapplication-TileColor': '#1a1a1e',
},
};
export default async function RootLayout({ children }: PropsWithChildren) {
return (
<html className={`${GeistSans.variable} ${GeistMono.variable} antialiased`} lang="en" suppressHydrationWarning>
<body className="relative bg-white dark:bg-[#121212]">
<LocalizedStringProvider locale="en-US" />
<Providers>
{ENV.IS_LOCAL_DEV ? (
<div className="fixed left-1/2 top-2 z-10 flex -translate-x-1/2 place-content-center place-items-center rounded-md border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
Local test environment
</div>
) : null}
{ENV.IS_PREVIEW ? (
<div className="fixed left-1/2 top-2 z-10 flex -translate-x-1/2 place-content-center place-items-center rounded-md border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
Preview environment
</div>
) : null}
{children}
</Providers>
<body className="text-base-md text-base-neutral-900 dark:text-base-neutral-40 overscroll-y-none bg-[#fbfbfb] dark:bg-[#1a1a1e]">
<Providers>{children}</Providers>
<Analytics />
</body>
</html>

View File

@@ -3,10 +3,10 @@ import Link from 'next/link';
export default function NotFound() {
return (
<div className="mx-auto flex min-h-[calc(100vh_-_100px)] max-w-lg flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">404</h1>
<h1 className="text-[9rem] leading-none font-black md:text-[12rem]">404</h1>
<h2 className="text-[2rem] md:text-[3rem]">Not found.</h2>
<Link
className="inline-flex rounded-md border border-transparent bg-blurple px-6 py-2 font-medium text-white"
className="bg-base-blurple-400 inline-flex rounded-md border border-transparent px-6 py-2 font-medium text-white"
href="/docs"
>
Take me back

View File

@@ -1,4 +1,5 @@
/* eslint-disable react/no-unknown-property */
import { ImageResponse } from 'next/og';
export const runtime = 'edge';
@@ -11,15 +12,19 @@ export const size = {
export const contentType = 'image/png';
export default async function Image() {
const fontData = await fetch(new URL('../assets/Geist-Black.ttf', import.meta.url), {
next: { revalidate: 604_800 },
}).then(async (res) => res.arrayBuffer());
return new ImageResponse(
(
<div tw="flex bg-[#121212] h-full w-full">
<div tw="flex bg-[#121214] h-full w-full">
<div tw="mx-auto flex items-center h-full">
<div tw="flex">
<div tw="flex">
<div tw="flex flex-col font-black text-[5.5rem] text-white">
<div tw="flex flex-col font-black text-8xl text-white leading-tight">
<div tw="flex flex-row">
The <span tw="bg-[#5865f2] rounded-lg py-1 px-6 ml-4">most popular</span>
The <span tw="bg-[#5865f2] rounded-md px-3 py-2 ml-4 bottom-2">most popular</span>
</div>
<span>way to build Discord</span>
<span>bots.</span>
@@ -31,6 +36,14 @@ export default async function Image() {
),
{
...size,
fonts: [
{
name: 'Geist',
data: fontData,
weight: 900,
style: 'normal',
},
],
},
);
}

View File

@@ -1,43 +1,42 @@
import { ExternalLink } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
import vercelLogo from '~/assets/powered-by-vercel.svg';
import workersLogo from '~/assets/powered-by-workers.png';
import { InstallButton } from '~/components/ui/InstallButton';
import { DESCRIPTION } from '~/util/constants';
import vercelLogo from '@/assets/powered-by-vercel.svg';
import workersLogo from '@/assets/powered-by-workers.png';
import { InstallButton } from '@/components/InstallButton';
import { buttonStyles } from '@/styles/ui/button';
import { DESCRIPTION } from '@/util/constants';
export default async function Page() {
return (
<div className="mx-auto flex min-h-screen w-full max-w-screen-lg flex-col place-content-center place-items-center gap-24 px-8 pb-16 pt-12">
<div className="mx-auto flex min-h-screen w-full max-w-screen-lg flex-col place-content-center place-items-center gap-24 px-8 pt-12 pb-16">
<div className="flex flex-col gap-10 text-center">
<h1 className="z-10 text-3xl font-black leading-tight sm:text-7xl sm:leading-tight">
The <span className="relative rounded bg-blurple px-3 py-1 text-white">most popular</span> way to build
Discord bots.
<h1 className="text-base-heading-xl font-black sm:text-7xl sm:leading-tight">
The{' '}
<span className="bg-base-blurple-400 text-base-neutral-0 relative rounded-sm px-3 py-2">most popular</span>{' '}
way to build Discord bots.
</h1>
<p className="z-10 leading-normal text-neutral-700 dark:text-neutral-300 md:my-6">{DESCRIPTION}</p>
<p className="text-base-neutral-600 dark:text-base-neutral-300 md:my-6">{DESCRIPTION}</p>
<div className="flex flex-wrap place-content-center gap-4 sm:flex-wrap md:flex-row">
<Link
className="inline-flex rounded-md border border-transparent bg-blurple px-6 py-2 font-medium text-white"
href="/docs"
>
<Link className={buttonStyles({ variant: 'filled' })} href="/docs">
Docs
</Link>
<a
className="inline-flex gap-2 rounded-md border border-neutral-300 bg-white px-6 py-2 font-medium hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800"
className={buttonStyles({ variant: 'discreet' })}
href="https://discordjs.guide"
rel="noopener noreferrer"
target="_blank"
>
Guide <ExternalLink aria-hidden size={20} />
Guide <ExternalLink aria-hidden data-slot="icon" size={18} />
</a>
<a
className="inline-flex gap-2 rounded-md border border-neutral-300 bg-white px-6 py-2 font-medium hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800"
className={buttonStyles({ variant: 'discreet' })}
href="https://github.com/discordjs/discord.js"
rel="external noopener noreferrer"
target="_blank"
>
GitHub <ExternalLink aria-hidden size={20} />
GitHub <ExternalLink aria-hidden data-slot="icon" size={18} />
</a>
</div>

View File

@@ -3,10 +3,12 @@
import { Provider as JotaiProvider } from 'jotai';
import { useRouter } from 'next/navigation';
import { ThemeProvider } from 'next-themes';
import { NuqsAdapter } from 'nuqs/adapters/next/app';
import type { PropsWithChildren } from 'react';
import { RouterProvider } from 'react-aria-components';
import { useSystemThemeFallback } from '~/hooks/useSystemThemeFallback';
import { useUnregisterServiceWorker } from '~/hooks/useUnregisterServiceWorker';
import { SidebarProvider } from '@/components/ui/Sidebar';
import { useSystemThemeFallback } from '@/hooks/useSystemThemeFallback';
import { useUnregisterServiceWorker } from '@/hooks/useUnregisterServiceWorker';
export function Providers({ children }: PropsWithChildren) {
const router = useRouter();
@@ -14,10 +16,14 @@ export function Providers({ children }: PropsWithChildren) {
useSystemThemeFallback();
return (
<RouterProvider navigate={router.push}>
<JotaiProvider>
<ThemeProvider attribute="class">{children}</ThemeProvider>
</JotaiProvider>
</RouterProvider>
<NuqsAdapter>
<ThemeProvider attribute="class">
<RouterProvider navigate={router.push}>
<JotaiProvider>
<SidebarProvider defaultOpen>{children}</SidebarProvider>
</JotaiProvider>
</RouterProvider>
</ThemeProvider>
</NuqsAdapter>
);
}