mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat(website): render syntax and mdx on the server (#9086)
This commit is contained in:
@@ -92,7 +92,6 @@ export default defineConfig({
|
||||
compress(),
|
||||
],
|
||||
markdown: {
|
||||
extendDefaultPlugins: true,
|
||||
syntaxHighlight: false,
|
||||
},
|
||||
vite: {
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
"@unocss/reset": "^0.50.6",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"@vitest/coverage-c8": "^0.29.7",
|
||||
"astro": "^1.9.2",
|
||||
"astro": "^2.1.5",
|
||||
"astro-compress": "^1.1.35",
|
||||
"astro-critters": "^1.1.31",
|
||||
"cross-env": "^7.0.3",
|
||||
@@ -80,7 +80,7 @@
|
||||
"shiki": "^0.14.1",
|
||||
"typescript": "^5.0.2",
|
||||
"unocss": "^0.50.6",
|
||||
"vercel": "^28.18.0",
|
||||
"vercel": "^28.18.1",
|
||||
"vitest": "^0.29.7"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -28,7 +28,7 @@ export function SidebarItems({ pages }: { pages: MDXPage[] }) {
|
||||
}, [state]);
|
||||
|
||||
return Object.keys(categories).map((category, idx) => (
|
||||
<Section key={idx} title={category}>
|
||||
<Section key={`${category}-${idx}`} title={category}>
|
||||
{categories[category]?.map((member, index) => (
|
||||
<a
|
||||
className={`dark:border-dark-100 border-light-800 focus:ring-width-2 focus:ring-blurple ml-5 flex flex-col border-l p-[5px] pl-6 outline-0 focus:rounded focus:border-0 focus:ring ${
|
||||
@@ -38,7 +38,7 @@ export function SidebarItems({ pages }: { pages: MDXPage[] }) {
|
||||
}`}
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
href={member.url || '/'}
|
||||
key={index}
|
||||
key={`${member.frontmatter.title}-${index}}`}
|
||||
title={member.frontmatter.title}
|
||||
>
|
||||
<div className="flex flex-row place-items-center gap-2 lg:text-sm">
|
||||
|
||||
@@ -28,7 +28,6 @@ const curCategoryPages = groupedPages[category];
|
||||
const curCategoryIndex = curCategoryPages!.findIndex((page) => page.url === url);
|
||||
|
||||
const pagePrev = curCategoryPages![curCategoryIndex - 1];
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
const pageNext = curCategoryPages![curCategoryIndex + 1];
|
||||
---
|
||||
|
||||
|
||||
2
apps/guide/src/env.d.ts
vendored
Normal file
2
apps/guide/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/* eslint-disable @typescript-eslint/triple-slash-reference */
|
||||
/// <reference types="@astrojs/image/client" />
|
||||
@@ -26,7 +26,7 @@ const props = Astro.props;
|
||||
<meta content="website" property="og:type" />
|
||||
<meta content="discord.js guide" property="og:title" />
|
||||
<meta content={DESCRIPTION} name="og:description" />
|
||||
<meta content="https://discordjs.dev/open-graph.png" property="og:image" />
|
||||
<meta content="https://discordjs.dev/api/open-graph.png" property="og:image" />
|
||||
<meta content="summary_large_image" name="twitter:card" />
|
||||
<meta content="@iCrawlToGo" name="twitter:creator" />
|
||||
|
||||
|
||||
@@ -78,15 +78,15 @@ You can use the [_`dotenv`_ package](https://www.npmjs.com/package/dotenv) for t
|
||||
|
||||
<CH.Code client:load>
|
||||
|
||||
```shellscript npm
|
||||
```sh npm
|
||||
npm install dotenv
|
||||
```
|
||||
|
||||
```shellscript yarn
|
||||
```sh yarn
|
||||
yarn add dotenv
|
||||
```
|
||||
|
||||
```shellscript pnpm
|
||||
```sh pnpm
|
||||
pnpm add dotenv
|
||||
```
|
||||
|
||||
|
||||
1
apps/website/next-env.d.ts
vendored
1
apps/website/next-env.d.ts
vendored
@@ -1,6 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference types="next/navigation-types/compat/navigation" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable tsdoc/syntax */
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import bundleAnalyzer from '@next/bundle-analyzer';
|
||||
|
||||
@@ -6,9 +5,6 @@ const withBundleAnalyzer = bundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
});
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
export default withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
eslint: {
|
||||
@@ -18,16 +14,15 @@ export default withBundleAnalyzer({
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
cleanDistDir: true,
|
||||
outputFileTracing: true,
|
||||
experimental: {
|
||||
appDir: true,
|
||||
serverComponentsExternalPackages: ['@microsoft/api-extractor-model', 'jju', 'shiki'],
|
||||
outputFileTracingRoot: fileURLToPath(new URL('../../', import.meta.url)),
|
||||
fallbackNodePolyfills: false,
|
||||
serverComponentsExternalPackages: ['@microsoft/api-extractor-model', 'jju'],
|
||||
},
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
|
||||
contentSecurityPolicy: "default-src 'self'; script-src 'none'; frame-src 'none'; sandbox;",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -52,23 +52,21 @@
|
||||
"@vercel/og": "^0.4.1",
|
||||
"@vscode/codicons": "^0.0.32",
|
||||
"ariakit": "^2.0.0-next.43",
|
||||
"bright": "^0.7.0",
|
||||
"cmdk": "^0.2.0",
|
||||
"meilisearch": "^0.31.1",
|
||||
"next": "^13.2.4",
|
||||
"next": "^13.2.5-canary.15",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-custom-scrollbars-2": "^4.5.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-use": "^17.4.0",
|
||||
"rehype-ignore": "^1.0.4",
|
||||
"rehype-pretty-code": "^0.9.4",
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-slug": "^5.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.31.3",
|
||||
"shiki": "^0.14.1",
|
||||
"swr": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -78,7 +76,6 @@
|
||||
"@types/node": "16.18.18",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"@unocss/cli": "^0.50.6",
|
||||
"@unocss/reset": "^0.50.6",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
@@ -94,7 +91,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.2.5",
|
||||
"typescript": "^5.0.2",
|
||||
"unocss": "^0.50.6",
|
||||
"vercel": "^28.18.0",
|
||||
"vercel": "^28.18.1",
|
||||
"vitest": "^0.29.7"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +1,15 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
|
||||
import type { ApiItemKind } from '@microsoft/api-extractor-model';
|
||||
import { ImageResponse } from '@vercel/og';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { ServerRuntime } from 'next/types';
|
||||
|
||||
export const runtime: ServerRuntime = 'edge';
|
||||
|
||||
const fonts = Promise.all([
|
||||
fetch(new URL('../../assets/fonts/Inter-Regular.ttf', import.meta.url)).then(async (res) => res.arrayBuffer()),
|
||||
fetch(new URL('../../assets/fonts/Inter-Bold.ttf', import.meta.url)).then(async (res) => res.arrayBuffer()),
|
||||
fetch(new URL('../../../assets/fonts/Inter-Regular.ttf', import.meta.url)).then(async (res) => res.arrayBuffer()),
|
||||
fetch(new URL('../../../assets/fonts/Inter-Bold.ttf', import.meta.url)).then(async (res) => res.arrayBuffer()),
|
||||
]);
|
||||
|
||||
function resolveIcon(icon: keyof typeof ApiItemKind, size = 88) {
|
||||
@@ -20,9 +24,9 @@ function resolveIcon(icon: keyof typeof ApiItemKind, size = 88) {
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14 2H8L7 3v3h1V3h6v5h-4v1h4l1-1V3l-1-1zM9 6h4v1H9.41L9 6.59V6zM7 7H2L1 8v5l1 1h6l1-1V8L8 7H7zm1 6H2V8h6v5zM3 9h4v1H3V9zm0 2h4v1H3v-1zm6-7h4v1H9V4z"
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -30,9 +34,9 @@ function resolveIcon(icon: keyof typeof ApiItemKind, size = 88) {
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7 3l1-1h6l1 1v5l-1 1h-4V8h4V3H8v3H7V3zm2 6V8L8 7H2L1 8v5l1 1h6l1-1V9zM8 8v5H2V8h6zm1.414-1L9 6.586V6h4v1H9.414zM9 4h4v1H9V4zm-2 6H3v1h4v-1z"
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -52,9 +56,9 @@ function resolveIcon(icon: keyof typeof ApiItemKind, size = 88) {
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2 5h2V4H1.5l-.5.5v8l.5.5H4v-1H2V5zm12.5-1H12v1h2v7h-2v1h2.5l.5-.5v-8l-.5-.5zm-2.74 2.57L12 7v2.51l-.3.45-4.5 2h-.46l-2.5-1.5-.24-.43v-2.5l.3-.46 4.5-2h.46l2.5 1.5zM5 9.71l1.5.9V9.28L5 8.38v1.33zm.58-2.15l1.45.87 3.39-1.5-1.45-.87-3.39 1.5zm1.95 3.17l3.5-1.56v-1.4l-3.5 1.55v1.41z"
|
||||
fill-rule="evenodd"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -73,10 +77,10 @@ function resolveIcon(icon: keyof typeof ApiItemKind, size = 88) {
|
||||
}
|
||||
}
|
||||
|
||||
export default async function handler(req: NextRequest) {
|
||||
export async function GET(request: NextRequest) {
|
||||
const fontData = await fonts;
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
const hasPkg = searchParams.has('pkg');
|
||||
const hasKind = searchParams.has('kind');
|
||||
@@ -164,7 +168,3 @@ export default async function handler(req: NextRequest) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
};
|
||||
@@ -1,11 +1,15 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
import { ImageResponse } from '@vercel/og';
|
||||
|
||||
const fonts = fetch(new URL('../../assets/fonts/Inter-Black.ttf', import.meta.url)).then(async (res) =>
|
||||
import { ImageResponse } from '@vercel/og';
|
||||
import type { ServerRuntime } from 'next/types';
|
||||
|
||||
export const runtime: ServerRuntime = 'edge';
|
||||
|
||||
const fonts = fetch(new URL('../../../assets/fonts/Inter-Black.ttf', import.meta.url)).then(async (res) =>
|
||||
res.arrayBuffer(),
|
||||
);
|
||||
|
||||
export default async function handler() {
|
||||
export async function GET() {
|
||||
const fontData = await fonts;
|
||||
|
||||
return new ImageResponse(
|
||||
@@ -38,7 +42,3 @@ export default async function handler() {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
runtime: 'experimental-edge',
|
||||
};
|
||||
@@ -15,7 +15,13 @@ export async function fetchModelJSON(packageName: string, version: string): Prom
|
||||
join(process.cwd(), '..', '..', 'packages', packageName, 'docs', 'docs.api.json'),
|
||||
'utf8',
|
||||
);
|
||||
return JSON.parse(res);
|
||||
|
||||
try {
|
||||
return JSON.parse(res);
|
||||
} catch {
|
||||
console.log(res);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${version}.api.json`, {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { Providers } from './providers';
|
||||
import { CmdKDialog } from '~/components/CmdK';
|
||||
|
||||
export default function ItemLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<Providers>
|
||||
<>{children}</>
|
||||
<CmdKDialog />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
// eslint-disable-next-line n/prefer-global/process
|
||||
import process, { cwd } from 'node:process';
|
||||
import { createApiModel, tryResolveSummaryText } from '@discordjs/scripts';
|
||||
import { addPackageToModel, tryResolveSummaryText } from '@discordjs/scripts';
|
||||
import type {
|
||||
ApiClass,
|
||||
ApiDeclaredItem,
|
||||
@@ -18,9 +13,9 @@ import type {
|
||||
ApiTypeAlias,
|
||||
ApiVariable,
|
||||
} from '@microsoft/api-extractor-model';
|
||||
import { ApiItemKind, ApiFunction } from '@microsoft/api-extractor-model';
|
||||
import type { Metadata } from 'next';
|
||||
import { ApiItemKind, ApiModel, ApiFunction } from '@microsoft/api-extractor-model';
|
||||
import { notFound } from 'next/navigation';
|
||||
import type { Metadata } from 'next/types';
|
||||
import { fetchModelJSON } from '~/app/docAPI';
|
||||
import { Class } from '~/components/model/Class';
|
||||
import { Interface } from '~/components/model/Interface';
|
||||
@@ -39,7 +34,7 @@ export interface ItemRouteParams {
|
||||
|
||||
async function fetchHeadMember({ package: packageName, version, item }: ItemRouteParams): Promise<ApiItem | undefined> {
|
||||
const modelJSON = await fetchModelJSON(packageName, version);
|
||||
const model = createApiModel(modelJSON);
|
||||
const model = addPackageToModel(new ApiModel(), modelJSON);
|
||||
const pkg = model.tryGetPackageByName(packageName);
|
||||
const entry = pkg?.entryPoints[0];
|
||||
|
||||
@@ -89,11 +84,11 @@ function resolveMemberSearchParams(packageName: string, member: ApiItem): URLSea
|
||||
return params;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: ItemRouteParams }): Promise<Metadata> {
|
||||
export async function generateMetadata({ params }: { params: ItemRouteParams }) {
|
||||
const member = (await fetchHeadMember(params))!;
|
||||
const name = `discord.js${member?.displayName ? ` | ${member.displayName}` : ''}`;
|
||||
const ogTitle = `${params.package ?? 'discord.js'}${member?.displayName ? ` | ${member.displayName}` : ''}`;
|
||||
const url = new URL('https://discordjs.dev/api/og_model');
|
||||
const url = new URL('https://discordjs.dev/api/dynamic-open-graph.png');
|
||||
const searchParams = resolveMemberSearchParams(params.package, member);
|
||||
url.search = searchParams.toString();
|
||||
const ogImage = url.toString();
|
||||
@@ -107,21 +102,21 @@ export async function generateMetadata({ params }: { params: ItemRouteParams }):
|
||||
description: description ?? 'Discord.js API Documentation',
|
||||
images: ogImage,
|
||||
},
|
||||
};
|
||||
} satisfies Metadata;
|
||||
}
|
||||
|
||||
export async function generateStaticParams({ params: { package: packageName, version } }: { params: ItemRouteParams }) {
|
||||
const modelJSON = await fetchModelJSON(packageName, version);
|
||||
const model = createApiModel(modelJSON);
|
||||
const model = addPackageToModel(new ApiModel(), modelJSON);
|
||||
|
||||
const pkg = model.tryGetPackageByName(packageName);
|
||||
const entry = pkg?.entryPoints[0];
|
||||
|
||||
if (!entry) {
|
||||
return notFound();
|
||||
notFound();
|
||||
}
|
||||
|
||||
return entry.members.map((member) => ({
|
||||
return entry.members.map((member: ApiItem) => ({
|
||||
item: member.displayName,
|
||||
}));
|
||||
}
|
||||
@@ -131,21 +126,20 @@ async function fetchMember({ package: packageName, version: branchName = 'main',
|
||||
notFound();
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
if (process.env.NEXT_PUBLIC_LOCAL_DEV) {
|
||||
const res = await readFile(join(cwd(), '..', '..', 'packages', packageName, 'docs', 'docs.api.json'), 'utf8');
|
||||
data = JSON.parse(res);
|
||||
} else {
|
||||
const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${branchName}.api.json`);
|
||||
data = await res.json();
|
||||
const model = new ApiModel();
|
||||
|
||||
if (branchName === 'main') {
|
||||
const modelJSONFiles = await Promise.all(PACKAGES.map(async (pkg) => fetchModelJSON(pkg, branchName)));
|
||||
|
||||
for (const modelJSONFile of modelJSONFiles) {
|
||||
addPackageToModel(model, modelJSONFile);
|
||||
}
|
||||
} catch {
|
||||
notFound();
|
||||
} else {
|
||||
const modelJSON = await fetchModelJSON(packageName, branchName);
|
||||
addPackageToModel(model, modelJSON);
|
||||
}
|
||||
|
||||
const [memberName, overloadIndex] = decodeURIComponent(item).split(OVERLOAD_SEPARATOR);
|
||||
const model = createApiModel(data);
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { containerKey, displayName: name } = findMember(model, packageName, memberName) ?? {};
|
||||
@@ -161,7 +155,7 @@ function Member({ member }: { member?: ApiItem }) {
|
||||
case 'Class':
|
||||
return <Class clazz={member as ApiClass} />;
|
||||
case 'Function':
|
||||
return <Function item={member as ApiFunction} key={member.containerKey} />;
|
||||
return <Function item={member as ApiFunction} key={`${member.displayName}-${member.containerKey}`} />;
|
||||
case 'Interface':
|
||||
return <Interface item={member as ApiInterface} />;
|
||||
case 'TypeAlias':
|
||||
@@ -179,7 +173,7 @@ export default async function Page({ params }: { params: ItemRouteParams }) {
|
||||
const member = await fetchMember(params);
|
||||
|
||||
return (
|
||||
<main
|
||||
<div
|
||||
className={
|
||||
(member?.kind === 'Class' || member?.kind === 'Interface') && (member as ApiClass | ApiInterface).members.length
|
||||
? 'xl:pr-64'
|
||||
@@ -189,6 +183,6 @@ export default async function Page({ params }: { params: ItemRouteParams }) {
|
||||
<article className="dark:bg-dark-600 bg-light-600">
|
||||
<div className="dark:bg-dark-800 bg-white p-6 pb-20 shadow">{member ? <Member member={member} /> : null}</div>
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
'use client';
|
||||
|
||||
// import { ThemeProvider } from 'next-themes';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { CmdKProvider } from '~/contexts/cmdK';
|
||||
import { NavProvider } from '~/contexts/nav';
|
||||
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<NavProvider>
|
||||
<CmdKProvider>
|
||||
{/* <ThemeProvider
|
||||
attribute="class"
|
||||
cookieName="theme"
|
||||
defaultTheme="system"
|
||||
disableTransitionOnChange
|
||||
value={{
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
}}
|
||||
> */}
|
||||
{children}
|
||||
{/* </ThemeProvider> */}
|
||||
</CmdKProvider>
|
||||
</NavProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
'use client';
|
||||
|
||||
export default function Error({ error }: { error: Error }) {
|
||||
console.error(error);
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex h-full max-w-lg flex-col place-content-center place-items-center gap-8 py-16 px-8 lg:py-0 lg:px-6">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">500</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Error.</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
import { createApiModel } from '@discordjs/scripts';
|
||||
import { addPackageToModel } from '@discordjs/scripts';
|
||||
import type { ApiFunction, ApiItem } from '@microsoft/api-extractor-model';
|
||||
import { ApiModel } from '@microsoft/api-extractor-model';
|
||||
import Image from 'next/image';
|
||||
import { notFound } from 'next/navigation';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { Providers } from './providers';
|
||||
import { fetchModelJSON, fetchVersions } from '~/app/docAPI';
|
||||
import vercelLogo from '~/assets/powered-by-vercel.svg';
|
||||
import { CmdKDialog } from '~/components/CmdK';
|
||||
import { Header } from '~/components/Header';
|
||||
import { Nav } from '~/components/Nav';
|
||||
import type { SidebarSectionItemData } from '~/components/Sidebar';
|
||||
@@ -41,18 +44,18 @@ function serializeIntoSidebarItemData(item: ApiItem): SidebarSectionItemData {
|
||||
|
||||
export default async function PackageLayout({ children, params }: PropsWithChildren<{ params: VersionRouteParams }>) {
|
||||
const modelJSON = await fetchModelJSON(params.package, params.version);
|
||||
const model = createApiModel(modelJSON);
|
||||
const model = addPackageToModel(new ApiModel(), modelJSON);
|
||||
|
||||
const pkg = model.tryGetPackageByName(params.package);
|
||||
|
||||
if (!pkg) {
|
||||
return notFound();
|
||||
notFound();
|
||||
}
|
||||
|
||||
const entry = pkg.entryPoints[0];
|
||||
|
||||
if (!entry) {
|
||||
return notFound();
|
||||
notFound();
|
||||
}
|
||||
|
||||
const members = entry.members.filter((member) => {
|
||||
@@ -64,80 +67,83 @@ export default async function PackageLayout({ children, params }: PropsWithChild
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Nav members={members.map((member) => serializeIntoSidebarItemData(member))} />
|
||||
<article className="pt-18 lg:pl-76">
|
||||
<div className="relative z-10 min-h-[calc(100vh_-_70px)]">{children}</div>
|
||||
<div className="h-76 md:h-52" />
|
||||
<footer className="dark:bg-dark-600 h-76 lg:pl-84 bg-light-600 fixed bottom-0 left-0 right-0 md:h-52 md:pl-4 md:pr-16">
|
||||
<div className="mx-auto flex max-w-6xl flex-col place-items-center gap-12 pt-12 lg:place-content-center">
|
||||
<div className="flex w-full flex-col place-content-between place-items-center gap-12 md:flex-row md:gap-0">
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Vercel"
|
||||
>
|
||||
<Image alt="Vercel" src={vercelLogo} />
|
||||
</a>
|
||||
<div className="flex flex-row gap-6 md:gap-12">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-lg font-semibold">Community</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://discord.gg/djs"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Discord
|
||||
</a>
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://github.com/discordjs/discord.js/discussions"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub discussions
|
||||
</a>
|
||||
<Providers>
|
||||
<main>
|
||||
<Header />
|
||||
<Nav members={members.map((member) => serializeIntoSidebarItemData(member))} />
|
||||
<article className="pt-18 lg:pl-76">
|
||||
<div className="relative z-10 min-h-[calc(100vh_-_70px)]">{children}</div>
|
||||
<div className="h-76 md:h-52" />
|
||||
<footer className="dark:bg-dark-600 h-76 lg:pl-84 bg-light-600 fixed bottom-0 left-0 right-0 md:h-52 md:pl-4 md:pr-16">
|
||||
<div className="mx-auto flex max-w-6xl flex-col place-items-center gap-12 pt-12 lg:place-content-center">
|
||||
<div className="flex w-full flex-col place-content-between place-items-center gap-12 md:flex-row md:gap-0">
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Vercel"
|
||||
>
|
||||
<Image alt="Vercel" src={vercelLogo} />
|
||||
</a>
|
||||
<div className="flex flex-row gap-6 md:gap-12">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-lg font-semibold">Community</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://discord.gg/djs"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Discord
|
||||
</a>
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://github.com/discordjs/discord.js/discussions"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub discussions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-lg font-semibold">Project</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://github.com/discordjs/discord.js"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
discord.js
|
||||
</a>
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://discordjs.guide"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
discord.js guide
|
||||
</a>
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://discord-api-types.dev"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
discord-api-types
|
||||
</a>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-lg font-semibold">Project</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://github.com/discordjs/discord.js"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
discord.js
|
||||
</a>
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://discordjs.guide"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
discord.js guide
|
||||
</a>
|
||||
<a
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 focus:ring"
|
||||
href="https://discord-api-types.dev"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
discord-api-types
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
</>
|
||||
</footer>
|
||||
</article>
|
||||
</main>
|
||||
<CmdKDialog />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,66 +1,36 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { serialize } from 'next-mdx-remote/serialize';
|
||||
import type { SerializeOptions } from 'next-mdx-remote/dist/types';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import rehypeIgnore from 'rehype-ignore';
|
||||
import rehypePrettyCode, { type Options } from 'rehype-pretty-code';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { getHighlighter } from 'shiki';
|
||||
import shikiLangJavascript from 'shiki/languages/javascript.tmLanguage.json';
|
||||
import shikiLangTypescript from 'shiki/languages/typescript.tmLanguage.json';
|
||||
import shikiThemeDarkPlus from 'shiki/themes/dark-plus.json';
|
||||
import shikiThemeLightPlus from 'shiki/themes/light-plus.json';
|
||||
import type { VersionRouteParams } from './layout';
|
||||
import { MDXRemote } from '~/components/MDXRemote';
|
||||
import { SyntaxHighlighter } from '~/components/SyntaxHighlighter';
|
||||
|
||||
async function loadREADME(packageName: string) {
|
||||
return readFile(join(process.cwd(), 'src', 'assets', 'readme', packageName, 'home-README.md'), 'utf8');
|
||||
}
|
||||
|
||||
async function generateMDX(readme: string) {
|
||||
return serialize(readme, {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
remarkRehypeOptions: { allowDangerousHtml: true },
|
||||
rehypePlugins: [
|
||||
rehypeRaw,
|
||||
rehypeIgnore,
|
||||
rehypeSlug,
|
||||
[
|
||||
rehypePrettyCode,
|
||||
{
|
||||
theme: {
|
||||
dark: shikiThemeDarkPlus,
|
||||
light: shikiThemeLightPlus,
|
||||
},
|
||||
getHighlighter: async (options?: Partial<Options>) =>
|
||||
getHighlighter({
|
||||
...options,
|
||||
langs: [
|
||||
// @ts-expect-error: Working as intended
|
||||
{ id: 'javascript', aliases: ['js'], scopeName: 'source.js', grammar: shikiLangJavascript },
|
||||
// @ts-expect-error: Working as intended
|
||||
{ id: 'typescript', aliases: ['ts'], scopeName: 'source.ts', grammar: shikiLangTypescript },
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
],
|
||||
format: 'md',
|
||||
},
|
||||
});
|
||||
}
|
||||
const mdxOptions = {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
remarkRehypeOptions: { allowDangerousHtml: true },
|
||||
rehypePlugins: [rehypeRaw, rehypeIgnore, rehypeSlug],
|
||||
format: 'md',
|
||||
},
|
||||
} satisfies SerializeOptions;
|
||||
|
||||
export default async function Page({ params }: { params: VersionRouteParams }) {
|
||||
const { package: packageName } = params;
|
||||
const readmeSource = await loadREADME(packageName);
|
||||
const mdxSource = await generateMDX(readmeSource);
|
||||
|
||||
return (
|
||||
<article className="dark:bg-dark-600 bg-white p-10">
|
||||
<div className="prose max-w-none">
|
||||
<MDXRemote {...mdxSource} />
|
||||
{/* @ts-expect-error async component */}
|
||||
<MDXRemote components={{ pre: SyntaxHighlighter }} options={mdxOptions} source={readmeSource} />
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { CmdKProvider } from '~/contexts/cmdK';
|
||||
import { NavProvider } from '~/contexts/nav';
|
||||
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<NavProvider>
|
||||
<CmdKProvider>{children}</CmdKProvider>
|
||||
</NavProvider>
|
||||
);
|
||||
}
|
||||
@@ -24,14 +24,14 @@ export default async function Page({ params }: { params: { package: string } })
|
||||
const data = await getData(params.package);
|
||||
|
||||
return (
|
||||
<div className="min-w-xs sm:w-md mx-auto flex h-full flex-row place-content-center place-items-center gap-8 py-0 px-4 lg:py-0 lg:px-6">
|
||||
<div className="min-w-xs sm:w-md mx-auto flex h-screen flex-row place-content-center place-items-center gap-8 py-0 px-4 lg:py-0 lg:px-6">
|
||||
<div className="flex grow flex-col place-content-center gap-4">
|
||||
<h1 className="text-2xl font-semibold">Select a version:</h1>
|
||||
{data.map((version) => (
|
||||
{data.map((version, idx) => (
|
||||
<Link
|
||||
className="dark:bg-dark-400 dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 focus:ring-width-2 focus:ring-blurple flex h-11 transform-gpu cursor-pointer select-none appearance-none flex-col place-content-center rounded border border-neutral-300 bg-transparent p-4 text-base font-semibold leading-none text-black outline-0 hover:bg-neutral-100 focus:ring active:translate-y-px active:bg-neutral-200 dark:text-white"
|
||||
href={`/docs/packages/${params.package}/${version}`}
|
||||
key={version}
|
||||
key={`${version}-${idx}`}
|
||||
>
|
||||
<div className="flex flex-row place-content-between place-items-center gap-4">
|
||||
<div className="flex flex-row place-content-between place-items-center gap-4">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { VscPackage } from '@react-icons/all-files/vsc/VscPackage';
|
||||
import Link from 'next/link';
|
||||
import { PACKAGES } from '~/util/constants';
|
||||
|
||||
export default async function Page() {
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="min-w-xs sm:w-md mx-auto flex min-h-screen flex-row place-content-center place-items-center gap-8 py-0 px-4 lg:py-0 lg:px-6">
|
||||
<div className="flex grow flex-col place-content-center gap-4">
|
||||
@@ -24,11 +24,11 @@ export default async function Page() {
|
||||
<VscArrowRight size={20} />
|
||||
</div>
|
||||
</a>
|
||||
{PACKAGES.map((pkg) => (
|
||||
{PACKAGES.map((pkg, idx) => (
|
||||
<Link
|
||||
className="dark:bg-dark-400 dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 focus:ring-width-2 focus:ring-blurple flex h-11 transform-gpu cursor-pointer select-none appearance-none flex-row place-content-between rounded border border-neutral-300 bg-transparent p-4 text-base font-semibold leading-none text-black outline-0 hover:bg-neutral-100 focus:ring active:translate-y-px active:bg-neutral-200 dark:text-white"
|
||||
href={`/docs/packages/${pkg}`}
|
||||
key={pkg}
|
||||
key={`${pkg}-${idx}`}
|
||||
>
|
||||
<div className="flex grow flex-row place-content-between place-items-center gap-4">
|
||||
<div className="flex grow flex-row place-content-between place-items-center gap-4">
|
||||
|
||||
12
apps/website/src/app/error.tsx
Normal file
12
apps/website/src/app/error.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
'use client';
|
||||
|
||||
export default function Error({ error }: { error: Error }) {
|
||||
console.error(error);
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex h-full max-w-lg flex-col place-content-center place-items-center gap-8 py-16 px-8 lg:py-0 lg:px-6">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">500</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Error.</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
apps/website/src/app/global-error.tsx
Normal file
23
apps/website/src/app/global-error.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { Providers } from './providers';
|
||||
import { inter } from '~/util/fonts';
|
||||
|
||||
export default function GlobalError({ error }: { error: Error }) {
|
||||
console.error(error);
|
||||
|
||||
return (
|
||||
<html className={inter.variable} lang="en" suppressHydrationWarning>
|
||||
<body className="dark:bg-dark-800 bg-white">
|
||||
<Providers>
|
||||
<main className="mx-auto h-screen max-w-2xl">
|
||||
<div className="mx-auto flex h-full max-w-lg flex-col place-content-center place-items-center gap-8 py-16 px-8 lg:py-0 lg:px-6">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">500</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Error.</h2>
|
||||
</div>
|
||||
</main>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,18 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { Providers } from './providers';
|
||||
import { inter } from '~/util/fonts';
|
||||
|
||||
import '@unocss/reset/tailwind.css';
|
||||
import '../styles/inter.css';
|
||||
import '@unocss/reset/tailwind-compat.css';
|
||||
import '../styles/unocss.css';
|
||||
import '../styles/cmdk.css';
|
||||
import '../styles/main.css';
|
||||
|
||||
export default function RootLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="dark:bg-dark-800 bg-white">{children}</body>
|
||||
<html className={inter.variable} lang="en" suppressHydrationWarning>
|
||||
<body className="dark:bg-dark-800 bg-white">
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { FiExternalLink } from '@react-icons/all-files/fi/FiExternalLink';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import type { Metadata } from 'next/types';
|
||||
import vercelLogo from '../assets/powered-by-vercel.svg';
|
||||
import { SyntaxHighlighter } from '~/components/SyntaxHighlighter';
|
||||
import { CODE_EXAMPLE, DESCRIPTION } from '~/util/constants';
|
||||
|
||||
export const metadata = {
|
||||
export const metadata: Metadata = {
|
||||
title: 'discord.js',
|
||||
description: DESCRIPTION,
|
||||
viewport: {
|
||||
@@ -51,7 +52,7 @@ export const metadata = {
|
||||
type: 'website',
|
||||
title: 'discord.js',
|
||||
description: DESCRIPTION,
|
||||
images: 'https://discordjs.dev/api/og',
|
||||
images: 'https://discordjs.dev/api/open-graph.png',
|
||||
},
|
||||
|
||||
twitter: {
|
||||
@@ -66,7 +67,7 @@ export const metadata = {
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="mx-auto flex max-w-6xl flex-col place-items-center gap-12 py-16 px-8 lg:h-full lg:place-content-center lg:py-0 lg:px-6">
|
||||
<div className="mx-auto flex h-screen max-w-6xl flex-col place-items-center gap-12 py-16 px-8 lg:place-content-center lg:py-0 lg:px-6">
|
||||
<div className="flex flex-col place-items-center gap-10 lg:flex-row lg:gap-6">
|
||||
<div className="flex max-w-lg flex-col gap-3 lg:mr-8">
|
||||
<h1 className="text-3xl font-black leading-tight sm:text-5xl sm:leading-tight">
|
||||
@@ -104,7 +105,10 @@ export default function Page() {
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<SyntaxHighlighter code={CODE_EXAMPLE} />
|
||||
<div className="max-w-sm sm:max-w-6xl">
|
||||
{/* @ts-expect-error async component */}
|
||||
<SyntaxHighlighter code={CODE_EXAMPLE} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row place-content-center">
|
||||
<a
|
||||
|
||||
8
apps/website/src/app/providers.tsx
Normal file
8
apps/website/src/app/providers.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
return <ThemeProvider attribute="class">{children}</ThemeProvider>;
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/website/src/assets/fonts/Inter.ttf
Normal file
BIN
apps/website/src/assets/fonts/Inter.ttf
Normal file
Binary file not shown.
@@ -48,10 +48,10 @@ export function CmdKDialog() {
|
||||
|
||||
const searchResultItems = useMemo(
|
||||
() =>
|
||||
searchResults?.map((item) => (
|
||||
searchResults?.map((item, idx) => (
|
||||
<Command.Item
|
||||
className="dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 [&[aria-selected]]:ring-blurple [&[aria-selected]]:ring-width-4 my-1 flex transform-gpu cursor-pointer select-none appearance-none flex-row place-content-center rounded bg-transparent px-4 py-2 text-base font-semibold leading-none text-black outline-0 hover:bg-neutral-100 active:translate-y-px active:bg-neutral-200 dark:text-white [&[aria-selected]]:ring"
|
||||
key={item.id}
|
||||
key={`${item.id}-${idx}`}
|
||||
onSelect={() => {
|
||||
router.push(item.path);
|
||||
dialog!.setOpen(false);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ApiModel, Excerpt } from '@microsoft/api-extractor-model';
|
||||
import { ExcerptTokenKind } from '@microsoft/api-extractor-model';
|
||||
import { ItemLink } from './ItemLink';
|
||||
import { resolveItemURI } from './documentation/util';
|
||||
|
||||
export interface ExcerptTextProps {
|
||||
/**
|
||||
@@ -29,7 +28,12 @@ export function ExcerptText({ model, excerpt }: ExcerptTextProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemLink className="text-blurple" itemURI={resolveItemURI(item)} key={item.containerKey}>
|
||||
<ItemLink
|
||||
className="text-blurple"
|
||||
itemURI={`${item.displayName}:${item.kind}`}
|
||||
key={`${item.displayName}-${item.containerKey}`}
|
||||
packageName={item.getAssociatedPackage()?.displayName.replace('@discordjs/', '')}
|
||||
>
|
||||
{token.text}
|
||||
</ItemLink>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { FiCommand } from '@react-icons/all-files/fi/FiCommand';
|
||||
// import { VscColorMode } from '@react-icons/all-files/vsc/VscColorMode';
|
||||
import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted';
|
||||
import { VscMenu } from '@react-icons/all-files/vsc/VscMenu';
|
||||
import { VscSearch } from '@react-icons/all-files/vsc/VscSearch';
|
||||
import { Button } from 'ariakit/button';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
// import { useTheme } from 'next-themes';
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { ThemeSwitcher } from './ThemeSwitcher';
|
||||
import { useCmdK } from '~/contexts/cmdK';
|
||||
import { useNav } from '~/contexts/nav';
|
||||
|
||||
export function Header() {
|
||||
const pathname = usePathname();
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { setOpened } = useNav();
|
||||
// const { resolvedTheme, setTheme } = useTheme();
|
||||
const dialog = useCmdK();
|
||||
// const toggleTheme = () => setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||
const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -40,7 +36,7 @@ export function Header() {
|
||||
<Link
|
||||
className="focus:ring-width-2 focus:ring-blurple rounded outline-0 hover:underline focus:ring"
|
||||
href={`/${original.slice(0, idx + 1).join('/')}`}
|
||||
key={idx}
|
||||
key={`${path}-${idx}`}
|
||||
>
|
||||
{path}
|
||||
</Link>
|
||||
@@ -53,7 +49,7 @@ export function Header() {
|
||||
pathElements.flatMap((el, idx, array) => {
|
||||
if (idx === 0) {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<Fragment key={`${el.key}-${idx}`}>
|
||||
<div className="mx-2">/</div>
|
||||
{el}
|
||||
<div className="mx-2">/</div>
|
||||
@@ -63,14 +59,14 @@ export function Header() {
|
||||
|
||||
if (idx !== array.length - 1) {
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
<Fragment key={`${el.key}-${idx}`}>
|
||||
{el}
|
||||
<div className="mx-2">/</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <Fragment key={idx}>{el}</Fragment>;
|
||||
return <Fragment key={`${el.key}-${idx}`}>{el}</Fragment>;
|
||||
}),
|
||||
[pathElements],
|
||||
);
|
||||
@@ -111,13 +107,7 @@ export function Header() {
|
||||
>
|
||||
<VscGithubInverted size={24} />
|
||||
</Button>
|
||||
{/* <Button
|
||||
aria-label="Toggle theme"
|
||||
className="focus:ring-width-2 focus:ring-blurple flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none flex-row place-items-center rounded-full rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-0 focus:ring active:translate-y-px"
|
||||
onClick={() => toggleTheme()}
|
||||
>
|
||||
<VscColorMode size={24} />
|
||||
</Button> */}
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { LinkProps } from 'next/link';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useCurrentPathMeta } from '~/hooks/useCurrentPathMeta';
|
||||
|
||||
export interface ItemLinkProps
|
||||
extends Omit<LinkProps, 'href'>,
|
||||
@@ -15,6 +16,11 @@ export interface ItemLinkProps
|
||||
* The URI of the api item to link to. (e.g. `/RestManager`)
|
||||
*/
|
||||
itemURI: string;
|
||||
|
||||
/**
|
||||
* The name of the package the item belongs to.
|
||||
*/
|
||||
packageName?: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,17 +32,13 @@ export interface ItemLinkProps
|
||||
*/
|
||||
export function ItemLink(props: PropsWithChildren<ItemLinkProps>) {
|
||||
const path = usePathname();
|
||||
const { packageName, version } = useCurrentPathMeta();
|
||||
|
||||
if (!path) {
|
||||
throw new Error('ItemLink must be used inside a Next.js page. (e.g. /docs/packages/foo/main)');
|
||||
}
|
||||
|
||||
// Check if the item is already in the current path, if so keep the current path
|
||||
const end = path?.split('/')?.length < 6 ? path?.length : -1;
|
||||
const { itemURI, packageName: pkgName, ...linkProps } = props;
|
||||
|
||||
const pathPrefix = path?.split('/').slice(0, end).join('/');
|
||||
|
||||
const { itemURI, ...linkProps } = props;
|
||||
|
||||
return <Link href={`${pathPrefix}${itemURI}`} {...linkProps} />;
|
||||
return <Link href={`/docs/packages/${pkgName ?? packageName}/${version}/${itemURI}`} {...linkProps} />;
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
'use client';
|
||||
|
||||
export { MDXRemote } from 'next-mdx-remote';
|
||||
@@ -8,7 +8,6 @@ import { VersionSelect } from './VersionSelect';
|
||||
import { useNav } from '~/contexts/nav';
|
||||
|
||||
export function Nav({ members }: { members: SidebarSectionItemData[] }) {
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
const { opened } = useNav();
|
||||
|
||||
return (
|
||||
|
||||
@@ -24,8 +24,8 @@ export function PackageSelect() {
|
||||
discord.js
|
||||
</MenuItem>
|
||||
</a>,
|
||||
...PACKAGES.map((pkg) => (
|
||||
<Link href={`/docs/packages/${pkg}/main`} key={pkg}>
|
||||
...PACKAGES.map((pkg, idx) => (
|
||||
<Link href={`/docs/packages/${pkg}/main`} key={`${pkg}-${idx}`}>
|
||||
<MenuItem
|
||||
className="hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 focus:ring-width-2 focus:ring-blurple my-0.5 rounded bg-white p-3 text-sm outline-0 focus:ring"
|
||||
id={pkg}
|
||||
|
||||
@@ -20,9 +20,9 @@ export function PropertyList({ item }: { item: ApiItemContainerMixin }) {
|
||||
|
||||
const propertyItems = useMemo(
|
||||
() =>
|
||||
members.map((prop) => {
|
||||
members.map((prop, idx) => {
|
||||
return (
|
||||
<Fragment key={prop.item.displayName}>
|
||||
<Fragment key={`${prop.item.displayName}-${idx}`}>
|
||||
<Property
|
||||
inheritedFrom={prop.inherited as ApiDeclaredItem & ApiItemContainerMixin}
|
||||
item={prop.item as ApiPropertyItem}
|
||||
|
||||
@@ -94,7 +94,7 @@ export function Sidebar({ members }: { members: SidebarSectionItemData[] }) {
|
||||
{(Object.keys(groupItems) as (keyof GroupedMembers)[])
|
||||
.filter((group) => groupItems[group].length)
|
||||
.map((group, idx) => (
|
||||
<Section icon={resolveIcon(group)} key={idx} title={group}>
|
||||
<Section icon={resolveIcon(group)} key={`${group}-${idx}`} title={group}>
|
||||
{groupItems[group].map((member, index) => (
|
||||
<ItemLink
|
||||
className={`dark:border-dark-100 border-light-800 focus:ring-width-2 focus:ring-blurple ml-5 flex flex-col border-l p-[5px] pl-6 outline-0 focus:rounded focus:border-0 focus:ring ${
|
||||
@@ -103,7 +103,7 @@ export function Sidebar({ members }: { members: SidebarSectionItemData[] }) {
|
||||
: 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
|
||||
}`}
|
||||
itemURI={member.href}
|
||||
key={index}
|
||||
key={`${member.name}-${index}`}
|
||||
onClick={() => setOpened(false)}
|
||||
title={member.name}
|
||||
>
|
||||
|
||||
@@ -1,32 +1,15 @@
|
||||
'use client';
|
||||
import { Code } from 'bright';
|
||||
|
||||
import { PrismAsyncLight } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus, prism } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||
|
||||
export function SyntaxHighlighter({ language = 'typescript', code }: { code: string; language?: string }) {
|
||||
export function SyntaxHighlighter(props: typeof Code) {
|
||||
return (
|
||||
<>
|
||||
<div data-theme="dark">
|
||||
<PrismAsyncLight
|
||||
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
|
||||
language={language}
|
||||
style={vscDarkPlus}
|
||||
wrapLines
|
||||
wrapLongLines
|
||||
>
|
||||
{code}
|
||||
</PrismAsyncLight>
|
||||
{/* @ts-expect-error async component */}
|
||||
<Code codeClassName="font-mono" lang={props.lang ?? 'typescript'} {...props} theme="github-dark-dimmed" />
|
||||
</div>
|
||||
<div data-theme="light">
|
||||
<PrismAsyncLight
|
||||
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
|
||||
language={language}
|
||||
style={prism}
|
||||
wrapLines
|
||||
wrapLongLines
|
||||
>
|
||||
{code}
|
||||
</PrismAsyncLight>
|
||||
{/* @ts-expect-error async component */}
|
||||
<Code codeClassName="font-mono" lang={props.lang ?? 'typescript'} {...props} theme="min-light" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -11,10 +11,10 @@ export function Table({
|
||||
}) {
|
||||
const cols = useMemo(
|
||||
() =>
|
||||
columns.map((column) => (
|
||||
columns.map((column, idx) => (
|
||||
<th
|
||||
className="border-light-900 dark:border-dark-100 break-normal border-b px-3 py-2 text-left text-sm"
|
||||
key={column}
|
||||
key={`${column}-${idx}`}
|
||||
>
|
||||
{column}
|
||||
</th>
|
||||
@@ -26,12 +26,12 @@ export function Table({
|
||||
() =>
|
||||
rows.map((row, idx) => (
|
||||
<tr className="[&>td]:last-of-type:border-0" key={idx}>
|
||||
{Object.entries(row).map(([colName, val]) => (
|
||||
{Object.entries(row).map(([colName, val], index) => (
|
||||
<td
|
||||
className={`border-light-900 dark:border-dark-100 border-b px-3 py-2 text-left text-sm ${
|
||||
columnStyles?.[colName] ?? ''
|
||||
}`}
|
||||
key={colName}
|
||||
key={`${colName}-${index}`}
|
||||
>
|
||||
{val}
|
||||
</td>
|
||||
|
||||
@@ -27,7 +27,7 @@ export function TableOfContentsPropertyItem({ property }: { property: TableOfCon
|
||||
<a
|
||||
className="dark:border-dark-100 border-light-800 dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800 pl-6.5 focus:ring-width-2 focus:ring-blurple ml-[10px] border-l p-[5px] text-sm outline-0 focus:rounded focus:border-0 focus:ring"
|
||||
href={`#${property.name}`}
|
||||
key={property.name}
|
||||
key={`${property.name}-${property.kind}`}
|
||||
title={property.name}
|
||||
>
|
||||
<span className="line-clamp-1">{property.name}</span>
|
||||
@@ -65,7 +65,7 @@ export function TableOfContentItems({ serializedMembers }: TableOfContentsItemPr
|
||||
(member): member is TableOfContentsSerializedProperty =>
|
||||
member.kind === 'Property' || member.kind === 'PropertySignature',
|
||||
)
|
||||
.map((prop) => <TableOfContentsPropertyItem key={prop.name} property={prop} />),
|
||||
.map((prop) => <TableOfContentsPropertyItem key={`${prop.name}-${prop.kind}`} property={prop} />),
|
||||
[serializedMembers],
|
||||
);
|
||||
|
||||
|
||||
30
apps/website/src/components/ThemeSwitcher.tsx
Normal file
30
apps/website/src/components/ThemeSwitcher.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import { VscColorMode } from '@react-icons/all-files/vsc/VscColorMode';
|
||||
import { Button } from 'ariakit/button';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function ThemeSwitcher() {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
const toggleTheme = () => setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
aria-label="Toggle theme"
|
||||
className="focus:ring-width-2 focus:ring-blurple flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none flex-row place-items-center rounded-full rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-0 focus:ring active:translate-y-px"
|
||||
onClick={() => toggleTheme()}
|
||||
>
|
||||
<VscColorMode size={24} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -18,8 +18,8 @@ export function VersionSelect() {
|
||||
const versionMenuItems = useMemo(
|
||||
() =>
|
||||
versions
|
||||
?.map((item) => (
|
||||
<Link href={`/docs/packages/${packageName}/${item}`} key={item}>
|
||||
?.map((item, idx) => (
|
||||
<Link href={`/docs/packages/${packageName}/${item}`} key={`${item}-${idx}`}>
|
||||
<MenuItem
|
||||
className="hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 focus:ring-width-2 focus:ring-blurple my-0.5 rounded bg-white p-3 text-sm outline-0 focus:ring"
|
||||
onClick={() => versionMenu.setOpen(false)}
|
||||
|
||||
@@ -42,8 +42,8 @@ export function HierarchyText({ item, type }: { item: ApiClass | ApiInterface; t
|
||||
<div className="flex flex-row place-items-center gap-4">
|
||||
<h3 className="text-xl font-bold">{type}</h3>
|
||||
<span className="space-y-2 break-all font-mono">
|
||||
{excerpts.map((excerpt, index) => (
|
||||
<ExcerptText excerpt={excerpt} key={index} model={model} />
|
||||
{excerpts.map((excerpt, idx) => (
|
||||
<ExcerptText excerpt={excerpt} key={idx} model={model} />
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -11,15 +11,13 @@ import { hasProperties, hasMethods, serializeMembers } from './util';
|
||||
|
||||
export function MemberContainerDocumentation({
|
||||
item,
|
||||
version,
|
||||
subheading,
|
||||
}: {
|
||||
item: ApiDeclaredItem & ApiItemContainerMixin & ApiTypeParameterListMixin;
|
||||
subheading?: ReactNode;
|
||||
version: string;
|
||||
}) {
|
||||
return (
|
||||
<Documentation item={item}>
|
||||
<Documentation>
|
||||
{subheading}
|
||||
<SyntaxHighlighter code={item.excerpt.text} />
|
||||
<SummarySection item={item} />
|
||||
|
||||
@@ -74,7 +74,7 @@ export function TSDoc({ item, tsdoc }: { item: ApiItem; tsdoc: DocNode }): JSX.E
|
||||
|
||||
case DocNodeKind.FencedCode: {
|
||||
const { language, code } = tsdoc as DocFencedCode;
|
||||
return <SyntaxHighlighter code={code} key={idx} language={language} />;
|
||||
return <SyntaxHighlighter code={code} key={idx} lang={language ?? 'typescript'} />;
|
||||
}
|
||||
|
||||
case DocNodeKind.Comment: {
|
||||
|
||||
@@ -14,8 +14,8 @@ export function Enum({ item }: { item: ApiEnum }) {
|
||||
<SummarySection item={item} />
|
||||
<DocumentationSection icon={<VscSymbolEnum size={20} />} padded title="Members">
|
||||
<div className="flex flex-col gap-4">
|
||||
{item.members.map((member) => (
|
||||
<Panel key={member.containerKey}>
|
||||
{item.members.map((member, idx) => (
|
||||
<Panel key={`${member.displayName}-${idx}`}>
|
||||
<EnumMember member={member} />
|
||||
</Panel>
|
||||
))}
|
||||
|
||||
@@ -9,7 +9,7 @@ export function Function({ item }: { item: ApiFunction }) {
|
||||
if (item.getMergedSiblings().length > 1) {
|
||||
const overloads = item
|
||||
.getMergedSiblings()
|
||||
.map((sibling, idx) => <FunctionBody item={sibling as ApiFunction} key={idx} />);
|
||||
.map((sibling, idx) => <FunctionBody item={sibling as ApiFunction} key={`${sibling.displayName}-${idx}`} />);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ApiFunction } from '@microsoft/api-extractor-model';
|
||||
import { SyntaxHighlighter } from '~/components/SyntaxHighlighter';
|
||||
import { Documentation } from '~/components/documentation/Documentation';
|
||||
import { Header } from '~/components/documentation/Header';
|
||||
import { ParameterSection } from '~/components/documentation/section/ParametersSection';
|
||||
import { SummarySection } from '~/components/documentation/section/SummarySection';
|
||||
import { TypeParameterSection } from '~/components/documentation/section/TypeParametersSection';
|
||||
@@ -13,7 +12,7 @@ export interface FunctionBodyProps {
|
||||
|
||||
export function FunctionBody({ item }: { item: ApiFunction }) {
|
||||
return (
|
||||
<Documentation item={item} showHeader={false}>
|
||||
<Documentation>
|
||||
<SyntaxHighlighter code={item.excerpt.text} />
|
||||
<SummarySection item={item} />
|
||||
{item.typeParameters.length ? <TypeParameterSection item={item} /> : null}
|
||||
|
||||
@@ -20,7 +20,9 @@ export function Method({
|
||||
// each overload node on the server.
|
||||
const overloads = method
|
||||
.getMergedSiblings()
|
||||
.map((sibling, idx) => <MethodDocumentation key={idx} method={sibling as ApiMethod | ApiMethodSignature} />);
|
||||
.map((sibling, idx) => (
|
||||
<MethodDocumentation key={`${sibling.displayName}-${idx}`} method={sibling as ApiMethod | ApiMethodSignature} />
|
||||
));
|
||||
|
||||
return (
|
||||
<OverloadSwitcher overloads={overloads}>
|
||||
|
||||
19
apps/website/src/hooks/useCurrentPathMeta.ts
Normal file
19
apps/website/src/hooks/useCurrentPathMeta.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export function useCurrentPathMeta() {
|
||||
const pathname = usePathname();
|
||||
|
||||
if (!pathname) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const [, , , packageName, version, item] = pathname.split('/');
|
||||
|
||||
return {
|
||||
packageName,
|
||||
version,
|
||||
item,
|
||||
};
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-Light.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-LightItalic.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-Regular.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-Italic.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-Medium.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-MediumItalic.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-SemiBold.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-SemiBoldItalic.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-Bold.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-BoldItalic.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-Black.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url('/fonts/Inter-BlackItalic.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-named-instance: 'Regular';
|
||||
src: url('/fonts/Inter-roman.var.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-named-instance: 'Italic';
|
||||
src: url('/fonts/Inter-italic.var.woff2?v=3.19') format('woff2');
|
||||
}
|
||||
@@ -1,30 +1,6 @@
|
||||
:root {
|
||||
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
|
||||
}
|
||||
|
||||
@supports (font-variation-settings: normal) {
|
||||
:root {
|
||||
font-family: 'Inter var', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
#__next {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[data-nextjs-scroll-focus-boundary] {
|
||||
height: 100%;
|
||||
font-family: var(--font-inter);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
|
||||
7
apps/website/src/util/fonts.ts
Normal file
7
apps/website/src/util/fonts.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import localFont from 'next/font/local';
|
||||
|
||||
export const inter = localFont({
|
||||
src: '../assets/fonts/Inter.ttf',
|
||||
variable: '--font-inter',
|
||||
display: 'swap',
|
||||
});
|
||||
Reference in New Issue
Block a user