import React, {useMemo} from 'react'
import NextLink from 'next/link'
import {NavList} from '@primer/react'
import {Heading} from '@primer/react-brand'
import type {Folder, MdxFile, PageMapItem} from 'nextra'
import styles from './Sidebar.module.css'
import {LinkExternalIcon} from '@primer/octicons-react'
import type {DocsItem, ExtendedPageItem} from '../../../index'
import {hasChildren} from '../../../helpers/hasChildren'
import {usePathname} from 'next/navigation'
import {useConfig} from '../../context/useConfig'
const hasShowTabs = (child: ExtendedPageItem): boolean => {
return child.name === 'index' && (child as MdxFile).frontMatter?.['show-tabs'] === true
}
type SidebarProps = {
pageMap: PageMapItem[]
}
export function Sidebar({pageMap}: SidebarProps) {
const pathname = usePathname()
const {headerLinks, sidebarLinks} = useConfig()
const activeHeaderLink = headerLinks.find(link => link.isActive)
/**
* Sorts the incoming data so that folders with a menu-position frontmatter value
* are sorted to the top of the list. If a folder does not have a menu-position
* value, it is sorted to the bottom of the list.
*/
const reorderedPageMap = useMemo(
() =>
[...pageMap].sort((a, b) => {
if (hasChildren(a)) {
const aIndex = a.children.find(child => (child as MdxFile).name === 'index')
const aPosition = (aIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity
if (hasChildren(b)) {
const bIndex = b.children.find(child => (child as MdxFile).name === 'index')
const bPosition = (bIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity
return aPosition - bPosition
}
}
return 0
}),
[pageMap],
)
return (
{activeHeaderLink && (
{activeHeaderLink.title}
navigation
)}
{reorderedPageMap.map(item => {
if (Object.hasOwn(item, 'data')) return null
if (!hasChildren(item)) return null
const indexPage = (item as Folder).children.find(child => (child as MdxFile).name === 'index') as MdxFile
const subNavName = indexPage.frontMatter?.title ?? ''
const shouldShowTabs = indexPage.frontMatter?.['show-tabs'] ?? false
if (shouldShowTabs) {
return (
{(indexPage as MdxFile).frontMatter?.title || item.name}
)
}
const normalizePath = (path: string) => {
// Remove trailing slash unless it's the root path
return path === '/' ? path : path.replace(/\/+$/, '')
}
return (
{subNavName}
{item.children
.sort((a, b) => {
// make sure index page is first
if ((a as MdxFile).name === 'index') return -1
if ((b as MdxFile).name === 'index') return 1
// Check for menu-position property in frontmatter
const aPos = (a as MdxFile).frontMatter?.['menu-position']
const bPos = (b as MdxFile).frontMatter?.['menu-position']
// If both have menu-position, sort by menu-position
if (typeof aPos === 'number' && typeof bPos === 'number') {
return aPos - bPos
}
// If only one has menu-position, it comes first
if (typeof aPos === 'number') return -1
if (typeof bPos === 'number') return 1
// Neither has menu-position, sort alphabetically by title or name
const aTitle = (a as MdxFile).frontMatter?.title || (a as MdxFile).name
const bTitle = (b as MdxFile).frontMatter?.title || (b as MdxFile).name
return aTitle.localeCompare(bTitle)
})
// only show index page if it has show-tabs
.filter(child => (child as MdxFile).name !== 'index' || hasShowTabs(child as ExtendedPageItem))
.map(child => {
if (!hasChildren(child)) {
const {name, route} = child as MdxFile
const cleanPathname = normalizePath(pathname)
return (
{(child as MdxFile).frontMatter?.title || name}
)
}
if (hasChildren(child)) {
const landingPageItem = (child as Folder).children.find(
innerChild => (innerChild as DocsItem).name === 'index',
) as MdxFile
// Then inside your component where you need to compare paths:
const cleanPathname = normalizePath(pathname)
const cleanRoute = normalizePath(landingPageItem.route)
// For checking if current path is this route or a direct child:
const isCurrentOrChild = cleanPathname === cleanRoute || cleanPathname.startsWith(`${cleanRoute}/`)
return (
{landingPageItem.frontMatter?.title || item.name}
)
}
})}
)
})}
{sidebarLinks.length > 0 && (
{sidebarLinks.map(link => {
return (
{link.title}
{link.isExternal && (
)}
)
})}
)}
)
}