feat(docs): add prev/next page navigation to doc pages
Add card-style previous/next links at the bottom of documentation pages using the sidebar navigation order from nav.ts. Includes section labels, arrow icons, hover effects, and dark mode support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Yechan Kim committed
Apr 12, 2026 at 16:43 UTC
52d9372cbbebedf696d86ecf5e18486807562d77
3 files changed
+102
docs/src/lib/components/PrevNextNav.svelte
new
+71
@@ -0,0 +1,71 @@
1
+<script lang="ts">
2
+ import { base } from '$app/paths';
3
+ import type { FlatNavItem } from '$lib/nav';
4
+
5
+ interface Props {
6
+ prev: FlatNavItem | null;
7
+ next: FlatNavItem | null;
8
+ }
9
+
10
+ let { prev, next }: Props = $props();
11
+</script>
12
+
13
+{#if prev || next}
14
+ <nav class="not-prose mt-12 grid grid-cols-1 gap-4 sm:grid-cols-2" aria-label="Page navigation">
15
+ {#if prev}
16
+ <a
17
+ href="{base}{prev.href}"
18
+ class="group flex items-center gap-4 rounded-lg border border-border/70 px-5 py-4 transition-colors hover:border-primary/40"
19
+ >
20
+ <svg
21
+ class="size-5 shrink-0 text-text-muted transition-transform group-hover:-translate-x-0.5"
22
+ xmlns="http://www.w3.org/2000/svg"
23
+ fill="none"
24
+ viewBox="0 0 24 24"
25
+ stroke-width="2"
26
+ stroke="currentColor"
27
+ aria-hidden="true"
28
+ >
29
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
30
+ </svg>
31
+ <div class="min-w-0">
32
+ <span class="block text-xs font-medium text-text-muted">Previous</span>
33
+ <span class="block text-xs text-text-muted">{prev.section}</span>
34
+ <span class="block truncate font-display text-sm font-semibold text-foreground">
35
+ {prev.title}
36
+ </span>
37
+ </div>
38
+ </a>
39
+ {:else}
40
+ <div class="hidden sm:block"></div>
41
+ {/if}
42
+
43
+ {#if next}
44
+ <a
45
+ href="{base}{next.href}"
46
+ class="group flex items-center justify-end gap-4 rounded-lg border border-border/70 px-5 py-4 text-right transition-colors hover:border-primary/40"
47
+ >
48
+ <div class="min-w-0">
49
+ <span class="block text-xs font-medium text-text-muted">Next</span>
50
+ <span class="block text-xs text-text-muted">{next.section}</span>
51
+ <span class="block truncate font-display text-sm font-semibold text-foreground">
52
+ {next.title}
53
+ </span>
54
+ </div>
55
+ <svg
56
+ class="size-5 shrink-0 text-text-muted transition-transform group-hover:translate-x-0.5"
57
+ xmlns="http://www.w3.org/2000/svg"
58
+ fill="none"
59
+ viewBox="0 0 24 24"
60
+ stroke-width="2"
61
+ stroke="currentColor"
62
+ aria-hidden="true"
63
+ >
64
+ <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
65
+ </svg>
66
+ </a>
67
+ {:else}
68
+ <div></div>
69
+ {/if}
70
+ </nav>
71
+{/if}
docs/src/lib/nav.ts
+24
@@ -9,6 +9,30 @@ export interface NavSection {
9
items: NavItem[];
10
}
11
12
+export interface FlatNavItem extends NavItem {
13
+ section: string;
14
+}
15
+
16
+export function flattenNavigation(): FlatNavItem[] {
17
+ return navigation.flatMap((s) => s.items.map((item) => ({ ...item, section: s.title })));
18
+}
19
+
20
+export function getPrevNext(
21
+ pathname: string,
22
+ basePath: string
23
+): { prev: FlatNavItem | null; next: FlatNavItem | null } {
24
+ const items = flattenNavigation();
25
+ const index = items.findIndex(
26
+ (item) =>
27
+ pathname === `${basePath}${item.href}/` || pathname === `${basePath}${item.href}`
28
+ );
29
+ if (index === -1) return { prev: null, next: null };
30
+ return {
31
+ prev: index > 0 ? items[index - 1] : null,
32
+ next: index < items.length - 1 ? items[index + 1] : null
33
+ };
34
+}
35
+
36
export const navigation: NavSection[] = [
37
{
38
title: 'Getting Started',
docs/src/routes/+layout.svelte
+7
@@ -6,6 +6,8 @@
6
import ThemeToggle from '$lib/components/ThemeToggle.svelte';
7
import MobileNav from '$lib/components/MobileNav.svelte';
8
import TableOfContents from '$lib/components/TableOfContents.svelte';
9
+ import PrevNextNav from '$lib/components/PrevNextNav.svelte';
10
+ import { getPrevNext } from '$lib/nav';
11
import '../app.css';
12
13
let { children } = $props();
@@ -14,6 +16,8 @@
16
const isLandingPage = $derived(
17
$page.url.pathname === `${base}/` || $page.url.pathname === base || $page.url.pathname === '/'
18
);
19
+
20
+ const prevNext = $derived(getPrevNext($page.url.pathname, base));
21
</script>
22
23
<ModeWatcher defaultMode="system" />
@@ -135,6 +139,9 @@
139
<article class="prose prose-gray dark:prose-invert mx-auto max-w-3xl">
140
{@render children()}
141
</article>
142
+ <div class="mx-auto max-w-3xl">
143
+ <PrevNextNav prev={prevNext.prev} next={prevNext.next} />
144
+ </div>
145
</main>
146
147
<aside class="hidden w-56 shrink-0 border-l border-gray-200 xl:block dark:border-white/8">