refactor: docs design (#8487)

This commit is contained in:
Noel
2022-08-15 14:48:00 +02:00
committed by GitHub
parent d09ef1e425
commit 4ab1d09997
44 changed files with 1533 additions and 1251 deletions

View File

@@ -6,8 +6,8 @@
"scripts": {
"test": "vitest run",
"build": "unbuild",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix",
"fmt": "yarn format"
},
"main": "./dist/index.mjs",

View File

@@ -5,8 +5,8 @@
"scripts": {
"test": "vitest run",
"build": "unbuild",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix",
"fmt": "yarn format",
"docs": "downlevel-dts . docs --to=3.7 && docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
"prepack": "yarn lint && yarn test && yarn build",

View File

@@ -5,8 +5,8 @@
"scripts": {
"test": "vitest run",
"build": "unbuild",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix",
"fmt": "yarn format",
"docs": "downlevel-dts . docs --to=3.7 && docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
"prepack": "yarn lint && yarn test && yarn build",

View File

@@ -4,8 +4,8 @@
"description": "The docs.json generator for discord.js and its related projects",
"scripts": {
"build": "unbuild",
"lint": "prettier --check . && eslint src --ext mjs,js,ts",
"format": "prettier --write . && eslint src --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src --ext mjs,js,ts --fix",
"fmt": "yarn format",
"prepack": "yarn format && yarn build",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/docgen/*'",
@@ -50,7 +50,7 @@
"devDependencies": {
"@favware/cliff-jumper": "^1.8.6",
"@types/jsdoc-to-markdown": "^7.0.3",
"@types/node": "^16.11.47",
"@types/node": "^16.11.48",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"eslint": "^8.22.0",
@@ -61,7 +61,7 @@
"prettier": "^2.7.1",
"rollup-plugin-typescript2": "0.32.1",
"typescript": "^4.7.4",
"unbuild": "^0.8.4"
"unbuild": "^0.8.8"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -4,8 +4,8 @@
"description": "Lightweight HTTP proxy for Discord's API, brought to you as a container 📦",
"scripts": {
"build": "unbuild",
"lint": "prettier --check . && eslint src --ext mjs,js,ts",
"format": "prettier --write . && eslint src --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src --ext mjs,js,ts --fix",
"fmt": "yarn format",
"prepack": "yarn lint && yarn test && yarn build",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/proxy-container/*'"

View File

@@ -5,8 +5,8 @@
"scripts": {
"test": "vitest run",
"build": "unbuild",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix",
"fmt": "yarn format",
"docs": "downlevel-dts . docs --to=3.7 && docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
"prepack": "yarn lint && yarn test && yarn build",

View File

@@ -5,8 +5,8 @@
"scripts": {
"test": "vitest run",
"build": "unbuild",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix",
"fmt": "yarn format",
"docs": "downlevel-dts . docs --to=3.7 && docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
"prepack": "yarn lint && yarn test && yarn build",

View File

@@ -6,8 +6,8 @@
"scripts": {
"test": "vitest run",
"build": "unbuild",
"lint": "prettier --check . && eslint src --ext mjs,js,ts",
"format": "prettier --write . && eslint src --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src --ext mjs,js,ts --fix",
"fmt": "yarn format"
},
"main": "./dist/index.cjs",

View File

@@ -5,8 +5,8 @@
"scripts": {
"build": "unbuild",
"test": "jest --coverage",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix",
"fmt": "yarn format",
"docs": "downlevel-dts . docs --to=3.7 && docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
"prepack": "yarn lint && yarn test && yarn build",

View File

@@ -12,8 +12,8 @@
"dev:next": "next dev",
"dev:css": "yarn generate:css --watch",
"generate:css": "unocss 'src/**/*.tsx' --out-file ./src/styles/unocss.css",
"lint": "prettier --check . && eslint src --ext mjs,js,ts,tsx",
"format": "prettier --write . && eslint src --ext mjs,js,ts,tsx --fix"
"lint": "prettier --check . && TIMING=1 eslint src --ext mjs,js,ts,tsx",
"format": "prettier --write . && TIMING=1 eslint src --ext mjs,js,ts,tsx --fix"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -47,11 +47,17 @@
},
"homepage": "https://discord.js.org",
"dependencies": {
"@emotion/react": "^11.10.0",
"@emotion/server": "^11.10.0",
"@mantine/core": "^5.1.6",
"@mantine/hooks": "^5.1.6",
"@mantine/next": "^5.1.6",
"@mantine/nprogress": "^5.1.6",
"@mantine/spotlight": "^5.1.6",
"@microsoft/api-extractor-model": "^7.23.0",
"@microsoft/tsdoc": "0.14.1",
"@microsoft/tsdoc-config": "0.16.1",
"@vscode/codicons": "^0.0.32",
"framer-motion": "^7.1.0",
"next": "^12.2.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -60,9 +66,6 @@
"sharp": "^0.30.7"
},
"devDependencies": {
"@testing-library/cypress": "^8.0.3",
"@testing-library/dom": "^8.17.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^16.11.48",
@@ -85,7 +88,6 @@
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"happy-dom": "^6.0.4",
"msw": "^0.44.2",
"prettier": "^2.7.1",
"typescript": "^4.7.4",
"unocss": "^0.45.6",

View File

@@ -1,3 +1,4 @@
import { Group, Stack, Title } from '@mantine/core';
import type { ReactNode } from 'react';
import { CommentSection } from './Comment';
import { HyperlinkedText } from './HyperlinkedText';
@@ -9,36 +10,33 @@ export enum CodeListingSeparatorType {
Value = '=',
}
export interface CodeListingProps {
export function CodeListing({
name,
separator = CodeListingSeparatorType.Type,
summary,
typeTokens,
children,
}: {
name: string;
summary?: ReturnType<DocItem['toJSON']>['summary'];
typeTokens: TokenDocumentation[];
separator?: CodeListingSeparatorType;
children?: ReactNode;
className?: string | undefined;
}
export function CodeListing({
name,
className,
separator = CodeListingSeparatorType.Type,
summary,
typeTokens,
children,
}: CodeListingProps) {
}) {
return (
<div className={className}>
<div key={name} className="flex flex-col">
<div className="w-full flex flex-row gap-3">
<h4 className="font-mono m-0">{`${name}`}</h4>
<h4 className="m-0">{separator}</h4>
<h4 className="font-mono m-0 break-all">
<Stack key={name}>
<Group>
<Title order={4} className="font-mono">
{name}
</Title>
<Title order={4}>{separator}</Title>
<Title order={4} className="font-mono break-all">
<HyperlinkedText tokens={typeTokens} />
</h4>
</div>
{summary && <CommentSection textClassName="text-dark-100 dark:text-gray-300" node={summary} />}
</Title>
</Group>
{summary && <CommentSection node={summary} />}
{children}
</div>
</div>
</Stack>
);
}

View File

@@ -1,27 +1,28 @@
import { Anchor, Box, Text } from '@mantine/core';
import Link from 'next/link';
import type { ReactNode } from 'react';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import type { CommentNode } from '~/DocModel/comment/CommentNode';
import type { CommentNodeContainer } from '~/DocModel/comment/CommentNodeContainer';
import type { FencedCodeCommentNode } from '~/DocModel/comment/FencedCodeCommentNode';
import type { LinkTagCommentNode } from '~/DocModel/comment/LinkTagCommentNode';
import type { PlainTextCommentNode } from '~/DocModel/comment/PlainTextCommentNode';
export interface RemarksBlockProps {
node: ReturnType<CommentNode['toJSON']>;
textClassName?: string | undefined;
}
export function CommentSection({ node, textClassName }: RemarksBlockProps): JSX.Element {
export function CommentSection({ node }: { node: ReturnType<CommentNode['toJSON']> }): JSX.Element {
const createNode = (node: ReturnType<CommentNode['toJSON']>, idx?: number): ReactNode => {
switch (node.kind) {
case 'PlainText':
return <span key={idx}>{(node as ReturnType<PlainTextCommentNode['toJSON']>).text}</span>;
return (
<Text key={idx} span>
{(node as ReturnType<PlainTextCommentNode['toJSON']>).text}
</Text>
);
case 'Paragraph':
return (
<p key={idx} className={textClassName}>
<Text key={idx} inline>
{(node as ReturnType<CommentNodeContainer['toJSON']>).nodes.map((node, idx) => createNode(node, idx))}
</p>
</Text>
);
case 'SoftBreak':
return <br key={idx} />;
@@ -29,18 +30,41 @@ export function CommentSection({ node, textClassName }: RemarksBlockProps): JSX.
const { codeDestination, urlDestination, text } = node as ReturnType<LinkTagCommentNode['toJSON']>;
if (codeDestination) {
return <Link href={codeDestination.path}>{text ?? codeDestination.name}</Link>;
return (
<Link key={idx} href={codeDestination.path} passHref>
<Anchor component="a" className="font-mono">
{text ?? codeDestination.name}
</Anchor>
</Link>
);
}
if (urlDestination) {
return <Link href={urlDestination}>{text ?? urlDestination}</Link>;
return (
<Link key={idx} href={urlDestination} passHref>
<Anchor component="a" className="font-mono">
{text ?? urlDestination}
</Anchor>
</Link>
);
}
return null;
}
case 'FencedCodeBlock': {
const { language, code } = node as ReturnType<FencedCodeCommentNode['toJSON']>;
return <SyntaxHighlighter language={language}>{code}</SyntaxHighlighter>;
return (
<SyntaxHighlighter
key={idx}
wrapLines
wrapLongLines
language={language}
style={vscDarkPlus}
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
>
{code}
</SyntaxHighlighter>
);
}
default:
break;
@@ -50,12 +74,12 @@ export function CommentSection({ node, textClassName }: RemarksBlockProps): JSX.
};
return (
<div>
<Box>
{node.kind === 'Paragraph' || node.kind === 'Section' ? (
<>{(node as CommentNodeContainer).nodes.map((node, idx) => createNode(node, idx))}</>
) : (
<>{createNode(node)}</>
createNode(node)
)}
</div>
</Box>
);
}

View File

@@ -1,3 +1,5 @@
import { Group, Stack, Title, Text, Box } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import type { ReactNode } from 'react';
import { VscListSelection, VscSymbolParameter } from 'react-icons/vsc';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
@@ -32,26 +34,23 @@ export function DocContainer({
extendsTokens,
implementsTokens,
}: DocContainerProps) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
return (
<div className="flex flex-col min-h-full max-h-full grow">
<div className="border-0.5 border-gray px-10 py-2">
<h2 className="flex gap-2 items-center break-all m-0 dark:text-white">
<Stack>
<Title order={2} ml="xs">
<Group>
{generateIcon(kind)}
{name}
</h2>
</div>
</Group>
</Title>
<div className="min-h-full overflow-y-auto overflow-x-clip px-10 pt-5 pb-10">
<Section iconElement={<VscListSelection />} title="Summary" className="dark:text-white mb-5">
{summary ? (
<CommentSection textClassName="text-dark-100 dark:text-gray-300" node={summary} />
) : (
<p className="text-dark-100 dark:text-gray-300">No summary provided.</p>
)}
<Section title="Summary" icon={<VscListSelection />} padded dense={matches}>
{summary ? <CommentSection node={summary} /> : <Text>No summary provided.</Text>}
</Section>
<div className={extendsTokens?.length ? 'mb-2' : 'mb-10'}>
<Box px="xs" pb="xs">
<SyntaxHighlighter
wrapLines
wrapLongLines
language="typescript"
style={vscDarkPlus}
@@ -59,46 +58,49 @@ export function DocContainer({
>
{excerpt}
</SyntaxHighlighter>
</div>
</Box>
{extendsTokens?.length ? (
<div
className={`flex flex-row items-center dark:text-white gap-3 ${implementsTokens?.length ? '' : 'mb-10'}`}
>
<h3 className="m-0">Extends</h3>
<h3 className="m-0">{CodeListingSeparatorType.Type}</h3>
<p className="font-mono break-all">
<Group noWrap>
<Title order={3} ml="xs">
Extends
</Title>
<Title order={3} ml="xs">
{CodeListingSeparatorType.Type}
</Title>
<Text className="font-mono break-all">
<HyperlinkedText tokens={extendsTokens} />
</p>
</div>
</Text>
</Group>
) : null}
{implementsTokens?.length ? (
<div className={`flex flex-row items-center dark:text-white gap-3 mb-10`}>
<h3 className="m-0">Implements</h3>
<h3 className="m-0">{CodeListingSeparatorType.Type}</h3>
<p className="font-mono break-all">
{implementsTokens.map((token, i) => (
<Group noWrap>
<Title order={3} ml="xs">
Implements
</Title>
<Title order={3} ml="xs">
{CodeListingSeparatorType.Type}
</Title>
<Text className="font-mono break-all">
{implementsTokens.map((token, idx) => (
<>
<HyperlinkedText key={i} tokens={token} />
{i < implementsTokens.length - 1 ? ', ' : ''}
<HyperlinkedText tokens={token} />
{idx < implementsTokens.length - 1 ? ', ' : ''}
</>
))}
</p>
</div>
</Text>
</Group>
) : null}
<div className="space-y-10">
<Stack>
{typeParams?.length ? (
<Section
iconElement={<VscSymbolParameter />}
title="Type Parameters"
className="dark:text-white"
defaultClosed
>
<Section title="Type Parameters" icon={<VscSymbolParameter />} padded dense={matches} defaultClosed>
<TypeParamTable data={typeParams} />
</Section>
) : null}
<div className="space-y-10">{children}</div>
</div>
</div>
</div>
<Stack>{children}</Stack>
</Stack>
</Stack>
);
}

View File

@@ -1,10 +1,7 @@
import { Anchor, Text } from '@mantine/core';
import Link from 'next/link';
import type { TokenDocumentation } from '~/util/parse.server';
export interface HyperlinkedTextProps {
tokens: TokenDocumentation[];
}
/**
* Constructs a hyperlinked html node based on token type references
*
@@ -12,22 +9,24 @@ export interface HyperlinkedTextProps {
*
* @returns An array of JSX elements and string comprising the hyperlinked text
*/
export function HyperlinkedText({ tokens }: HyperlinkedTextProps) {
export function HyperlinkedText({ tokens }: { tokens: TokenDocumentation[] }) {
return (
<>
{tokens.map((token) => {
{tokens.map((token, idx) => {
if (token.path) {
return (
<Link key={token.text} href={token.path}>
<a className="text-blue-500 dark:text-blue-300 no-underline">{token.text}</a>
<Link key={idx} href={token.path} passHref>
<Anchor component="a" inherit>
{token.text}
</Anchor>
</Link>
);
}
return (
<span key={token.text} className="text-blue-500 dark:text-blue-300">
<Text key={idx} span unstyled>
{token.text}
</span>
</Text>
);
})}
</>

View File

@@ -1,43 +0,0 @@
import { FiMenu } from 'react-icons/fi';
import { VscPackage } from 'react-icons/vsc';
import { ListSidebar } from './ListSidebar';
import type { DocItem } from '~/DocModel/DocItem';
import type { getMembers } from '~/util/parse.server';
export interface ItemListProps {
packageName: string;
data: {
members: ReturnType<typeof getMembers>;
};
selectedMember?: ReturnType<DocItem['toJSON']> | undefined;
}
function onMenuClick() {
console.log('menu clicked');
// Todo show/hide list
}
export function ItemSidebar({ packageName, data, selectedMember }: ItemListProps) {
return (
<div className="flex flex-col min-h-full max-h-full grow min-w-[270px] lg:border-r-solid border-0.5 border-gray">
<div className="border-b-0.5 border-gray py-2">
<h2 className="flex gap-2 items-center m-0 px-2 dark:text-white">
<VscPackage />
{`${packageName}`}
</h2>
<button
type="button"
className="lg:hidden mr-2 bg-transparent border-none cursor-pointer"
title="Menu"
onClick={onMenuClick}
>
<FiMenu size={32} />
</button>
</div>
<div className="hidden lg:block lg:min-h-full overflow-y-auto overflow-x-clip py-3 px-4">
<ListSidebar members={data.members} title="test" selectedMember={selectedMember} />
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { FiLink } from 'react-icons/fi';
import { Group, Stack, Title } from '@mantine/core';
import { CommentSection } from './Comment';
import { HyperlinkedText } from './HyperlinkedText';
import { ParameterTable } from './ParameterTable';
@@ -7,10 +7,6 @@ import type { DocMethodSignature } from '~/DocModel/DocMethodSignature';
type MethodResolvable = ReturnType<DocMethod['toJSON']> | ReturnType<DocMethodSignature['toJSON']>;
export interface MethodItemProps {
data: MethodResolvable;
}
function getShorthandName(data: MethodResolvable) {
return `${data.name}(${data.parameters.reduce((prev, cur, index) => {
if (index === 0) {
@@ -21,37 +17,24 @@ function getShorthandName(data: MethodResolvable) {
}, '')})`;
}
function onAnchorClick() {
console.log('anchor clicked');
// Todo implement jump-to links
}
export function MethodItem({ data }: MethodItemProps) {
export function MethodItem({ data }: { data: MethodResolvable }) {
return (
<div className="flex flex-col">
<div className="flex">
<button
type="button"
className="bg-transparent border-none cursor-pointer dark:text-white"
title="Anchor"
onClick={onAnchorClick}
>
<FiLink size={16} />
</button>
<div className="flex flex-col">
<div className="w-full flex flex-row gap-3">
<h4 className="font-mono m-0 break-all">{`${getShorthandName(data)}`}</h4>
<h4 className="m-0">:</h4>
<h4 className="font-mono m-0 break-all">
<Stack>
<Group>
<Stack>
<Group>
<Title order={5} className="font-mono break-all">{`${getShorthandName(data)}`}</Title>
<Title order={5}>:</Title>
<Title order={5} className="font-mono break-all">
<HyperlinkedText tokens={data.returnTypeTokens} />
</h4>
</div>
</div>
</div>
<div className="mx-7 mb-5">
{data.summary && <CommentSection textClassName="text-dark-100 dark:text-gray-300" node={data.summary} />}
</Title>
</Group>
</Stack>
</Group>
<Group sx={{ display: data.summary || data.parameters.length ? 'block' : 'none' }} mb="lg">
{data.summary ? <CommentSection node={data.summary} /> : null}
{data.parameters.length ? <ParameterTable data={data.parameters} /> : null}
</div>
</div>
</Group>
</Stack>
);
}

View File

@@ -1,19 +1,18 @@
import { Stack } from '@mantine/core';
import { MethodItem } from './MethodItem';
import type { DocMethod } from '~/DocModel/DocMethod';
import type { DocMethodSignature } from '~/DocModel/DocMethodSignature';
export interface MethodListProps {
export function MethodList({
data,
}: {
data: (ReturnType<DocMethod['toJSON']> | ReturnType<DocMethodSignature['toJSON']>)[];
}
export function MethodList({ data }: MethodListProps) {
}) {
return (
<div>
<div className="flex flex-col gap-5">
<Stack>
{data.map((method) => (
<MethodItem key={method.name} data={method} />
))}
</div>
</div>
</Stack>
);
}

View File

@@ -2,12 +2,7 @@ import { HyperlinkedText } from './HyperlinkedText';
import { Table } from './Table';
import type { ParameterDocumentation } from '~/util/parse.server';
interface ParameterDetailProps {
data: ParameterDocumentation[];
className?: string | undefined;
}
export function ParameterTable({ data, className }: ParameterDetailProps) {
export function ParameterTable({ data }: { data: ParameterDocumentation[] }) {
const rows = data.map((param) => ({
Name: param.name,
Type: <HyperlinkedText tokens={param.tokens} />,
@@ -20,12 +15,5 @@ export function ParameterTable({ data, className }: ParameterDetailProps) {
Type: 'font-mono',
};
return (
<Table
className={className}
columns={['Name', 'Type', 'Optional', 'Description']}
rows={rows}
columnStyles={columnStyles}
/>
);
return <Table columns={['Name', 'Type', 'Optional', 'Description']} rows={rows} columnStyles={columnStyles} />;
}

View File

@@ -1,16 +1,13 @@
import { Stack } from '@mantine/core';
import { CodeListing } from './CodeListing';
import type { DocProperty } from '~/DocModel/DocProperty';
export interface PropertyListProps {
data: ReturnType<DocProperty['toJSON']>[];
}
export function PropertyList({ data }: PropertyListProps) {
export function PropertyList({ data }: { data: ReturnType<DocProperty['toJSON']>[] }) {
return (
<div className="flex flex-col gap-5">
<Stack>
{data.map((prop) => (
<CodeListing key={prop.name} name={prop.name} typeTokens={prop.propertyTypeTokens} summary={prop.summary} />
))}
</div>
</Stack>
);
}

View File

@@ -0,0 +1,24 @@
import { startNavigationProgress, resetNavigationProgress, NavigationProgress } from '@mantine/nprogress';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
export function RouterTransition() {
const router = useRouter();
useEffect(() => {
const handleStart = (url: string) => url !== router.asPath && startNavigationProgress();
const handleComplete = () => resetNavigationProgress();
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
}, [router.asPath]);
return <NavigationProgress />;
}

View File

@@ -1,72 +1,71 @@
import { AnimatePresence, motion } from 'framer-motion';
import { createStyles, UnstyledButton, Group, ThemeIcon, Collapse, Box, Text } from '@mantine/core';
import { type ReactNode, useState } from 'react';
import { VscChevronDown, VscChevronRight } from 'react-icons/vsc';
import { Separator } from './Seperator';
import { VscChevronDown } from 'react-icons/vsc';
export interface SectionProps {
children: ReactNode;
title: string;
className?: string | undefined;
defaultClosed?: boolean;
iconElement?: JSX.Element;
showSeparator?: boolean;
margin?: boolean;
}
const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
control: {
display: 'block',
width: '100%',
padding: `${theme.spacing.xs}px ${theme.spacing.xs}px`,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
fontSize: theme.fontSizes.sm,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![7] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
icon: {
transition: 'transform 150ms ease',
transform: opened ? 'rotate(180deg)' : 'rotate(0deg)',
},
}));
export function Section({
title,
icon,
padded = false,
dense = false,
defaultClosed = false,
children,
className,
defaultClosed,
iconElement,
showSeparator = true,
margin = true,
}: SectionProps) {
const [collapsed, setCollapsed] = useState(defaultClosed ?? false);
}: {
title: string;
icon?: JSX.Element;
padded?: boolean;
dense?: boolean;
defaultClosed?: boolean;
children: ReactNode;
}) {
const [opened, setOpened] = useState(!defaultClosed);
const { classes } = useStyles({ opened });
return (
<div className={className}>
<h3
className="flex gap-2 whitespace-pre-wrap font-semibold dark:text-white cursor-pointer"
onClick={() => setCollapsed(!collapsed)}
>
{collapsed ? <VscChevronRight size={20} /> : <VscChevronDown size={20} />}
{iconElement ?? null}
<Box className="break-all">
<UnstyledButton className={classes.control} onClick={() => setOpened((o) => !o)}>
<Group position="apart">
<Group>
{icon ? (
<ThemeIcon variant="outline" size={30}>
{icon}
</ThemeIcon>
) : null}
<Text weight={600} size="md">
{title}
</h3>
<AnimatePresence initial={false} exitBeforeEnter>
{collapsed ? null : (
<>
<motion.div
transition={{ duration: 0.5, ease: [0.04, 0.62, 0.23, 0.98] }}
key="content"
initial="collapsed"
animate="open"
exit="collapsed"
variants={{
open: {
opacity: 1,
height: 'auto',
paddingLeft: '1.75rem',
paddingRight: '1.75rem',
marginBottom: margin ? '1.25rem' : 0,
},
collapsed: {
opacity: 0,
height: 0,
paddingLeft: '1.75rem',
paddingRight: '1.75rem',
paddingBottom: 0,
marginBottom: 0,
},
}}
>
</Text>
</Group>
<VscChevronDown size={20} className={classes.icon} />
</Group>
</UnstyledButton>
<Collapse in={opened}>
{padded ? (
<Box py={20} px={dense ? 0 : 31} mx={dense ? 10 : 25}>
{children}
</motion.div>
{showSeparator && <Separator />}
</>
</Box>
) : (
children
)}
</AnimatePresence>
</div>
</Collapse>
</Box>
);
}

View File

@@ -1,3 +1,4 @@
import { useMediaQuery } from '@mantine/hooks';
import { VscSymbolConstant, VscSymbolMethod, VscSymbolProperty } from 'react-icons/vsc';
import { MethodList } from './MethodList';
import { ParameterTable } from './ParameterTable';
@@ -6,37 +7,31 @@ import { Section } from './Section';
import type { DocInterface } from '~/DocModel/DocInterface';
import type { ParameterDocumentation } from '~/util/parse.server';
export interface PropertiesSectionProps {
data: ReturnType<DocInterface['toJSON']>['properties'];
}
export function PropertiesSection({ data }: { data: ReturnType<DocInterface['toJSON']>['properties'] }) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
export function PropertiesSection({ data }: PropertiesSectionProps) {
return data.length ? (
<Section iconElement={<VscSymbolProperty />} title="Properties" className="dark:text-white">
<Section title="Properties" icon={<VscSymbolProperty />} padded dense={matches}>
<PropertyList data={data} />
</Section>
) : null;
}
export interface MethodsSectionProps {
data: ReturnType<DocInterface['toJSON']>['methods'];
}
export function MethodsSection({ data }: { data: ReturnType<DocInterface['toJSON']>['methods'] }) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
export function MethodsSection({ data }: MethodsSectionProps) {
return data.length ? (
<Section iconElement={<VscSymbolMethod />} title="Methods" className="dark:text-white">
<Section title="Methods" icon={<VscSymbolMethod />} padded dense={matches}>
<MethodList data={data} />
</Section>
) : null;
}
export interface ParametersSectionProps {
data: ParameterDocumentation[];
}
export function ParametersSection({ data }: { data: ParameterDocumentation[] }) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
export function ParametersSection({ data }: ParametersSectionProps) {
return data.length ? (
<Section iconElement={<VscSymbolConstant />} title="Parameters" className="dark:text-white">
<Section title="Parameters" icon={<VscSymbolConstant />} padded dense={matches}>
<ParameterTable data={data} />
</Section>
) : null;

View File

@@ -1,3 +0,0 @@
export function Separator() {
return <div className="h-[1px] w-full bg-gray-300" />;
}

View File

@@ -1,32 +1,16 @@
import { createStyles, UnstyledButton, Group, Text } from '@mantine/core';
import Link from 'next/link';
import type { Dispatch, SetStateAction } from 'react';
import {
VscSymbolClass,
VscSymbolEnum,
VscSymbolField,
VscSymbolInterface,
VscSymbolMethod,
VscSymbolField,
VscSymbolVariable,
VscSymbolMethod,
} from 'react-icons/vsc';
import type { ItemListProps } from './ItemSidebar';
import { Section } from './Section';
import type { DocItem } from '~/DocModel/DocItem';
export type Members = ItemListProps['data']['members'];
export interface ListSidebarSectionProps {
members: Members;
selectedMember?: ReturnType<DocItem['toJSON']> | undefined;
title: string;
}
interface GroupedMembers {
Classes: Members;
Functions: Members;
Enums: Members;
Interfaces: Members;
Types: Members;
Variables: Members;
}
import type { GroupedMembers, Members } from './SidebarLayout';
function groupMembers(members: Members): GroupedMembers {
const Classes: Members = [];
@@ -81,42 +65,54 @@ function resolveIcon(item: keyof GroupedMembers) {
}
}
export function ListSidebar({ members, selectedMember }: ListSidebarSectionProps) {
const useStyles = createStyles((theme) => ({
link: {
fontWeight: 500,
display: 'block',
padding: 5,
paddingLeft: 31,
marginLeft: 25,
fontSize: theme.fontSizes.sm,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.colors.gray![7],
borderLeft: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark![4] : theme.colors.gray![3]}`,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![7] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
}));
export function SidebarItems({
members,
setOpened,
}: {
members: Members;
setOpened: Dispatch<SetStateAction<boolean>>;
}) {
const { classes } = useStyles();
const groupItems = groupMembers(members);
return (
<>
{(Object.keys(groupItems) as (keyof GroupedMembers)[])
.filter((group) => groupItems[group].length)
.map((group, i) => (
<Section iconElement={resolveIcon(group)} key={i} title={group} showSeparator={false}>
<div className="space-y-2">
.map((group, idx) => (
<Section key={idx} title={group} icon={resolveIcon(group)}>
{groupItems[group].map((member, i) => (
<div
key={i}
className="flex gap-2 whitespace-pre-wrap no-underline break-all justify-between text-blue-500 dark:text-blue-300"
>
<Link href={member.path}>
<a
className={`no-underline m-0 text-sm font-semibold ${
selectedMember?.containerKey === member.containerKey
? 'text-blue-500 dark:text-blue-300'
: 'text-gray-500 dark:text-gray-300 hover:text-dark-100 dark:hover:text-white'
}`}
>
{member.name}
</a>
</Link>
<div>
<Link key={i} href={member.path} passHref>
<UnstyledButton className={classes.link} component="a" onClick={() => setOpened((o) => !o)}>
<Group>
<Text>{member.name}</Text>
{member.overloadIndex && member.overloadIndex > 1 ? (
<div className="flex font-mono w-[15px] h-[15px] items-center justify-center rounded-md border border-2 font-bold text-sm color-gray-500 dark:color-gray-300">
<p className="font-semibold">{`${member.overloadIndex}`}</p>
</div>
<Text size="xs" color="dimmed">
{member.overloadIndex}
</Text>
) : null}
</div>
</div>
</Group>
</UnstyledButton>
</Link>
))}
</div>
</Section>
))}
</>

View File

@@ -1,30 +1,207 @@
import type { PropsWithChildren } from 'react';
import { type ItemListProps, ItemSidebar } from './ItemSidebar';
import {
useMantineTheme,
AppShell,
Navbar,
MediaQuery,
// Aside,
Header,
Burger,
Anchor,
Breadcrumbs,
ScrollArea,
Group,
Text,
ThemeIcon,
Box,
UnstyledButton,
createStyles,
Menu,
ActionIcon,
useMantineColorScheme,
} from '@mantine/core';
import { NextLink } from '@mantine/next';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { type PropsWithChildren, useState } from 'react';
import { VscChevronDown, VscPackage } from 'react-icons/vsc';
import { WiDaySunny, WiNightClear } from 'react-icons/wi';
import { SidebarItems } from './SidebarItems';
import type { DocItem } from '~/DocModel/DocItem';
import type { findMember } from '~/util/model.server';
import type { getMembers } from '~/util/parse.server';
export interface SidebarLayoutProps {
packageName: string;
data: {
members: ReturnType<typeof getMembers>;
member: ReturnType<typeof findMember>;
};
selectedMember?: ReturnType<DocItem['toJSON']> | undefined;
}
export type Members = SidebarLayoutProps['data']['members'];
export interface GroupedMembers {
Classes: Members;
Functions: Members;
Enums: Members;
Interfaces: Members;
Types: Members;
Variables: Members;
}
const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
control: {
display: 'block',
width: '100%',
padding: theme.spacing.xs,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![7] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
icon: {
transition: 'transform 150ms ease',
transform: opened ? 'rotate(180deg)' : 'rotate(0deg)',
},
}));
const libraries = [
{ label: 'builders', value: 'builders' },
{ label: 'collection', value: 'collection' },
{ label: 'discord.js', value: 'discord.js' },
{ label: 'proxy', value: 'proxy' },
{ label: 'rest', value: 'rest' },
{ label: 'voice', value: 'voice' },
{ label: 'ws', value: 'ws' },
];
export function SidebarLayout({ packageName, data, children }: PropsWithChildren<Partial<SidebarLayoutProps>>) {
const router = useRouter();
const theme = useMantineTheme();
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark';
const [opened, setOpened] = useState(false);
const [openedPicker, setOpenedPicker] = useState(false);
const { classes } = useStyles({ opened: openedPicker });
const libraryMenuItems = libraries.map((item) => (
<Menu.Item key={item.label} component={NextLink} href={`/docs/main/packages/${item.value}`}>
{item.label}
</Menu.Item>
));
const asPathWithoutQuery = router.asPath.split('?')[0];
const breadcrumbs = asPathWithoutQuery?.split('/').map((path, idx, original) => (
<Link key={idx} href={original.slice(0, idx + 1).join('/')} passHref>
<Anchor component="a">{path}</Anchor>
</Link>
));
export function SidebarLayout({
packageName,
data,
children,
}: PropsWithChildren<Partial<ItemListProps & { data: { member: ReturnType<typeof findMember> } }>>) {
return (
<div className="flex flex-col max-w-full h-full max-h-full bg-white dark:bg-dark">
<div className="flex min-h-[40px] border-0.5 border-b-0 border-gray items-center m-0 px-2 dark:text-white">
Breadcrumbs
</div>
<div className="flex flex-col lg:flex-row overflow-hidden">
<div className="h-full w-full lg:max-w-[310px] lg:min-w-[310px]">
<AppShell
styles={{
main: {
background: theme.colorScheme === 'dark' ? theme.colors.dark![8] : theme.colors.gray![0],
},
}}
navbarOffsetBreakpoint="sm"
asideOffsetBreakpoint="sm"
navbar={
<Navbar hiddenBreakpoint="sm" hidden={!opened} width={{ sm: 200, lg: 300 }}>
{packageName && data ? (
<ItemSidebar packageName={packageName} data={data} selectedMember={data.member} />
<>
<Navbar.Section p="xs">
<>
<Menu
onOpen={() => setOpenedPicker(true)}
onClose={() => setOpenedPicker(false)}
radius="md"
width="target"
>
<Menu.Target>
<UnstyledButton className={classes.control}>
<Group position="apart">
<Group>
<ThemeIcon variant="outline" size={30}>
<VscPackage />
</ThemeIcon>
<Text weight="600" size="md">
{packageName}
</Text>
</Group>
<VscChevronDown className={classes.icon} size={20} />
</Group>
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>{libraryMenuItems}</Menu.Dropdown>
</Menu>
</>
</Navbar.Section>
<Navbar.Section p="xs" grow component={ScrollArea}>
<SidebarItems members={data.members} setOpened={setOpened} />
</Navbar.Section>
</>
) : null}
</Navbar>
}
// aside={
// packageName && data?.member ? (
// <MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
// <Aside hiddenBreakpoint="sm" width={{ sm: 200, lg: 300 }}>
// <ScrollArea p="xs">
// <SidebarItems members={data.members} />
// </ScrollArea>
// </Aside>
// </MediaQuery>
// ) : (
// <></>
// )
// }
// footer={
// <Footer height={60} p="md">
// Application footer
// </Footer>
// }
header={
<Header height={70} p="md">
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', height: '100%' }}>
<div>
<MediaQuery largerThan="sm" styles={{ display: 'none' }}>
<Burger
opened={opened}
onClick={() => setOpened((o) => !o)}
size="sm"
color={theme.colors.gray![6]}
mr="xl"
/>
</MediaQuery>
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
<Breadcrumbs>{breadcrumbs}</Breadcrumbs>
</MediaQuery>
</div>
<div className="h-full grow">{children}</div>
<div className="h-full w-full lg:max-w-[310px] lg:min-w-[310px]">
{packageName && data?.member ? (
<ItemSidebar packageName={packageName} data={data} selectedMember={data.member} />
) : null}
</div>
</div>
</div>
<ActionIcon
variant="outline"
color={dark ? 'yellow' : 'blurple'}
onClick={() => toggleColorScheme()}
title="Toggle color scheme"
>
{dark ? <WiDaySunny size={18} /> : <WiNightClear size={18} />}
</ActionIcon>
</Box>
</Header>
}
>
{children}
</AppShell>
);
}

View File

@@ -1,45 +1,37 @@
import { Table as MantineTable } from '@mantine/core';
import type { ReactNode } from 'react';
export interface RowData {
monospace?: boolean;
content: string;
}
export interface TableProps {
export function Table({
rows,
columns,
columnStyles,
}: {
columns: string[];
columnStyles?: Record<string, string>;
rows: Record<string, ReactNode>[];
className?: string | undefined;
}
export function Table({ rows, columns, columnStyles, className }: TableProps) {
}) {
return (
<div className={className}>
<table className="table-fixed w-full pb-10 border-collapse">
<MantineTable>
<thead>
<tr>
{columns.map((column) => (
<th key={column} className="border-b z-10 text-left text-sm pl-2 border-gray">
<th key={column} className="break-normal">
{column}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i}>
{rows.map((row, idx) => (
<tr key={idx}>
{Object.entries(row).map(([colName, val]) => (
<td
key={colName}
className={`p-2 text-sm border-b text-left border-gray break-all ${columnStyles?.[colName] ?? ''}`}
>
<td key={colName} className={columnStyles?.[colName] ?? ''}>
{val}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
</MantineTable>
);
}

View File

@@ -2,12 +2,7 @@ import { HyperlinkedText } from './HyperlinkedText';
import { Table } from './Table';
import type { TypeParameterData } from '~/util/parse.server';
export interface TableProps {
data: TypeParameterData[];
className?: string;
}
export function TypeParamTable({ data, className }: TableProps) {
export function TypeParamTable({ data }: { data: TypeParameterData[] }) {
const rows = data.map((typeParam) => ({
Name: typeParam.name,
Constraints: <HyperlinkedText tokens={typeParam.constraintTokens} />,
@@ -24,7 +19,6 @@ export function TypeParamTable({ data, className }: TableProps) {
return (
<Table
className={className}
columns={['Name', 'Constraints', 'Optional', 'Default', 'Description']}
rows={rows}
columnStyles={rowElements}

View File

@@ -2,11 +2,7 @@ import { DocContainer } from '../DocContainer';
import { MethodsSection, PropertiesSection } from '../Sections';
import type { DocClass } from '~/DocModel/DocClass';
export interface ClassProps {
data: ReturnType<DocClass['toJSON']>;
}
export function Class({ data }: ClassProps) {
export function Class({ data }: { data: ReturnType<DocClass['toJSON']> }) {
return (
<DocContainer
name={data.name}

View File

@@ -1,18 +1,18 @@
import { Stack } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { VscSymbolEnumMember } from 'react-icons/vsc';
import { CodeListing, CodeListingSeparatorType } from '../CodeListing';
import { DocContainer } from '../DocContainer';
import { Section } from '../Section';
import type { DocEnum } from '~/DocModel/DocEnum';
export interface EnumProps {
data: ReturnType<DocEnum['toJSON']>;
}
export function Enum({ data }: { data: ReturnType<DocEnum['toJSON']> }) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
export function Enum({ data }: EnumProps) {
return (
<DocContainer name={data.name} kind={data.kind} excerpt={data.excerpt} summary={data.summary}>
<Section iconElement={<VscSymbolEnumMember />} title="Members" className="dark:text-white">
<div className="flex flex-col gap-5">
<Section title="Members" icon={<VscSymbolEnumMember />} padded dense={matches}>
<Stack>
{data.members.map((member) => (
<CodeListing
key={member.name}
@@ -22,7 +22,7 @@ export function Enum({ data }: EnumProps) {
summary={member.summary}
/>
))}
</div>
</Stack>
</Section>
</DocContainer>
);

View File

@@ -2,11 +2,7 @@ import { DocContainer } from '../DocContainer';
import { ParametersSection } from '../Sections';
import type { DocFunction } from '~/DocModel/DocFunction';
export interface FunctionProps {
data: ReturnType<DocFunction['toJSON']>;
}
export function Function({ data }: FunctionProps) {
export function Function({ data }: { data: ReturnType<DocFunction['toJSON']> }) {
return (
<DocContainer
name={`${data.name}${data.overloadIndex ? ` (${data.overloadIndex})` : ''}`}

View File

@@ -2,11 +2,7 @@ import { DocContainer } from '../DocContainer';
import { MethodsSection, PropertiesSection } from '../Sections';
import type { DocInterface } from '~/DocModel/DocInterface';
export interface InterfaceProps {
data: ReturnType<DocInterface['toJSON']>;
}
export function Interface({ data }: InterfaceProps) {
export function Interface({ data }: { data: ReturnType<DocInterface['toJSON']> }) {
return (
<DocContainer
name={data.name}

View File

@@ -1,11 +1,7 @@
import { DocContainer } from '../DocContainer';
import type { DocTypeAlias } from '~/DocModel/DocTypeAlias';
export interface TypeAliasProps {
data: ReturnType<DocTypeAlias['toJSON']>;
}
export function TypeAlias({ data }: TypeAliasProps) {
export function TypeAlias({ data }: { data: ReturnType<DocTypeAlias['toJSON']> }) {
return (
<DocContainer
name={data.name}

View File

@@ -1,10 +1,6 @@
import { DocContainer } from '../DocContainer';
import type { DocVariable } from '~/DocModel/DocVariable';
export interface VariableProps {
data: ReturnType<DocVariable['toJSON']>;
}
export function Variable({ data }: VariableProps) {
export function Variable({ data }: { data: ReturnType<DocVariable['toJSON']> }) {
return <DocContainer name={data.name} kind={data.kind} excerpt={data.excerpt} summary={data.summary} />;
}

View File

@@ -5,11 +5,10 @@ export type DocItemJSON = ReturnType<DocItem['toJSON']>;
export const MemberContext = createContext<DocItemJSON | undefined>(undefined);
export interface MemberProviderProps {
export const MemberProvider = ({
member,
children,
}: {
member: DocItemJSON | undefined;
children: React.ReactNode;
}
export const MemberProvider = ({ member, children }: MemberProviderProps) => (
<MemberContext.Provider value={member}>{children}</MemberContext.Provider>
);
}) => <MemberContext.Provider value={member}>{children}</MemberContext.Provider>;

View File

@@ -2,7 +2,7 @@ import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export default function middleware(request: NextRequest) {
return NextResponse.rewrite(new URL('/docs/main/packages/builders', request.url));
return NextResponse.redirect(new URL('/docs/main/packages/builders', request.url));
}
export const config = {

View File

@@ -1,8 +1,57 @@
import { ColorScheme, ColorSchemeProvider, MantineProvider } from '@mantine/core';
import { useColorScheme } from '@mantine/hooks';
import type { AppProps } from 'next/app';
import '@unocss/reset/normalize.css';
import Head from 'next/head';
import { useEffect, useState } from 'react';
import { RouterTransition } from '~/components/RouterTransition';
import '../styles/unocss.css';
import '../styles/main.css';
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
const preferredColorScheme = useColorScheme('dark', { getInitialValueInEffect: true });
const [colorScheme, setColorScheme] = useState<ColorScheme>(preferredColorScheme);
const toggleColorScheme = (value?: ColorScheme) =>
setColorScheme(value ?? (colorScheme === 'dark' ? 'light' : 'dark'));
useEffect(() => {
setColorScheme(preferredColorScheme);
}, [preferredColorScheme]);
return (
<>
<Head>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
</Head>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
<MantineProvider
theme={{
fontFamily: 'Inter',
colorScheme,
colors: {
blurple: [
'#5865F2',
'#5865F2',
'#5865F2',
'#5865F2',
'#5865F2',
'#5865F2',
'#5865F2',
'#5865F2',
'#5865F2',
'#5865F2',
],
},
primaryColor: 'blurple',
}}
withCSSVariables
withNormalizeCSS
withGlobalStyles
>
<RouterTransition />
<Component {...pageProps} />
</MantineProvider>
</ColorSchemeProvider>
</>
);
}

View File

@@ -1,24 +1,19 @@
import { Html, Head, Main, NextScript } from 'next/document';
import { createGetInitialProps } from '@mantine/next';
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
const getInitialProps = createGetInitialProps();
export default class _Document extends Document {
public static override getInitialProps = getInitialProps;
public override render() {
return (
<Html lang="en">
<Html>
<Head />
<body>
<script
dangerouslySetInnerHTML={{
__html: `(() => {
const prefersDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
const persistedColorPreference = localStorage.getItem('theme') || 'auto';
if (persistedColorPreference === 'dark' || (prefersDarkMode && persistedColorPreference !== 'light')) {
document.documentElement.classList.toggle('dark', true);
}
})();`,
}}
/>
<Main />
<NextScript />
</body>
</Html>
);
}
}

View File

@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-throw-literal */
import { Box } from '@mantine/core';
import { ApiFunction } from '@microsoft/api-extractor-model';
import type { GetStaticPaths, GetStaticProps } from 'next/types';
import type { DocClass } from '~/DocModel/DocClass';
@@ -7,8 +8,7 @@ import type { DocFunction } from '~/DocModel/DocFunction';
import type { DocInterface } from '~/DocModel/DocInterface';
import type { DocTypeAlias } from '~/DocModel/DocTypeAlias';
import type { DocVariable } from '~/DocModel/DocVariable';
import type { ItemListProps } from '~/components/ItemSidebar';
import { SidebarLayout } from '~/components/SidebarLayout';
import { SidebarLayout, type SidebarLayoutProps } from '~/components/SidebarLayout';
import { Class } from '~/components/model/Class';
import { Enum } from '~/components/model/Enum';
import { Function } from '~/components/model/Function';
@@ -26,10 +26,6 @@ export const getStaticPaths: GetStaticPaths = async () => {
const pkgs = (
await Promise.all(
packages.map(async (packageName) => {
if (packageName === 'rest') {
return { params: { slug: ['main', 'packages', packageName] } };
}
try {
const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/main.api.json`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -40,7 +36,11 @@ export const getStaticPaths: GetStaticPaths = async () => {
return [
{ params: { slug: ['main', 'packages', packageName] } },
...getMembers(pkg!).map((member) => {
...getMembers(pkg!)
// Filtering out enum `RESTEvents` because of interface with similar name `RestEvents`
// causing next.js export to error
.filter((member) => member.name !== 'RESTEvents')
.map((member) => {
if (member.kind === 'Function' && member.overloadIndex) {
return {
params: {
@@ -48,6 +48,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
},
};
}
return { params: { slug: ['main', 'packages', packageName, member.name] } };
}),
];
@@ -77,8 +78,8 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
const model = createApiModel(data);
const pkg = findPackage(model, packageName);
let { containerKey, name } = findMember(model, packageName, memberName)!;
if (overloadIndex) {
let { containerKey, name } = findMember(model, packageName, memberName) ?? {};
if (name && overloadIndex) {
containerKey = ApiFunction.getContainerKey(name, parseInt(overloadIndex, 10));
}
@@ -87,7 +88,8 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
packageName,
data: {
members: pkg ? getMembers(pkg) : [],
member: memberName ? findMemberByKey(model, packageName, containerKey)?.toJSON() ?? null : null,
member:
memberName && containerKey ? findMemberByKey(model, packageName, containerKey)?.toJSON() ?? null : null,
},
},
};
@@ -116,13 +118,11 @@ const member = (props: any) => {
case 'Enum':
return <Enum data={props as ReturnType<DocEnum['toJSON']>} />;
default:
return <div>Cannot render that item type</div>;
return <Box>Cannot render that item type</Box>;
}
};
export default function Slug(
props: Partial<ItemListProps & { error?: string; data: { member: ReturnType<typeof findMember> } }>,
) {
export default function Slug(props: Partial<SidebarLayoutProps & { error?: string }>) {
return props.error ? (
<div className="flex max-w-full h-full bg-white dark:bg-dark">{props.error}</div>
) : (

View File

@@ -1,3 +0,0 @@
export default function DocsLanding() {
return <div>Documentation</div>;
}

View File

@@ -1,73 +1,117 @@
import { createStyles, Container, Title, Button, Group, Text, Center } from '@mantine/core';
import Image from 'next/future/image';
import Link from 'next/link';
import { forwardRef, MouseEventHandler, Ref } from 'react';
import codeSample from '../assets/code-sample.png';
import logo from '../assets/djs_logo_rainbow_400x400.png';
import vercelLogo from '../assets/powered-by-vercel.svg';
import text from '../text.json';
interface ButtonProps {
label: string;
href?: string;
ref?: Ref<HTMLAnchorElement>;
onClick?: MouseEventHandler<HTMLAnchorElement>;
}
const useStyles = createStyles((theme) => ({
inner: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: theme.spacing.xl * 4,
paddingBottom: theme.spacing.xl * 4,
// eslint-disable-next-line react/display-name
const LinkButton = forwardRef<HTMLAnchorElement, ButtonProps>(({ label, onClick, href }: ButtonProps, ref) => (
<a
href={href}
onClick={onClick}
ref={ref}
className="no-underline max-h-[70px] bg-blurple px-3 py-4 rounded-lg font-semibold text-white"
>
{label}
</a>
));
[theme.fn.smallerThan('md')]: {
flexDirection: 'column',
gap: 50,
},
},
content: {
maxWidth: 480,
marginRight: theme.spacing.xl * 3,
[theme.fn.smallerThan('md')]: {
marginRight: 0,
},
},
title: {
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
fontSize: 44,
lineHeight: 1.2,
fontWeight: 900,
[theme.fn.smallerThan('xs')]: {
fontSize: 28,
},
},
highlight: {
position: 'relative',
backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background,
borderRadius: theme.radius.sm,
padding: '4px 12px',
},
control: {
[theme.fn.smallerThan('xs')]: {
flex: 1,
},
},
image: {
flex: 1,
height: '100%',
maxWidth: 550,
[theme.fn.smallerThan('xs')]: {
maxWidth: 350,
},
},
vercel: {
height: '100%',
maxWidth: 250,
paddingBottom: theme.spacing.xl * 4,
},
}));
export default function IndexRoute() {
const { classes } = useStyles();
return (
<main className="w-full max-w-full max-h-full h-full flex-col bg-white dark:bg-dark overflow-y-auto">
<div className="flex h-[65px] sticky top-0 border-b border-gray justify-center px-10 bg-white dark:bg-dark">
<div className="flex items-center w-full max-w-[1100px] justify-between">
<div className="h-[50px] w-[50px] rounded-lg overflow-hidden">
<Image className="h-[50px] w-[50px]" src={logo} />
</div>
<div className="flex flex-row space-x-8">
<div>
<Container size="lg">
<div className={classes.inner}>
<div className={classes.content}>
<Title className={classes.title}>
The <span className={classes.highlight}>most popular</span> way to build Discord <br /> bots.
</Title>
<Text color="dimmed" mt="md">
discord.js is a powerful Node.js module that allows you to interact with the Discord API very easily. It
takes a much more object-oriented approach than most other JS Discord libraries, making your bot&apos;s
code significantly tidier and easier to comprehend.
</Text>
<Group mt={30}>
<Link href="/docs" passHref>
<a className="no-underline text-blurple font-semibold">Docs</a>
<Button component="a" radius="xl" size="md" className={classes.control}>
Docs
</Button>
</Link>
<a className="text-blurple font-semibold">Guide</a>
</div>
</div>
</div>
<div className="xl:flex xl:flex-col xl:justify-center w-full max-w-full box-border p-10">
<div className="flex flex-col xl:flex-row grow max-w-[1100px] pb-10 space-y-10 xl:space-x-20 place-items-center place-self-center">
<div className="flex flex-col max-w-[800px] lt-xl:items-center">
<h1 className="font-bold text-6xl text-blurple my-2">{text.heroTitle}</h1>
<p className="text-xl text-dark-100 dark:text-gray-300">{text.heroDescription}</p>
<div className="flex flew-row space-x-4">
<LinkButton label="Read the guide" />
<Link href="/docs" passHref>
<LinkButton label="Check out the docs" />
<Link href="https://discordjs.guide" passHref>
<Button component="a" variant="default" radius="xl" size="md" className={classes.control}>
Guide
</Button>
</Link>
</Group>
</div>
<Image src={codeSample} className={classes.image} />
</div>
<div className="sm:flex sm:grow sm:shrink h-full xl:items-center hidden">
<Image src={codeSample} className="max-w-[600px] h-full rounded-xl shadow-md overflow-hidden" />
</div>
</div>
<div className="flex place-content-center">
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss" title="Vercel">
<Center>
<Link href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss" passHref>
<a title="Vercel">
<Image
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
src={vercelLogo}
alt="Vercel"
className="max-w-[250px] shadow-md overflow-hidden"
className={classes.vercel}
/>
</a>
</Link>
</Center>
</Container>
</div>
</div>
</main>
);
}

View File

@@ -1,4 +0,0 @@
{
"heroTitle": "The most popular way to build Discord bots.",
"heroDescription": "discord.js is a powerful Node.js module that allows you to interact with the Discord API very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend."
}

View File

@@ -5,8 +5,8 @@
"scripts": {
"test": "vitest run",
"build": "unbuild",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"lint": "prettier --check . && TIMING=1 eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix",
"docs": "docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
"prepack": "yarn build && yarn lint",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/ws/*'",

View File

@@ -3,11 +3,15 @@
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["**/dist/**"]
"outputs": ["dist/**"]
},
"@discordjs/website#build": {
"dependsOn": ["^build"],
"outputs": [".next/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["**/coverage/**"]
"outputs": []
},
"lint": {
"dependsOn": ["^build"],
@@ -19,7 +23,7 @@
},
"docs": {
"dependsOn": ["^build"],
"outputs": ["**/docs/docs.json", "**/docs/docs.api.json"]
"outputs": ["docs/docs.json", "docs/docs.api.json"]
},
"changelog": {
"dependsOn": ["^build"],

1403
yarn.lock

File diff suppressed because it is too large Load Diff