From e78c9c9ee9626b5aad7cbbd551bcdef666c11828 Mon Sep 17 00:00:00 2001 From: Suneet Tipirneni <77477100+suneettipirneni@users.noreply.github.com> Date: Thu, 7 Jul 2022 16:09:19 -0400 Subject: [PATCH] feat(website): show package members in a sidebar (#8245) * feat(website): show package members in a sidebar * fix: put response instead of loader * Apply suggestions from code review Co-authored-by: Noel * chore: make requested changes * refactor: make only package list scrollable * feat: make sidebar mobile responsive * fix: breakpoints for sidebar Co-authored-by: Noel --- packages/website/src/DocModel/DocClass.ts | 9 ++-- packages/website/src/DocModel/DocFunction.ts | 21 +++------ packages/website/src/DocModel/DocInterface.ts | 9 ++-- packages/website/src/DocModel/DocItem.ts | 2 + packages/website/src/DocModel/DocMethod.ts | 23 +--------- .../src/DocModel/DocMethodSignature.ts | 23 +--------- packages/website/src/DocModel/DocTypeAlias.ts | 8 ++-- .../src/DocModel/TypeParameterMixin.ts | 24 ++++++++++ .../website/src/components/DocContainer.tsx | 30 ++++++------- .../website/src/components/ItemSidebar.tsx | 45 +++++++++++++++++++ .../website/src/components/model/Class.tsx | 2 +- .../website/src/components/model/Function.tsx | 2 +- .../src/components/model/Interface.tsx | 2 +- .../src/components/model/TypeAlias.tsx | 2 +- .../index.tsx => $packageName.tsx} | 37 ++++++++++----- packages/website/src/util/icon.tsx | 14 ++++++ packages/website/src/util/parse.server.ts | 1 + packages/website/unocss.config.ts | 8 +++- 18 files changed, 156 insertions(+), 106 deletions(-) create mode 100644 packages/website/src/DocModel/TypeParameterMixin.ts create mode 100644 packages/website/src/components/ItemSidebar.tsx rename packages/website/src/routes/docs/$branchName/packages/{$packageName/index.tsx => $packageName.tsx} (50%) create mode 100644 packages/website/src/util/icon.tsx diff --git a/packages/website/src/DocModel/DocClass.ts b/packages/website/src/DocModel/DocClass.ts index d12430c5b..96c067708 100644 --- a/packages/website/src/DocModel/DocClass.ts +++ b/packages/website/src/DocModel/DocClass.ts @@ -8,14 +8,14 @@ import { import { DocItem } from './DocItem'; import { DocMethod } from './DocMethod'; import { DocProperty } from './DocProperty'; -import { type TokenDocumentation, genToken, TypeParameterData, generateTypeParamData } from '~/util/parse.server'; +import { TypeParameterMixin } from './TypeParameterMixin'; +import { type TokenDocumentation, genToken } from '~/util/parse.server'; -export class DocClass extends DocItem { +export class DocClass extends TypeParameterMixin(DocItem) { public readonly extendsTokens: TokenDocumentation[] | null; public readonly implementsTokens: TokenDocumentation[][]; public readonly methods: DocMethod[] = []; public readonly properties: DocProperty[] = []; - public readonly typeParameters: TypeParameterData[] = []; public constructor(model: ApiModel, item: ApiClass) { super(model, item); @@ -29,8 +29,6 @@ export class DocClass extends DocItem { excerpt.excerpt.spannedTokens.map((token) => genToken(this.model, token)), ); - this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(model, typeParam)); - for (const member of item.members) { switch (member.kind) { case ApiItemKind.Method: @@ -52,7 +50,6 @@ export class DocClass extends DocItem { implementsTokens: this.implementsTokens, methods: this.methods.map((method) => method.toJSON()), properties: this.properties.map((prop) => prop.toJSON()), - typeParameters: this.typeParameters, }; } } diff --git a/packages/website/src/DocModel/DocFunction.ts b/packages/website/src/DocModel/DocFunction.ts index 7b87a5c11..03731e2af 100644 --- a/packages/website/src/DocModel/DocFunction.ts +++ b/packages/website/src/DocModel/DocFunction.ts @@ -1,26 +1,18 @@ -import type { ApiFunction, ApiModel } from '@microsoft/api-extractor-model'; +import type { ApiFunction, ApiModel, ApiParameterListMixin } from '@microsoft/api-extractor-model'; import { DocItem } from './DocItem'; -import { - type ParameterDocumentation, - type TokenDocumentation, - genParameter, - genToken, - type TypeParameterData, - generateTypeParamData, -} from '~/util/parse.server'; +import { TypeParameterMixin } from './TypeParameterMixin'; +import { type TokenDocumentation, genToken, genParameter, ParameterDocumentation } from '~/util/parse.server'; -export class DocFunction extends DocItem { - public readonly parameters: ParameterDocumentation[]; +export class DocFunction extends TypeParameterMixin(DocItem) { public readonly returnTypeTokens: TokenDocumentation[]; public readonly overloadIndex: number; - public readonly typeParameters: TypeParameterData[] = []; + public readonly parameters: ParameterDocumentation[]; public constructor(model: ApiModel, item: ApiFunction) { super(model, item); - this.parameters = item.parameters.map((param) => genParameter(this.model, param)); this.returnTypeTokens = item.returnTypeExcerpt.spannedTokens.map((token) => genToken(this.model, token)); this.overloadIndex = item.overloadIndex; - this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(model, typeParam)); + this.parameters = (item as ApiParameterListMixin).parameters.map((param) => genParameter(this.model, param)); } public override toJSON() { @@ -29,7 +21,6 @@ export class DocFunction extends DocItem { parameters: this.parameters, returnTypeTokens: this.returnTypeTokens, overloadIndex: this.overloadIndex, - typeParameters: this.typeParameters, }; } } diff --git a/packages/website/src/DocModel/DocInterface.ts b/packages/website/src/DocModel/DocInterface.ts index c02f59b2c..e134a9939 100644 --- a/packages/website/src/DocModel/DocInterface.ts +++ b/packages/website/src/DocModel/DocInterface.ts @@ -1,14 +1,14 @@ import { DocItem } from './DocItem'; import { DocMethodSignature } from './DocMethodSignature'; import { DocProperty } from './DocProperty'; +import { TypeParameterMixin } from './TypeParameterMixin'; import { ApiInterface, ApiItemKind, ApiMethodSignature, ApiModel, ApiPropertySignature } from '~/api-extractor.server'; -import { type TokenDocumentation, genToken, type TypeParameterData, generateTypeParamData } from '~/util/parse.server'; +import { type TokenDocumentation, genToken } from '~/util/parse.server'; -export class DocInterface extends DocItem { +export class DocInterface extends TypeParameterMixin(DocItem) { public readonly extendsTokens: TokenDocumentation[][] | null; public readonly methods: DocMethodSignature[] = []; public readonly properties: DocProperty[] = []; - public readonly typeParameters: TypeParameterData[] = []; public constructor(model: ApiModel, item: ApiInterface) { super(model, item); @@ -17,8 +17,6 @@ export class DocInterface extends DocItem { excerpt.excerpt.spannedTokens.map((token) => genToken(this.model, token)), ); - this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam)); - for (const member of item.members) { switch (member.kind) { case ApiItemKind.MethodSignature: @@ -39,7 +37,6 @@ export class DocInterface extends DocItem { extendsTokens: this.extendsTokens, methods: this.methods.map((method) => method.toJSON()), properties: this.properties.map((prop) => prop.toJSON()), - typeParameters: this.typeParameters, }; } } diff --git a/packages/website/src/DocModel/DocItem.ts b/packages/website/src/DocModel/DocItem.ts index 1ba586454..3d4374c1c 100644 --- a/packages/website/src/DocModel/DocItem.ts +++ b/packages/website/src/DocModel/DocItem.ts @@ -2,6 +2,8 @@ import type { ApiModel, ApiDeclaredItem } from '@microsoft/api-extractor-model'; import type { ReferenceData } from '~/model.server'; import { resolveName, genReference, resolveDocComment, TokenDocumentation, genToken } from '~/util/parse.server'; +export type DocItemConstructor = new (...args: any[]) => T; + export class DocItem { public readonly item: T; public readonly name: string; diff --git a/packages/website/src/DocModel/DocMethod.ts b/packages/website/src/DocModel/DocMethod.ts index 7c6afb11f..60ed61075 100644 --- a/packages/website/src/DocModel/DocMethod.ts +++ b/packages/website/src/DocModel/DocMethod.ts @@ -1,33 +1,17 @@ import type { ApiMethod, ApiModel } from '@microsoft/api-extractor-model'; -import { DocItem } from './DocItem'; +import { DocFunction } from './DocFunction'; import { Visibility } from './Visibility'; -import { - type ParameterDocumentation, - type TokenDocumentation, - genParameter, - genToken, - generateTypeParamData, - TypeParameterData, -} from '~/util/parse.server'; -export class DocMethod extends DocItem { - public readonly parameters: ParameterDocumentation[]; +export class DocMethod extends DocFunction { public readonly static: boolean; public readonly optional: boolean; public readonly visibility: Visibility; - public readonly returnTypeTokens: TokenDocumentation[]; - public readonly overloadIndex: number; - public readonly typeParameters: TypeParameterData[] = []; public constructor(model: ApiModel, item: ApiMethod) { super(model, item); - this.parameters = item.parameters.map((param) => genParameter(this.model, param)); this.static = item.isStatic; this.optional = item.isOptional; this.visibility = item.isProtected ? Visibility.Protected : Visibility.Public; - this.returnTypeTokens = item.returnTypeExcerpt.spannedTokens.map((token) => genToken(this.model, token)); - this.overloadIndex = item.overloadIndex; - this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam)); } public override toJSON() { @@ -36,9 +20,6 @@ export class DocMethod extends DocItem { static: this.static, optional: this.optional, visibility: this.visibility, - parameters: this.parameters, - returnTypeTokens: this.returnTypeTokens, - overloadIndex: this.overloadIndex, }; } } diff --git a/packages/website/src/DocModel/DocMethodSignature.ts b/packages/website/src/DocModel/DocMethodSignature.ts index 5f44439da..f5f508786 100644 --- a/packages/website/src/DocModel/DocMethodSignature.ts +++ b/packages/website/src/DocModel/DocMethodSignature.ts @@ -1,37 +1,18 @@ import type { ApiMethodSignature, ApiModel } from '@microsoft/api-extractor-model'; -import { DocItem } from './DocItem'; -import { - type ParameterDocumentation, - type TokenDocumentation, - genParameter, - genToken, - generateTypeParamData, - type TypeParameterData, -} from '~/util/parse.server'; +import { DocFunction } from './DocFunction'; -export class DocMethodSignature extends DocItem { - public readonly parameters: ParameterDocumentation[]; +export class DocMethodSignature extends DocFunction { public readonly optional: boolean; - public readonly returnTypeTokens: TokenDocumentation[]; - public readonly overloadIndex: number; - public readonly typeParameters: TypeParameterData[] = []; public constructor(model: ApiModel, item: ApiMethodSignature) { super(model, item); - this.parameters = item.parameters.map((param) => genParameter(this.model, param)); this.optional = item.isOptional; - this.returnTypeTokens = item.returnTypeExcerpt.spannedTokens.map((token) => genToken(this.model, token)); - this.overloadIndex = item.overloadIndex; - this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam)); } public override toJSON() { return { ...super.toJSON(), optional: this.optional, - parameters: this.parameters, - returnTypeTokens: this.returnTypeTokens, - overloadIndex: this.overloadIndex, }; } } diff --git a/packages/website/src/DocModel/DocTypeAlias.ts b/packages/website/src/DocModel/DocTypeAlias.ts index edf093c53..7101176cc 100644 --- a/packages/website/src/DocModel/DocTypeAlias.ts +++ b/packages/website/src/DocModel/DocTypeAlias.ts @@ -1,22 +1,20 @@ import type { ApiModel, ApiTypeAlias } from '@microsoft/api-extractor-model'; import { DocItem } from './DocItem'; -import { type TokenDocumentation, genToken, generateTypeParamData, type TypeParameterData } from '~/util/parse.server'; +import { TypeParameterMixin } from './TypeParameterMixin'; +import { type TokenDocumentation, genToken } from '~/util/parse.server'; -export class DocTypeAlias extends DocItem { +export class DocTypeAlias extends TypeParameterMixin(DocItem) { public readonly typeTokens: TokenDocumentation[]; - public readonly typeParameters: TypeParameterData[] = []; public constructor(model: ApiModel, item: ApiTypeAlias) { super(model, item); this.typeTokens = item.typeExcerpt.spannedTokens.map((token) => genToken(model, token)); - this.typeParameters = item.typeParameters.map((typeParam) => generateTypeParamData(this.model, typeParam)); } public override toJSON() { return { ...super.toJSON(), typeTokens: this.typeTokens, - typeParameters: this.typeParameters, }; } } diff --git a/packages/website/src/DocModel/TypeParameterMixin.ts b/packages/website/src/DocModel/TypeParameterMixin.ts new file mode 100644 index 000000000..9fe01c638 --- /dev/null +++ b/packages/website/src/DocModel/TypeParameterMixin.ts @@ -0,0 +1,24 @@ +import type { ApiItem, ApiModel, ApiTypeParameterListMixin } from '@microsoft/api-extractor-model'; +import type { DocItemConstructor } from './DocItem'; +import { generateTypeParamData, TypeParameterData } from '~/util/parse.server'; + +export function TypeParameterMixin(Base: TBase) { + return class Mixed extends Base { + public readonly typeParameters: TypeParameterData[] = []; + + public constructor(...args: any[]); + public constructor(model: ApiModel, item: ApiItem) { + super(model, item); + this.typeParameters = (item as ApiTypeParameterListMixin).typeParameters.map((typeParam) => + generateTypeParamData(this.model, typeParam), + ); + } + + public override toJSON() { + return { + ...super.toJSON(), + typeParameterData: this.typeParameters, + }; + } + }; +} diff --git a/packages/website/src/components/DocContainer.tsx b/packages/website/src/components/DocContainer.tsx index 70455dfe9..0ee99ba46 100644 --- a/packages/website/src/components/DocContainer.tsx +++ b/packages/website/src/components/DocContainer.tsx @@ -1,7 +1,7 @@ -import { VscSymbolClass, VscSymbolMethod, VscSymbolEnum, VscSymbolInterface, VscSymbolVariable } from 'react-icons/vsc'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vs } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import { TypeParamTable } from './TypeParamTable'; +import { generateIcon } from '~/util/icon'; import type { TypeParameterData } from '~/util/parse.server'; export interface DocContainerProps { @@ -13,27 +13,25 @@ export interface DocContainerProps { typeParams?: TypeParameterData[]; } -const symbolClass = 'mr-2'; -const icons = { - Class: , - Method: , - Function: , - Enum: , - Interface: , - TypeAlias: , -}; - export function DocContainer({ name, kind, excerpt, summary, typeParams, children }: DocContainerProps) { return (
-

- {icons[kind as keyof typeof icons]} +

+ {generateIcon(kind, 'mr-2')} {name}

Code declaration:

- - {excerpt} - +
+ + {excerpt} + +
{typeParams?.length ? ( <>

Type Parameters

diff --git a/packages/website/src/components/ItemSidebar.tsx b/packages/website/src/components/ItemSidebar.tsx new file mode 100644 index 000000000..39482f4d5 --- /dev/null +++ b/packages/website/src/components/ItemSidebar.tsx @@ -0,0 +1,45 @@ +import { AiOutlineMenu } from 'react-icons/ai'; +import { VscPackage } from 'react-icons/vsc'; +import { generateIcon } from '~/util/icon'; +import type { getMembers } from '~/util/parse.server'; + +export interface ItemListProps { + packageName: string; + data: { + members: ReturnType; + }; +} + +function onMenuClick() { + console.log('menu clicked'); + // Todo show/hide list +} + +export function ItemSidebar({ packageName, data }: ItemListProps) { + return ( +
+
+

+ + {`${packageName}`} +

+ +
+
+ {data.members.map((member, i) => ( + + ))} +
+
+ ); +} diff --git a/packages/website/src/components/model/Class.tsx b/packages/website/src/components/model/Class.tsx index c011f3a71..5c054513d 100644 --- a/packages/website/src/components/model/Class.tsx +++ b/packages/website/src/components/model/Class.tsx @@ -14,7 +14,7 @@ export function Class({ data }: ClassProps) { kind={data.kind} excerpt={data.excerpt} summary={data.summary} - typeParams={data.typeParameters} + typeParams={data.typeParameterData} > <> {data.properties.length ? : null} diff --git a/packages/website/src/components/model/Function.tsx b/packages/website/src/components/model/Function.tsx index 5119a655c..5879f61aa 100644 --- a/packages/website/src/components/model/Function.tsx +++ b/packages/website/src/components/model/Function.tsx @@ -13,7 +13,7 @@ export function Function({ data }: FunctionProps) { kind={data.kind} excerpt={data.excerpt} summary={data.summary} - typeParams={data.typeParameters} + typeParams={data.typeParameterData} > diff --git a/packages/website/src/components/model/Interface.tsx b/packages/website/src/components/model/Interface.tsx index 12d15ec63..4878bc088 100644 --- a/packages/website/src/components/model/Interface.tsx +++ b/packages/website/src/components/model/Interface.tsx @@ -14,7 +14,7 @@ export function Interface({ data }: InterfaceProps) { kind={data.kind} excerpt={data.excerpt} summary={data.summary} - typeParams={data.typeParameters} + typeParams={data.typeParameterData} > <> {data.properties.length ? : null} diff --git a/packages/website/src/components/model/TypeAlias.tsx b/packages/website/src/components/model/TypeAlias.tsx index d4c77e6d6..eda5208c5 100644 --- a/packages/website/src/components/model/TypeAlias.tsx +++ b/packages/website/src/components/model/TypeAlias.tsx @@ -12,7 +12,7 @@ export function TypeAlias({ data }: TypeAliasProps) { kind={data.kind} excerpt={data.excerpt} summary={data.summary} - typeParams={data.typeParameters} + typeParams={data.typeParameterData} >
WIP
diff --git a/packages/website/src/routes/docs/$branchName/packages/$packageName/index.tsx b/packages/website/src/routes/docs/$branchName/packages/$packageName.tsx similarity index 50% rename from packages/website/src/routes/docs/$branchName/packages/$packageName/index.tsx rename to packages/website/src/routes/docs/$branchName/packages/$packageName.tsx index 36477e4c2..070d5eee0 100644 --- a/packages/website/src/routes/docs/$branchName/packages/$packageName/index.tsx +++ b/packages/website/src/routes/docs/$branchName/packages/$packageName.tsx @@ -1,19 +1,32 @@ +/* eslint-disable @typescript-eslint/no-throw-literal */ import { json } from '@remix-run/node'; -import { Params, useLoaderData } from '@remix-run/react'; +import { Outlet, Params, useLoaderData, useParams } from '@remix-run/react'; +import { ItemSidebar } from '~/components/ItemSidebar'; import { createApiModel } from '~/util/api-model.server'; import { findPackage, getMembers } from '~/util/parse.server'; export async function loader({ params }: { params: Params }) { + const UnknownResponse = new Response('Not Found', { + status: 404, + }); + const res = await fetch( `https://raw.githubusercontent.com/discordjs/docs/main/${params.packageName!}/${params.branchName!}.api.json`, ); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const data = await res.json(); - const model = createApiModel(data); + const data = await res.json().catch(() => { + throw UnknownResponse; + }); + const model = createApiModel(data); const pkg = findPackage(model, params.packageName!); + + if (!pkg) { + throw UnknownResponse; + } + return json({ - members: getMembers(pkg!), + members: getMembers(pkg), }); } @@ -23,14 +36,16 @@ interface LoaderData { export default function Package() { const data = useLoaderData(); + const { packageName } = useParams(); return ( -
    - {data.members.map((member, i) => ( -
  • - {member.name} -
  • - ))} -
+
+
+ +
+
+ +
+
); } diff --git a/packages/website/src/util/icon.tsx b/packages/website/src/util/icon.tsx new file mode 100644 index 000000000..9cbd4ec04 --- /dev/null +++ b/packages/website/src/util/icon.tsx @@ -0,0 +1,14 @@ +import { VscSymbolClass, VscSymbolMethod, VscSymbolEnum, VscSymbolInterface, VscSymbolVariable } from 'react-icons/vsc'; + +export function generateIcon(kind: string, className?: string) { + const icons = { + Class: , + Method: , + Function: , + Enum: , + Interface: , + TypeAlias: , + }; + + return icons[kind as keyof typeof icons]; +} diff --git a/packages/website/src/util/parse.server.ts b/packages/website/src/util/parse.server.ts index 3da4b6123..ae14ea8b9 100644 --- a/packages/website/src/util/parse.server.ts +++ b/packages/website/src/util/parse.server.ts @@ -165,6 +165,7 @@ export function genParameter(model: ApiModel, param: Parameter): ParameterDocume export function getMembers(pkg: ApiPackage) { return pkg.members[0]!.members.map((member) => ({ name: member.displayName, + kind: member.kind, path: generatePath(member.getHierarchy()), })); } diff --git a/packages/website/unocss.config.ts b/packages/website/unocss.config.ts index 87e509360..e17570aa0 100644 --- a/packages/website/unocss.config.ts +++ b/packages/website/unocss.config.ts @@ -1,3 +1,9 @@ import { defineConfig } from 'unocss'; -export default defineConfig({}); +export default defineConfig({ + theme: { + fontFamily: { + mono: ['JetBrains Mono'], + }, + }, +});