File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -76,8 +76,8 @@ export default function Home() {
7676 return (
7777 < main className = "flex flex-1 flex-col" >
7878 { /* Nav */ }
79- < header className = "sticky top-0 z-10 border-b bg-background/80 backdrop-blur " >
80- < div className = "mx-auto flex w-full max-w-5xl items-center justify-between px-6 py-4 " >
79+ < header className = "sticky top-0 z-20 px-4 pt-4 " >
80+ < div className = "mx-auto flex w-full max-w-6xl items-center justify-between rounded-2xl border bg-background/70 px-5 py-3 shadow-sm backdrop-blur-md " >
8181 < div className = "flex items-center gap-2.5" >
8282 < Image src = { asset ( "/webskrap-logo.png" ) } alt = "WebSkrap" width = { 28 } height = { 28 } />
8383 < span className = "font-semibold tracking-tight" > WebSkrap</ span >
Original file line number Diff line number Diff line change 11import type { Metadata } from "next" ;
22import { notFound } from "next/navigation" ;
33import { getDoc , getDocSlugs } from "@/lib/docs" ;
4+ import { CodeCopy } from "@/components/code-copy" ;
45
56export const dynamicParams = false ;
67
@@ -24,9 +25,12 @@ export default async function DocPage(props: DocPageProps) {
2425 if ( ! doc ) notFound ( ) ;
2526
2627 return (
27- < article
28- className = "prose prose-neutral max-w-none dark:prose-invert prose-headings:scroll-mt-24 prose-pre:border prose-pre:bg-card prose-a:text-primary"
29- dangerouslySetInnerHTML = { { __html : doc . html } }
30- />
28+ < >
29+ < article
30+ className = "prose prose-neutral max-w-none dark:prose-invert prose-headings:scroll-mt-24 prose-pre:rounded-2xl prose-pre:border prose-pre:bg-card prose-pre:p-6 prose-a:text-primary"
31+ dangerouslySetInnerHTML = { { __html : doc . html } }
32+ />
33+ < CodeCopy />
34+ </ >
3135 ) ;
3236}
Original file line number Diff line number Diff line change @@ -14,8 +14,8 @@ export default function DocsLayout({
1414} ) {
1515 return (
1616 < div className = "flex min-h-full flex-col" >
17- < header className = "sticky top-0 z-10 border-b bg-background/80 backdrop-blur " >
18- < div className = "mx-auto flex w-full max-w-6xl items-center justify-between px-6 py-4 " >
17+ < header className = "sticky top-0 z-20 px-4 pt-4 " >
18+ < div className = "mx-auto flex w-full max-w-6xl items-center justify-between rounded-2xl border bg-background/70 px-5 py-3 shadow-sm backdrop-blur-md " >
1919 < Link href = "/" className = "flex items-center gap-2.5" >
2020 < Image src = { asset ( "/webskrap-logo.png" ) } alt = "WebSkrap" width = { 28 } height = { 28 } />
2121 < span className = "font-semibold tracking-tight" > WebSkrap</ span >
Original file line number Diff line number Diff line change 11import { codeToHtml } from "shiki" ;
2+ import { CopyButton } from "@/components/copy-button" ;
23
34interface CodeBlockProps {
45 code : string ;
@@ -13,10 +14,13 @@ export async function CodeBlock({ code, lang }: CodeBlockProps) {
1314 } ) ;
1415
1516 return (
16- < div
17- className = "overflow-x-auto rounded-2xl border bg-card p-6 text-sm leading-relaxed [&_pre]:bg-transparent [&_pre]:font-mono [&_code]:font-mono"
18- // shiki output is trusted, build-time generated from static strings
19- dangerouslySetInnerHTML = { { __html : html } }
20- />
17+ < div className = "relative" >
18+ < CopyButton text = { code } />
19+ < div
20+ className = "overflow-x-auto rounded-2xl border bg-card p-6 text-sm leading-relaxed [&_pre]:bg-transparent [&_pre]:font-mono [&_code]:font-mono"
21+ // shiki output is trusted, build-time generated from static strings
22+ dangerouslySetInnerHTML = { { __html : html } }
23+ />
24+ </ div >
2125 ) ;
2226}
Original file line number Diff line number Diff line change 1+ "use client" ;
2+
3+ import { useEffect } from "react" ;
4+
5+ const COPY_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>` ;
6+ const CHECK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>` ;
7+
8+ // Docs HTML is server-rendered (dangerouslySetInnerHTML), so attach copy
9+ // buttons to each <pre> on the client after mount.
10+ export function CodeCopy ( ) {
11+ useEffect ( ( ) => {
12+ const pres = document . querySelectorAll < HTMLPreElement > ( "article pre" ) ;
13+ pres . forEach ( ( pre ) => {
14+ if ( pre . dataset . copyReady ) return ;
15+ pre . dataset . copyReady = "1" ;
16+ pre . classList . add ( "relative" ) ;
17+ const text = pre . innerText ;
18+
19+ const btn = document . createElement ( "button" ) ;
20+ btn . type = "button" ;
21+ btn . setAttribute ( "aria-label" , "Copy code" ) ;
22+ btn . innerHTML = COPY_SVG ;
23+ btn . className =
24+ "absolute right-3 top-3 z-10 inline-flex size-7 items-center justify-center rounded-md border bg-background/80 text-muted-foreground backdrop-blur transition-colors hover:text-foreground" ;
25+ btn . addEventListener ( "click" , async ( ) => {
26+ await navigator . clipboard . writeText ( text ) ;
27+ btn . innerHTML = CHECK_SVG ;
28+ setTimeout ( ( ) => {
29+ btn . innerHTML = COPY_SVG ;
30+ } , 1500 ) ;
31+ } ) ;
32+ pre . appendChild ( btn ) ;
33+ } ) ;
34+ } , [ ] ) ;
35+
36+ return null ;
37+ }
Original file line number Diff line number Diff line change 1+ "use client" ;
2+
3+ import * as React from "react" ;
4+ import { Check , Copy } from "lucide-react" ;
5+
6+ export function CopyButton ( { text } : { text : string } ) {
7+ const [ copied , setCopied ] = React . useState ( false ) ;
8+
9+ return (
10+ < button
11+ type = "button"
12+ aria-label = "Copy code"
13+ onClick = { async ( ) => {
14+ await navigator . clipboard . writeText ( text ) ;
15+ setCopied ( true ) ;
16+ setTimeout ( ( ) => setCopied ( false ) , 1500 ) ;
17+ } }
18+ className = "absolute right-3 top-3 z-10 inline-flex size-7 items-center justify-center rounded-md border bg-background/80 text-muted-foreground backdrop-blur transition-colors hover:text-foreground [&_svg]:size-3.5"
19+ >
20+ { copied ? < Check /> : < Copy /> }
21+ </ button >
22+ ) ;
23+ }
You can’t perform that action at this time.
0 commit comments