From 92c0c60519cd845551975816a9c411d3ad04a5d2 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Mon, 22 Aug 2022 23:08:23 +0200 Subject: [PATCH] feat: version picker --- packages/website/package.json | 3 +- .../src/components/RouterTransition.tsx | 1 + .../website/src/components/SidebarLayout.tsx | 104 +++++++++++++----- packages/website/src/contexts/member.tsx | 16 +-- packages/website/src/pages/docs/[...slug].tsx | 21 +++- yarn.lock | 10 ++ 6 files changed, 119 insertions(+), 36 deletions(-) diff --git a/packages/website/package.json b/packages/website/package.json index a0cded536..464b5c93d 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -70,7 +70,8 @@ "rehype-raw": "^6.1.1", "rehype-slug": "^5.0.1", "remark-gfm": "^3.0.1", - "sharp": "^0.30.7" + "sharp": "^0.30.7", + "swr": "^1.3.0" }, "devDependencies": { "@testing-library/react": "^13.3.0", diff --git a/packages/website/src/components/RouterTransition.tsx b/packages/website/src/components/RouterTransition.tsx index bd8652337..2fa6dd979 100644 --- a/packages/website/src/components/RouterTransition.tsx +++ b/packages/website/src/components/RouterTransition.tsx @@ -18,6 +18,7 @@ export function RouterTransition() { router.events.off('routeChangeComplete', handleComplete); router.events.off('routeChangeError', handleComplete); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [router.asPath]); return ; diff --git a/packages/website/src/components/SidebarLayout.tsx b/packages/website/src/components/SidebarLayout.tsx index 4c14345b9..14e566288 100644 --- a/packages/website/src/components/SidebarLayout.tsx +++ b/packages/website/src/components/SidebarLayout.tsx @@ -18,6 +18,7 @@ import { ActionIcon, useMantineColorScheme, Center, + Stack, } from '@mantine/core'; import { NextLink } from '@mantine/next'; import type { MDXRemoteSerializeResult } from 'next-mdx-remote'; @@ -25,15 +26,19 @@ import Image from 'next/future/image'; import Link from 'next/link'; import { useRouter } from 'next/router'; 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 useSWR from 'swr'; import { SidebarItems } from './SidebarItems'; import type { ApiItemJSON } from '~/DocModel/ApiNodeJSONEncoder'; import type { findMember } from '~/util/model.server'; import type { getMembers } from '~/util/parse.server'; +const fetcher = (url: string) => fetch(url).then((res) => res.json()); + export interface SidebarLayoutProps { packageName: string; + branchName: string; data: { members: ReturnType; member: ReturnType; @@ -54,24 +59,31 @@ export interface GroupedMembers { 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, +const useStyles = createStyles( + (theme, { openedLib, openedVersion }: { openedLib: boolean; openedVersion: 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![6] : theme.colors.gray![0], - color: theme.colorScheme === 'dark' ? theme.white : theme.black, + '&:hover': { + backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0], + color: theme.colorScheme === 'dark' ? theme.white : theme.black, + }, }, - }, - icon: { - transition: 'transform 150ms ease', - transform: opened ? 'rotate(180deg)' : 'rotate(0deg)', - }, -})); + iconLib: { + transition: 'transform 150ms ease', + transform: openedLib ? 'rotate(180deg)' : 'rotate(0deg)', + }, + + iconVersion: { + transition: 'transform 150ms ease', + transform: openedVersion ? 'rotate(180deg)' : 'rotate(0deg)', + }, + }), +); const libraries = [ { label: 'builders', value: 'builders' }, @@ -83,17 +95,26 @@ const libraries = [ { label: 'ws', value: 'ws' }, ]; -export function SidebarLayout({ packageName, data, children }: PropsWithChildren>) { +export function SidebarLayout({ + packageName, + branchName, + data, + children, +}: PropsWithChildren>) { const router = useRouter(); - + const { data: versions } = useSWR( + `https://docs.discordjs.dev/api/info?package=${packageName ?? 'builders'}`, + fetcher, + ); const theme = useMantineTheme(); const { colorScheme, toggleColorScheme } = useMantineColorScheme(); const dark = colorScheme === 'dark'; 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) => ( @@ -101,6 +122,13 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren )); + const versionMenuItems = + versions?.map((item) => ( + + {item} + + )) ?? []; + const asPathWithoutQuery = router.asPath.split('?')[0]?.split('#')[0]; const breadcrumbs = asPathWithoutQuery?.split('/').map((path, idx, original) => ( @@ -124,10 +152,10 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren {packageName && data ? ( <> - <> + setOpenedPicker(true)} - onClose={() => setOpenedPicker(false)} + onOpen={() => setOpenedLibPicker(true)} + onClose={() => setOpenedLibPicker(false)} radius="xs" width="target" > @@ -142,16 +170,40 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren {packageName} - + {libraryMenuItems} - + + setOpenedVersionPicker(true)} + onClose={() => setOpenedVersionPicker(false)} + radius="xs" + width="target" + > + + + + + + + + + {branchName} + + + + + + + {versionMenuItems} + + - + diff --git a/packages/website/src/contexts/member.tsx b/packages/website/src/contexts/member.tsx index d45059596..f45fd071b 100644 --- a/packages/website/src/contexts/member.tsx +++ b/packages/website/src/contexts/member.tsx @@ -1,12 +1,12 @@ -import { createContext } from 'react'; +import { createContext, useContext, type ReactNode } from 'react'; import type { ApiItemJSON } from '~/DocModel/ApiNodeJSONEncoder'; export const MemberContext = createContext(undefined); -export const MemberProvider = ({ - member, - children, -}: { - member: ApiItemJSON | undefined; - children: React.ReactNode; -}) => {children}; +export const MemberProvider = ({ member, children }: { member: ApiItemJSON | undefined; children: ReactNode }) => ( + {children} +); + +export function useMember() { + return useContext(MemberContext); +} diff --git a/packages/website/src/pages/docs/[...slug].tsx b/packages/website/src/pages/docs/[...slug].tsx index 045f5965a..d241743af 100644 --- a/packages/website/src/pages/docs/[...slug].tsx +++ b/packages/website/src/pages/docs/[...slug].tsx @@ -1,11 +1,13 @@ import { readFile } from 'node:fs/promises'; 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 { MDXRemote } from 'next-mdx-remote'; import { serialize } from 'next-mdx-remote/serialize'; import Head from 'next/head'; import type { GetStaticPaths, GetStaticProps } from 'next/types'; +import { VscChevronUp } from 'react-icons/vsc'; import rehypeHighlight from 'rehype-highlight'; import rehypeIgnore from 'rehype-ignore'; import rehypeRaw from 'rehype-raw'; @@ -165,6 +167,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { return { props: { packageName, + branchName, data: { members: pkg ? getMembers(pkg, branchName) : [], member: @@ -202,6 +205,9 @@ const member = (props?: ApiItemJSON | undefined) => { }; export default function Slug(props: Partial) { + const [scroll, scrollTo] = useWindowScroll(); + const matches = useMediaQuery('(max-width: 1200px)', true, { getInitialValueInEffect: false }); + return props.error ? ( {props.error} ) : ( @@ -213,6 +219,19 @@ export default function Slug(props: Partialdiscord.js | {props.data.member.name} {member(props.data.member)} + + 250}> + {(transitionStyles) => ( + + )} + + ) : props.data?.source ? ( diff --git a/yarn.lock b/yarn.lock index c78c4ae79..c865c4e27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2052,6 +2052,7 @@ __metadata: rehype-slug: ^5.0.1 remark-gfm: ^3.0.1 sharp: ^0.30.7 + swr: ^1.3.0 typescript: ^4.7.4 unocss: ^0.45.12 vercel: ^28.1.0 @@ -15633,6 +15634,15 @@ __metadata: languageName: node 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": version: 6.1.0 resolution: "sync-request@npm:6.1.0"