-
-
Notifications
You must be signed in to change notification settings - Fork 229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🎉 Entry Emulator - sidebar table of contents #2869
Changes from all commits
a0784ed
a4d3ed3
c4830d7
696d80d
55b7fd4
f760aa8
9ae7422
edda1fc
013e4f3
9919f7e
a306481
2f7def2
1b88d99
112cd11
7aaf4bb
71b46ec
3baee54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,10 @@ interface TableOfContentsData { | |
headings: TocHeading[] | ||
pageTitle: string | ||
hideSubheadings?: boolean | ||
headingLevels?: { | ||
primary: number | ||
secondary: number | ||
} | ||
} | ||
|
||
const isRecordTopViewport = (record: IntersectionObserverEntry) => { | ||
|
@@ -34,17 +38,36 @@ export const TableOfContents = ({ | |
headings, | ||
pageTitle, | ||
hideSubheadings, | ||
// Original WP articles used a hierarchy of h2 and h3 headings | ||
// New Gdoc articles use a hierarchy of h1 and h2 headings | ||
headingLevels = { | ||
primary: 2, | ||
secondary: 3, | ||
}, | ||
}: TableOfContentsData) => { | ||
const [isOpen, setIsOpen] = useState(false) | ||
const [activeHeading, setActiveHeading] = useState("") | ||
const { primary, secondary } = headingLevels | ||
const tocRef = useRef<HTMLElement>(null) | ||
|
||
const toggleIsOpen = () => { | ||
setIsOpen(!isOpen) | ||
} | ||
// The Gdocs sidebar can't rely on the same CSS logic that old-style entries use, so we need to | ||
// explicitly trigger these toggles based on screen width | ||
const toggleIsOpenOnMobile = () => { | ||
if (window.innerWidth < 1536) { | ||
toggleIsOpen() | ||
} | ||
} | ||
|
||
useTriggerWhenClickOutside(tocRef, isOpen, setIsOpen) | ||
|
||
// Open the sidebar on desktop by default when mounting | ||
useEffect(() => { | ||
setIsOpen(window.innerWidth >= 1536) | ||
}, []) | ||
|
||
useEffect(() => { | ||
if ("IntersectionObserver" in window) { | ||
const previousHeadings = headings.map((heading, i) => ({ | ||
|
@@ -107,16 +130,26 @@ export const TableOfContents = ({ | |
) | ||
|
||
let contentHeadings = null | ||
// In Gdocs articles, these sections are ID'd via unique elements | ||
const appendixDivs = | ||
", h3#article-endnotes, section#article-citation, section#article-licence" | ||
if (hideSubheadings) { | ||
contentHeadings = document.querySelectorAll("h2") | ||
contentHeadings = document.querySelectorAll( | ||
`h${secondary} ${appendixDivs}` | ||
) | ||
} else { | ||
contentHeadings = document.querySelectorAll("h2, h3") | ||
contentHeadings = document.querySelectorAll( | ||
`h${primary}, h${secondary} ${appendixDivs}` | ||
) | ||
} | ||
contentHeadings.forEach((contentHeading) => { | ||
observer.observe(contentHeading) | ||
}) | ||
|
||
return () => observer.disconnect() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice! |
||
} | ||
}, [headings, hideSubheadings]) | ||
return | ||
}, [headings, hideSubheadings, primary, secondary]) | ||
|
||
return ( | ||
<div className={TOC_WRAPPER_CLASSNAME}> | ||
|
@@ -131,7 +164,7 @@ export const TableOfContents = ({ | |
<li> | ||
<a | ||
onClick={() => { | ||
toggleIsOpen() | ||
toggleIsOpenOnMobile() | ||
setActiveHeading("") | ||
}} | ||
href="#" | ||
|
@@ -159,7 +192,7 @@ export const TableOfContents = ({ | |
} | ||
> | ||
<a | ||
onClick={toggleIsOpen} | ||
onClick={toggleIsOpenOnMobile} | ||
href={`#${heading.slug}`} | ||
data-track-note="toc_link" | ||
> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm sure hover states (toggle, title vs close) are on your radar, just making a note here for good measure. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,123 @@ | |
color: $vermillion !important; | ||
} | ||
} | ||
|
||
.toc-wrapper { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A lot of this duplicates the existing site sidebar CSS because much of that CSS is stuck inside media queries (because for old-style entries, the sidebar is always open on xxlg, which we can't do here because it would obscure full-width content beneath it) |
||
position: sticky; | ||
top: 0; | ||
height: 0; | ||
// Above explorer chrome | ||
z-index: 3; | ||
margin-top: -48px; | ||
.entry-sidebar { | ||
height: 100vh; | ||
position: absolute; | ||
transition: margin 300ms ease; | ||
width: 400px; | ||
margin-left: -400px; | ||
box-shadow: none; | ||
@include sm-only { | ||
width: 100vw; | ||
margin-left: -100vw; | ||
} | ||
@include sm-up { | ||
ul { | ||
margin-left: 32px; | ||
} | ||
} | ||
|
||
li { | ||
&:first-child { | ||
margin-top: 36px; | ||
} | ||
|
||
&.section { | ||
margin-top: 20px; | ||
} | ||
&.subsection a { | ||
color: $blue-60; | ||
margin-left: 16px; | ||
line-height: 1.125em; | ||
} | ||
&.active a { | ||
border-left-color: $vermillion; | ||
background: unset; | ||
font-weight: bold; | ||
} | ||
a { | ||
padding-left: 16px; | ||
color: $blue-90; | ||
border-width: 4px; | ||
padding-right: 32px; | ||
margin-left: 0; | ||
font-weight: 400; | ||
|
||
&:hover { | ||
background: none; | ||
text-decoration: underline; | ||
} | ||
} | ||
} | ||
|
||
.toggle-toc { | ||
margin-left: 0; | ||
transform: translateX(calc(100% + 16px)); | ||
position: absolute; | ||
top: 0; | ||
bottom: 0; | ||
right: 0; | ||
padding: 16px 0; | ||
pointer-events: none; | ||
display: unset; | ||
transition: transform 300ms ease; | ||
button { | ||
@include popover-box-button; | ||
z-index: 20; | ||
position: sticky; | ||
top: 16px; | ||
pointer-events: auto; | ||
white-space: nowrap; | ||
box-shadow: none; | ||
background: #fff; | ||
border: 1px solid $blue-20; | ||
line-height: 1.25; | ||
padding: 6px; | ||
border-radius: 4px; | ||
|
||
&:hover { | ||
background: #fff; | ||
svg { | ||
color: $blue-100; | ||
} | ||
} | ||
svg { | ||
margin-right: 0; | ||
color: $blue-90; | ||
height: 12px; | ||
} | ||
|
||
span { | ||
color: $blue-90; | ||
margin-left: 5px; | ||
position: relative; | ||
top: 1px; | ||
} | ||
} | ||
} | ||
&.entry-sidebar--is-open { | ||
margin-left: 0; | ||
.toggle-toc { | ||
transform: translateX(-16px); | ||
button { | ||
border: none; | ||
span { | ||
display: none; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
$banner-height: 200px; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I came across two headings with the same title/slug in time-use ("Additional information"), which made them both light up at the same time in the ToC. We had a provision in WP code to handle this special case but it hasn't been ported over yet. It is not the most critical bug but it can be quite visible so I'm a bit on the fence as to whether to make the fix par of this PR.
Here is an example doc: https://docs.google.com/document/d/1uYZEcCP2shwa5U1EYaGb6cRvLFEz0MrUJv9m8UASbqs/edit