refactor: inline table of contents

This commit is contained in:
iCrawl
2022-08-17 23:21:23 +02:00
parent 872ce801a0
commit 17ab0e652c
14 changed files with 97 additions and 90 deletions

View File

@@ -42,7 +42,7 @@ export function CodeListing({
{optional ? '?' : ''} {optional ? '?' : ''}
</Title> </Title>
<Title order={4}>{separator}</Title> <Title order={4}>{separator}</Title>
<Title order={4} className="font-mono break-all"> <Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono">
<HyperlinkedText tokens={typeTokens} /> <HyperlinkedText tokens={typeTokens} />
</Title> </Title>
</Group> </Group>

View File

@@ -1,4 +1,4 @@
import { Group, Stack, Title, Text, Box } from '@mantine/core'; import { Group, Stack, Title, Text, Box, MediaQuery, Aside, ScrollArea } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks'; import { useMediaQuery } from '@mantine/hooks';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { VscListSelection, VscSymbolParameter } from 'react-icons/vsc'; import { VscListSelection, VscSymbolParameter } from 'react-icons/vsc';
@@ -6,8 +6,10 @@ import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { HyperlinkedText } from './HyperlinkedText'; import { HyperlinkedText } from './HyperlinkedText';
import { Section } from './Section'; import { Section } from './Section';
import { TableOfContentsItems } from './TableOfContentsItems';
import { TypeParamTable } from './TypeParamTable'; import { TypeParamTable } from './TypeParamTable';
import { TSDoc } from './tsdoc/TSDoc'; import { TSDoc } from './tsdoc/TSDoc';
import type { DocClass } from '~/DocModel/DocClass';
import type { DocItem } from '~/DocModel/DocItem'; import type { DocItem } from '~/DocModel/DocItem';
import type { AnyDocNodeJSON } from '~/DocModel/comment/CommentNode'; import type { AnyDocNodeJSON } from '~/DocModel/comment/CommentNode';
import { generateIcon } from '~/util/icon'; import { generateIcon } from '~/util/icon';
@@ -23,6 +25,7 @@ export interface DocContainerProps {
implementsTokens?: TokenDocumentation[][]; implementsTokens?: TokenDocumentation[][];
typeParams?: TypeParameterData[]; typeParams?: TypeParameterData[];
comment?: AnyDocNodeJSON | null; comment?: AnyDocNodeJSON | null;
methods?: ReturnType<DocClass['toJSON']>['methods'] | null;
} }
export function DocContainer({ export function DocContainer({
@@ -34,68 +37,85 @@ export function DocContainer({
children, children,
extendsTokens, extendsTokens,
implementsTokens, implementsTokens,
methods,
}: DocContainerProps) { }: DocContainerProps) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false }); const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
return ( return (
<Stack> <Group>
<Title order={2} ml="xs" className="break-all"> <Stack sx={{ flexGrow: 1 }}>
<Group> <Title sx={{ wordBreak: 'break-all' }} order={2} ml="xs">
{generateIcon(kind)} <Group>
{name} {generateIcon(kind)}
</Group> {name}
</Title> </Group>
</Title>
<Section title="Summary" icon={<VscListSelection />} padded dense={matches}> <Section title="Summary" icon={<VscListSelection />} padded dense={matches}>
{summary ? <TSDoc node={summary} /> : <Text>No summary provided.</Text>} {summary ? <TSDoc node={summary} /> : <Text>No summary provided.</Text>}
</Section> </Section>
<Box px="xs" pb="xs"> <Box px="xs" pb="xs">
<SyntaxHighlighter <SyntaxHighlighter
wrapLongLines wrapLongLines
language="typescript" language="typescript"
style={vscDarkPlus} style={vscDarkPlus}
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }} codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
> >
{excerpt} {excerpt}
</SyntaxHighlighter> </SyntaxHighlighter>
</Box> </Box>
{extendsTokens?.length ? ( {extendsTokens?.length ? (
<Group noWrap> <Group noWrap>
<Title order={3} ml="xs"> <Title order={3} ml="xs">
Extends Extends
</Title> </Title>
<Text className="font-mono break-all"> <Text sx={{ wordBreak: 'break-all' }} className="font-mono">
<HyperlinkedText tokens={extendsTokens} /> <HyperlinkedText tokens={extendsTokens} />
</Text> </Text>
</Group> </Group>
) : null}
{implementsTokens?.length ? (
<Group noWrap>
<Title order={3} ml="xs">
Implements
</Title>
<Text className="font-mono break-all">
{implementsTokens.map((token, idx) => (
<>
<HyperlinkedText tokens={token} />
{idx < implementsTokens.length - 1 ? ', ' : ''}
</>
))}
</Text>
</Group>
) : null}
<Stack>
{typeParams?.length ? (
<Section title="Type Parameters" icon={<VscSymbolParameter />} padded dense={matches} defaultClosed>
<TypeParamTable data={typeParams} />
</Section>
) : null} ) : null}
<Stack>{children}</Stack>
{implementsTokens?.length ? (
<Group noWrap>
<Title order={3} ml="xs">
Implements
</Title>
<Text sx={{ wordBreak: 'break-all' }} className="font-mono">
{implementsTokens.map((token, idx) => (
<>
<HyperlinkedText tokens={token} />
{idx < implementsTokens.length - 1 ? ', ' : ''}
</>
))}
</Text>
</Group>
) : null}
<Stack>
{typeParams?.length ? (
<Section title="Type Parameters" icon={<VscSymbolParameter />} padded dense={matches} defaultClosed>
<TypeParamTable data={typeParams} />
</Section>
) : null}
<Stack>{children}</Stack>
</Stack>
</Stack> </Stack>
</Stack> {kind === 'Class' && methods ? (
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
<Aside
sx={{ backgroundColor: 'transparent' }}
hiddenBreakpoint="md"
width={{ md: 200, lg: 300 }}
withBorder={false}
>
<ScrollArea p="xs">
<TableOfContentsItems members={methods}></TableOfContentsItems>
</ScrollArea>
</Aside>
</MediaQuery>
) : null}
</Group>
); );
} }

View File

@@ -34,9 +34,9 @@ export function MethodItem({ data }: { data: MethodResolvable }) {
<Badge variant="filled">Protected</Badge> <Badge variant="filled">Protected</Badge>
) : null} ) : null}
{data.kind === 'Method' && method.static ? <Badge variant="filled">Static</Badge> : null} {data.kind === 'Method' && method.static ? <Badge variant="filled">Static</Badge> : null}
<Title order={4} className="font-mono break-all">{`${getShorthandName(data)}`}</Title> <Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono">{`${getShorthandName(data)}`}</Title>
<Title order={4}>:</Title> <Title order={4}>:</Title>
<Title order={4} className="font-mono break-all"> <Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono">
<HyperlinkedText tokens={data.returnTypeTokens} /> <HyperlinkedText tokens={data.returnTypeTokens} />
</Title> </Title>
</Group> </Group>

View File

@@ -17,7 +17,7 @@ export function ParameterTable({ data }: { data: ParameterDocumentation[] }) {
}; };
return ( return (
<Box className="overflow-x-auto"> <Box sx={{ overflowX: 'auto' }}>
<Table columns={['Name', 'Type', 'Optional', 'Description']} rows={rows} columnStyles={columnStyles} /> <Table columns={['Name', 'Type', 'Optional', 'Description']} rows={rows} columnStyles={columnStyles} />
</Box> </Box>
); );

View File

@@ -41,7 +41,7 @@ export function Section({
const { classes } = useStyles({ opened }); const { classes } = useStyles({ opened });
return ( return (
<Box className="break-all"> <Box sx={{ wordBreak: 'break-all' }}>
<UnstyledButton className={classes.control} onClick={() => setOpened((o) => !o)}> <UnstyledButton className={classes.control} onClick={() => setOpened((o) => !o)}>
<Group position="apart"> <Group position="apart">
<Group> <Group>

View File

@@ -103,7 +103,9 @@ export function SidebarItems({
<Link key={i} href={member.path} passHref> <Link key={i} href={member.path} passHref>
<UnstyledButton className={classes.link} component="a" onClick={() => setOpened((o) => !o)}> <UnstyledButton className={classes.link} component="a" onClick={() => setOpened((o) => !o)}>
<Group> <Group>
<Text className="line-clamp-1 text-ellipsis overflow-hidden">{member.name}</Text> <Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1">
{member.name}
</Text>
{member.overloadIndex && member.overloadIndex > 1 ? ( {member.overloadIndex && member.overloadIndex > 1 ? (
<Text size="xs" color="dimmed"> <Text size="xs" color="dimmed">
{member.overloadIndex} {member.overloadIndex}

View File

@@ -3,7 +3,6 @@ import {
AppShell, AppShell,
Navbar, Navbar,
MediaQuery, MediaQuery,
Aside,
Header, Header,
Burger, Burger,
Anchor, Anchor,
@@ -26,8 +25,6 @@ import { type PropsWithChildren, useState } from 'react';
import { VscChevronDown, VscPackage } from 'react-icons/vsc'; import { VscChevronDown, VscPackage } from 'react-icons/vsc';
import { WiDaySunny, WiNightClear } from 'react-icons/wi'; import { WiDaySunny, WiNightClear } from 'react-icons/wi';
import { SidebarItems } from './SidebarItems'; import { SidebarItems } from './SidebarItems';
import { TableOfContentsItems } from './TableOfContentsItems';
import type { DocClass } from '~/DocModel/DocClass';
import type { DocItem } from '~/DocModel/DocItem'; import type { DocItem } from '~/DocModel/DocItem';
import type { findMember } from '~/util/model.server'; import type { findMember } from '~/util/model.server';
import type { getMembers } from '~/util/parse.server'; import type { getMembers } from '~/util/parse.server';
@@ -155,21 +152,6 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
) : null} ) : null}
</Navbar> </Navbar>
} }
aside={
packageName && data?.member && data.member.kind === 'Class' ? (
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
<Aside hiddenBreakpoint="sm" width={{ sm: 200, lg: 300 }}>
<ScrollArea p="xs">
<TableOfContentsItems
members={(data.member as unknown as ReturnType<DocClass['toJSON']>).methods}
></TableOfContentsItems>
</ScrollArea>
</Aside>
</MediaQuery>
) : (
<></>
)
}
// footer={ // footer={
// <Footer height={60} p="md"> // <Footer height={60} p="md">
// Application footer // Application footer

View File

@@ -38,7 +38,9 @@ export function TableOfContentsItems({
return ( return (
<Box<'a'> key={key} href={`#${key}`} component="a" className={classes.link}> <Box<'a'> key={key} href={`#${key}`} component="a" className={classes.link}>
<Group> <Group>
<Text className="line-clamp-1 text-ellipsis overflow-hidden">{member.name}</Text> <Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1">
{member.name}
</Text>
{member.overloadIndex && member.overloadIndex > 1 ? ( {member.overloadIndex && member.overloadIndex > 1 ? (
<Text size="xs" color="dimmed"> <Text size="xs" color="dimmed">
{member.overloadIndex} {member.overloadIndex}

View File

@@ -19,7 +19,7 @@ export function TypeParamTable({ data }: { data: TypeParameterData[] }) {
}; };
return ( return (
<Box className="overflow-x-auto"> <Box sx={{ overflowX: 'auto' }}>
<Table <Table
columns={['Name', 'Constraints', 'Optional', 'Default', 'Description']} columns={['Name', 'Constraints', 'Optional', 'Default', 'Description']}
rows={rows} rows={rows}

View File

@@ -13,6 +13,7 @@ export function Class({ data }: { data: ReturnType<DocClass['toJSON']> }) {
extendsTokens={data.extendsTokens} extendsTokens={data.extendsTokens}
implementsTokens={data.implementsTokens} implementsTokens={data.implementsTokens}
comment={data.comment} comment={data.comment}
methods={data.methods}
> >
<PropertiesSection data={data.properties} /> <PropertiesSection data={data.properties} />
<MethodsSection data={data.methods} /> <MethodsSection data={data.methods} />

View File

@@ -1,4 +1,4 @@
import { Alert } from '@mantine/core'; import { Alert, Box, Title } from '@mantine/core';
import { StandardTags } from '@microsoft/tsdoc'; import { StandardTags } from '@microsoft/tsdoc';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { VscWarning } from 'react-icons/vsc'; import { VscWarning } from 'react-icons/vsc';
@@ -10,10 +10,10 @@ export interface BlockProps {
export function Block({ children, title }: BlockProps) { export function Block({ children, title }: BlockProps) {
return ( return (
<div> <Box>
<h3 className="m-0">{title}</h3> <Title order={3}>{title}</Title>
{children} {children}
</div> </Box>
); );
} }

View File

@@ -1,4 +1,4 @@
import { Anchor, Box, Text } from '@mantine/core'; import { Anchor, Box, Code, Text } from '@mantine/core';
import { DocNodeKind, StandardTags } from '@microsoft/tsdoc'; import { DocNodeKind, StandardTags } from '@microsoft/tsdoc';
import Link from 'next/link'; import Link from 'next/link';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
@@ -61,9 +61,9 @@ export function TSDoc({ node }: { node: AnyDocNodeJSON }): JSX.Element {
case DocNodeKind.CodeSpan: { case DocNodeKind.CodeSpan: {
const { code } = node as DocFencedCodeJSON; const { code } = node as DocFencedCodeJSON;
return ( return (
<pre key={idx} className="inline"> <Code key={idx} sx={{ display: 'inline' }} className="text-sm font-mono">
{code} {code}
</pre> </Code>
); );
} }
case DocNodeKind.FencedCode: { case DocNodeKind.FencedCode: {

View File

@@ -146,7 +146,7 @@ const member = (props: any) => {
export default function Slug(props: Partial<SidebarLayoutProps & { error?: string }>) { export default function Slug(props: Partial<SidebarLayoutProps & { error?: string }>) {
return props.error ? ( return props.error ? (
<div className="flex max-w-full h-full bg-white dark:bg-dark">{props.error}</div> <Box sx={{ display: 'flex', maxWidth: '100%', height: '100%' }}>{props.error}</Box>
) : ( ) : (
<MemberProvider member={props.data?.member}> <MemberProvider member={props.data?.member}>
<SidebarLayout {...props}>{props.data?.member ? member(props.data.member) : null}</SidebarLayout> <SidebarLayout {...props}>{props.data?.member ? member(props.data.member) : null}</SidebarLayout>

View File

@@ -38,7 +38,7 @@ export function generatePath(items: readonly ApiItem[]) {
const functionItem = item as ApiFunction; const functionItem = item as ApiFunction;
path += `${functionItem.displayName}${ path += `${functionItem.displayName}${
functionItem.overloadIndex && functionItem.overloadIndex > 1 ? `:${functionItem.overloadIndex}` : '' functionItem.overloadIndex && functionItem.overloadIndex > 1 ? `:${functionItem.overloadIndex}` : ''
}:/`; }/`;
break; break;
default: default:
path += `${item.displayName}/`; path += `${item.displayName}/`;