From 2335fcc79c5a1f5270c629c13a719a77bd670415 Mon Sep 17 00:00:00 2001 From: Leonardo Anjos Date: Mon, 13 Jun 2022 17:17:02 -0300 Subject: [PATCH] fix(table-of-contents): fix bugs when scrolling fast or following anchor links --- src/components/markdown-renderer/index.tsx | 37 +++++++---- .../markdown-renderer/styles.module.css | 9 +++ src/components/table-of-contents/index.tsx | 17 +++-- src/utils/contexts/api-guide.tsx | 65 ++++++++++++------- 4 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/components/markdown-renderer/index.tsx b/src/components/markdown-renderer/index.tsx index 1722165db..2f07c6bbc 100644 --- a/src/components/markdown-renderer/index.tsx +++ b/src/components/markdown-renderer/index.tsx @@ -28,50 +28,61 @@ const components = { ), h2: ({ node, ...props }: Component) => { const [y, setY] = useState(Infinity) - const { activeItem, setActiveItem, setActiveSubItem, goToPreviousItem } = + const { activeItem, setActiveItem, goToPreviousItem } = useContext(APIGuideContext) const slug = slugify(childrenToString(props.children)) return ( { if (inView) { - setActiveItem(slug) - if (activeItem !== slug) { - setActiveSubItem('') - } - } else if (entry.boundingClientRect.y > y && activeItem === slug) { + setActiveItem(({ item, subItem }) => ({ + item: slug, + subItem: item !== slug ? '' : subItem, + })) + } else if ( + entry.boundingClientRect.y > y && + activeItem.item === slug + ) { goToPreviousItem() } setY(entry.boundingClientRect.y) }} > -

+

) }, h3: ({ node, ...props }: Component) => { const [y, setY] = useState(Infinity) - const { activeSubItem, setActiveSubItem, goToPreviousSubItem } = + const { activeItem, setActiveItem, goToPreviousSubItem } = useContext(APIGuideContext) const slug = slugify(childrenToString(props.children)) return ( { if (inView) { - setActiveSubItem(slug) - } else if (entry.boundingClientRect.y > y && activeSubItem === slug) { + setActiveItem(({ item }) => ({ + item, + subItem: slug, + })) + } else if ( + entry.boundingClientRect.y > y && + activeItem.subItem === slug + ) { goToPreviousSubItem() } setY(entry.boundingClientRect.y) }} > -

+

) }, diff --git a/src/components/markdown-renderer/styles.module.css b/src/components/markdown-renderer/styles.module.css index 6951f37e2..0323b26e2 100644 --- a/src/components/markdown-renderer/styles.module.css +++ b/src/components/markdown-renderer/styles.module.css @@ -9,3 +9,12 @@ .img { max-width: 100%; } + +.header::before { + content: ""; + display: block; + height: calc(5rem + 16px); + margin-top: calc(-5rem - 16px); + visibility: hidden; + pointer-events: none; +} diff --git a/src/components/table-of-contents/index.tsx b/src/components/table-of-contents/index.tsx index a96950c65..b21dd620a 100644 --- a/src/components/table-of-contents/index.tsx +++ b/src/components/table-of-contents/index.tsx @@ -16,7 +16,7 @@ export interface Item extends SubItem { } const TableOfContents = () => { - const { headers, activeItem, activeSubItem } = useContext(APIGuideContext) + const { headers, activeItem, setActiveItem } = useContext(APIGuideContext) const Item = ({ title, @@ -31,7 +31,14 @@ const TableOfContents = () => { }) => { return ( - + { + setActiveItem(({ item }) => ({ + item: level === 1 ? slug : item, + subItem: level === 1 ? '' : slug, + })) + }} + > {title} @@ -46,9 +53,9 @@ const TableOfContents = () => { title={item.title} slug={item.slug} level={1} - active={item.slug === activeItem} + active={item.slug === activeItem.item} /> - {item.slug === activeItem && ( + {item.slug === activeItem.item && ( {item.children.map((subItem) => ( { title={subItem.title} slug={subItem.slug} level={2} - active={subItem.slug === activeSubItem} + active={subItem.slug === activeItem.subItem} /> ))} diff --git a/src/utils/contexts/api-guide.tsx b/src/utils/contexts/api-guide.tsx index b460185d3..35f06e9b8 100644 --- a/src/utils/contexts/api-guide.tsx +++ b/src/utils/contexts/api-guide.tsx @@ -3,22 +3,26 @@ import { createContext, useState } from 'react' import type { Item } from 'components/table-of-contents' +type ActiveItem = { + item: string + subItem: string +} + type ContextType = { headers: Item[] - activeItem: string - activeSubItem: string - setActiveItem: Dispatch> - setActiveSubItem: Dispatch> + activeItem: ActiveItem + setActiveItem: Dispatch> goToPreviousItem: () => void goToPreviousSubItem: () => void } export const APIGuideContext = createContext({ headers: [], - activeItem: '', - activeSubItem: '', + activeItem: { + item: '', + subItem: '', + }, setActiveItem: () => undefined, - setActiveSubItem: () => undefined, goToPreviousItem: () => undefined, goToPreviousSubItem: () => undefined, }) @@ -28,30 +32,43 @@ interface Props { } const APIGuideContextProvider: React.FC = ({ children, headers }) => { - const [activeItem, setActiveItem] = useState('') - const [activeSubItem, setActiveSubItem] = useState('') + const [activeItem, setActiveItem] = useState({ + item: '', + subItem: '', + }) const goToPreviousItem = () => { - const index = headers.findIndex((header) => header.slug === activeItem) - const previousSlug = !index ? '' : headers[index - 1].slug + setActiveItem(({ item, subItem }) => { + const index = headers.findIndex((header) => header.slug === item) + if (index === -1) return { item, subItem } - const previousChildren = !index ? [] : headers[index - 1].children - const subItem = !previousChildren.length - ? '' - : previousChildren.slice(-1)[0].slug + const previousItem = !index ? '' : headers[index - 1].slug + const previousChildren = !index ? [] : headers[index - 1].children + const previousSubItem = !previousChildren.length + ? '' + : previousChildren.slice(-1)[0].slug - setActiveItem(previousSlug) - setActiveSubItem(subItem) + return { + item: previousItem, + subItem: previousSubItem, + } + }) } const goToPreviousSubItem = () => { - const { children } = headers.find( - (header) => header.slug === activeItem - ) as Item + setActiveItem(({ item, subItem }) => { + const header = headers.find((header) => header.slug === item) + const index = header?.children.findIndex( + (child) => child.slug === subItem + ) + + if (!header || index === -1) return { item, subItem } - const index = children.findIndex((child) => child.slug === activeSubItem) - const previousSlug = !index ? '' : children[index - 1].slug - setActiveSubItem(previousSlug) + return { + item, + subItem: !index ? '' : header.children[index - 1].slug, + } + }) } return ( @@ -59,9 +76,7 @@ const APIGuideContextProvider: React.FC = ({ children, headers }) => { value={{ headers, activeItem, - activeSubItem, setActiveItem, - setActiveSubItem, goToPreviousItem, goToPreviousSubItem, }}