Skip to content

Commit

Permalink
feat: add table of contents for docs pages (#308)
Browse files Browse the repository at this point in the history
Co-authored-by: Adil Kairolla <[email protected]>
Co-authored-by: SeanCassiere <[email protected]>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent f0505cd commit 6bfd2c8
Show file tree
Hide file tree
Showing 17 changed files with 195 additions and 66 deletions.
101 changes: 77 additions & 24 deletions app/components/Doc.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,93 @@
import * as React from 'react'
import { FaEdit } from 'react-icons/fa'
import { marked } from 'marked'
import markedAlert from 'marked-alert'
import { gfmHeadingId, getHeadingList } from 'marked-gfm-heading-id'
import { DocTitle } from '~/components/DocTitle'
import { Markdown } from '~/components/Markdown'
import { Toc } from './Toc'
import { twMerge } from 'tailwind-merge'

type DocProps = {
title: string
content: string
repo: string
branch: string
filePath: string
shouldRenderToc?: boolean
colorFrom?: string
colorTo?: string
}

export function Doc({
title,
content,
repo,
branch,
filePath,
}: {
title: string
content: string
repo: string
branch: string
filePath: string
}) {
shouldRenderToc = false,
colorFrom,
colorTo,
}: DocProps) {
const { markup, headings } = React.useMemo(() => {
const markup = marked.use(
{ gfm: true },
gfmHeadingId(),
markedAlert()
)(content) as string

const headings = getHeadingList()

return { markup, headings }
}, [content])

const isTocVisible = shouldRenderToc && headings && headings.length > 1

return (
<div className="p-4 lg:p-6 overflow-auto w-full bg-white/70 dark:bg-black/30 m-2 md:m-4 xl:m-8 rounded-xl">
{title ? <DocTitle>{title}</DocTitle> : null}
<div className="h-4" />
<div className="h-px bg-gray-500 opacity-20" />
<div className="h-4" />
<div className="prose prose-gray prose-sm prose-p:leading-7 dark:prose-invert max-w-none">
<Markdown code={content} />
</div>
<div className="h-12" />
<div className="w-full h-px bg-gray-500 opacity-30" />
<div className="py-4 opacity-70">
<a
href={`https://github.com/${repo}/tree/${branch}/${filePath}`}
className="flex items-center gap-2"
<div className="w-full p-2 md:p-4 xl:p-8">
<div
className={twMerge(
'flex bg-white/70 dark:bg-black/30 mx-auto rounded-xl max-w-[936px]',
isTocVisible && 'max-w-full'
)}
>
<div
className={twMerge(
'flex overflow-auto flex-col w-full p-4 lg:p-6',
isTocVisible && 'border-r border-gray-500/20 !pr-0'
)}
>
<FaEdit /> Edit on GitHub
</a>
{title ? <DocTitle>{title}</DocTitle> : null}
<div className="h-4" />
<div className="h-px bg-gray-500 opacity-20" />
<div className="h-4" />
<div
className={twMerge(
'prose prose-gray prose-sm prose-p:leading-7 dark:prose-invert max-w-none',
isTocVisible && 'pr-4 lg:pr-6'
)}
>
<Markdown htmlMarkup={markup} />
</div>
<div className="h-12" />
<div className="w-full h-px bg-gray-500 opacity-30" />
<div className="py-4 opacity-70">
<a
href={`https://github.com/${repo}/tree/${branch}/${filePath}`}
className="flex items-center gap-2"
>
<FaEdit /> Edit on GitHub
</a>
</div>
<div className="h-24" />
</div>

{isTocVisible && (
<div className="max-w-52 w-full hidden 2xl:block transition-all">
<Toc headings={headings} colorFrom={colorFrom} colorTo={colorTo} />
</div>
)}
</div>
<div className="h-24" />
</div>
)
}
4 changes: 2 additions & 2 deletions app/components/DocsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ export function DocsLayout({
<div
className={twMerge(
`max-w-full min-w-0 flex relative justify-center w-full min-h-[88dvh] lg:min-h-0`,
!isExample && 'mx-auto w-[1000px]'
!isExample && 'mx-auto w-[1208px]'
)}
>
{children}
Expand Down Expand Up @@ -601,7 +601,7 @@ export function DocsLayout({
</div>
</div>
</div>
<div className="-ml-2 pl-2 w-64 hidden md:block sticky top-0 max-h-screen overflow-y-auto">
<div className="-ml-2 pl-2 w-64 shrink-0 hidden md:block sticky top-0 max-h-screen overflow-y-auto">
<div className="ml-auto flex flex-col space-y-4">
<div className="bg-white dark:bg-gray-900/30 border-gray-500/20 shadow-xl divide-y divide-gray-500/20 flex flex-col border border-r-0 border-t-0 rounded-bl-lg">
<div className="uppercase font-black text-center p-3 opacity-50">
Expand Down
58 changes: 33 additions & 25 deletions app/components/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,33 +206,41 @@ const getHighlighter = cache(async (language: string, themes: string[]) => {
return highlighter
})

export function Markdown({ code }: { code: string }) {
const jsx = React.useMemo(() => {
const markup = marked.use(
{ gfm: true },
gfmHeadingId(),
markedAlert()
)(code) as string

const options: HTMLReactParserOptions = {
replace: (domNode) => {
if (domNode instanceof Element && domNode.attribs) {
const replacer = markdownComponents[domNode.name]
if (replacer) {
return React.createElement(
replacer,
attributesToProps(domNode.attribs),
domToReact(domNode.children, options)
)
}
}
const options: HTMLReactParserOptions = {
replace: (domNode) => {
if (domNode instanceof Element && domNode.attribs) {
const replacer = markdownComponents[domNode.name]
if (replacer) {
return React.createElement(
replacer,
attributesToProps(domNode.attribs),
domToReact(domNode.children, options)
)
}
}

return
},
}

type MarkdownProps = { rawContent?: string; htmlMarkup?: string }

return
},
export function Markdown({ rawContent, htmlMarkup }: MarkdownProps) {
return React.useMemo(() => {
if (rawContent) {
const markup = marked.use(
{ gfm: true },
gfmHeadingId(),
markedAlert()
)(rawContent) as string

return parse(markup, options)
}

return parse(markup, options)
}, [code])
if (htmlMarkup) {
return parse(htmlMarkup, options)
}

return jsx
return null
}, [rawContent, htmlMarkup])
}
59 changes: 59 additions & 0 deletions app/components/Toc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react'
import { twMerge } from 'tailwind-merge'
import { HeadingData } from 'marked-gfm-heading-id'
import { useLocation } from '@tanstack/react-router'

const headingLevels: Record<number, string> = {
1: 'pl-2',
2: 'pl-2',
3: 'pl-6',
4: 'pl-10',
5: 'pl-14',
6: 'pl-16',
}

type TocProps = {
headings: HeadingData[]
colorFrom?: string
colorTo?: string
}

export function Toc({ headings, colorFrom, colorTo }: TocProps) {
const location = useLocation()

const [hash, setHash] = React.useState('')

React.useEffect(() => {
setHash(location.hash)
}, [location])

return (
<nav className="flex flex-col p-2 gap-1 sticky top-2 max-h-screen">
<h3 className="text-[.9em] font-medium px-2">On this page</h3>

<ul
className={twMerge('flex flex-col overflow-y-auto gap-0.5 text-[.8em]')}
>
{headings?.map((heading) => (
<li
key={heading.id}
className={twMerge(
'cursor-pointer py-[.1rem] w-full rounded-lg hover:bg-gray-500 hover:bg-opacity-10',
headingLevels[heading.level]
)}
>
<a
title={heading.id}
href={`#${heading.id}`}
aria-current={hash === heading.id && 'location'}
className={`truncate block aria-current:bg-gradient-to-r ${colorFrom} ${colorTo} aria-current:bg-clip-text aria-current:text-transparent`}
dangerouslySetInnerHTML={{
__html: heading.text,
}}
/>
</li>
))}
</ul>
</nav>
)
}
3 changes: 3 additions & 0 deletions app/routes/$libraryId/$version.docs.$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ function Docs() {
repo={library.repo}
branch={branch}
filePath={filePath}
colorFrom={library.colorFrom}
colorTo={library.colorTo}
shouldRenderToc
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ function Docs() {
repo={library.repo}
branch={branch}
filePath={filePath}
colorFrom={library.colorFrom}
colorTo={library.colorTo}
shouldRenderToc
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import { seo } from '~/utils/seo'
import { capitalize, slugToTitle } from '~/utils/utils'

export const Route = createFileRoute(
'/$libraryId/$version/docs/framework/$framework/examples/$',
'/$libraryId/$version/docs/framework/$framework/examples/$'
)({
head: ({ params }) => {
const library = getLibrary(params.libraryId)

return {
meta: seo({
title: `${capitalize(params.framework)} ${library.name} ${slugToTitle(
params._splat || '',
params._splat || ''
)} Example | ${library.name} Docs`,
description: `An example showing how to implement ${slugToTitle(
params._splat || '',
params._splat || ''
)} in ${capitalize(params.framework)} using ${library.name}.`,
}),
}
Expand Down
4 changes: 2 additions & 2 deletions app/routes/$libraryId/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export const Route = createFileRoute('/$libraryId')({
.map(
(framework) =>
`${framework.charAt(0).toUpperCase()}${framework.slice(
1,
)} ${library.name.replace('TanStack ', '')}`,
1
)} ${library.name.replace('TanStack ', '')}`
)
.join(', ')}`
: '',
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_libraries/blog.$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function BlogPost() {

const blogContent = `_by ${formatAuthors(authors)} on ${format(
new Date(published || 0),
'MMM dd, yyyy',
'MMM dd, yyyy'
)}._
${content}`

Expand Down
2 changes: 1 addition & 1 deletion app/routes/_libraries/blog.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function BlogIndex() {
<div
className={`text-sm mt-4 text-black dark:text-white leading-7`}
>
<Markdown code={excerpt || ''} />
<Markdown rawContent={excerpt || ''} />
</div>
</div>
<div>
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_libraries/dedicated-support.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const Route = createFileRoute('/_libraries/dedicated-support')({
let indices = shuffle(
Array.from({ length: teamMembers.length - 1 })
.fill(0)
.map((_, i) => i + 1),
.map((_, i) => i + 1)
)

return indices
Expand Down
6 changes: 3 additions & 3 deletions app/routes/_libraries/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ function Index() {
params
className={twMerge(
`border-4 border-transparent rounded-lg shadow-lg p-4 md:p-8 text-white transition-all bg-white dark:bg-gray-900 dark:border dark:border-gray-800`,
library.cardStyles,
library.cardStyles
)}
style={{
zIndex: i,
Expand All @@ -204,7 +204,7 @@ function Index() {
return (
<div
className={twMerge(
`text-2xl font-extrabold`,
`text-2xl font-extrabold`
// isPending && `[view-transition-name:library-name]`
)}
style={{
Expand All @@ -220,7 +220,7 @@ function Index() {
<div
className={twMerge(
`uppercase text-white bg-yellow-500 rounded-full px-2 py-1 text-xs font-black animate-pulse`,
library.bgStyle,
library.bgStyle
)}
>
{library.badge}
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_libraries/query.$version.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ export default function VersionIndex() {
'ReactBricks',
'Nozzle.io',
'Uber',
],
]
)
.map((d, i) => (
<span key={i} className="opacity-70 even:opacity-40">
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_libraries/start.$version.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ export default function VersionIndex() {
</a>
<a
href={`https://twitter.com/intent/post?text=${encodeURIComponent(
`TanStack Start is in BETA! It's a new full-stack React framework from @Tan_Stack and you can check it out at https://tanstack.com/start/`,
`TanStack Start is in BETA! It's a new full-stack React framework from @Tan_Stack and you can check it out at https://tanstack.com/start/`
)}`}
target="_blank"
className={`flex items-center gap-2 py-2 px-4 bg-cyan-500 rounded text-white uppercase font-extrabold`}
Expand Down
2 changes: 1 addition & 1 deletion app/routes/_libraries/support.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function LoginComp() {
>
{d}
</div>
),
)
)}
</div>
</Link>
Expand Down
Loading

0 comments on commit 6bfd2c8

Please sign in to comment.