Skip to content

Commit eeea049

Browse files
committed
add header links
1 parent 798ecef commit eeea049

2 files changed

Lines changed: 83 additions & 24 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { useState } from "react";
2+
import { Link, Check } from "lucide-react";
3+
import { cn } from "@/shared/lib/utils.ts";
4+
5+
interface HeaderWithLinkProps {
6+
id: string;
7+
level: 1 | 2 | 3 | 4;
8+
children: React.ReactNode;
9+
className?: string;
10+
}
11+
12+
const HeaderWithLink: React.FC<HeaderWithLinkProps> = ({
13+
id,
14+
level,
15+
children,
16+
className,
17+
}) => {
18+
const [copied, setCopied] = useState(false);
19+
20+
const handleCopyLink = async () => {
21+
const url = `${window.location.origin}${window.location.pathname}#${id}`;
22+
23+
try {
24+
await navigator.clipboard.writeText(url);
25+
setCopied(true);
26+
setTimeout(() => setCopied(false), 2000);
27+
} catch (err) {
28+
// Fallback for older browsers
29+
const textArea = document.createElement("textarea");
30+
textArea.value = url;
31+
document.body.appendChild(textArea);
32+
textArea.select();
33+
document.execCommand("copy");
34+
document.body.removeChild(textArea);
35+
setCopied(true);
36+
setTimeout(() => setCopied(false), 2000);
37+
}
38+
};
39+
40+
const baseClasses = "group relative scroll-mt-24 text-foreground";
41+
const levelClasses = {
42+
1: "text-3xl font-bold mt-12 mb-2 leading-tight",
43+
2: "text-2xl font-semibold mt-6 mb-4 leading-snug",
44+
3: "text-xl font-semibold mt-8 mb-3 leading-snug",
45+
4: "text-lg font-medium mt-6 mb-2"
46+
};
47+
48+
const Tag = `h${level}` as keyof JSX.IntrinsicElements;
49+
50+
return (
51+
<Tag
52+
id={id}
53+
className={cn(baseClasses, levelClasses[level], className)}
54+
>
55+
<span className="flex items-center gap-2">
56+
{children}
57+
<button
58+
onClick={handleCopyLink}
59+
className="opacity-0 group-hover:opacity-100 transition-opacity duration-200 p-1 rounded hover:bg-muted"
60+
title="Copy link to this section"
61+
aria-label="Copy link to this section"
62+
>
63+
{copied ? (
64+
<Check className="h-4 w-4 text-green-600" />
65+
) : (
66+
<Link className="h-4 w-4 text-muted-foreground hover:text-foreground" />
67+
)}
68+
</button>
69+
</span>
70+
</Tag>
71+
);
72+
};
73+
74+
export default HeaderWithLink;

src/docs-app/ui/components/content/MarkdownContent.tsx

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown";
33
import remarkGfm from "remark-gfm";
44
import rehypeRaw from "rehype-raw";
55
import CodeBlock from "@/docs-app/ui/components/content/CodeBlock.tsx";
6+
import HeaderWithLink from "@/docs-app/ui/components/content/HeaderWithLink.tsx";
67
import type { Components } from "react-markdown";
78
import type { Root, Element as MdElement, Text as MdText } from "hast"; // Import HAST types
89
import {
@@ -113,13 +114,9 @@ const MarkdownContent: React.FC<MarkdownContentProps> = ({ content }) => {
113114
const id = generateHeadingId(text);
114115
return (
115116
<>
116-
<h1
117-
id={id}
118-
className="text-3xl font-bold mt-12 mb-2 scroll-mt-24 text-foreground leading-tight"
119-
{...props}
120-
>
117+
<HeaderWithLink id={id} level={1} {...props}>
121118
{children}
122-
</h1>
119+
</HeaderWithLink>
123120
<Separator className="mb-2 opacity-60" />
124121
</>
125122
);
@@ -128,39 +125,27 @@ const MarkdownContent: React.FC<MarkdownContentProps> = ({ content }) => {
128125
const text = extractTextFromChildren(children);
129126
const id = generateHeadingId(text);
130127
return (
131-
<h2
132-
id={id}
133-
className="text-2xl font-semibold mt-6 mb-4 scroll-mt-24 text-foreground leading-snug"
134-
{...props}
135-
>
128+
<HeaderWithLink id={id} level={2} {...props}>
136129
{children}
137-
</h2>
130+
</HeaderWithLink>
138131
);
139132
},
140133
h3: ({ node, children, ...props }) => {
141134
const text = extractTextFromChildren(children);
142135
const id = generateHeadingId(text);
143136
return (
144-
<h3
145-
id={id}
146-
className="text-xl font-semibold mt-8 mb-3 scroll-mt-24 text-foreground leading-snug"
147-
{...props}
148-
>
137+
<HeaderWithLink id={id} level={3} {...props}>
149138
{children}
150-
</h3>
139+
</HeaderWithLink>
151140
);
152141
},
153142
h4: ({ node, children, ...props }) => {
154143
const text = extractTextFromChildren(children);
155144
const id = generateHeadingId(text);
156145
return (
157-
<h4
158-
id={id}
159-
className="text-lg font-medium mt-6 mb-2 scroll-mt-24 text-foreground"
160-
{...props}
161-
>
146+
<HeaderWithLink id={id} level={4} {...props}>
162147
{children}
163-
</h4>
148+
</HeaderWithLink>
164149
);
165150
},
166151
a: ({ node, ...props }) => (

0 commit comments

Comments
 (0)