Skip to content

Commit

Permalink
Merge pull request #8 from primer/rezrah/add-related-content-support
Browse files Browse the repository at this point in the history
Add related content support and navigation using keywords
  • Loading branch information
rezrah authored Jan 17, 2024
2 parents 43a8685 + 0162a97 commit bbd1b6a
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/hide-mean-turkeys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/doctocat-nextjs': patch
---

Added OpenGraph tags for improved social sharing experience.
31 changes: 31 additions & 0 deletions .changeset/mean-turkeys-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'@primer/doctocat-nextjs': patch
---

Enabled related content navigation using `keywords` and `related` properties in Markdown frontmatter.

Example:

```
---
title: Page A
keywords: ['keyword', 'another keyword']
---
```

```
---
title: Page B
keywords: ['keyword', 'another keyword']
---
```

The matching keyword values above across both pages, will enable automatic related content navigation between the two pages.

or using the `related` property:

```
---
related: [{title: External link example, href: https://example.com}]
---
```
5 changes: 5 additions & 0 deletions .changeset/turkeys-mean-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/doctocat-nextjs': patch
---

Fixed accessibility violations arising from duplicate landmarks and missing aria labels.
3 changes: 1 addition & 2 deletions .github/workflows/release_canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ on:

jobs:
release-canary:
if: false
#if: ${{ github.repository == 'primer/doctocat-nextjs' }}
if: ${{ github.repository == 'primer/doctocat-nextjs' }}
name: Canary
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 2 additions & 0 deletions packages/site/pages/content-examples/kitchen-sink.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ action-1-text: Primary action
action-1-link: /
action-2-text: Secondary action
action-2-link: /content-examples/kitchen-sink
keywords: ['accessibility', 'introduction', 'simple']
related: [{title: External link example, href: https://primer.style}]
---

import {DoDontContainer, Do, Dont, Caption} from '@primer/doctocat-nextjs'
Expand Down
1 change: 1 addition & 0 deletions packages/site/pages/content-examples/simple.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Simple
description: A simple page with plain markdown
keywords: ['kitchen sink', 'introduction', 'simple']
---

## Arva qua ferarum victa
Expand Down
1 change: 1 addition & 0 deletions packages/site/pages/getting-started/introduction.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
title: Introduction
description: Learn how to create a new site with Doctocat
keywords: ['accessibility', 'introduction', 'simple']
---

This guide will walk you through creating, customizing, and deploying a new documentation site powered by Doctocat on [Next.js](https://nextjs.org/).
Expand Down
6 changes: 5 additions & 1 deletion packages/theme/components/layout/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,11 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
}, [])

return (
<nav className={clsx(styles.Header, isSearchOpen && styles['Header--searchAreaOpen'])}>
<nav
className={clsx(styles.Header, isSearchOpen && styles['Header--searchAreaOpen'])}
role="navigation"
aria-label="Header Navigation"
>
<Link href="/" className={styles.Header__siteTitle}>
<MarkGithubIcon size={24} />
<Text as="p" size="300" weight="semibold">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.list {
margin-block: var(--base-size-24);
margin-inline-start: 0 !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import {NavList} from '@primer/react'
import {Text, Heading, UnorderedList, InlineLink} from '@primer/react-brand'
import {MdxFile} from 'nextra'

import styles from './RelatedContentLinks.module.css'
import Link from 'next/link'
import {LinkExternalIcon} from '@primer/octicons-react'

export type RelatedContentLink = MdxFile & {
title: string
}

export type RelatedContentLinksProps = {
links: RelatedContentLink[]
}

export function RelatedContentLinks({links}: RelatedContentLinksProps) {
if (!links.length) return null

return (
<div className="custom-component">
<Heading as="h2">Related content</Heading>
<UnorderedList className={styles.list}>
{links.map(page => (
<UnorderedList.Item key={page.route}>
<InlineLink href={page.route}>{page.title}</InlineLink>{' '}
{page.route.startsWith('http') ? <LinkExternalIcon /> : null}
</UnorderedList.Item>
))}
</UnorderedList>
</div>
)
}
102 changes: 93 additions & 9 deletions packages/theme/components/layout/root-layout/Theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {IndexCards} from '../index-cards/IndexCards'
import {useColorMode} from '../../context/color-modes/useColorMode'
import {getComponents} from '../../mdx-components/mdx-components'
import {SkipToMainContent} from '../skip-to-main-content/SkipToMainContent'
import {RelatedContentLink, RelatedContentLinks} from '../related-content-links/RelatedContentLinks'

const {publicRuntimeConfig} = getConfig()

Expand Down Expand Up @@ -64,14 +65,84 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
? ((data as Folder).children.filter(child => child.kind === 'MdxPage') as MdxFile[])
: []

/**
* Uses a frontmatter 'keywords' value (as an array)
* to find adjacent pages that share the same values.
* @returns {RelatedContentLink[]}
*/
const getRelatedPages = () => {
const currentPageKeywords = frontMatter.keywords || []
const relatedLinks = frontMatter['related'] || []
const matches: RelatedContentLink[] = []

// 1. Check keywords property and find local matches
for (const page of flatDocsDirectories) {
if (page.route === route) continue

if ('frontMatter' in page) {
const pageKeywords = page.frontMatter?.keywords || []
const intersection = pageKeywords.filter(keyword => currentPageKeywords.includes(keyword))

if (intersection.length) {
matches.push(page)
}
}
}

// 2. Check related property for internal and external links
for (const link of relatedLinks) {
if (!link.title || !link.href || link.href === route) continue

if (link.href.startsWith('/')) {
const page = flatDocsDirectories.find(localPage => localPage.route === link.href) as
| RelatedContentLink
| undefined

if (page) {
const entry = {
...page,
title: link.title || page.title,
route: link.href || page.route,
}
matches.push(entry)
}
} else {
matches.push({...link, route: link.href})
}
}

return matches
}

return (
<>
<BrandThemeProvider dir="ltr" colorMode={colorMode}>
<ThemeProvider colorMode={colorMode}>
<BaseStyles>
<Head>
<title>{title}</title>
<meta name="og:image" content={frontMatter.image} />
{frontMatter.description && <meta name="description" content={frontMatter.description} />}
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
{frontMatter.description && <meta property="og:description" content={frontMatter.description} />}
<meta
property="og:image"
content={
frontMatter.image ||
'https://github.com/primer/brand/assets/19292210/8562a9a5-a1e4-4722-9ec7-47ebccd5901e'
}
/>
{/* X (Twitter) OG */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
{frontMatter.description && <meta name="twitter:description" content={frontMatter.description} />}
<meta
name="twitter:image"
content={
frontMatter.image ||
'https://github.com/primer/brand/assets/19292210/8562a9a5-a1e4-4722-9ec7-47ebccd5901e'
}
/>
</Head>
<AnimationProvider runOnce visibilityOptions={1} autoStaggerChildren={false}>
<Animate animate="fade-in">
Expand All @@ -92,7 +163,7 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
</PRCBox>
<PageLayout rowGap="none" columnGap="none" padding="none" containerWidth="full">
<PageLayout.Content padding="normal">
<main id="main">
<div id="main">
<PRCBox sx={!isHomePage && {display: 'flex', maxWidth: 1600, margin: '0 auto'}}>
<PRCBox sx={!isHomePage && {maxWidth: 800, width: '100%', margin: '0 auto'}}>
<Stack direction="vertical" padding="none" gap="spacious">
Expand Down Expand Up @@ -170,7 +241,14 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
{isIndexPage ? (
<IndexCards folderData={flatDocsDirectories} route={route} />
) : (
<MDXProvider components={getComponents()}>{children}</MDXProvider>
<>
<MDXProvider components={getComponents()}>{children}</MDXProvider>
{getRelatedPages().length > 0 && (
<PRCBox sx={{pt: 5}}>
<RelatedContentLinks links={getRelatedPages()} />
</PRCBox>
)}
</>
)}
</article>
<footer>
Expand Down Expand Up @@ -203,16 +281,22 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
</footer>
</Stack>
</PRCBox>
{!isHomePage && headings.length > 0 && (
<PRCBox sx={{py: 2, pr: 3, display: ['none', null, null, null, 'block']}}>
<TableOfContents headings={headings} />
<PRCBox sx={{py: 2, pr: 3, display: ['none', null, null, null, 'block']}}>
<PRCBox
sx={{
position: 'sticky',
top: 112,
width: 220,
}}
>
{!isHomePage && headings.length > 0 && <TableOfContents headings={headings} />}
</PRCBox>
)}
</PRCBox>
</PRCBox>
</main>
</div>
</PageLayout.Content>
<PageLayout.Pane
aria-label="Sticky pane"
aria-label="Side navigation"
width="small"
sticky
padding="none"
Expand Down
2 changes: 1 addition & 1 deletion packages/theme/components/layout/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function Sidebar({pageMap}: SidebarProps) {

return (
<div className={styles.Sidebar}>
<NavList className={styles.NavList}>
<NavList className={styles.NavList} aria-label="Menu links">
{reorderedPageMap.map(item => {
if (item.kind === 'MdxPage' && item.route === '/') return null

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
.wrapper {
position: sticky;
top: var(--base-size-112);
width: 220px;
}

.heading {
font-size: var(--base-size-12);
padding-inline-start: var(--base-size-16);
Expand All @@ -15,6 +9,7 @@
margin-block-end: var(--base-size-4);
transition: transform var(--brand-animation-duration-fast) var(--brand-animation-easing-default);
}

.item[aria-current='location'] {
transform: translateX(var(--base-size-4));
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function TableOfContents({headings}: TableOfContentsProps) {
<Text as="p" size="100" variant="muted" weight="normal" className={styles.heading}>
On this page
</Text>
<NavList>
<NavList aria-label="Table of contents">
{depth2Headings.map(heading => (
<NavList.Item
className={styles.item}
Expand Down

0 comments on commit bbd1b6a

Please sign in to comment.