From ed78001e12ebba11664351b42380c0d383617983 Mon Sep 17 00:00:00 2001 From: Simon Byford Date: Wed, 9 Apr 2025 14:50:36 +0100 Subject: [PATCH 1/2] ToC should always be sticky --- .../src/components/TableOfContents.importable.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dotcom-rendering/src/components/TableOfContents.importable.tsx b/dotcom-rendering/src/components/TableOfContents.importable.tsx index 048b9c055a5..2d9b968525c 100644 --- a/dotcom-rendering/src/components/TableOfContents.importable.tsx +++ b/dotcom-rendering/src/components/TableOfContents.importable.tsx @@ -141,10 +141,7 @@ export const TableOfContents = ({ tableOfContents, format }: Props) => { return (
5 ? stickyStyles : undefined, - ]} + css={[detailsStyles, stickyStyles]} data-component="table-of-contents" > Date: Wed, 9 Apr 2025 15:33:43 +0100 Subject: [PATCH 2/2] Close ToC when it sticks --- .../components/TableOfContents.importable.tsx | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/dotcom-rendering/src/components/TableOfContents.importable.tsx b/dotcom-rendering/src/components/TableOfContents.importable.tsx index 2d9b968525c..24b412f3d09 100644 --- a/dotcom-rendering/src/components/TableOfContents.importable.tsx +++ b/dotcom-rendering/src/components/TableOfContents.importable.tsx @@ -9,7 +9,7 @@ import { SvgChevronDownSingle, SvgChevronUpSingle, } from '@guardian/source/react-components'; -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { ArticleDisplay, type ArticleFormat } from '../lib/articleFormat'; import { getZIndex } from '../lib/getZIndex'; import type { TableOfContentsItem } from '../model/enhanceTableOfContents'; @@ -75,7 +75,7 @@ const detailsStyles = css` `; const stickyStyles = css` position: sticky; - top: 0; + top: -1px; background: ${palette('--article-background')}; z-index: ${getZIndex('tableOfContents')}; summary { @@ -137,9 +137,37 @@ const verticalStyle = css` export const TableOfContents = ({ tableOfContents, format }: Props) => { const [open, setOpen] = useState(tableOfContents.length < 5); + const tocRef = useRef(null); + + // Automatically collapse the ToC when it becomes sticky (i.e. when it reaches the top of the viewport). This + // approach is inspired by: + // https://css-tricks.com/how-to-detect-when-a-sticky-element-gets-pinned/ + useEffect(() => { + const tocElement = tocRef.current; + + if (!tocElement) return; + + const observer = new IntersectionObserver( + ([e]) => { + // Verify whether the ToC is at the top of the viewport or the bottom. It should only collapse when it + // reaches the top. + if (e && e.boundingClientRect.top < 0) { + setOpen(false); + } + }, + { threshold: [1] }, + ); + + observer.observe(tocElement); + + return () => { + observer.disconnect(); + }; + }, []); return (