2024-01-15 12:33:15 +00:00
|
|
|
'use client'
|
|
|
|
|
|
|
|
import {
|
2024-02-01 14:16:17 +00:00
|
|
|
APIGroup,
|
2024-01-30 14:19:25 +00:00
|
|
|
ArticleHeadings,
|
2024-01-15 12:33:15 +00:00
|
|
|
SidebarContentArticleLink,
|
|
|
|
SidebarContentCategoryLink,
|
|
|
|
SidebarContentLink,
|
|
|
|
SidebarContentList,
|
|
|
|
SidebarContentSectionLink,
|
|
|
|
} from '@/types/content-types'
|
2024-01-30 14:19:25 +00:00
|
|
|
import * as Accordion from '@radix-ui/react-accordion'
|
2024-01-15 12:33:15 +00:00
|
|
|
import Link from 'next/link'
|
|
|
|
import { usePathname } from 'next/navigation'
|
|
|
|
import { createContext, useContext, useEffect } from 'react'
|
2024-01-30 14:19:25 +00:00
|
|
|
import { SectionLinks } from './Header'
|
|
|
|
import { Chevron } from './Icons'
|
2024-01-15 12:33:15 +00:00
|
|
|
import { Search } from './Search'
|
|
|
|
import { SidebarCloseButton } from './SidebarCloseButton'
|
|
|
|
import { ToggleMenuButton } from './ToggleMenuButton'
|
|
|
|
|
|
|
|
type SidebarProps = SidebarContentList
|
|
|
|
|
2024-01-30 14:19:25 +00:00
|
|
|
const linkContext = createContext<{
|
|
|
|
activeId: string | null
|
|
|
|
articleId: string | null
|
|
|
|
categoryId: string | null
|
|
|
|
sectionId: string | null
|
|
|
|
} | null>(null)
|
2024-01-15 12:33:15 +00:00
|
|
|
|
2024-01-30 14:19:25 +00:00
|
|
|
export function Sidebar({
|
|
|
|
headings,
|
|
|
|
links,
|
|
|
|
sectionId,
|
|
|
|
categoryId,
|
|
|
|
articleId,
|
|
|
|
searchQuery,
|
|
|
|
searchType,
|
|
|
|
}: SidebarProps) {
|
2024-01-15 12:33:15 +00:00
|
|
|
const activeId = articleId ?? categoryId ?? sectionId
|
|
|
|
|
|
|
|
const pathName = usePathname()
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
document.body.classList.remove('sidebar-open')
|
2024-01-30 14:19:25 +00:00
|
|
|
|
2024-02-01 14:16:17 +00:00
|
|
|
document.querySelector('.sidebar__nav [data-active=true]')?.scrollIntoView({ block: 'center' })
|
2024-01-30 14:19:25 +00:00
|
|
|
|
|
|
|
// XXX(mime): scrolling the sidebar into position also scrolls the page to the wrong
|
|
|
|
// spot. this compensates for that but, ugh.
|
|
|
|
document.documentElement.scrollTop = 0
|
2024-01-15 12:33:15 +00:00
|
|
|
}, [pathName])
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2024-01-30 14:19:25 +00:00
|
|
|
<linkContext.Provider value={{ activeId, articleId, categoryId, sectionId }}>
|
2024-01-15 12:33:15 +00:00
|
|
|
<div className="sidebar" onScroll={(e) => e.stopPropagation()}>
|
2024-01-30 14:19:25 +00:00
|
|
|
<Search prevQuery={searchQuery} prevType={searchType} />
|
|
|
|
<div className="sidebar__section__links">
|
|
|
|
<SectionLinks sectionId={sectionId} />
|
|
|
|
</div>
|
|
|
|
<SidebarLinks headings={headings} links={links} />
|
2024-01-15 12:33:15 +00:00
|
|
|
<SidebarCloseButton />
|
|
|
|
</div>
|
|
|
|
<ToggleMenuButton />
|
2024-01-30 14:19:25 +00:00
|
|
|
</linkContext.Provider>
|
2024-01-15 12:33:15 +00:00
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-30 14:19:25 +00:00
|
|
|
export function SidebarLinks({
|
|
|
|
headings,
|
|
|
|
links,
|
|
|
|
}: {
|
|
|
|
headings?: ArticleHeadings
|
|
|
|
links: SidebarContentLink[]
|
|
|
|
}) {
|
2024-01-15 12:33:15 +00:00
|
|
|
return (
|
|
|
|
<nav className="sidebar__nav">
|
|
|
|
<ul className="sidebar__list sidebar__sections__list">
|
|
|
|
{links.map((link) => (
|
2024-01-30 14:19:25 +00:00
|
|
|
<SidebarLink key={link.url} headings={headings} {...link} />
|
2024-01-15 12:33:15 +00:00
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</nav>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-30 14:19:25 +00:00
|
|
|
function SidebarLink({ headings, ...props }: SidebarContentLink & { headings?: ArticleHeadings }) {
|
2024-01-15 12:33:15 +00:00
|
|
|
switch (props.type) {
|
|
|
|
case 'section': {
|
2024-01-30 14:19:25 +00:00
|
|
|
return <SidebarSection headings={headings} {...props} />
|
2024-01-15 12:33:15 +00:00
|
|
|
}
|
|
|
|
case 'article': {
|
2024-01-30 14:19:25 +00:00
|
|
|
return <SidebarArticle headings={headings} {...props} />
|
2024-01-15 12:33:15 +00:00
|
|
|
}
|
|
|
|
case 'category': {
|
2024-02-01 14:16:17 +00:00
|
|
|
return <SidebarCategory headings={headings} {...props} />
|
2024-01-15 12:33:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-30 14:19:25 +00:00
|
|
|
function SidebarSection({
|
|
|
|
title,
|
|
|
|
children,
|
|
|
|
headings,
|
|
|
|
}: SidebarContentSectionLink & { headings?: ArticleHeadings }) {
|
2024-01-15 12:33:15 +00:00
|
|
|
if (children.length === 0) return null
|
|
|
|
|
|
|
|
return (
|
|
|
|
<li className="sidebar__section">
|
2024-01-30 14:19:25 +00:00
|
|
|
{title && <span className="sidebar__section__title">{title}</span>}
|
2024-01-15 12:33:15 +00:00
|
|
|
<ul className="sidebar__list">
|
|
|
|
{children.map((link) => (
|
2024-01-30 14:19:25 +00:00
|
|
|
<SidebarLink key={link.url} headings={headings} {...link} />
|
2024-01-15 12:33:15 +00:00
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</li>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-02-01 14:16:17 +00:00
|
|
|
function SidebarCategory({
|
|
|
|
title,
|
|
|
|
children,
|
|
|
|
headings,
|
|
|
|
}: SidebarContentCategoryLink & { headings?: ArticleHeadings }) {
|
2024-01-30 14:19:25 +00:00
|
|
|
const linkCtx = useContext(linkContext)
|
2024-01-15 12:33:15 +00:00
|
|
|
if (children.length === 0) return null
|
2024-01-30 14:19:25 +00:00
|
|
|
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
|
2024-02-01 14:16:17 +00:00
|
|
|
const groups = Object.values(APIGroup)
|
2024-01-15 12:33:15 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<li className="sidebar__category">
|
2024-01-30 14:19:25 +00:00
|
|
|
{hasGroups ? (
|
|
|
|
<>
|
|
|
|
<span className="sidebar__link">{title}</span>
|
|
|
|
<Accordion.Root
|
|
|
|
type="multiple"
|
|
|
|
defaultValue={[`${linkCtx?.categoryId}-${activeGroup}-${linkCtx?.articleId}`]}
|
|
|
|
>
|
|
|
|
{groups.map((group) => {
|
|
|
|
const articles = children.filter(
|
|
|
|
(child) => (child as SidebarContentArticleLink).groupId === group
|
|
|
|
)
|
|
|
|
if (articles.length === 0) return null
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Accordion.Item
|
|
|
|
key={group}
|
|
|
|
value={`${linkCtx?.categoryId}-${group}-${linkCtx?.articleId}`}
|
|
|
|
>
|
|
|
|
<Accordion.Trigger
|
|
|
|
className="sidebar__section__group__title"
|
|
|
|
style={{ marginLeft: '8px', paddingRight: '8px' }}
|
|
|
|
>
|
|
|
|
{group}
|
|
|
|
<Chevron />
|
|
|
|
</Accordion.Trigger>
|
|
|
|
<Accordion.Content>
|
2024-02-01 14:16:17 +00:00
|
|
|
<ul className="sidebar__list sidebar__group" style={{ paddingLeft: '8px' }}>
|
2024-01-30 14:19:25 +00:00
|
|
|
{articles.map((link) => (
|
2024-02-01 14:16:17 +00:00
|
|
|
<SidebarLink key={link.url} headings={headings} {...link} />
|
2024-01-30 14:19:25 +00:00
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</Accordion.Content>
|
|
|
|
</Accordion.Item>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</Accordion.Root>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<Link href={children[0].url} title={title} className="sidebar__link">
|
|
|
|
{title}
|
|
|
|
</Link>
|
|
|
|
<ul className="sidebar__list">
|
|
|
|
{children.map((link) => (
|
|
|
|
<SidebarLink key={link.url} {...link} />
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</>
|
|
|
|
)}
|
2024-01-15 12:33:15 +00:00
|
|
|
<hr />
|
|
|
|
</li>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-01-30 14:19:25 +00:00
|
|
|
function SidebarArticle({
|
|
|
|
title,
|
|
|
|
url,
|
|
|
|
articleId,
|
|
|
|
headings,
|
|
|
|
}: SidebarContentArticleLink & { headings?: ArticleHeadings }) {
|
2024-02-01 14:16:17 +00:00
|
|
|
const activeLink = useContext(linkContext)
|
|
|
|
const isActive = activeLink?.activeId === articleId
|
2024-01-15 12:33:15 +00:00
|
|
|
|
|
|
|
return (
|
|
|
|
<li className="sidebar__article">
|
2024-01-30 14:19:25 +00:00
|
|
|
<Link href={url} className="sidebar__link" data-active={isActive}>
|
|
|
|
{title}
|
2024-01-15 12:33:15 +00:00
|
|
|
</Link>
|
2024-01-30 14:19:25 +00:00
|
|
|
|
|
|
|
{isActive && (
|
|
|
|
<ul className="sidebar__list">
|
|
|
|
{headings
|
2024-02-01 14:16:17 +00:00
|
|
|
?.filter((heading) => heading.level < 4)
|
2024-01-30 14:19:25 +00:00
|
|
|
.map((heading) => (
|
2024-02-01 14:16:17 +00:00
|
|
|
<li
|
|
|
|
key={heading.slug}
|
|
|
|
data-heading-level={heading.title === 'Constructor' ? 2 : heading.level}
|
|
|
|
>
|
2024-01-30 14:19:25 +00:00
|
|
|
<Link href={`#${heading.slug}`} className="sidebar__link">
|
|
|
|
{heading.isCode ? (
|
|
|
|
<code>{heading.title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')}</code>
|
|
|
|
) : (
|
|
|
|
heading.title.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
|
|
|
)}
|
|
|
|
</Link>
|
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
)}
|
2024-01-15 12:33:15 +00:00
|
|
|
</li>
|
|
|
|
)
|
|
|
|
}
|