-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add toc and sidebar in documentation (#154)
feat: add TOC and SideBar
- Loading branch information
Showing
20 changed files
with
524 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"tailwindCSS.experimental.classRegex": ["([\"'`][^\"'`]*.*?[\"'`])", "[\"'`]([^\"'`]*).*?[\"'`]"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import metadata from "@/lib/metadata" | ||
import { cn } from "@/lib/utils" | ||
import type { ComponentPropsWithoutRef, FC } from "react" | ||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./ui/accordion" | ||
|
||
export type FileNode = { | ||
type: "file" | ||
slug: string | ||
title: string | ||
} | ||
|
||
export type DirectoryNode = { | ||
type: "directory" | ||
slug: string | ||
title: string | ||
children: Node[] | ||
} | ||
|
||
export type Node = FileNode | DirectoryNode | ||
|
||
export type NavSideBarProps = ComponentPropsWithoutRef<"div"> & { | ||
entries: Node[] | ||
activeSlug: string | ||
} | ||
|
||
export const NavSideBar: FC<NavSideBarProps> = ({ className, entries, activeSlug, ...props }) => { | ||
return ( | ||
<div> | ||
<div | ||
className={cn("sticky top-[calc(3.5rem+1px)] hidden min-w-56 flex-col gap-3 p-4 md:flex", className)} | ||
{...props} | ||
> | ||
<span className="inline-block w-full border-b pb-2 font-bold">Documentation</span> | ||
<nav> | ||
<Nodes nodes={entries} activeSlug={activeSlug} nested={false} /> | ||
</nav> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export type NodesProps = ComponentPropsWithoutRef<"div"> & { | ||
nodes: Node[] | ||
activeSlug: string | ||
nested: boolean | ||
} | ||
|
||
export const Nodes: FC<NodesProps> = ({ nodes, activeSlug, nested, ...props }) => { | ||
return ( | ||
<div {...props}> | ||
{nodes.map((node) => | ||
node.type === "directory" ? ( | ||
<Directory key={node.slug} node={node} activeSlug={activeSlug} nested={nested} /> | ||
) : ( | ||
<File key={node.slug} node={node} activeSlug={activeSlug} nested={nested} /> | ||
), | ||
)} | ||
</div> | ||
) | ||
} | ||
|
||
type FileProps = ComponentPropsWithoutRef<"a"> & { | ||
node: Node & { type: "file" } | ||
activeSlug: string | ||
nested: boolean | ||
} | ||
|
||
const File: FC<FileProps> = ({ className, node, activeSlug, nested, ...props }) => { | ||
return ( | ||
<a | ||
href={`${metadata.base}${node.slug}`} | ||
data-active={node.slug === activeSlug} | ||
className={cn( | ||
"inline-block w-full py-1.5 text-muted-foreground transition data-[active=true]:font-bold data-[active=true]:text-foreground hover:text-foreground", | ||
nested && "ml-1 border-l pl-4", | ||
className, | ||
)} | ||
{...props} | ||
> | ||
{node.title} | ||
</a> | ||
) | ||
} | ||
|
||
type DirectoryProps = { | ||
node: Node & { type: "directory" } | ||
activeSlug: string | ||
nested: boolean | ||
} | ||
|
||
const Directory: FC<DirectoryProps> = ({ node, activeSlug, nested }) => { | ||
console.log(node) | ||
return ( | ||
<Accordion | ||
type="single" | ||
collapsible | ||
defaultValue={has(node.children, activeSlug) ? node.title : undefined} | ||
className={cn(nested && "ml-1 border-l pl-4")} | ||
> | ||
<AccordionItem className="border-b-0" value={node.title}> | ||
<AccordionTrigger className="py-1.5 text-muted-foreground hover:text-foreground hover:no-underline"> | ||
{node.title} | ||
</AccordionTrigger> | ||
<AccordionContent className="pb-1.5 text-base"> | ||
<Nodes nodes={node.children} activeSlug={activeSlug} nested /> | ||
</AccordionContent> | ||
</AccordionItem> | ||
</Accordion> | ||
) | ||
} | ||
|
||
const has = (nodes: Node[], activeSlug: string) => { | ||
for (const node of nodes) { | ||
if (node.type === "file" && node.slug === activeSlug) { | ||
return true | ||
} | ||
if (node.type === "directory" && has(node.children, activeSlug)) { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { cn } from "@/lib/utils" | ||
import type { MarkdownHeading } from "astro" | ||
import { type ComponentPropsWithoutRef, type FC, useEffect } from "react" | ||
import { tv } from "tailwind-variants" | ||
|
||
export type TOCProps = ComponentPropsWithoutRef<"div"> & { | ||
headings: MarkdownHeading[] | ||
} | ||
|
||
export const TOC: FC<TOCProps> = ({ headings, className, ...props }) => { | ||
useEffect(() => { | ||
let activeTocItem: Element | null = null | ||
const observer = new IntersectionObserver((entries) => { | ||
const entry = entries.reduce((prev, current) => | ||
current.intersectionRatio > prev.intersectionRatio ? current : prev, | ||
) | ||
if (entry.intersectionRatio > 0) { | ||
const toActivateTocItem = document.querySelector(`a[href="#${entry.target.id}"]`) | ||
activeTocItem?.setAttribute("data-active", "false") | ||
toActivateTocItem?.setAttribute("data-active", "true") | ||
activeTocItem = toActivateTocItem | ||
} | ||
}) | ||
for (const headingElm of document.querySelectorAll("h2[id], h3[id], h4[id], h5[id]")) { | ||
observer.observe(headingElm) | ||
} | ||
}, []) | ||
|
||
return ( | ||
<div> | ||
<div | ||
className={cn("sticky top-[calc(3.5rem+1px)] mx-8 hidden min-w-56 flex-col gap-4 xl:flex", className)} | ||
{...props} | ||
> | ||
<span className="mt-6 font-bold">On this page</span> | ||
<ul className="flex flex-col gap-1"> | ||
{headings.map((heading) => ( | ||
<TOCItem key={heading.slug} heading={heading} /> | ||
))} | ||
</ul> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
type TOCItemProps = ComponentPropsWithoutRef<"a"> & { | ||
heading: MarkdownHeading | ||
} | ||
|
||
const TOCItem: FC<TOCItemProps> = ({ heading, className, ...props }) => { | ||
return ( | ||
<li> | ||
<a | ||
data-active="false" | ||
className={tocItem({ | ||
depth: Math.max(1, Math.min(heading.depth, 5)) as 1 | 2 | 3 | 4 | 5, | ||
className, | ||
})} | ||
href={`#${heading.slug}`} | ||
{...props} | ||
> | ||
{heading.text} | ||
</a> | ||
</li> | ||
) | ||
} | ||
|
||
const tocItem = tv({ | ||
base: "text-muted-foreground data-[active=true]:text-foreground", | ||
variants: { | ||
depth: { | ||
1: "ml-0", | ||
2: "ml-0", | ||
3: "ml-4", | ||
4: "ml-8", | ||
5: "ml-12", | ||
}, | ||
}, | ||
}) |
Oops, something went wrong.