'use client'
import { type IDocsConfig, useDocsConfig } from '@duck-docs/context'
import { scrollIntoViewWithin } from '@duck-docs/lib/scroll-into-view-within'
import type { ISidebarNavItem } from '@duck-docs/types/nav'
import { cn } from '@gentleduck/libs/cn'
import { ChevronRight } from 'lucide-react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import * as React from 'react'
export interface IDocsSidebarNavProps {
config?: IDocsConfig
}
function getSidebarItemKey(item: ISidebarNavItem) {
return item.href ?? `${item.title}-${item.label ?? 'item'}`
}
function isPathActive(pathname: string, href: string) {
return pathname === href || (href !== '/docs' && pathname.startsWith(`${href}/`))
}
function isPathCurrent(pathname: string, href: string) {
return pathname === href
}
function hasActivePath(item: ISidebarNavItem, pathname: string | null): boolean {
if (!pathname) return false
if (item.href && isPathActive(pathname, item.href)) return true
return Boolean(item.items?.some((child) => hasActivePath(child, pathname)))
}
export function DocsSidebarNav({ config }: IDocsSidebarNavProps) {
const pathname = usePathname()
const fallbackConfig = useDocsConfig()
const resolvedConfig = config ?? fallbackConfig
const items = pathname?.startsWith('/charts') ? resolvedConfig.chartsNav : resolvedConfig.sidebarNav
return items?.length ? (
{items.map((item) => (
))}
) : null
}
function SidebarItem({
item,
pathname,
depth = 0,
}: {
item: ISidebarNavItem
pathname: string | null
depth?: number
}) {
const hasChildren = Boolean(item.items?.length)
const isCollapsible = hasChildren && Boolean(item.collapsible)
const isCurrent = Boolean(pathname && item.href && isPathCurrent(pathname, item.href))
const isActiveBranch = hasActivePath(item, pathname)
const isActive = isCurrent || (hasChildren && isActiveBranch)
const [isOpen, setIsOpen] = React.useState(() => Boolean(item.defaultOpen))
const linkRef = React.useRef(null)
React.useEffect(() => {
if (!isCurrent || !linkRef.current) return
scrollIntoViewWithin(linkRef.current, linkRef.current.closest('[class*="overflow"]'))
}, [isCurrent])
const toggle = isCollapsible ? (
{
e.preventDefault()
e.stopPropagation()
setIsOpen((o) => !o)
}}
type="button">
) : null
const childList = hasChildren ? (
{depth === 0 ? (
{item.items?.map((child) => (
))}
) : (
{item.items?.map((child) => (
))}
)}
) : null
if (depth === 0) {
return (
{item.href ? (
{item.title ||
Section overview }
) : (
{item.title}
)}
{item.label && (
{item.label}
)}
{toggle}
{childList}
)
}
// Pull the active border out by the parent border width so it sits ON
// TOP of the parent border instead of next to it. Depth-1 parent uses
// border-l-2; deeper parents use border-l (1px).
const activeOverlay = depth === 1 ? '-ml-0.5 border-primary border-l-2' : '-ml-px border-primary border-l'
return (
{item.href && !item.disabled ? (
1 && 'px-3',
isActive ? 'text-foreground' : 'text-muted-foreground',
)}
href={item.href}
rel={item.external ? 'noreferrer' : ''}
scroll
target={item.external ? '_blank' : ''}>
{item.title}
{item.label && (
{item.label}
)}
{item.external &&
(opens in a new tab) }
) : (
1 && 'px-3',
)}>
{item.title}
{item.label && (
{item.label}
)}
)}
{toggle &&
{toggle}
}
{childList}
)
}
function AnimatedHeightCollapse({ open, children }: { open: boolean; children: React.ReactNode }) {
return (
)
}
export { SidebarItem as DocsSidebarNavItem }
export function DocsSidebarNavItems({
items,
pathname,
depth = 0,
}: {
items: ISidebarNavItem[]
pathname: string | null
className?: string
depth?: number
accordionDefault?: boolean
}) {
return items?.length ? (
0 && 'ml-3 border-l')}>
{items.map((item) => (
))}
) : null
}