fix: sidebar behaviour when switching package/version

This commit is contained in:
iCrawl
2025-05-15 17:11:47 +02:00
parent 14e226b72b
commit c92a8c27a2
18 changed files with 377 additions and 208 deletions

View File

@@ -1,5 +1,3 @@
'use cache';
import { VscSymbolParameter } from '@react-icons/all-files/vsc/VscSymbolParameter';
import { ConstructorNode } from './ConstructorNode';
import { DeprecatedNode } from './DeprecatedNode';

View File

@@ -1,23 +1,25 @@
'use client';
import { useParams, useRouter } from 'next/navigation';
import { use } from 'react';
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
import { Select, SelectList, SelectOption, SelectTrigger } from './ui/Select';
export function EntryPointSelect({
entryPointsPromise,
}: {
readonly entryPointsPromise: Promise<{ readonly entryPoint: string }[]>;
}) {
export function EntryPointSelect({ entryPoints }: { readonly entryPoints: { readonly entryPoint: string }[] }) {
const router = useRouter();
const params = useParams();
const entryPoints = use(entryPointsPromise);
const params = useParams<{
item?: string[] | undefined;
packageName: string;
version: string;
}>();
const { entryPoints: parsedEntrypoints } = parseDocsPathParams(params.item as string[] | undefined);
const { entryPoints: parsedEntrypoints } = parseDocsPathParams(params.item);
return (
<Select aria-label="Select an entrypoint" defaultSelectedKey={parsedEntrypoints.join('/')}>
<Select
aria-label="Select an entrypoint"
defaultSelectedKey={parsedEntrypoints.join('/')}
key={parsedEntrypoints.join('/')}
>
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={entryPoints}>
{(item) => (

View File

@@ -1,26 +1,38 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { notFound } from 'next/navigation';
import { fetchSitemap } from '@/util/fetchSitemap';
import { notFound, useParams } from 'next/navigation';
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
import { resolveNodeKind } from './DocKind';
import { NavigationItem } from './NavigationItem';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
export async function Navigation({
entryPoint,
packageName,
version,
}: {
readonly entryPoint?: string | undefined;
readonly packageName: string;
readonly version: string;
}) {
const node = await fetchSitemap({ entryPoint, packageName, version });
export function Navigation() {
const params = useParams<{
item?: string[] | undefined;
packageName: string;
version: string;
}>();
if (!node) {
const { entryPoints: parsedEntrypoints } = parseDocsPathParams(params.item);
const { data: node, status } = useQuery({
queryKey: ['sitemap', params.packageName, params.version, parsedEntrypoints.join('.')],
queryFn: async () => {
const response = await fetch(
`/api/docs/sitemap?packageName=${params.packageName}&version=${params.version}&entryPoint=${parsedEntrypoints.join('.')}`,
);
return response.json();
},
});
if ((status === 'success' && !node) || status === 'error') {
notFound();
}
const groupedNodes = node.reduce((acc: any, node: any) => {
const groupedNodes = node?.reduce((acc: any, node: any) => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
(acc[node.kind.toLowerCase()] ||= []).push(node);
return acc;
@@ -28,7 +40,7 @@ export async function Navigation({
return (
<nav className="flex flex-col gap-2 pr-3">
{groupedNodes.class?.length ? (
{groupedNodes?.class?.length ? (
<Collapsible className="flex flex-col gap-2" defaultOpen>
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
<h4 className="font-semibold">Classes</h4>
@@ -40,7 +52,12 @@ export async function Navigation({
{groupedNodes.class.map((node: any, idx: number) => {
const kind = resolveNodeKind(node.kind);
return (
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
<NavigationItem
key={`${node.name}-${idx}`}
node={node}
packageName={params.packageName}
version={params.version}
>
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
{node.kind[0]}
</div>{' '}
@@ -53,7 +70,7 @@ export async function Navigation({
</Collapsible>
) : null}
{groupedNodes.function?.length ? (
{groupedNodes?.function?.length ? (
<Collapsible className="flex flex-col gap-2" defaultOpen>
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
<h4 className="font-semibold">Functions</h4>
@@ -65,7 +82,12 @@ export async function Navigation({
{groupedNodes.function.map((node: any, idx: number) => {
const kind = resolveNodeKind(node.kind);
return (
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
<NavigationItem
key={`${node.name}-${idx}`}
node={node}
packageName={params.packageName}
version={params.version}
>
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
{node.kind[0]}
</div>{' '}
@@ -78,7 +100,7 @@ export async function Navigation({
</Collapsible>
) : null}
{groupedNodes.enum?.length ? (
{groupedNodes?.enum?.length ? (
<Collapsible className="flex flex-col gap-2" defaultOpen>
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
<h4 className="font-semibold">Enums</h4>
@@ -90,7 +112,12 @@ export async function Navigation({
{groupedNodes.enum.map((node: any, idx: number) => {
const kind = resolveNodeKind(node.kind);
return (
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
<NavigationItem
key={`${node.name}-${idx}`}
node={node}
packageName={params.packageName}
version={params.version}
>
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
{node.kind[0]}
</div>{' '}
@@ -103,7 +130,7 @@ export async function Navigation({
</Collapsible>
) : null}
{groupedNodes.interface?.length ? (
{groupedNodes?.interface?.length ? (
<Collapsible className="flex flex-col gap-2" defaultOpen>
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
<h4 className="font-semibold">Interfaces</h4>
@@ -115,7 +142,12 @@ export async function Navigation({
{groupedNodes.interface.map((node: any, idx: number) => {
const kind = resolveNodeKind(node.kind);
return (
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
<NavigationItem
key={`${node.name}-${idx}`}
node={node}
packageName={params.packageName}
version={params.version}
>
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
{node.kind[0]}
</div>{' '}
@@ -128,7 +160,7 @@ export async function Navigation({
</Collapsible>
) : null}
{groupedNodes.typealias?.length ? (
{groupedNodes?.typealias?.length ? (
<Collapsible className="flex flex-col gap-2" defaultOpen>
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
<h4 className="font-semibold">Types</h4>
@@ -140,7 +172,12 @@ export async function Navigation({
{groupedNodes.typealias.map((node: any, idx: number) => {
const kind = resolveNodeKind(node.kind);
return (
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
<NavigationItem
key={`${node.name}-${idx}`}
node={node}
packageName={params.packageName}
version={params.version}
>
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
{node.kind[0]}
</div>{' '}
@@ -153,7 +190,7 @@ export async function Navigation({
</Collapsible>
) : null}
{groupedNodes.variable?.length ? (
{groupedNodes?.variable?.length ? (
<Collapsible className="flex flex-col gap-2" defaultOpen>
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
<h4 className="font-semibold">Variables</h4>
@@ -165,7 +202,12 @@ export async function Navigation({
{groupedNodes.variable.map((node: any, idx: number) => {
const kind = resolveNodeKind(node.kind);
return (
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
<NavigationItem
key={`${node.name}-${idx}`}
node={node}
packageName={params.packageName}
version={params.version}
>
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
{node.kind[0]}
</div>{' '}

View File

@@ -6,10 +6,12 @@ import { PACKAGES } from '@/util/constants';
export function PackageSelect() {
const router = useRouter();
const params = useParams();
const params = useParams<{
packageName: string;
}>();
return (
<Select aria-label="Select a package" defaultSelectedKey={params.packageName as string}>
<Select aria-label="Select a package" defaultSelectedKey={params.packageName} key={params.packageName}>
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={PACKAGES}>
{(item) => (

View File

@@ -0,0 +1,70 @@
'use client';
import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted';
import { useQuery } from '@tanstack/react-query';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import { EntryPointSelect } from '@/components/EntrypointSelect';
import { PackageSelect } from '@/components/PackageSelect';
import { SearchButton } from '@/components/SearchButton';
import { ThemeSwitchNoSRR } from '@/components/ThemeSwitch';
import { VersionSelect } from '@/components/VersionSelect';
import { SidebarHeader as BasSidebarHeader } from '@/components/ui/Sidebar';
import { buttonStyles } from '@/styles/ui/button';
import { PACKAGES_WITH_ENTRY_POINTS } from '@/util/constants';
export function SidebarHeader() {
const params = useParams<{
packageName: string;
version: string;
}>();
const hasEntryPoints = PACKAGES_WITH_ENTRY_POINTS.includes(params.packageName);
const { data: entryPoints } = useQuery({
queryKey: ['entryPoints', params.packageName, params.version],
queryFn: async () => {
const response = await fetch(`/api/docs/entrypoints?packageName=${params.packageName}&version=${params.version}`);
return response.json();
},
});
const { data: versions } = useQuery({
queryKey: ['versions', params.packageName],
queryFn: async () => {
const response = await fetch(`/api/docs/versions?packageName=${params.packageName}`);
return response.json();
},
});
return (
<BasSidebarHeader className="bg-[#f3f3f4] p-4 dark:bg-[#121214]">
<div className="flex flex-col gap-2">
<div className="flex place-content-between place-items-center p-1">
<Link className="text-xl font-bold" href={`/docs/packages/${params.packageName}/${params.version}`}>
{params.packageName}
</Link>
<div className="flex place-items-center gap-2">
<Link
aria-label="GitHub"
className={buttonStyles({ variant: 'filled', size: 'icon-sm' })}
href="https://github.com/discordjs/discord.js"
rel="external noopener noreferrer"
target="_blank"
>
<VscGithubInverted aria-hidden data-slot="icon" size={18} />
</Link>
<ThemeSwitchNoSRR />
</div>
</div>
<PackageSelect />
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
<VersionSelect versions={versions ?? []} />
{hasEntryPoints ? <EntryPointSelect entryPoints={entryPoints ?? []} /> : null}
<SearchButton />
</div>
</BasSidebarHeader>
);
}

View File

@@ -1,26 +1,27 @@
'use client';
import { useParams, useRouter } from 'next/navigation';
import { use } from 'react';
import { Select, SelectList, SelectOption, SelectTrigger } from './ui/Select';
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
import { DEFAULT_ENTRY_POINT, PACKAGES_WITH_ENTRY_POINTS } from '@/util/constants';
export function VersionSelect({
versionsPromise,
}: {
readonly versionsPromise: Promise<{ readonly version: string }[]>;
}) {
export function VersionSelect({ versions }: { readonly versions: { readonly version: string }[] }) {
const router = useRouter();
const params = useParams();
const versions = use(versionsPromise);
const params = useParams<{ packageName: string; version: string }>();
const hasEntryPoints = PACKAGES_WITH_ENTRY_POINTS.includes(params.packageName);
return (
<Select aria-label="Select a version" defaultSelectedKey={params.version as string}>
<Select
aria-label="Select a version"
defaultSelectedKey={params.version}
key={`${params.packageName}-${params.version}`}
>
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={versions}>
{(item) => (
<SelectOption
className="dark:pressed:bg-[#313135] bg-[#f3f3f4] dark:bg-[#28282d] dark:hover:bg-[#313135]"
href={`/docs/packages/${params.packageName}/${item.version}`}
href={`/docs/packages/${params.packageName}/${item.version}${hasEntryPoints ? ['', ...DEFAULT_ENTRY_POINT].join('/') : ''}`}
id={item.version}
key={item.version}
onHoverStart={() => router.prefetch(`/docs/packages/${params.packageName}/${item.version}`)}