Skip to content

Commit 703d8a0

Browse files
authored
feat: add mobile version of the "table of contents" (#366)
* refactor: move `space-y-` margin utils to the single caller's source * feat: add mobile table of contents * chore: more accessible and lessen the indentation
1 parent bdb8f6e commit 703d8a0

File tree

4 files changed

+100
-41
lines changed

4 files changed

+100
-41
lines changed

app/components/Doc.tsx

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { DocTitle } from '~/components/DocTitle'
77
import { Markdown } from '~/components/Markdown'
88
import { Toc } from './Toc'
99
import { twMerge } from 'tailwind-merge'
10+
import { TocMobile } from './TocMobile'
1011

1112
type DocProps = {
1213
title: string
@@ -89,55 +90,59 @@ export function Doc({
8990
}, [])
9091

9192
return (
92-
<div
93-
className={twMerge(
94-
'w-full flex bg-white/70 dark:bg-black/40 mx-auto rounded-xl max-w-[936px]',
95-
isTocVisible && 'max-w-full'
96-
)}
97-
>
93+
<React.Fragment>
94+
{shouldRenderToc ? <TocMobile headings={headings} /> : null}
9895
<div
9996
className={twMerge(
100-
'flex overflow-auto flex-col w-full p-4 lg:p-6',
101-
isTocVisible && '!pr-0'
97+
'w-full flex bg-white/70 dark:bg-black/40 mx-auto rounded-xl max-w-[936px]',
98+
isTocVisible && 'max-w-full',
99+
shouldRenderToc && 'lg:pt-0'
102100
)}
103101
>
104-
{title ? <DocTitle>{title}</DocTitle> : null}
105-
<div className="h-4" />
106-
<div className="h-px bg-gray-500 opacity-20" />
107-
<div className="h-4" />
108102
<div
109-
ref={markdownContainerRef}
110103
className={twMerge(
111-
'prose prose-gray prose-sm prose-p:leading-7 dark:prose-invert max-w-none',
112-
isTocVisible && 'pr-4 lg:pr-6',
113-
'styled-markdown-content'
104+
'flex overflow-auto flex-col w-full p-4 lg:p-6',
105+
isTocVisible && '!pr-0'
114106
)}
115107
>
116-
<Markdown htmlMarkup={markup} />
117-
</div>
118-
<div className="h-12" />
119-
<div className="w-full h-px bg-gray-500 opacity-30" />
120-
<div className="py-4 opacity-70">
121-
<a
122-
href={`https://github.com/${repo}/tree/${branch}/${filePath}`}
123-
className="flex items-center gap-2"
108+
{title ? <DocTitle>{title}</DocTitle> : null}
109+
<div className="h-4" />
110+
<div className="h-px bg-gray-500 opacity-20" />
111+
<div className="h-4" />
112+
<div
113+
ref={markdownContainerRef}
114+
className={twMerge(
115+
'prose prose-gray prose-sm prose-p:leading-7 dark:prose-invert max-w-none',
116+
isTocVisible && 'pr-4 lg:pr-6',
117+
'styled-markdown-content'
118+
)}
124119
>
125-
<FaEdit /> Edit on GitHub
126-
</a>
120+
<Markdown htmlMarkup={markup} />
121+
</div>
122+
<div className="h-12" />
123+
<div className="w-full h-px bg-gray-500 opacity-30" />
124+
<div className="py-4 opacity-70">
125+
<a
126+
href={`https://github.com/${repo}/tree/${branch}/${filePath}`}
127+
className="flex items-center gap-2"
128+
>
129+
<FaEdit /> Edit on GitHub
130+
</a>
131+
</div>
132+
<div className="h-24" />
127133
</div>
128-
<div className="h-24" />
129-
</div>
130134

131-
{isTocVisible && (
132-
<div className="border-l border-gray-500/20 max-w-52 w-full hidden 2xl:block transition-all">
133-
<Toc
134-
headings={headings}
135-
activeHeadings={activeHeadings}
136-
colorFrom={colorFrom}
137-
colorTo={colorTo}
138-
/>
139-
</div>
140-
)}
141-
</div>
135+
{isTocVisible && (
136+
<div className="border-l border-gray-500/20 max-w-52 w-full hidden 2xl:block transition-all">
137+
<Toc
138+
headings={headings}
139+
activeHeadings={activeHeadings}
140+
colorFrom={colorFrom}
141+
colorTo={colorTo}
142+
/>
143+
</div>
144+
)}
145+
</div>
146+
</React.Fragment>
142147
)
143148
}

app/components/DocContainer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function DocContainer({
99
<div
1010
{...props}
1111
className={twMerge(
12-
'w-full max-w-full space-y-2 md:space-y-6 lg:space-y-8 p-2 md:p-6 lg:p-8',
12+
'w-full max-w-full p-2 md:p-6 lg:p-8',
1313
props.className
1414
)}
1515
>

app/components/TocMobile.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react'
2+
import { Link } from '@tanstack/react-router'
3+
import type { HeadingData } from 'marked-gfm-heading-id'
4+
import { FaCaretRight, FaCaretDown } from 'react-icons/fa6'
5+
6+
interface TocMobileProps {
7+
headings: Array<HeadingData>
8+
}
9+
10+
export function TocMobile({ headings }: TocMobileProps) {
11+
const [isOpen, setIsOpen] = React.useState(false)
12+
13+
const handleToggle = (event: React.SyntheticEvent<HTMLDetailsElement>) => {
14+
setIsOpen(event.currentTarget.open)
15+
}
16+
17+
if (headings.length === 0) {
18+
return null
19+
}
20+
21+
return (
22+
<div className="lg:hidden flex -mx-2 -mt-2 md:-mx-6 md:-mt-6 pb-3 md:pb-5">
23+
<details className="w-full" onToggle={handleToggle}>
24+
<summary
25+
className="px-4 py-3 text-sm font-medium w-full flex content-start items-center gap-2 bg-white/50 dark:bg-black/60 backdrop-blur-lg border-b border-gray-500 border-opacity-20"
26+
aria-expanded={isOpen}
27+
>
28+
<span>{isOpen ? <FaCaretDown /> : <FaCaretRight />}</span>
29+
<span>On this page</span>
30+
</summary>
31+
<div className="px-2 py-2">
32+
<ul className="list-none grid gap-2">
33+
{headings.map((heading) => (
34+
<li
35+
key={`mobile-toc-${heading.id}`}
36+
style={{
37+
paddingLeft: `${(heading.level - 1) * 0.7}rem`,
38+
}}
39+
>
40+
<Link
41+
to="."
42+
className="text-sm"
43+
hash={heading.id}
44+
dangerouslySetInnerHTML={{ __html: heading.text }}
45+
aria-label={heading.text.replace(/<\/?[^>]+(>|$)/g, '')}
46+
/>
47+
</li>
48+
))}
49+
</ul>
50+
</div>
51+
</details>
52+
</div>
53+
)
54+
}

app/routes/_libraries/blog.$.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ ${content}`
8282

8383
return (
8484
<DocContainer>
85-
<div>
85+
<div className="mb-2 md:mb-6 lg:mb-8">
8686
<Link
8787
from="/blog/$"
8888
to="/blog"

0 commit comments

Comments
 (0)