mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-20 13:33:30 +01:00
feat: version picker
This commit is contained in:
@@ -70,7 +70,8 @@
|
|||||||
"rehype-raw": "^6.1.1",
|
"rehype-raw": "^6.1.1",
|
||||||
"rehype-slug": "^5.0.1",
|
"rehype-slug": "^5.0.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"sharp": "^0.30.7"
|
"sharp": "^0.30.7",
|
||||||
|
"swr": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export function RouterTransition() {
|
|||||||
router.events.off('routeChangeComplete', handleComplete);
|
router.events.off('routeChangeComplete', handleComplete);
|
||||||
router.events.off('routeChangeError', handleComplete);
|
router.events.off('routeChangeError', handleComplete);
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [router.asPath]);
|
}, [router.asPath]);
|
||||||
|
|
||||||
return <NavigationProgress />;
|
return <NavigationProgress />;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
ActionIcon,
|
ActionIcon,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
Center,
|
Center,
|
||||||
|
Stack,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { NextLink } from '@mantine/next';
|
import { NextLink } from '@mantine/next';
|
||||||
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
|
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
|
||||||
@@ -25,15 +26,19 @@ import Image from 'next/future/image';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { type PropsWithChildren, useState } from 'react';
|
import { type PropsWithChildren, useState } from 'react';
|
||||||
import { VscChevronDown, VscPackage } from 'react-icons/vsc';
|
import { VscChevronDown, VscPackage, VscVersions } from 'react-icons/vsc';
|
||||||
import { WiDaySunny, WiNightClear } from 'react-icons/wi';
|
import { WiDaySunny, WiNightClear } from 'react-icons/wi';
|
||||||
|
import useSWR from 'swr';
|
||||||
import { SidebarItems } from './SidebarItems';
|
import { SidebarItems } from './SidebarItems';
|
||||||
import type { ApiItemJSON } from '~/DocModel/ApiNodeJSONEncoder';
|
import type { ApiItemJSON } from '~/DocModel/ApiNodeJSONEncoder';
|
||||||
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';
|
||||||
|
|
||||||
|
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||||
|
|
||||||
export interface SidebarLayoutProps {
|
export interface SidebarLayoutProps {
|
||||||
packageName: string;
|
packageName: string;
|
||||||
|
branchName: string;
|
||||||
data: {
|
data: {
|
||||||
members: ReturnType<typeof getMembers>;
|
members: ReturnType<typeof getMembers>;
|
||||||
member: ReturnType<typeof findMember>;
|
member: ReturnType<typeof findMember>;
|
||||||
@@ -54,7 +59,8 @@ export interface GroupedMembers {
|
|||||||
Variables: Members;
|
Variables: Members;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
|
const useStyles = createStyles(
|
||||||
|
(theme, { openedLib, openedVersion }: { openedLib: boolean; openedVersion: boolean }) => ({
|
||||||
control: {
|
control: {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -67,11 +73,17 @@ const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
icon: {
|
iconLib: {
|
||||||
transition: 'transform 150ms ease',
|
transition: 'transform 150ms ease',
|
||||||
transform: opened ? 'rotate(180deg)' : 'rotate(0deg)',
|
transform: openedLib ? 'rotate(180deg)' : 'rotate(0deg)',
|
||||||
},
|
},
|
||||||
}));
|
|
||||||
|
iconVersion: {
|
||||||
|
transition: 'transform 150ms ease',
|
||||||
|
transform: openedVersion ? 'rotate(180deg)' : 'rotate(0deg)',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const libraries = [
|
const libraries = [
|
||||||
{ label: 'builders', value: 'builders' },
|
{ label: 'builders', value: 'builders' },
|
||||||
@@ -83,17 +95,26 @@ const libraries = [
|
|||||||
{ label: 'ws', value: 'ws' },
|
{ label: 'ws', value: 'ws' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function SidebarLayout({ packageName, data, children }: PropsWithChildren<Partial<SidebarLayoutProps>>) {
|
export function SidebarLayout({
|
||||||
|
packageName,
|
||||||
|
branchName,
|
||||||
|
data,
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<Partial<SidebarLayoutProps>>) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { data: versions } = useSWR<string[]>(
|
||||||
|
`https://docs.discordjs.dev/api/info?package=${packageName ?? 'builders'}`,
|
||||||
|
fetcher,
|
||||||
|
);
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === 'dark';
|
const dark = colorScheme === 'dark';
|
||||||
|
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
const [openedPicker, setOpenedPicker] = useState(false);
|
const [openedLibPicker, setOpenedLibPicker] = useState(false);
|
||||||
|
const [openedVersionPicker, setOpenedVersionPicker] = useState(false);
|
||||||
|
|
||||||
const { classes } = useStyles({ opened: openedPicker });
|
const { classes } = useStyles({ openedLib: openedLibPicker, openedVersion: openedVersionPicker });
|
||||||
|
|
||||||
const libraryMenuItems = libraries.map((item) => (
|
const libraryMenuItems = libraries.map((item) => (
|
||||||
<Menu.Item key={item.label} component={NextLink} href={`/docs/packages/${item.value}/main`}>
|
<Menu.Item key={item.label} component={NextLink} href={`/docs/packages/${item.value}/main`}>
|
||||||
@@ -101,6 +122,13 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
const versionMenuItems =
|
||||||
|
versions?.map((item) => (
|
||||||
|
<Menu.Item key={item} component={NextLink} href={`/docs/packages/${packageName ?? 'builders'}/${item}`}>
|
||||||
|
{item}
|
||||||
|
</Menu.Item>
|
||||||
|
)) ?? [];
|
||||||
|
|
||||||
const asPathWithoutQuery = router.asPath.split('?')[0]?.split('#')[0];
|
const asPathWithoutQuery = router.asPath.split('?')[0]?.split('#')[0];
|
||||||
const breadcrumbs = asPathWithoutQuery?.split('/').map((path, idx, original) => (
|
const breadcrumbs = asPathWithoutQuery?.split('/').map((path, idx, original) => (
|
||||||
<Link key={idx} href={original.slice(0, idx + 1).join('/')} passHref>
|
<Link key={idx} href={original.slice(0, idx + 1).join('/')} passHref>
|
||||||
@@ -124,10 +152,10 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
|
|||||||
{packageName && data ? (
|
{packageName && data ? (
|
||||||
<>
|
<>
|
||||||
<Navbar.Section p="xs">
|
<Navbar.Section p="xs">
|
||||||
<>
|
<Stack spacing="xs">
|
||||||
<Menu
|
<Menu
|
||||||
onOpen={() => setOpenedPicker(true)}
|
onOpen={() => setOpenedLibPicker(true)}
|
||||||
onClose={() => setOpenedPicker(false)}
|
onClose={() => setOpenedLibPicker(false)}
|
||||||
radius="xs"
|
radius="xs"
|
||||||
width="target"
|
width="target"
|
||||||
>
|
>
|
||||||
@@ -142,16 +170,40 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
|
|||||||
{packageName}
|
{packageName}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
<VscChevronDown className={classes.icon} size={20} />
|
<VscChevronDown className={classes.iconLib} size={20} />
|
||||||
</Group>
|
</Group>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
<Menu.Dropdown>{libraryMenuItems}</Menu.Dropdown>
|
<Menu.Dropdown>{libraryMenuItems}</Menu.Dropdown>
|
||||||
</Menu>
|
</Menu>
|
||||||
</>
|
|
||||||
|
<Menu
|
||||||
|
onOpen={() => setOpenedVersionPicker(true)}
|
||||||
|
onClose={() => setOpenedVersionPicker(false)}
|
||||||
|
radius="xs"
|
||||||
|
width="target"
|
||||||
|
>
|
||||||
|
<Menu.Target>
|
||||||
|
<UnstyledButton className={classes.control}>
|
||||||
|
<Group position="apart">
|
||||||
|
<Group>
|
||||||
|
<ThemeIcon size={30}>
|
||||||
|
<VscVersions size={20} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text weight="600" size="md">
|
||||||
|
{branchName}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<VscChevronDown className={classes.iconVersion} size={20} />
|
||||||
|
</Group>
|
||||||
|
</UnstyledButton>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>{versionMenuItems}</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
</Stack>
|
||||||
</Navbar.Section>
|
</Navbar.Section>
|
||||||
|
|
||||||
<Navbar.Section p="xs" grow component={ScrollArea}>
|
<Navbar.Section px="xs" pb="xs" grow component={ScrollArea}>
|
||||||
<SidebarItems members={data.members} setOpened={setOpened} />
|
<SidebarItems members={data.members} setOpened={setOpened} />
|
||||||
</Navbar.Section>
|
</Navbar.Section>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { createContext } from 'react';
|
import { createContext, useContext, type ReactNode } from 'react';
|
||||||
import type { ApiItemJSON } from '~/DocModel/ApiNodeJSONEncoder';
|
import type { ApiItemJSON } from '~/DocModel/ApiNodeJSONEncoder';
|
||||||
|
|
||||||
export const MemberContext = createContext<ApiItemJSON | undefined>(undefined);
|
export const MemberContext = createContext<ApiItemJSON | undefined>(undefined);
|
||||||
|
|
||||||
export const MemberProvider = ({
|
export const MemberProvider = ({ member, children }: { member: ApiItemJSON | undefined; children: ReactNode }) => (
|
||||||
member,
|
<MemberContext.Provider value={member}>{children}</MemberContext.Provider>
|
||||||
children,
|
);
|
||||||
}: {
|
|
||||||
member: ApiItemJSON | undefined;
|
export function useMember() {
|
||||||
children: React.ReactNode;
|
return useContext(MemberContext);
|
||||||
}) => <MemberContext.Provider value={member}>{children}</MemberContext.Provider>;
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { Box } from '@mantine/core';
|
import { Affix, Box, Button, Transition } from '@mantine/core';
|
||||||
|
import { useMediaQuery, useWindowScroll } from '@mantine/hooks';
|
||||||
import { ApiFunction, ApiPackage } from '@microsoft/api-extractor-model';
|
import { ApiFunction, ApiPackage } from '@microsoft/api-extractor-model';
|
||||||
import { MDXRemote } from 'next-mdx-remote';
|
import { MDXRemote } from 'next-mdx-remote';
|
||||||
import { serialize } from 'next-mdx-remote/serialize';
|
import { serialize } from 'next-mdx-remote/serialize';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import type { GetStaticPaths, GetStaticProps } from 'next/types';
|
import type { GetStaticPaths, GetStaticProps } from 'next/types';
|
||||||
|
import { VscChevronUp } from 'react-icons/vsc';
|
||||||
import rehypeHighlight from 'rehype-highlight';
|
import rehypeHighlight from 'rehype-highlight';
|
||||||
import rehypeIgnore from 'rehype-ignore';
|
import rehypeIgnore from 'rehype-ignore';
|
||||||
import rehypeRaw from 'rehype-raw';
|
import rehypeRaw from 'rehype-raw';
|
||||||
@@ -165,6 +167,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
packageName,
|
packageName,
|
||||||
|
branchName,
|
||||||
data: {
|
data: {
|
||||||
members: pkg ? getMembers(pkg, branchName) : [],
|
members: pkg ? getMembers(pkg, branchName) : [],
|
||||||
member:
|
member:
|
||||||
@@ -202,6 +205,9 @@ const member = (props?: ApiItemJSON | undefined) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Slug(props: Partial<SidebarLayoutProps & { error?: string }>) {
|
export default function Slug(props: Partial<SidebarLayoutProps & { error?: string }>) {
|
||||||
|
const [scroll, scrollTo] = useWindowScroll();
|
||||||
|
const matches = useMediaQuery('(max-width: 1200px)', true, { getInitialValueInEffect: false });
|
||||||
|
|
||||||
return props.error ? (
|
return props.error ? (
|
||||||
<Box sx={{ display: 'flex', maxWidth: '100%', height: '100%' }}>{props.error}</Box>
|
<Box sx={{ display: 'flex', maxWidth: '100%', height: '100%' }}>{props.error}</Box>
|
||||||
) : (
|
) : (
|
||||||
@@ -213,6 +219,19 @@ export default function Slug(props: Partial<SidebarLayoutProps & { error?: strin
|
|||||||
<title key="title">discord.js | {props.data.member.name}</title>
|
<title key="title">discord.js | {props.data.member.name}</title>
|
||||||
</Head>
|
</Head>
|
||||||
{member(props.data.member)}
|
{member(props.data.member)}
|
||||||
|
<Affix position={{ bottom: 20, right: matches ? 20 : 280 }}>
|
||||||
|
<Transition transition="slide-up" mounted={scroll.y > 250}>
|
||||||
|
{(transitionStyles) => (
|
||||||
|
<Button
|
||||||
|
leftIcon={<VscChevronUp size={20} />}
|
||||||
|
style={transitionStyles}
|
||||||
|
onClick={() => scrollTo({ y: 0 })}
|
||||||
|
>
|
||||||
|
Scroll to top
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
</Affix>
|
||||||
</>
|
</>
|
||||||
) : props.data?.source ? (
|
) : props.data?.source ? (
|
||||||
<Box px="xl">
|
<Box px="xl">
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@@ -2052,6 +2052,7 @@ __metadata:
|
|||||||
rehype-slug: ^5.0.1
|
rehype-slug: ^5.0.1
|
||||||
remark-gfm: ^3.0.1
|
remark-gfm: ^3.0.1
|
||||||
sharp: ^0.30.7
|
sharp: ^0.30.7
|
||||||
|
swr: ^1.3.0
|
||||||
typescript: ^4.7.4
|
typescript: ^4.7.4
|
||||||
unocss: ^0.45.12
|
unocss: ^0.45.12
|
||||||
vercel: ^28.1.0
|
vercel: ^28.1.0
|
||||||
@@ -15633,6 +15634,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"swr@npm:^1.3.0":
|
||||||
|
version: 1.3.0
|
||||||
|
resolution: "swr@npm:1.3.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.11.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: e7a184f0d560e9c8be85c023cc8e65e56a88a6ed46f9394b301b07f838edca23d2e303685319a4fcd620b81d447a7bcb489c7fa0a752c259f91764903c690cdb
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"sync-request@npm:^6.1.0":
|
"sync-request@npm:^6.1.0":
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
resolution: "sync-request@npm:6.1.0"
|
resolution: "sync-request@npm:6.1.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user