'use client' import { type IDocsConfig, useDocsConfig } from '@duck-docs/context' import type { INavItem, INavItemWithChildren } from '@duck-docs/types/nav' import { cn } from '@gentleduck/libs/cn' import { buttonVariants } from '@gentleduck/registry-ui/button' import { ArrowLeft, ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react' import Link from 'next/link' import * as React from 'react' interface IDocsPagerProps { doc: { slug?: string title: string } /** Override the global docsConfig with a package-specific sidebar (used by per-package slug pages). */ config?: IDocsConfig } export function DocsPagerBottom({ doc, config }: IDocsPagerProps) { const fallbackConfig = useDocsConfig() const resolvedConfig = config ?? fallbackConfig const pager = React.useMemo(() => getPagerForDoc(doc, resolvedConfig), [doc, resolvedConfig]) if (!pager?.prev?.href && !pager?.next?.href) { return null } return (
{pager?.prev?.href && (
) } export function DocsPagerTop({ doc, config }: IDocsPagerProps) { const fallbackConfig = useDocsConfig() const resolvedConfig = config ?? fallbackConfig const pager = React.useMemo(() => getPagerForDoc(doc, resolvedConfig), [doc, resolvedConfig]) if (!pager?.prev?.href && !pager?.next?.href) { return null } return (
{pager?.prev?.href && ( svg]:!size-4 size-8 items-center md:size-7', size: 'sm', variant: 'secondary', }), )} href={pager.prev.href} scroll>
) } export function getPagerForDoc(doc: IDocsPagerProps['doc'], docsConfig: IDocsConfig) { const allNav = [...(docsConfig.sidebarNav ?? []), ...(docsConfig.chartsNav ?? [])] const flattenedLinks = [null, ...flatten(allNav), null] // Normalize slug: ensure leading /, strip leading /docs/ (velite prefix), strip trailing /index const normalizedSlug = `/${doc.slug ?? ''}` .replace(/\/+/g, '/') .replace(/^\/docs\//, '/') .replace(/\/index$/, '') const activeIndex = flattenedLinks.findIndex((link) => { if (!link?.href) return false return link.href.replace(/\/index$/, '') === normalizedSlug }) const prev = activeIndex > 0 ? flattenedLinks[activeIndex - 1] : null const next = activeIndex < flattenedLinks.length - 1 ? flattenedLinks[activeIndex + 1] : null return { next, prev, } } function flatten(links: INavItemWithChildren[]): INavItem[] { return links .reduce((flat, link) => { if (link.items?.length) { // Include the parent itself if it has an href (it's a real page), then flatten children return flat.concat(link.href ? [link] : [], flatten(link.items)) } return flat.concat(link) }, []) .filter((link) => !link?.disabled) }