Skip to content

Commit 32a4eda

Browse files
committed
feat: heading links
1 parent 057fb77 commit 32a4eda

File tree

8 files changed

+130
-6
lines changed

8 files changed

+130
-6
lines changed

website/src/layout/components/ContentContainer/styles.module.scss

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,8 @@ $sidebar-width: sidebar.$desktop-sidebar-width;
88
$toc-width: toc.$desktop-toc-width;
99

1010
.contentContainer {
11-
padding: 1rem;
11+
padding: 2rem;
1212
margin: 0 auto;
1313
width: calc(100% - #{$toc-width});
1414
max-width: 1200px; // TODO: pick less arbitrary value
15-
16-
@include mixins.desktop() {
17-
padding: 1rem 2rem;
18-
}
1915
}

website/src/mdx/Heading/index.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import c from "clsx";
2+
3+
import { linkIconSize } from "./styles.export.module.scss";
4+
import { heading, anchor, svg, path } from "./styles.module.scss"
5+
6+
import type { HTMLAttributes } from "react"
7+
import type { HeadingLevel } from "@/lib/unified/toc/types"
8+
9+
interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
10+
as: `h${HeadingLevel}`
11+
}
12+
13+
export default function Heading ({
14+
as: Tag,
15+
className,
16+
children,
17+
...props
18+
}: HeadingProps) {
19+
return (
20+
<Tag className={c(heading, className)} {...props}>
21+
<span>{children}</span>
22+
{!props.id ? null : (
23+
<a
24+
className={anchor}
25+
href={`#${props.id}`}
26+
title="Direct link to heading"
27+
>
28+
{/* link icon */}
29+
<svg className={svg} width={linkIconSize} height={linkIconSize} viewBox="0 0 24 24">
30+
<path className={path} d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" />
31+
</svg>
32+
</a>
33+
)}
34+
</Tag>
35+
)
36+
}
37+
38+
export const getHeading = (as: `h${HeadingLevel}`) => (
39+
(props: HTMLAttributes<HTMLHeadingElement>) => <Heading as={as} {...props} />
40+
)
41+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@use "@/styles/utils";
2+
@use "./styles.module.scss" as *;
3+
4+
:export {
5+
linkIconSize: utils.strip-units($link-icon-size);
6+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// AUTOGENERATED FILE -- DO NOT EDIT DIRECTLY
2+
declare namespace StylesExportModuleScssNamespace {
3+
export interface IStylesExportModuleScss {
4+
anchor: string;
5+
heading: string;
6+
linkIconSize: string;
7+
path: string;
8+
svg: string;
9+
}
10+
}
11+
12+
declare const StylesExportModuleScssModule: StylesExportModuleScssNamespace.IStylesExportModuleScss;
13+
14+
export = StylesExportModuleScssModule;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
@use "@/styles/colors";
2+
3+
$link-icon-size: 16px;
4+
5+
$link-padding: 0.5rem;
6+
7+
.heading {
8+
position: relative;
9+
}
10+
11+
.anchor {
12+
position: absolute;
13+
left: calc(-1 * (#{$link-icon-size} + #{2 * $link-padding}));
14+
height: 100%;
15+
padding: $link-padding;
16+
display: inline-flex;
17+
align-items: center;
18+
user-select: none;
19+
20+
opacity: 0;
21+
transition: opacity 200ms;
22+
23+
.heading:hover &, &:hover {
24+
opacity: 1;
25+
}
26+
27+
&:hover .path {
28+
fill: colors.$red;
29+
}
30+
31+
.svg {
32+
width: $link-icon-size;
33+
height: $link-icon-size;
34+
}
35+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// AUTOGENERATED FILE -- DO NOT EDIT DIRECTLY
2+
declare namespace StylesModuleScssNamespace {
3+
export interface IStylesModuleScss {
4+
anchor: string;
5+
heading: string;
6+
path: string;
7+
svg: string;
8+
}
9+
}
10+
11+
declare const StylesModuleScssModule: StylesModuleScssNamespace.IStylesModuleScss;
12+
13+
export = StylesModuleScssModule;

website/src/mdx/components.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Link from 'next/link';
1111
import CodeBlock from './CodeBlock';
1212

1313
import type { ComponentMap } from "mdx-bundler/client";
14+
import Heading, { getHeading } from './Heading';
1415

1516
const MDXComponents: ComponentMap = {
1617
code: (props: any) => {
@@ -48,7 +49,13 @@ const MDXComponents: ComponentMap = {
4849
: {...props}))}
4950
/>
5051
);
51-
}
52+
},
53+
h1: getHeading('h1'),
54+
h2: getHeading('h2'),
55+
h3: getHeading('h3'),
56+
h4: getHeading('h4'),
57+
h5: getHeading('h5'),
58+
h6: getHeading('h6'),
5259
}
5360

5461
export default MDXComponents;

website/src/mdx/markdown.module.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@
3232

3333
/* apply these styles only in markdown "content" (i.e. articles) */
3434
&.content {
35+
36+
// heading margins
37+
h1, h2, h3, h4, h5, h6 {
38+
margin: 2em 0 1em;
39+
}
40+
41+
// don't add margin to first child (the whole markdown container has top margin already)
42+
> *:first-child {
43+
margin-top: 0;
44+
}
45+
46+
// block margins
3547
> * {
3648
margin-bottom: 1rem;
3749
}

0 commit comments

Comments
 (0)