Skip to content

Commit d06b360

Browse files
authored
fix(docs): fix copy page button and header hook (#2284)
1 parent 0713580 commit d06b360

File tree

7 files changed

+103
-24
lines changed

7 files changed

+103
-24
lines changed

apps/docs/app/[lang]/[[...slug]]/page.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import Link from 'next/link'
66
import { notFound } from 'next/navigation'
77
import { PageNavigationArrows } from '@/components/docs-layout/page-navigation-arrows'
88
import { TOCFooter } from '@/components/docs-layout/toc-footer'
9+
import { LLMCopyButton } from '@/components/page-actions'
910
import { StructuredData } from '@/components/structured-data'
1011
import { CodeBlock } from '@/components/ui/code-block'
11-
import { CopyPageButton } from '@/components/ui/copy-page-button'
12+
import { Heading } from '@/components/ui/heading'
1213
import { source } from '@/lib/source'
1314

1415
export default async function Page(props: { params: Promise<{ slug?: string[]; lang: string }> }) {
@@ -202,7 +203,7 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
202203
<div className='relative mt-6 sm:mt-0'>
203204
<div className='absolute top-1 right-0 flex items-center gap-2'>
204205
<div className='hidden sm:flex'>
205-
<CopyPageButton markdownUrl={`${page.url}.mdx`} />
206+
<LLMCopyButton markdownUrl={`${page.url}.mdx`} />
206207
</div>
207208
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
208209
</div>
@@ -214,6 +215,12 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
214215
components={{
215216
...defaultMdxComponents,
216217
CodeBlock,
218+
h1: (props) => <Heading as='h1' {...props} />,
219+
h2: (props) => <Heading as='h2' {...props} />,
220+
h3: (props) => <Heading as='h3' {...props} />,
221+
h4: (props) => <Heading as='h4' {...props} />,
222+
h5: (props) => <Heading as='h5' {...props} />,
223+
h6: (props) => <Heading as='h6' {...props} />,
217224
}}
218225
/>
219226
</DocsBody>

apps/docs/app/global.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
@import "fumadocs-ui/css/neutral.css";
33
@import "fumadocs-ui/css/preset.css";
44

5+
/* Prevent overscroll bounce effect on the page */
6+
html,
7+
body {
8+
overscroll-behavior: none;
9+
}
10+
511
@theme {
612
--color-fd-primary: #802fff; /* Purple from control-bar component */
713
--font-geist-sans: var(--font-geist-sans);

apps/docs/app/llms.mdx/[[...slug]]/route.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import { notFound } from 'next/navigation'
22
import { type NextRequest, NextResponse } from 'next/server'
3+
import { i18n } from '@/lib/i18n'
34
import { getLLMText } from '@/lib/llms'
45
import { source } from '@/lib/source'
56

67
export const revalidate = false
78

89
export async function GET(_req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> }) {
910
const { slug } = await params
10-
const page = source.getPage(slug)
11+
12+
let lang: (typeof i18n.languages)[number] = i18n.defaultLanguage
13+
let pageSlug = slug
14+
15+
if (slug && slug.length > 0 && i18n.languages.includes(slug[0] as typeof lang)) {
16+
lang = slug[0] as typeof lang
17+
pageSlug = slug.slice(1)
18+
}
19+
20+
const page = source.getPage(pageSlug, lang)
1121
if (!page) notFound()
1222

1323
return new NextResponse(await getLLMText(page), {

apps/docs/components/ui/copy-page-button.tsx renamed to apps/docs/components/page-actions.tsx

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,50 @@
11
'use client'
22

33
import { useState } from 'react'
4+
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'
45
import { Check, Copy } from 'lucide-react'
56

67
const cache = new Map<string, string>()
78

8-
interface CopyPageButtonProps {
9+
export function LLMCopyButton({
10+
markdownUrl,
11+
}: {
12+
/**
13+
* A URL to fetch the raw Markdown/MDX content of page
14+
*/
915
markdownUrl: string
10-
}
11-
12-
export function CopyPageButton({ markdownUrl }: CopyPageButtonProps) {
13-
const [copied, setCopied] = useState(false)
16+
}) {
1417
const [isLoading, setLoading] = useState(false)
15-
16-
const handleCopy = async () => {
18+
const [checked, onClick] = useCopyButton(async () => {
1719
const cached = cache.get(markdownUrl)
18-
if (cached) {
19-
await navigator.clipboard.writeText(cached)
20-
setCopied(true)
21-
setTimeout(() => setCopied(false), 2000)
22-
return
23-
}
20+
if (cached) return navigator.clipboard.writeText(cached)
2421

2522
setLoading(true)
23+
2624
try {
2725
await navigator.clipboard.write([
2826
new ClipboardItem({
2927
'text/plain': fetch(markdownUrl).then(async (res) => {
3028
const content = await res.text()
3129
cache.set(markdownUrl, content)
30+
3231
return content
3332
}),
3433
}),
3534
])
36-
setCopied(true)
37-
setTimeout(() => setCopied(false), 2000)
38-
} catch (err) {
39-
console.error('Failed to copy:', err)
4035
} finally {
4136
setLoading(false)
4237
}
43-
}
38+
})
4439

4540
return (
4641
<button
4742
disabled={isLoading}
48-
onClick={handleCopy}
43+
onClick={onClick}
4944
className='flex cursor-pointer items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
50-
aria-label={copied ? 'Copied to clipboard' : 'Copy page content'}
45+
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
5146
>
52-
{copied ? (
47+
{checked ? (
5348
<>
5449
<Check className='h-3.5 w-3.5' />
5550
<span>Copied</span>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client'
2+
3+
import { type ComponentPropsWithoutRef, useState } from 'react'
4+
import { Check, Link } from 'lucide-react'
5+
import { cn } from '@/lib/utils'
6+
7+
type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
8+
9+
interface HeadingProps extends ComponentPropsWithoutRef<'h1'> {
10+
as?: HeadingTag
11+
}
12+
13+
export function Heading({ as, className, ...props }: HeadingProps) {
14+
const [copied, setCopied] = useState(false)
15+
const As = as ?? 'h1'
16+
17+
if (!props.id) {
18+
return <As className={className} {...props} />
19+
}
20+
21+
const handleClick = async (e: React.MouseEvent) => {
22+
e.preventDefault()
23+
24+
const url = `${window.location.origin}${window.location.pathname}#${props.id}`
25+
26+
try {
27+
await navigator.clipboard.writeText(url)
28+
setCopied(true)
29+
30+
// Update URL hash without scrolling
31+
window.history.pushState(null, '', `#${props.id}`)
32+
33+
setTimeout(() => setCopied(false), 2000)
34+
} catch {
35+
// Fallback: just navigate to the anchor
36+
window.location.hash = props.id as string
37+
}
38+
}
39+
40+
return (
41+
<As className={cn('group flex scroll-m-28 flex-row items-center gap-2', className)} {...props}>
42+
<a data-card='' href={`#${props.id}`} className='peer' onClick={handleClick}>
43+
{props.children}
44+
</a>
45+
{copied ? (
46+
<Check
47+
aria-hidden
48+
className='size-3.5 shrink-0 text-green-500 opacity-100 transition-opacity'
49+
/>
50+
) : (
51+
<Link
52+
aria-hidden
53+
className='size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 peer-hover:opacity-100'
54+
/>
55+
)}
56+
</As>
57+
)
58+
}

apps/docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"dependencies": {
1414
"@tabler/icons-react": "^3.31.0",
1515
"@vercel/og": "^0.6.5",
16+
"class-variance-authority": "^0.7.1",
1617
"clsx": "^2.1.1",
1718
"fumadocs-core": "16.2.3",
1819
"fumadocs-mdx": "14.1.0",

bun.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"lockfileVersion": 1,
3+
"configVersion": 0,
34
"workspaces": {
45
"": {
56
"name": "simstudio",
@@ -42,6 +43,7 @@
4243
"dependencies": {
4344
"@tabler/icons-react": "^3.31.0",
4445
"@vercel/og": "^0.6.5",
46+
"class-variance-authority": "^0.7.1",
4547
"clsx": "^2.1.1",
4648
"fumadocs-core": "16.2.3",
4749
"fumadocs-mdx": "14.1.0",

0 commit comments

Comments
 (0)