Primer Update: Table of Contents (#22933)
* update to ActionList * fix nested mini tocs * adding key * remove li and fix tests * update font size to 14px * remove border radius
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import cx from 'classnames'
|
import cx from 'classnames'
|
||||||
import { Heading } from '@primer/components'
|
import { ActionList, Heading } from '@primer/components'
|
||||||
|
|
||||||
import { ZapIcon, InfoIcon, ShieldLockIcon } from '@primer/octicons-react'
|
import { ZapIcon, InfoIcon, ShieldLockIcon } from '@primer/octicons-react'
|
||||||
import { Callout } from 'components/ui/Callout'
|
import { Callout } from 'components/ui/Callout'
|
||||||
@@ -45,12 +45,19 @@ export const ArticlePage = () => {
|
|||||||
|
|
||||||
const renderTocItem = (item: MiniTocItem) => {
|
const renderTocItem = (item: MiniTocItem) => {
|
||||||
return (
|
return (
|
||||||
<li key={item.contents} className={cx(item.platform, 'mb-2 lh-condensed')}>
|
<ActionList.Item
|
||||||
<div className="mb-2 lh-condensed" dangerouslySetInnerHTML={{ __html: item.contents }} />
|
as="a"
|
||||||
|
href={item.link}
|
||||||
|
key={item.title}
|
||||||
|
sx={{ listStyle: 'none', padding: '2px', fontSize: '14px' }}
|
||||||
|
>
|
||||||
|
<div className={cx(item.platform)}>
|
||||||
|
{item.title}
|
||||||
{item.items && item.items.length > 0 ? (
|
{item.items && item.items.length > 0 ? (
|
||||||
<ul className="list-style-none pl-0 f5 mb-0 ml-3">{item.items.map(renderTocItem)}</ul>
|
<ul className="ml-3">{item.items.map(renderTocItem)}</ul>
|
||||||
) : null}
|
) : null}
|
||||||
</li>
|
</div>
|
||||||
|
</ActionList.Item>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,13 +118,18 @@ export const ArticlePage = () => {
|
|||||||
{miniTocItems.length > 1 && (
|
{miniTocItems.length > 1 && (
|
||||||
<>
|
<>
|
||||||
<Heading as="h2" fontSize={1} id="in-this-article" className="mb-1">
|
<Heading as="h2" fontSize={1} id="in-this-article" className="mb-1">
|
||||||
<a className="Link--primary" href="#in-this-article">
|
<Link href="#in-this-article">{t('miniToc')}</Link>
|
||||||
{t('miniToc')}
|
|
||||||
</a>
|
|
||||||
</Heading>
|
</Heading>
|
||||||
<ul className="list-style-none pl-0 f5 mb-0">
|
|
||||||
{miniTocItems.map(renderTocItem)}
|
<ActionList
|
||||||
</ul>
|
items={miniTocItems.map((items) => {
|
||||||
|
return {
|
||||||
|
key: title,
|
||||||
|
text: title,
|
||||||
|
renderItem: () => <ul>{renderTocItem(items)}</ul>,
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ export type LearningTrack = {
|
|||||||
|
|
||||||
export type MiniTocItem = {
|
export type MiniTocItem = {
|
||||||
platform: string
|
platform: string
|
||||||
contents: string
|
title: string
|
||||||
|
link: string
|
||||||
items?: MiniTocItem[]
|
items?: MiniTocItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { RepoIcon } from '@primer/octicons-react'
|
import { RepoIcon } from '@primer/octicons-react'
|
||||||
import { CodeExample } from 'components/context/ProductLandingContext'
|
import { CodeExample } from 'components/context/ProductLandingContext'
|
||||||
import { TruncateLines } from 'components/ui/TruncateLines'
|
import { TruncateLines } from 'components/ui/TruncateLines'
|
||||||
|
import { Label } from '@primer/components'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
example: CodeExample
|
example: CodeExample
|
||||||
@@ -21,12 +22,9 @@ export const CodeExampleCard = ({ example }: Props) => {
|
|||||||
<div className="d-flex flex-wrap">
|
<div className="d-flex flex-wrap">
|
||||||
{example.tags.map((tag) => {
|
{example.tags.map((tag) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<Label key={tag} variant="small" sx={{ bg: 'accent.emphasis', mb: 1, mr: 2 }}>
|
||||||
key={tag}
|
|
||||||
className="IssueLabel color-fg-on-emphasis color-bg-accent-emphasis mr-2 mb-1"
|
|
||||||
>
|
|
||||||
{tag}
|
{tag}
|
||||||
</span>
|
</Label>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ArticleGuide } from 'components/context/ProductSubLandingContext'
|
import { ArticleGuide } from 'components/context/ProductSubLandingContext'
|
||||||
|
import { Label } from '@primer/components'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
card: ArticleGuide
|
card: ArticleGuide
|
||||||
@@ -18,13 +19,14 @@ export const ArticleCard = ({ card, typeLabel }: Props) => {
|
|||||||
<div>
|
<div>
|
||||||
{card.topics.map((topic) => {
|
{card.topics.map((topic) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<Label
|
||||||
data-testid="article-card-topic"
|
|
||||||
key={topic}
|
key={topic}
|
||||||
className="IssueLabel color-bg-accent-emphasis color-fg-on-emphasis mr-1"
|
data-testid="article-card-topic"
|
||||||
|
variant="small"
|
||||||
|
sx={{ bg: 'accent.emphasis', mr: 1 }}
|
||||||
>
|
>
|
||||||
{topic}
|
{topic}
|
||||||
</span>
|
</Label>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope
|
|||||||
|
|
||||||
// return an array of objects containing each heading's contents, level, and optional platform.
|
// return an array of objects containing each heading's contents, level, and optional platform.
|
||||||
// Article layout uses these as follows:
|
// Article layout uses these as follows:
|
||||||
// - `contents` to render the mini TOC headings
|
// - `title` and `link` to render the mini TOC headings
|
||||||
// - `headingLevel` the `2` in `h2`; used for determining required indentation
|
// - `headingLevel` the `2` in `h2`; used for determining required indentation
|
||||||
// - `platform` to show or hide platform-specific headings via client JS
|
// - `platform` to show or hide platform-specific headings via client JS
|
||||||
|
|
||||||
@@ -38,7 +38,8 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope
|
|||||||
// remove any <strong> tags but leave content
|
// remove any <strong> tags but leave content
|
||||||
$('strong', item).map((i, el) => $(el).replaceWith($(el).contents()))
|
$('strong', item).map((i, el) => $(el).replaceWith($(el).contents()))
|
||||||
|
|
||||||
const contents = `<a href="${href}">${$(item).html()}</a>`
|
const link = href
|
||||||
|
const title = $(item).html()
|
||||||
const headingLevel = parseInt($(item)[0].name.match(/\d+/)[0], 10) || 0 // the `2` from `h2`
|
const headingLevel = parseInt($(item)[0].name.match(/\d+/)[0], 10) || 0 // the `2` from `h2`
|
||||||
|
|
||||||
const platform = $(item).parent('.extended-markdown').attr('class') || ''
|
const platform = $(item).parent('.extended-markdown').attr('class') || ''
|
||||||
@@ -48,7 +49,7 @@ export default function getMiniTocItems(html, maxHeadingLevel = 2, headingScope
|
|||||||
mostImportantHeadingLevel = headingLevel
|
mostImportantHeadingLevel = headingLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
return { contents, headingLevel, platform }
|
return { link, title, headingLevel, platform }
|
||||||
})
|
})
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
// set the indentation level for each item based on the most important
|
// set the indentation level for each item based on the most important
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ describe('server', () => {
|
|||||||
test('renders mini TOC in articles with more than one heading', async () => {
|
test('renders mini TOC in articles with more than one heading', async () => {
|
||||||
const $ = await getDOM('/en/github/getting-started-with-github/githubs-products')
|
const $ = await getDOM('/en/github/getting-started-with-github/githubs-products')
|
||||||
expect($('h2#in-this-article').length).toBe(1)
|
expect($('h2#in-this-article').length).toBe(1)
|
||||||
expect($('h2#in-this-article + ul li a').length).toBeGreaterThan(1)
|
expect($('h2#in-this-article + div div ul').length).toBeGreaterThan(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('renders mini TOC in articles that includes h3s when specified by frontmatter', async () => {
|
test('renders mini TOC in articles that includes h3s when specified by frontmatter', async () => {
|
||||||
@@ -336,8 +336,8 @@ describe('server', () => {
|
|||||||
'/en/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise'
|
'/en/admin/policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise'
|
||||||
)
|
)
|
||||||
expect($('h2#in-this-article').length).toBe(1)
|
expect($('h2#in-this-article').length).toBe(1)
|
||||||
expect($('h2#in-this-article + ul li').length).toBeGreaterThan(0) // non-indented items
|
expect($('h2#in-this-article + div div ul').length).toBeGreaterThan(0) // non-indented items
|
||||||
expect($('h2#in-this-article + ul li ul.ml-3').length).toBeGreaterThan(0) // indented items
|
expect($('h2#in-this-article + div div ul a div div div ul.ml-3').length).toBeGreaterThan(0) // indented items
|
||||||
})
|
})
|
||||||
|
|
||||||
test('does not render mini TOC in articles with only one heading', async () => {
|
test('does not render mini TOC in articles with only one heading', async () => {
|
||||||
@@ -361,14 +361,14 @@ describe('server', () => {
|
|||||||
const $ = await getDOM(
|
const $ = await getDOM(
|
||||||
'/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates'
|
'/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates'
|
||||||
)
|
)
|
||||||
expect($('h2#in-this-article + ul li a[href="#package-ecosystem"]').length).toBe(1)
|
expect($('h2#in-this-article + div div ul a[href="#package-ecosystem"]').length).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('renders mini TOC with correct links when headings contain markup in localized content', async () => {
|
test('renders mini TOC with correct links when headings contain markup in localized content', async () => {
|
||||||
const $ = await getDOM(
|
const $ = await getDOM(
|
||||||
'/ja/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates'
|
'/ja/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates'
|
||||||
)
|
)
|
||||||
expect($('h2#in-this-article + ul li a[href="#package-ecosystem"]').length).toBe(1)
|
expect($('h2#in-this-article + div div ul a[href="#package-ecosystem"]').length).toBe(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -878,9 +878,15 @@ describe('extended Markdown', () => {
|
|||||||
test('renders expected mini TOC headings in platform-specific content', async () => {
|
test('renders expected mini TOC headings in platform-specific content', async () => {
|
||||||
const $ = await getDOM('/en/github/using-git/associating-text-editors-with-git')
|
const $ = await getDOM('/en/github/using-git/associating-text-editors-with-git')
|
||||||
expect($('h2#in-this-article').length).toBe(1)
|
expect($('h2#in-this-article').length).toBe(1)
|
||||||
expect($('h2#in-this-article + ul li.extended-markdown.mac').length).toBeGreaterThan(1)
|
expect(
|
||||||
expect($('h2#in-this-article + ul li.extended-markdown.windows').length).toBeGreaterThan(1)
|
$('h2#in-this-article + div div ul a div div div.extended-markdown.mac').length
|
||||||
expect($('h2#in-this-article + ul li.extended-markdown.linux').length).toBeGreaterThan(1)
|
).toBeGreaterThan(1)
|
||||||
|
expect(
|
||||||
|
$('h2#in-this-article + div div ul a div div div.extended-markdown.windows').length
|
||||||
|
).toBeGreaterThan(1)
|
||||||
|
expect(
|
||||||
|
$('h2#in-this-article + div div ul a div div div.extended-markdown.linux').length
|
||||||
|
).toBeGreaterThan(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user