fix(theme): Fix CSS `scroll-margin-top` when clicking footnote items, factorize code (#11466)

This commit is contained in:
Sébastien Lorber 2025-10-09 12:34:54 +02:00 committed by GitHub
parent 598af3b8e8
commit ac630f8279
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 211 additions and 75 deletions

View File

@ -8,18 +8,16 @@
import React, {type ReactNode} from 'react';
import clsx from 'clsx';
import {translate} from '@docusaurus/Translate';
import {useThemeConfig} from '@docusaurus/theme-common';
import {useAnchorTargetClassName} from '@docusaurus/theme-common';
import Link from '@docusaurus/Link';
import useBrokenLinks from '@docusaurus/useBrokenLinks';
import type {Props} from '@theme/Heading';
import styles from './styles.module.css';
import './styles.module.css';
export default function Heading({as: As, id, ...props}: Props): ReactNode {
const brokenLinks = useBrokenLinks();
const {
navbar: {hideOnScroll},
} = useThemeConfig();
const anchorTargetClassName = useAnchorTargetClassName(id);
// H1 headings do not need an id because they don't appear in the TOC.
if (As === 'h1' || !id) {
return <As {...props} id={undefined} />;
@ -41,13 +39,7 @@ export default function Heading({as: As, id, ...props}: Props): ReactNode {
return (
<As
{...props}
className={clsx(
'anchor',
hideOnScroll
? styles.anchorWithHideOnScrollNavbar
: styles.anchorWithStickyNavbar,
props.className,
)}
className={clsx('anchor', anchorTargetClassName, props.className)}
id={id}>
{props.children}
<Link

View File

@ -5,19 +5,6 @@
* LICENSE file in the root directory of this source tree.
*/
/*
When the navbar is sticky, ensure that on anchor click,
the browser does not scroll that anchor behind the navbar
See https://x.com/JoshWComeau/status/1332015868725891076
*/
.anchorWithStickyNavbar {
scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem);
}
.anchorWithHideOnScrollNavbar {
scroll-margin-top: 0.5rem;
}
:global(.hash-link) {
opacity: 0;
padding-left: 0.5rem;

View File

@ -8,35 +8,14 @@
import React, {type ReactNode} from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import {useThemeConfig} from '@docusaurus/theme-common';
import {useAnchorTargetClassName} from '@docusaurus/theme-common';
import type {Props} from '@theme/MDXComponents/A';
import styles from './styles.module.css';
function isFootnoteRef(props: Props) {
return props['data-footnote-ref'] === true;
}
function FootnoteRefLink(props: Props) {
const {
navbar: {hideOnScroll},
} = useThemeConfig();
return (
<Link
{...props}
className={clsx(
hideOnScroll
? styles.footnoteRefHideOnScrollNavbar
: styles.footnoteRefStickyNavbar,
props.className,
)}
/>
);
}
export default function MDXA(props: Props): ReactNode {
if (isFootnoteRef(props)) {
return <FootnoteRefLink {...props} />;
}
return <Link {...props} />;
// MDX Footnotes have ids such as <a id="user-content-fn-1-953011" ...>
const anchorTargetClassName = useAnchorTargetClassName(props.id);
return (
<Link {...props} className={clsx(anchorTargetClassName, props.className)} />
);
}

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
When the navbar is sticky, ensure that on footnote click,
the browser does not scroll to the ref behind the navbar
See https://github.com/facebook/docusaurus/issues/11232
See also headings case https://x.com/JoshWComeau/status/1332015868725891076
*/
.footnoteRefStickyNavbar {
scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem);
}
.footnoteRefHideOnScrollNavbar {
scroll-margin-top: 0.5rem;
}

View File

@ -6,12 +6,17 @@
*/
import React, {type ReactNode} from 'react';
import clsx from 'clsx';
import useBrokenLinks from '@docusaurus/useBrokenLinks';
import {useAnchorTargetClassName} from '@docusaurus/theme-common';
import type {Props} from '@theme/MDXComponents/Li';
export default function MDXLi(props: Props): ReactNode | undefined {
// MDX Footnotes have ids such as <li id="user-content-fn-1-953011">
useBrokenLinks().collectAnchor(props.id);
const anchorTargetClassName = useAnchorTargetClassName(props.id);
return <li {...props} />;
return (
<li className={clsx(anchorTargetClassName, props.className)} {...props} />
);
}

View File

@ -109,6 +109,8 @@ export {
useSearchLinkCreator,
} from './hooks/useSearchPage';
export {useAnchorTargetClassName} from './utils/anchorUtils';
export {isMultiColumnFooterLinks} from './utils/footerUtils';
export {isRegexpStringMatch} from './utils/regexpUtils';

View File

@ -0,0 +1,14 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
.anchorTargetStickyNavbar {
scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem);
}
.anchorTargetHideOnScrollNavbar {
scroll-margin-top: 0.5rem;
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {useThemeConfig} from './useThemeConfig';
import styles from './anchorUtils.module.css';
/**
* When the navbar is sticky, this ensures that when clicking a hash link,
* we do not navigate to an anchor that will appear below the navbar.
* This happens in particular for MDX headings and footnotes.
*
* See https://github.com/facebook/docusaurus/issues/11232
* See also headings case https://x.com/JoshWComeau/status/1332015868725891076
*/
export function useAnchorTargetClassName(
id: string | undefined,
): string | undefined {
const {
navbar: {hideOnScroll},
} = useThemeConfig();
if (typeof id === 'undefined') {
return undefined;
}
return hideOnScroll
? styles.anchorTargetHideOnScrollNavbar
: styles.anchorTargetStickyNavbar;
}

View File

@ -0,0 +1,146 @@
# Footnotes
Lorem ipsum dolor sit amet [^1] [^2]
Lorem ipsum dolor sit amet [^3]
Lorem ipsum dolor sit amet [^4]
Lorem ipsum dolor sit amet [^5]
Lorem ipsum dolor sit amet [^6]
Lorem ipsum dolor sit amet [^7]
Lorem ipsum dolor sit amet [^8]
Lorem ipsum dolor sit amet [^9]
Lorem ipsum dolor sit amet [^10]
Lorem ipsum dolor sit amet [^11]
Lorem ipsum dolor sit amet [^12]
Lorem ipsum dolor sit amet [^13]
Lorem ipsum dolor sit amet [^14]
Lorem ipsum dolor sit amet [^15]
Lorem ipsum dolor sit amet [^16]
Lorem ipsum dolor sit amet [^17]
Lorem ipsum dolor sit amet [^18]
Lorem ipsum dolor sit amet [^19]
Lorem ipsum dolor sit amet [^20]
Lorem ipsum dolor sit amet [^21] [^22]
Lorem ipsum dolor sit amet [^23]
Lorem ipsum dolor sit amet [^24]
Lorem ipsum dolor sit amet [^25]
Lorem ipsum dolor sit amet [^26]
Lorem ipsum dolor sit amet [^27]
Lorem ipsum dolor sit amet [^28]
Lorem ipsum dolor sit amet [^29]
Lorem ipsum dolor sit amet [^30]
Lorem ipsum dolor sit amet [^31]
Lorem ipsum dolor sit amet [^32]
Lorem ipsum dolor sit amet [^33]
Lorem ipsum dolor sit amet [^34]
Lorem ipsum dolor sit amet [^35]
Lorem ipsum dolor sit amet [^36]
Lorem ipsum dolor sit amet [^37]
Lorem ipsum dolor sit amet [^38]
Lorem ipsum dolor sit amet [^39]
Lorem ipsum dolor sit amet [^40]
Lorem ipsum dolor sit amet [^41]
Lorem ipsum dolor sit amet [^42]
Lorem ipsum dolor sit amet [^43]
Lorem ipsum dolor sit amet [^44]
Lorem ipsum dolor sit amet [^45]
Lorem ipsum dolor sit amet [^46]
Lorem ipsum dolor sit amet [^47]
Lorem ipsum dolor sit amet [^48]
Lorem ipsum dolor sit amet [^49] [^50]
[^1]: footnote 1
[^2]: footnote 2
[^3]: footnote 3
[^4]: footnote 4
[^5]: footnote 5
[^6]: footnote 6
[^7]: footnote 7
[^8]: footnote 8
[^9]: footnote 9
[^10]: footnote 10
[^11]: footnote 11
[^12]: footnote 12
[^13]: footnote 13
[^14]: footnote 14
[^15]: footnote 15
[^16]: footnote 16
[^17]: footnote 17
[^18]: footnote 18
[^19]: footnote 19
[^20]: footnote 20
[^21]: footnote 21
[^22]: footnote 22
[^23]: footnote 23
[^24]: footnote 24
[^25]: footnote 25
[^26]: footnote 26
[^27]: footnote 27
[^28]: footnote 28
[^29]: footnote 29
[^30]: footnote 30
[^31]: footnote 31
[^32]: footnote 32
[^33]: footnote 33
[^34]: footnote 34
[^35]: footnote 35
[^36]: footnote 36
[^37]: footnote 37
[^38]: footnote 38
[^39]: footnote 39
[^40]: footnote 40
[^41]: footnote 41
[^42]: footnote 42
[^43]: footnote 43
[^44]: footnote 44
[^45]: footnote 45
[^46]: footnote 46
[^47]: footnote 47
[^48]: footnote 48
[^49]: footnote 49
[^50]: footnote 50