From d487cbe6bb3760f8ae57f7b07b61bfa8d041f5dc Mon Sep 17 00:00:00 2001 From: syam-bukkuru Date: Mon, 18 May 2026 22:01:10 +0530 Subject: [PATCH] add MDX heading permalinks for tutorial pages --- package-lock.json | 16 ++++++ src/components/Dropdown/styles.module.scss | 2 +- src/components/HeadingPermalink/index.tsx | 62 ++++++++++++++++++++++ src/components/Nav/styles.module.scss | 3 +- src/components/Settings/styles.module.scss | 2 +- src/layouts/ContributorDocLayout.astro | 22 +++++++- src/layouts/HomepageLayout.astro | 2 +- src/layouts/TutorialLayout.astro | 14 +++++ styles/base.scss | 8 +-- styles/global.scss | 2 +- styles/markdown.scss | 42 ++++++++++++++- 11 files changed, 161 insertions(+), 14 deletions(-) create mode 100644 src/components/HeadingPermalink/index.tsx diff --git a/package-lock.json b/package-lock.json index 88d8f91d32..adfba7a841 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3882,6 +3882,7 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3921,6 +3922,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3941,6 +3943,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3961,6 +3964,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3981,6 +3985,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4001,6 +4006,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4021,6 +4027,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4041,6 +4048,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4061,6 +4069,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4081,6 +4090,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4101,6 +4111,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4121,6 +4132,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4141,6 +4153,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4161,6 +4174,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -18034,6 +18048,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, "license": "MIT", "optional": true }, @@ -23484,6 +23499,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, diff --git a/src/components/Dropdown/styles.module.scss b/src/components/Dropdown/styles.module.scss index 219f81cca5..d274b5e507 100644 --- a/src/components/Dropdown/styles.module.scss +++ b/src/components/Dropdown/styles.module.scss @@ -1,4 +1,4 @@ -@import "/styles/variables.scss"; +@use "/styles/variables" as *; .container { position: relative; diff --git a/src/components/HeadingPermalink/index.tsx b/src/components/HeadingPermalink/index.tsx new file mode 100644 index 0000000000..63f92d3b30 --- /dev/null +++ b/src/components/HeadingPermalink/index.tsx @@ -0,0 +1,62 @@ +import type { ComponentChildren, HTMLAttributes, VNode } from "preact"; + +type HeadingTag = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; + +type HeadingProps = HTMLAttributes & { + as: HeadingTag; + children?: ComponentChildren; + id?: string; +}; + +type HeadingOverrideProps = Omit; + +const joinClasses = (...classes: unknown[]) => + classes + .filter((value): value is string => typeof value === "string") + .join(" "); + +export const HeadingPermalink = ({ + as: Tag, + children, + class: classProp, + className, + id, + ...props +}: HeadingProps) => { + const hasPermalink = typeof id === "string" && id.length > 0; + + const mergedClassName = joinClasses( + "heading-permalink", + hasPermalink && "heading-permalink--enabled", + classProp, + className, + ); + + return ( + + {hasPermalink ? ( + + {children} + + ) : ( + children + )} + + ); +}; + +const createHeadingOverride = (as: HeadingTag) => { + const Component = (props: HeadingOverrideProps): VNode => ( + + ); + + Component.displayName = `HeadingPermalink${as.toUpperCase()}`; + return Component; +}; + +export const HeadingPermalinkH1 = createHeadingOverride("h1"); +export const HeadingPermalinkH2 = createHeadingOverride("h2"); +export const HeadingPermalinkH3 = createHeadingOverride("h3"); +export const HeadingPermalinkH4 = createHeadingOverride("h4"); +export const HeadingPermalinkH5 = createHeadingOverride("h5"); +export const HeadingPermalinkH6 = createHeadingOverride("h6"); diff --git a/src/components/Nav/styles.module.scss b/src/components/Nav/styles.module.scss index 9171252f57..7cdd7b5717 100644 --- a/src/components/Nav/styles.module.scss +++ b/src/components/Nav/styles.module.scss @@ -1,5 +1,4 @@ -@import "/styles/variables.scss"; -@import "/styles/global.scss"; +@use "/styles/variables" as *; .container { height: fit-content; diff --git a/src/components/Settings/styles.module.scss b/src/components/Settings/styles.module.scss index ee71f81177..c4473551fa 100644 --- a/src/components/Settings/styles.module.scss +++ b/src/components/Settings/styles.module.scss @@ -1,4 +1,4 @@ -@import "/styles/variables.scss"; +@use "/styles/variables" as *; .container { position: relative; diff --git a/src/layouts/ContributorDocLayout.astro b/src/layouts/ContributorDocLayout.astro index 2bb65aa025..1d9ae8f569 100644 --- a/src/layouts/ContributorDocLayout.astro +++ b/src/layouts/ContributorDocLayout.astro @@ -9,6 +9,14 @@ import { type JumpToState, } from "../globals/state"; import { getCurrentLocale, getUiTranslator } from "../i18n/utils"; +import { + HeadingPermalinkH1, + HeadingPermalinkH2, + HeadingPermalinkH3, + HeadingPermalinkH4, + HeadingPermalinkH5, + HeadingPermalinkH6, +} from "@components/HeadingPermalink"; interface Props { entry: CollectionEntry<"contributor-docs">; @@ -18,7 +26,7 @@ const currentLocale = getCurrentLocale(Astro.url.pathname); const t = await getUiTranslator(currentLocale); const { entry } = Astro.props; -const { Content, headings } = await entry.render(); +const { Content, components, headings } = await entry.render(); const jumpToLinks = headings .filter((heading: MarkdownHeading) => heading.depth <= 3) @@ -45,6 +53,16 @@ setJumpToState(jumpToState); className="contribute" >
- +
diff --git a/src/layouts/HomepageLayout.astro b/src/layouts/HomepageLayout.astro index 42558cab26..4682660268 100644 --- a/src/layouts/HomepageLayout.astro +++ b/src/layouts/HomepageLayout.astro @@ -104,7 +104,7 @@ setJumpToState(null);