Skip to content

Commit 21b59b5

Browse files
graceparkrachmari
andauthored
Move mini tocs rest + collapsible (github#26069)
* starting minitocs change * moving rest mini tocs and adding functionality to collapse * adding some comments * update margins * minor updates: fix scroll to top button add padding to resthttpmethod verbs and background color for nested arrows in minitocs * Update components/rest/RestReferencePage.tsx Co-authored-by: Rachael Sewell <[email protected]> * Update components/rest/RestReferencePage.tsx Co-authored-by: Rachael Sewell <[email protected]> * fix comment * fix wording and z index * bring back articlegridlayout * updating margins for lg and xl Co-authored-by: Rachael Sewell <[email protected]>
1 parent 3b8b6e0 commit 21b59b5

File tree

6 files changed

+119
-59
lines changed

6 files changed

+119
-59
lines changed

components/DefaultLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export const DefaultLayout = (props: Props) => {
9393
style={{ height: '100vh' }}
9494
>
9595
<Header />
96-
<main id="main-content">
96+
<main id="main-content" style={{ scrollMarginTop: '5rem' }}>
9797
<DeprecationBanner />
9898
<RestBanner />
9999

components/rest/CodeBlock.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function CodeBlock({ verb, headingLang, codeBlock, highlight }: Props) {
5050
>
5151
<code>
5252
{verb && (
53-
<span className="color-bg-accent-emphasis color-fg-on-emphasis rounded-1 text-uppercase">
53+
<span className="color-bg-accent-emphasis color-fg-on-emphasis rounded-1 text-uppercase p-1">
5454
{verb}
5555
</span>
5656
)}{' '}

components/rest/RestParameterTable.module.scss

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.parameterTable {
22
table-layout: fixed !important;
3+
z-index: 0;
34

45
thead {
56
tr {

components/rest/RestReferencePage.tsx

+94-41
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import { useState, useEffect } from 'react'
1+
import React, { useState, useEffect } from 'react'
2+
import cx from 'classnames'
23
import { useRouter } from 'next/router'
34
import dynamic from 'next/dynamic'
45

56
import { DefaultLayout } from 'components/DefaultLayout'
6-
import { ArticleTitle } from 'components/article/ArticleTitle'
77
import { useMainContext } from 'components/context/MainContext'
88
import { MarkdownContent } from 'components/ui/MarkdownContent'
99
import { Lead } from 'components/ui/Lead'
10-
import { ArticleGridLayout } from 'components/article/ArticleGridLayout'
1110
import { MiniTocItem } from 'components/context/ArticleContext'
1211
import { RestCategoryOperationsT } from './types'
1312
import { RestOperation } from './RestOperation'
14-
import { MiniTocs } from 'components/ui/MiniTocs'
13+
import { ChevronDownIcon, ChevronUpIcon, SearchIcon } from '@primer/octicons-react'
14+
import { useTranslation } from 'components/hooks/useTranslation'
15+
import { ActionList } from '@primer/react'
1516

1617
const ClientSideHighlightJS = dynamic(() => import('components/article/ClientSideHighlightJS'), {
1718
ssr: false,
@@ -37,9 +38,11 @@ export const RestReferencePage = ({
3738
restOperations,
3839
miniTocItems,
3940
}: StructuredContentT) => {
41+
const { t } = useTranslation('pages')
4042
const { asPath } = useRouter()
4143
const { page } = useMainContext()
4244
const subcategories = Object.keys(restOperations)
45+
const [collapsed, setCollapsed] = useState({} as Record<number, boolean>)
4346

4447
// We have some one-off redirects for rest api docs
4548
// currently those are limited to the repos page, but
@@ -106,54 +109,104 @@ export const RestReferencePage = ({
106109
// consecutive one does.
107110
}, [asPath])
108111

112+
// Resetting the collapsed array when we move to another REST page
113+
useEffect(() => {
114+
setCollapsed({})
115+
}, [asPath])
116+
117+
const handleClick = (param: number) => {
118+
setCollapsed((prevState) => {
119+
return { ...prevState, [param]: !prevState[param] }
120+
})
121+
}
122+
123+
const renderTocItem = (item: MiniTocItem, index: number) => {
124+
return (
125+
<ActionList.Item
126+
as="li"
127+
key={item.contents}
128+
className={item.platform}
129+
sx={{ listStyle: 'none', padding: '2px' }}
130+
>
131+
<div className={cx('lh-condensed d-block width-full')}>
132+
<div className="d-inline-flex" dangerouslySetInnerHTML={{ __html: item.contents }} />
133+
{item.items && item.items.length > 0 && (
134+
<button
135+
className="background-transparent border-0 ml-1"
136+
onClick={() => handleClick(index)}
137+
>
138+
{!collapsed[index] ? <ChevronDownIcon /> : <ChevronUpIcon />}
139+
</button>
140+
)}
141+
{collapsed[index] && item.items && item.items.length > 0 ? (
142+
<ul className="ml-3">{item.items.map(renderTocItem)}</ul>
143+
) : null}
144+
</div>
145+
</ActionList.Item>
146+
)
147+
}
148+
109149
return (
110150
<DefaultLayout>
111151
{/* Doesn't matter *where* this is included because it will
112152
never render anything. It always just return null. */}
113153
{loadClientsideRedirectExceptions && <ClientSideRedirectExceptions />}
114154
{lazyLoadHighlightJS && <ClientSideHighlightJS />}
115155

116-
<div className="container-xl px-3 px-md-6 my-4">
117-
<ArticleGridLayout
118-
topper={<ArticleTitle>{page.title}</ArticleTitle>}
119-
intro={
120-
<>
121-
{page.introPlainText && (
122-
<Lead data-testid="lead" data-search="lead">
123-
{page.introPlainText}
124-
</Lead>
125-
)}
126-
</>
127-
}
128-
toc={
129-
<>
130-
{miniTocItems && miniTocItems.length > 1 && (
131-
<MiniTocs pageTitle={page.title} miniTocItems={miniTocItems} />
132-
)}
133-
</>
134-
}
135-
>
136-
<div key={`restCategory-introContent`}>
137-
<div dangerouslySetInnerHTML={{ __html: introContent }} />
156+
<div className="container-xl px-3 px-md-6 my-4 mx-xl-12 mx-lg-12">
157+
<h1>{page.title}</h1>
158+
{page.introPlainText && (
159+
<Lead data-testid="lead" data-search="lead" className="markdown-body">
160+
{page.introPlainText}
161+
</Lead>
162+
)}
163+
<div key={`restCategory-introContent`}>
164+
<div dangerouslySetInnerHTML={{ __html: introContent }} />
165+
</div>
166+
<div className="my-3 d-flex">
167+
<div className="pr-3 mt-1">
168+
<Circle className="color-fg-on-emphasis color-bg-emphasis">
169+
<SearchIcon className="" size={15} />
170+
</Circle>
138171
</div>
139172
<div id="article-contents">
140-
<MarkdownContent>
141-
{subcategories.map((subcategory, index) => (
142-
<div key={`restCategory-${index}`}>
143-
<div dangerouslySetInnerHTML={{ __html: descriptions[subcategory] }} />
144-
{restOperations[subcategory].map((operation, index) => (
145-
<RestOperation
146-
key={`restOperation-${index}`}
147-
operation={operation}
148-
index={index}
149-
/>
150-
))}
151-
</div>
152-
))}
153-
</MarkdownContent>
173+
<h3>{t('miniToc')}</h3>
174+
{miniTocItems && (
175+
<ActionList
176+
key={page.title}
177+
items={miniTocItems.map((items, i) => {
178+
return {
179+
key: page.title + i,
180+
text: page.title,
181+
renderItem: () => <ul>{renderTocItem(items, i)}</ul>,
182+
}
183+
})}
184+
/>
185+
)}
154186
</div>
155-
</ArticleGridLayout>
187+
</div>
188+
<MarkdownContent>
189+
{subcategories.map((subcategory, index) => (
190+
<div key={`restCategory-${index}`}>
191+
<div dangerouslySetInnerHTML={{ __html: descriptions[subcategory] }} />
192+
{restOperations[subcategory].map((operation, index) => (
193+
<RestOperation key={`restOperation-${index}`} operation={operation} index={index} />
194+
))}
195+
</div>
196+
))}
197+
</MarkdownContent>
156198
</div>
157199
</DefaultLayout>
158200
)
159201
}
202+
203+
const Circle = ({ className, children }: { className?: string; children?: React.ReactNode }) => {
204+
return (
205+
<div
206+
className={cx('circle d-flex flex-justify-center flex-items-center', className)}
207+
style={{ width: 24, height: 24 }}
208+
>
209+
{children}
210+
</div>
211+
)
212+
}

components/ui/ScrollButton/ScrollButton.tsx

+16-16
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,26 @@ export const ScrollButton = ({ className, ariaLabel }: ScrollButtonPropsT) => {
1111
const [show, setShow] = useState(false)
1212

1313
useEffect(() => {
14-
// show scroll button only when view is scrolled down
15-
const onScroll = function () {
16-
const y = document.documentElement.scrollTop // get the height from page top
17-
if (y < 100) {
18-
setShow(false)
19-
} else if (y >= 100) {
20-
setShow(true)
21-
}
22-
}
23-
window.addEventListener('scroll', onScroll)
14+
// We cannot determine document.documentElement.scrollTop height because we set the height: 100vh and set overflow to auto to keep the header sticky
15+
// That means window.scrollTop height is always 0
16+
// Using IntersectionObserver we can detemine if the h1 header is in view or not. If not, we show the scroll to top button, if so, we hide it
17+
const observer = new IntersectionObserver(
18+
function (entries) {
19+
if (entries[0].isIntersecting === false) {
20+
setShow(true)
21+
} else {
22+
setShow(false)
23+
}
24+
},
25+
{ threshold: [0] }
26+
)
2427

25-
return () => {
26-
window.removeEventListener('scroll', onScroll)
27-
}
28+
observer.observe(document.getElementsByTagName('h1')[0])
2829
}, [])
2930

3031
const onClick = () => {
31-
window.scrollTo(0, 0)
32-
const topOfPage = document.getElementById('github-logo')
33-
if (topOfPage) topOfPage.focus()
32+
document?.getElementById('github-logo')?.focus()
33+
document?.getElementById('main-content')?.scrollIntoView()
3434
}
3535

3636
return (

stylesheets/utilities.scss

+6
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@
7878
border-color: transparent !important;
7979
}
8080

81+
/* Background colors
82+
------------------------------------------------------------------------------*/
83+
.background-transparent {
84+
background-color: transparent;
85+
}
86+
8187
/* Widths / Heights
8288
------------------------------------------------------------------------------*/
8389
.max-w-xs {

0 commit comments

Comments
 (0)