feat: implement scrollspy

This commit is contained in:
Jean-Philippe Sirois
2020-05-28 00:43:23 -04:00
parent 8edb4358f9
commit d1c375ecf1
7 changed files with 78 additions and 59 deletions
-6
View File
@@ -73,12 +73,6 @@ module.exports = {
},
},
`gatsby-transformer-sharp`,
{
resolve: "gatsby-plugin-anchor-links",
options: {
offset: -100,
},
},
`gatsby-plugin-react-svg`,
`gatsby-plugin-remove-trailing-slashes`,
`gatsby-plugin-sharp`,
+19 -24
View File
@@ -3753,6 +3753,15 @@
"@types/react": "*"
}
},
"@types/react-scrollspy": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@types/react-scrollspy/-/react-scrollspy-3.3.2.tgz",
"integrity": "sha512-nbCB4wg2AhsFWKZ0LnMqGZWWC+6fNOC4USCzvBC++YST7tLMGvQKLVdWCQHec5/HzSlrSD4UfnYYVcuSYIhvmg==",
"dev": true,
"requires": {
"@types/react": "*"
}
},
"@types/rimraf": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.4.tgz",
@@ -10151,14 +10160,6 @@
}
}
},
"gatsby-plugin-anchor-links": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/gatsby-plugin-anchor-links/-/gatsby-plugin-anchor-links-1.1.1.tgz",
"integrity": "sha512-mgSHUAEa7RRWD/lcmJVUWD9mIT14EIJvMPcxJ1W2Ev+rNuqBBpk1j4vDWy1uxas+qusi0vrtZ4HdU7mse12qDw==",
"requires": {
"scroll-to-element": "^2.0.3"
}
},
"gatsby-plugin-google-fonts": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gatsby-plugin-google-fonts/-/gatsby-plugin-google-fonts-1.0.1.tgz",
@@ -17348,14 +17349,6 @@
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
},
"raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"requires": {
"performance-now": "^2.1.0"
}
},
"ramda": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.0.tgz",
@@ -17734,6 +17727,16 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.7.2.tgz",
"integrity": "sha512-u5l7fhAJXecWUJzVxzMRU2Zvw8m4QmDNHlTrT5uo3KBlYBhmChd7syAakBoay1yIiVhx/8Fi7a6v6kQZfsw81Q=="
},
"react-scrollspy": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/react-scrollspy/-/react-scrollspy-3.4.2.tgz",
"integrity": "sha512-mFev6IBPPGdn0WsAiS0eCoHGUUet0ZRZUHHUw3qLcsvsQZPlubik0N9cf8CcgIbdVzNd04K9L9A0inHhPt7drw==",
"requires": {
"babel-runtime": "^6.26.0",
"classnames": "^2.2.5",
"prop-types": "^15.5.10"
}
},
"react-side-effect": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz",
@@ -18716,14 +18719,6 @@
"invariant": "^2.2.4"
}
},
"scroll-to-element": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/scroll-to-element/-/scroll-to-element-2.0.3.tgz",
"integrity": "sha512-5herPcm9jMfQgRwu94lH5mei+2YhipR4RQ2nAvnBxJb2tG+P7O0ctOKAaAZBXbBejnn+MImh3wrAUA5EcLnjEQ==",
"requires": {
"raf": "^3.4.0"
}
},
"scuid": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz",
+2 -1
View File
@@ -33,7 +33,6 @@
"@reach/router": "^1.3.3",
"classnames": "^2.2.6",
"gatsby": "^2.22.11",
"gatsby-plugin-anchor-links": "^1.1.1",
"gatsby-plugin-google-fonts": "^1.0.1",
"gatsby-plugin-layout": "^1.3.2",
"gatsby-plugin-manifest": "^2.4.9",
@@ -57,6 +56,7 @@
"react-highlight": "^0.12.0",
"react-particles-js": "^3.2.1",
"react-perfect-scrollbar": "^1.5.8",
"react-scrollspy": "^3.4.2",
"styled-components": "^5.1.1",
"typescript": "^3.9.3"
},
@@ -72,6 +72,7 @@
"@types/react": "^16.9.35",
"@types/react-dom": "^16.9.8",
"@types/react-helmet": "^6.0.0",
"@types/react-scrollspy": "^3.3.2",
"@types/styled-components": "^5.1.0",
"babel-plugin-polished": "^1.1.0",
"babel-plugin-styled-components": "^1.10.7",
+1 -1
View File
@@ -16,7 +16,7 @@ export const PageSidebarLink: FC<IPageSidebarLinkProps> = ({
type = "internal",
}) => {
if (type === "anchor") {
return <SC.Anchor to={href}>{text}</SC.Anchor>
return <SC.Anchor href={href}>{text}</SC.Anchor>
}
if (type === "external") {
+1 -2
View File
@@ -1,4 +1,3 @@
import { AnchorLink } from "gatsby-plugin-anchor-links"
import styled, { css } from "styled-components"
import { Link } from "gatsby"
@@ -13,7 +12,7 @@ const extraLink = css`
}
`
export const Anchor = styled(AnchorLink)`
export const Anchor = styled.a`
${extraLink};
`
+31 -20
View File
@@ -1,4 +1,6 @@
import cx from "classnames"
import React, { FC } from "react"
import Scrollspy from "react-scrollspy"
import { PageSidebarLink } from "../PageSidebarLink"
import { ITocItem } from "../../types"
import * as SC from "./styles"
@@ -24,31 +26,40 @@ function extractTitle(title: string): ITitle {
}
export const Toc: FC<ITocProps> = ({ header, items }) => {
const windowGlobal = typeof window !== "undefined" && window
const [scrollSpyCache, setScrollSpyCache] = React.useState<string>()
// window is not available on build
if (!windowGlobal) {
return null
}
const { pathname } = windowGlobal.location
return (
<SC.TocWrapper>
{header}
{items.map((item) => {
const { prefix, title } = extractTitle(item.title)
<Scrollspy
items={items.map((item) => item.link.substring(1))}
currentClassName="scrollspy-current"
scrolledPastClassName="scrollspy-past"
componentTag={SC.ScrollspyWrapper}
onUpdate={(updatedElement: HTMLDivElement) => {
// @ts-ignore
const id = updatedElement?.id
if (id) {
setScrollSpyCache(id)
}
}}
>
{items.map((item) => {
const { prefix, title } = extractTitle(item.title)
return (
<SC.TocItem key={item.link} className={`depth-${item.depth}`}>
{prefix}
<PageSidebarLink
href={`${pathname}${item.link}`}
text={title}
type="anchor"
/>
</SC.TocItem>
)
})}
return (
<SC.TocItem
key={item.link}
className={cx(`depth-${item.depth}`, {
"scrollspy-cached": scrollSpyCache === item.link.substring(1),
})}
>
{prefix}
<PageSidebarLink href={item.link} text={title} type="anchor" />
</SC.TocItem>
)
})}
</Scrollspy>
</SC.TocWrapper>
)
}
+24 -5
View File
@@ -5,15 +5,34 @@ export const TocWrapper = styled.div`
flex-direction: column;
`
export const ScrollspyWrapper = styled.div`
display: flex;
flex-direction: column;
`
export const TocItem = styled.div`
display: inline-block;
&.depth-3 {
margin-left: 8px;
&.scrollspy-current,
&.scrollspy-cached {
position: relative;
text-shadow: 0 0 0.65px ${(props) => props.theme.main.foreground},
0 0 0.65px ${(props) => props.theme.main.foreground};
&::after {
position: absolute;
top: 0;
left: -16px;
content: "";
display: block;
background: ${(props) => props.theme.main.foreground};
width: 2px;
height: 100%;
}
}
&.depth-2 + &.depth-2,
&.depth-3 + &.depth-2 {
margin-top: 8px;
&.depth-2,
&.depth-3 {
padding: 4px 0;
}
`