From 97b0ef233b489e0053589d4d02422208d456cf19 Mon Sep 17 00:00:00 2001 From: Polo Date: Wed, 20 May 2026 20:27:39 +0200 Subject: [PATCH 1/4] Implement collapsible nested navigation in docs sidebar and mobile menu --- src/components/docs-sidebar.tsx | 104 +++++++++++++++++++------ src/components/mobile-nav.tsx | 132 +++++++++++++++++++++++++++----- src/config/docs.ts | 13 +++- 3 files changed, 206 insertions(+), 43 deletions(-) diff --git a/src/components/docs-sidebar.tsx b/src/components/docs-sidebar.tsx index 1e1458d..195ed14 100644 --- a/src/components/docs-sidebar.tsx +++ b/src/components/docs-sidebar.tsx @@ -1,7 +1,9 @@ 'use client' +import * as React from 'react' import Link from 'next/link' import { usePathname } from 'next/navigation' +import { ChevronRight } from 'lucide-react' import { docsConfig, type NavItemWithChildren } from '@/config/docs' import { cn } from '@/lib/utils' @@ -35,46 +37,102 @@ interface DocsSidebarNavItemsProps { function DocsSidebarNavItems({ items, pathname }: DocsSidebarNavItemsProps) { return items.length ? ( -
- {items.map((item, index) => - item.href ? ( +
+ {items.map((item, index) => ( + + ))} +
+ ) : null +} + +function SidebarNavItem({ item, pathname }: { item: NavItemWithChildren; pathname: string | null }) { + const hasChildren = item.items && item.items.length > 0 + + // Recursively check if a child is active + const isChildActive = React.useMemo(() => { + const checkActive = (navItem: NavItemWithChildren): boolean => { + if (navItem.href && pathname?.startsWith(navItem.href) && navItem.href !== '/') return true + if (navItem.items && navItem.items.length > 0) { + return navItem.items.some(checkActive) + } + return false + } + return checkActive(item) + }, [item, pathname]) + + const [isOpen, setIsOpen] = React.useState(isChildActive) + const isActive = pathname === item.href + + return ( +
+
+ {item.href ? ( - {item.title} - {item.label ? ( - - {item.label} - - ) : null} + + {item.title} + {item.label ? ( + + {item.label} + + ) : null} + ) : ( setIsOpen(!isOpen) : undefined} > - {item.title} - {item.label ? ( - - {item.label} - - ) : null} + + {item.title} + {item.label ? ( + + {item.label} + + ) : null} + - ), + )} + + {hasChildren && ( + + )} +
+ + {hasChildren && isOpen && ( +
+ {item.items.map((child, index) => ( + + ))} +
)}
- ) : null + ) } diff --git a/src/components/mobile-nav.tsx b/src/components/mobile-nav.tsx index d4b0ec2..e0ca34d 100644 --- a/src/components/mobile-nav.tsx +++ b/src/components/mobile-nav.tsx @@ -3,8 +3,8 @@ import * as React from "react" import Link from "next/link" import { usePathname } from "next/navigation" -import { Github, Menu } from "lucide-react" -import { docsConfig } from "@/config/docs" +import { Github, Menu, ChevronRight } from "lucide-react" +import { docsConfig, type NavItemWithChildren } from "@/config/docs" import { Button } from "@/components/ui/button" import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet" import { cn } from "@/lib/utils" @@ -82,19 +82,14 @@ export function MobileNav() { {section.title}
- {section.items.map((item) => - item.href ? ( - - {item.title} - - ) : null - )} + {section.items.map((item, index) => ( + + ))}
))} @@ -121,10 +116,20 @@ function MobileLink({ children, className, }: MobileLinkProps) { - const allHrefs = [ - ...docsConfig.mainNav.map((n) => n.href), - ...docsConfig.sidebarNav.flatMap((s) => s.items.map((i) => i.href)), - ].filter(Boolean) as string[] + const allHrefs = React.useMemo(() => { + const hrefs: string[] = [] + const extractHrefs = (items: NavItemWithChildren[]) => { + items.forEach((item) => { + if (item.href) hrefs.push(item.href) + if (item.items) extractHrefs(item.items) + }) + } + docsConfig.mainNav.forEach((item) => { + if (item.href) hrefs.push(item.href) + }) + extractHrefs(docsConfig.sidebarNav as NavItemWithChildren[]) + return hrefs + }, []) const active = pathname === href || @@ -151,3 +156,92 @@ function MobileLink({ ) } + +function MobileSidebarNavItem({ + item, + pathname, + onOpenChange, +}: { + item: NavItemWithChildren + pathname: string + onOpenChange?: (open: boolean) => void +}) { + const hasChildren = item.items && item.items.length > 0 + + const isChildActive = React.useMemo(() => { + const checkActive = (navItem: NavItemWithChildren): boolean => { + if (navItem.href && pathname?.startsWith(navItem.href) && navItem.href !== '/') return true + if (navItem.items && navItem.items.length > 0) { + return navItem.items.some(checkActive) + } + return false + } + return checkActive(item) + }, [item, pathname]) + + const [isOpen, setIsOpen] = React.useState(isChildActive) + const isActive = pathname === item.href + + return ( +
+
+ {item.href ? ( + onOpenChange?.(false)} + className={cn( + "flex w-full items-center rounded-lg px-2 py-1.5 pr-8 transition-colors", + item.disabled && "cursor-not-allowed opacity-60", + isActive + ? "bg-zinc-100 font-medium text-zinc-950 dark:bg-zinc-900 dark:text-zinc-50" + : "text-zinc-600 hover:bg-zinc-100 hover:text-zinc-950 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-50" + )} + > + {item.title} + + ) : ( + setIsOpen(!isOpen) : undefined} + > + {item.title} + + )} + + {hasChildren && ( + + )} +
+ + {hasChildren && isOpen && ( +
+ {item.items.map((child, index) => ( + + ))} +
+ )} +
+ ) +} diff --git a/src/config/docs.ts b/src/config/docs.ts index 8fdc93f..4a7147c 100644 --- a/src/config/docs.ts +++ b/src/config/docs.ts @@ -103,7 +103,18 @@ export const docsConfig: DocsConfig = { { title: 'AI Service API', href: '/docs/api/ai-service', - items: [], + items: [ + { + title: 'Authentication', + href: '/docs/api/ai-service/authentication', + items: [], + }, + { + title: 'Endpoints', + href: '/docs/api/ai-service/endpoints', + items: [], + }, + ], }, { title: 'Backend API', From c6223a673cf063efed6e4f28a821cb9944b92f8d Mon Sep 17 00:00:00 2001 From: Polo Date: Wed, 20 May 2026 20:46:05 +0200 Subject: [PATCH 2/4] Add defaultOpen support to sidebar navigation and update documentation structure with hidden scrollbars --- src/components/docs-sidebar.tsx | 4 +- src/components/mobile-nav.tsx | 2 +- src/config/docs.ts | 142 +++++++++++++++++++++++++++----- 3 files changed, 124 insertions(+), 24 deletions(-) diff --git a/src/components/docs-sidebar.tsx b/src/components/docs-sidebar.tsx index 195ed14..cae2d95 100644 --- a/src/components/docs-sidebar.tsx +++ b/src/components/docs-sidebar.tsx @@ -11,7 +11,7 @@ export function DocsSidebar() { const pathname = usePathname() return ( -