'use client' import { APIGroup, ArticleHeadings, SidebarContentArticleLink, SidebarContentCategoryLink, SidebarContentLink, SidebarContentList, SidebarContentSectionLink, } from '@/types/content-types' import * as Accordion from '@radix-ui/react-accordion' import Link from 'next/link' import { usePathname } from 'next/navigation' import { UIEvent, createContext, useContext, useEffect, useRef } from 'react' import { SectionLinks } from './Header' import { Chevron } from './Icons' import { Search } from './Search' import { SidebarCloseButton } from './SidebarCloseButton' import { ToggleMenuButton } from './ToggleMenuButton' type SidebarProps = SidebarContentList const linkContext = createContext<{ activeId: string | null articleId: string | null categoryId: string | null sectionId: string | null } | null>(null) // N.B. UGH, the sidebar's scroll position keeps getting lost when navigation occurs // and we keep track of the last known position here outside of the component because // it keeps re-rendering. let scrollPosition = 0 export function Sidebar({ links, sectionId, categoryId, articleId }: SidebarProps) { const activeId = articleId ?? categoryId ?? sectionId const sidebarRef = useRef(null) const pathName = usePathname() useEffect(() => { document.body.classList.remove('sidebar-open') const sidebarEl = sidebarRef.current if (!sidebarEl) return sidebarEl.scrollTo(0, scrollPosition) const activeLink = document.querySelector('.sidebar__nav [data-active=true]') as HTMLElement if ( activeLink && (activeLink.offsetTop < sidebarEl.scrollTop || activeLink.offsetTop > sidebarEl.scrollTop + sidebarEl.clientHeight) ) { // The above will *mostly* work to keep the position but we have some accordions that will collapse // (like in the Reference docs) and we need to scroll to the active item. activeLink.scrollIntoView({ block: 'center' }) } }, [pathName]) const handleScroll = (e: UIEvent) => { e.stopPropagation() scrollPosition = sidebarRef.current?.scrollTop ?? 0 } return ( <>
) } export function SidebarLinks({ links }: { links: SidebarContentLink[] }) { return ( ) } function SidebarLink(props: SidebarContentLink) { switch (props.type) { case 'section': { return } case 'article': { return } case 'category': { return } } } function SidebarSection({ title, children }: SidebarContentSectionLink) { if (children.length === 0) return null return (
  • {title && {title}}
      {children.map((link) => ( ))}
  • ) } function SidebarCategory({ title, children }: SidebarContentCategoryLink) { const linkCtx = useContext(linkContext) if (children.length === 0) return null const hasGroups = children.some((child) => !!(child as SidebarContentArticleLink).groupId) const activeArticle = children.find( (child) => (child as SidebarContentArticleLink).articleId === linkCtx?.articleId ) const activeGroup = activeArticle && (activeArticle as SidebarContentArticleLink).groupId const groups = Object.values(APIGroup) return (
  • {hasGroups ? ( <> {title} {groups.map((group) => { const articles = children.filter( (child) => (child as SidebarContentArticleLink).groupId === group ) if (articles.length === 0) return null const value = `${linkCtx?.categoryId}-${group}-${linkCtx?.articleId}` return ( {group}
      {articles.map((link) => ( ))}
    ) })}
    ) : ( <> {title}
      {children.map((link) => ( ))}
    )}
  • ) } function SidebarArticle({ title, url, articleId, headings, }: SidebarContentArticleLink & { headings?: ArticleHeadings }) { const activeLink = useContext(linkContext) const isActive = activeLink?.activeId === articleId return (
  • {title} {isActive && (
      {headings ?.filter((heading) => heading.level < 4) .map((heading) => (
    • {heading.isCode ? ( {heading.title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')} ) : ( heading.title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') )}
    • ))}
    )}
  • ) }