Skip to content

Commit 180a4e3

Browse files
committed
Make docs nav not dumb -- one section per page. (fix James' original sin)
1 parent cd1ba6a commit 180a4e3

File tree

4 files changed

+114
-120
lines changed

4 files changed

+114
-120
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
'use client'
2+
3+
import dynamic from 'next/dynamic'
4+
import NextLink from 'next/link'
5+
import { notFound } from 'next/navigation'
6+
import React from 'react'
7+
8+
import type { Doc } from '@/types/docs'
9+
10+
import { Mdx } from '@/components/docs/mdx/mdx-components'
11+
import { getDocsByCategory } from '@/lib/docs'
12+
import { allDocs } from '.contentlayer/generated'
13+
14+
const DocNavigation = ({
15+
sortedDocs,
16+
category,
17+
currentSlug,
18+
}: {
19+
sortedDocs: Doc[]
20+
category: string
21+
currentSlug: string
22+
}) => {
23+
const currentIndex = sortedDocs.findIndex((d) => d.slug === currentSlug)
24+
const prevDoc = currentIndex > 0 ? sortedDocs[currentIndex - 1] : null
25+
const nextDoc =
26+
currentIndex < sortedDocs.length - 1 ? sortedDocs[currentIndex + 1] : null
27+
28+
return (
29+
<div className="flex justify-between items-center pt-8 mt-8 border-t">
30+
{prevDoc && (
31+
<NextLink
32+
href={`/docs/${category}/${prevDoc.slug}`}
33+
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-white/5 text-gray-300 hover:bg-white/10 hover:text-white transition-colors"
34+
>
35+
<svg
36+
xmlns="http://www.w3.org/2000/svg"
37+
className="h-5 w-5"
38+
viewBox="0 0 24 24"
39+
fill="none"
40+
stroke="currentColor"
41+
strokeWidth="2"
42+
strokeLinecap="round"
43+
strokeLinejoin="round"
44+
>
45+
<path d="M19 12H5M12 19l-7-7 7-7" />
46+
</svg>
47+
<span className="font-medium">{prevDoc.title}</span>
48+
</NextLink>
49+
)}
50+
{nextDoc && (
51+
<NextLink
52+
href={`/docs/${category}/${nextDoc.slug}`}
53+
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-white/5 text-gray-300 hover:bg-white/10 hover:text-white transition-colors ml-auto"
54+
>
55+
<span className="font-medium">{nextDoc.title}</span>
56+
<svg
57+
xmlns="http://www.w3.org/2000/svg"
58+
className="h-5 w-5"
59+
viewBox="0 0 24 24"
60+
fill="none"
61+
stroke="currentColor"
62+
strokeWidth="2"
63+
strokeLinecap="round"
64+
strokeLinejoin="round"
65+
>
66+
<path d="M5 12h14M12 5l7 7-7 7" />
67+
</svg>
68+
</NextLink>
69+
)}
70+
</div>
71+
)
72+
}
73+
74+
interface DocPageProps {
75+
params: { category: string; slug: string }
76+
}
77+
78+
export default function DocPage({ params }: DocPageProps) {
79+
const docs = getDocsByCategory(params.category)
80+
const doc = docs.find((d: Doc) => d.slug === params.slug)
81+
82+
if (!doc) {
83+
return notFound()
84+
}
85+
86+
const sortedDocs = [...docs].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
87+
88+
return (
89+
<div className="max-w-3xl mx-auto">
90+
<article className="prose dark:prose-invert prose-compact max-w-none overflow-x-auto">
91+
<Mdx code={doc.body.code} />
92+
93+
{React.createElement(
94+
dynamic(() =>
95+
import(`@/content/${doc.category}/_cta.mdx`).catch(() => () => null)
96+
)
97+
)}
98+
</article>
99+
100+
<DocNavigation
101+
sortedDocs={sortedDocs}
102+
category={params.category}
103+
currentSlug={params.slug}
104+
/>
105+
</div>
106+
)
107+
}
Lines changed: 5 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,21 @@
1-
'use client'
1+
import { redirect } from 'next/navigation'
22

3-
import dynamic from 'next/dynamic'
4-
import NextLink from 'next/link'
5-
import { notFound } from 'next/navigation'
6-
import React from 'react'
7-
8-
import type { Doc } from '@/types/docs'
9-
10-
import { sections } from '@/components/docs/doc-sidebar'
11-
import { Mdx } from '@/components/docs/mdx/mdx-components'
123
import { getDocsByCategory } from '@/lib/docs'
134

14-
const DocNavigation = ({ category }: { category: string }) => {
15-
const currentIndex = sections.findIndex((s) => s.href === `/docs/${category}`)
16-
const prevSection = currentIndex > 0 ? sections[currentIndex - 1] : null
17-
const nextSection =
18-
currentIndex < sections.length - 1 ? sections[currentIndex + 1] : null
19-
20-
return (
21-
<div className="flex justify-between items-center pt-8 mt-8 border-t">
22-
{prevSection && (
23-
<NextLink
24-
href={prevSection.href}
25-
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-white/5 text-gray-300 hover:bg-white/10 hover:text-white transition-colors"
26-
>
27-
<svg
28-
xmlns="http://www.w3.org/2000/svg"
29-
className="h-5 w-5"
30-
viewBox="0 0 24 24"
31-
fill="none"
32-
stroke="currentColor"
33-
strokeWidth="2"
34-
strokeLinecap="round"
35-
strokeLinejoin="round"
36-
>
37-
<path d="M19 12H5M12 19l-7-7 7-7" />
38-
</svg>
39-
<span className="font-medium">{prevSection.title}</span>
40-
</NextLink>
41-
)}
42-
{nextSection && (
43-
<NextLink
44-
href={nextSection.href}
45-
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-white/5 text-gray-300 hover:bg-white/10 hover:text-white transition-colors ml-auto"
46-
>
47-
<span className="font-medium">{nextSection.title}</span>
48-
<svg
49-
xmlns="http://www.w3.org/2000/svg"
50-
className="h-5 w-5"
51-
viewBox="0 0 24 24"
52-
fill="none"
53-
stroke="currentColor"
54-
strokeWidth="2"
55-
strokeLinecap="round"
56-
strokeLinejoin="round"
57-
>
58-
<path d="M5 12h14M12 5l7 7-7 7" />
59-
</svg>
60-
</NextLink>
61-
)}
62-
</div>
63-
)
64-
}
65-
665
interface CategoryPageProps {
676
params: { category: string }
687
}
698

70-
const DocPage = ({ doc }: { doc: Doc }) => {
71-
return (
72-
<article className="prose dark:prose-invert prose-compact [&_h1]:scroll-mt-24 [&_h2]:scroll-mt-24 [&_h3]:scroll-mt-24 max-w-none overflow-x-auto">
73-
<Mdx code={doc.body.code} />
74-
75-
{React.createElement(
76-
dynamic(() =>
77-
import(`@/content/${doc.category}/_cta.mdx`).catch(() => () => null)
78-
)
79-
)}
80-
</article>
81-
)
82-
}
83-
849
export default function CategoryPage({ params }: CategoryPageProps) {
8510
const docs = getDocsByCategory(params.category)
8611

8712
if (!docs.length) {
88-
return notFound()
13+
redirect('/docs')
8914
}
9015

91-
// Sort by order field
16+
// Sort by order field and redirect to first doc
9217
const sortedDocs = [...docs].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
18+
const firstDoc = sortedDocs[0]
9319

94-
return (
95-
<div className="max-w-3xl mx-auto grid divide-y divide-border [&>*]:py-12 first:[&>*]:pt-0 last:[&>*]:pb-0">
96-
{sortedDocs.map((doc) => (
97-
<DocPage key={doc.slug} doc={doc} />
98-
))}
99-
100-
<DocNavigation category={params.category} />
101-
</div>
102-
)
20+
redirect(`/docs/${params.category}/${firstDoc.slug}`)
10321
}

web/src/app/docs/layout.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,6 @@ export default function DocsLayout({
3636
return () => window.removeEventListener('scroll', handleScroll)
3737
}, [])
3838

39-
// New: Smoothly scroll to hash target on back/forward navigation
40-
useEffect(() => {
41-
const handleHashChange = () => {
42-
const id = window.location.hash.slice(1)
43-
if (id) {
44-
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' })
45-
}
46-
}
47-
48-
// If landing with a hash, ensure smooth scroll to target
49-
handleHashChange()
50-
51-
window.addEventListener('hashchange', handleHashChange)
52-
return () => window.removeEventListener('hashchange', handleHashChange)
53-
}, [])
54-
5539
// Handle sidebar scroll for dynamic fade effects
5640
useEffect(() => {
5741
const sidebarElement = sidebarRef.current

web/src/components/docs/doc-sidebar.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,9 @@ export function DocSidebar({
122122
{section.subsections.map((subsection) => (
123123
<Link
124124
key={subsection.href}
125-
href={
126-
section.external
127-
? subsection.href
128-
: `${section.href}#${subsection.title.toLowerCase().replace(/\s+/g, '-')}`
129-
}
125+
href={subsection.href}
130126
target={section.external ? '_blank' : undefined}
131-
onClick={(e) => {
132-
onNavigate?.()
133-
if (pathname.startsWith(section.href)) {
134-
e.preventDefault()
135-
const id = subsection.title
136-
.toLowerCase()
137-
.replace(/\s+/g, '-')
138-
document
139-
.getElementById(id)
140-
?.scrollIntoView({ behavior: 'smooth', block: 'start' })
141-
history.replaceState(null, '', `#${id}`)
142-
}
127+
onClick={() => {
143128
const sheet = document.querySelector('[data-state="open"]')
144129
if (sheet) sheet.setAttribute('data-state', 'closed')
145130
onNavigate?.()

0 commit comments

Comments
 (0)