Skip to content

Commit

Permalink
fix(table-of-contents): fix bugs when scrolling fast or following anc…
Browse files Browse the repository at this point in the history
…hor links
  • Loading branch information
leonardoAnjos16 committed Jun 22, 2022
1 parent 62f1059 commit 2335fcc
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 43 deletions.
37 changes: 24 additions & 13 deletions src/components/markdown-renderer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<InView
rootMargin="-40% 0px"
threshold={1}
rootMargin="0px 0px -50% 0px"
onChange={(inView, entry) => {
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)
}}
>
<h2 id={slug} {...props} />
<h2 id={slug} className={styles.header} {...props} />
</InView>
)
},
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 (
<InView
rootMargin="-40% 0px"
threshold={1}
rootMargin="0px 0px -50% 0px"
onChange={(inView, entry) => {
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)
}}
>
<h3 id={slug} {...props} />
<h3 id={slug} className={styles.header} {...props} />
</InView>
)
},
Expand Down
9 changes: 9 additions & 0 deletions src/components/markdown-renderer/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
17 changes: 12 additions & 5 deletions src/components/table-of-contents/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -31,7 +31,14 @@ const TableOfContents = () => {
}) => {
return (
<Link href={`#${slug}`}>
<a>
<a
onClick={() => {
setActiveItem(({ item }) => ({
item: level === 1 ? slug : item,
subItem: level === 1 ? '' : slug,
}))
}}
>
<Text sx={styles.item(level, active)}>{title}</Text>
</a>
</Link>
Expand All @@ -46,17 +53,17 @@ 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 && (
<Box sx={styles.subItemsContainer}>
{item.children.map((subItem) => (
<Item
key={subItem.slug}
title={subItem.title}
slug={subItem.slug}
level={2}
active={subItem.slug === activeSubItem}
active={subItem.slug === activeItem.subItem}
/>
))}
</Box>
Expand Down
65 changes: 40 additions & 25 deletions src/utils/contexts/api-guide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<SetStateAction<string>>
setActiveSubItem: Dispatch<SetStateAction<string>>
activeItem: ActiveItem
setActiveItem: Dispatch<SetStateAction<ActiveItem>>
goToPreviousItem: () => void
goToPreviousSubItem: () => void
}

export const APIGuideContext = createContext<ContextType>({
headers: [],
activeItem: '',
activeSubItem: '',
activeItem: {
item: '',
subItem: '',
},
setActiveItem: () => undefined,
setActiveSubItem: () => undefined,
goToPreviousItem: () => undefined,
goToPreviousSubItem: () => undefined,
})
Expand All @@ -28,40 +32,51 @@ interface Props {
}

const APIGuideContextProvider: React.FC<Props> = ({ children, headers }) => {
const [activeItem, setActiveItem] = useState('')
const [activeSubItem, setActiveSubItem] = useState('')
const [activeItem, setActiveItem] = useState<ActiveItem>({
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 (
<APIGuideContext.Provider
value={{
headers,
activeItem,
activeSubItem,
setActiveItem,
setActiveSubItem,
goToPreviousItem,
goToPreviousSubItem,
}}
Expand Down

0 comments on commit 2335fcc

Please sign in to comment.