Skip to content
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

Sidebar Navigation for Reference Pages #50

Merged
merged 15 commits into from
May 18, 2020
Merged
10 changes: 7 additions & 3 deletions src/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import './Sidebar.scss';
// recursively create navigation
const renderNav = (page, index) => (
<li key={index}>
<Link to={page.url} className={cx({ 'is-active': page.active })}>
{page.displayName}
</Link>
{page.url ? (
<Link to={page.url} className={cx({ 'is-active': page.active })}>
{page.displayName}
</Link>
) : (
<div>{page.displayName}</div>
)}
{page.children && <ul>{page.children.map(renderNav)}</ul>}
</li>
);
Expand Down
10 changes: 10 additions & 0 deletions src/markdown-pages/foo-bar-baz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
path: '/foo/baz'
duration: '0 min'
title: 'Baz'
template: 'GuideTemplate'
---

## This is the Baz page within Foo

Page added to test the sidebar navigation.
10 changes: 10 additions & 0 deletions src/markdown-pages/foo-bar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
path: '/foo/bar'
duration: '0 min'
title: 'Bar'
template: 'GuideTemplate'
---

## This is the Bar page within Foo

Page added to test the sidebar navigation.
10 changes: 10 additions & 0 deletions src/markdown-pages/guide-index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
path: '/guides/index'
duration: '0 min'
title: 'Guides'
template: 'GuideTemplate'
---

## This is the top-level guide page

Page added to test the sidebar navigation.
67 changes: 18 additions & 49 deletions src/pages/reference.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,33 @@
import React, { useState } from 'react';
import { useStaticQuery, graphql } from 'gatsby';

import Container from '../components/Container';
import Layout from '../components/Layout';
import Sidebar from '../components/Sidebar';
import navFromEdges from '../utils/nav-from-edges';

// TODO: move this js file to same directory and update import
import '../templates/Reference.scss';

// TODO: pull this in from Gatsby
const pages = [
{ displayName: 'Overview', url: '' },
{
displayName: 'CLI',
url: '',
children: [
{ displayName: 'newrelic', url: '' },
{ displayName: 'nr1', url: '' },
],
},
{ displayName: 'GraphQL', url: '' },
{
displayName: 'Applications',
url: '',
children: [
{ displayName: 'Component Library', url: '', active: true },
{ displayName: 'File structure', url: '' },
],
},
{
displayName: 'Data Collectors',
url: '',
children: [
{ displayName: 'Custom Attributes', url: '' },
{ displayName: 'Custom Events', url: '' },
{ displayName: 'Open Telemetry', url: '' },
{ displayName: 'Telemetry SDK', url: '' },
],
},
{
displayName: 'Automation',
url: '',
children: [
{ displayName: 'Cloud Formation Provider', url: '' },
{ displayName: 'Terraform Provider', url: '' },
{
displayName: 'Agent Deploy',
url: '',
children: [
{ displayName: 'Ansible', url: '' },
{ displayName: 'Chef', url: '' },
{ displayName: 'Puppet', url: '' },
],
},
],
},
];

const Reference = () => {
const [isOpen, setIsOpen] = useState(false);

const data = useStaticQuery(graphql`
query {
allMarkdownRemark(limit: 1000) {
edges {
node {
frontmatter {
path
title
}
}
}
}
}
`);
const pages = navFromEdges(data.allMarkdownRemark.edges);

return (
<Layout>
<Container className="ReferenceTemplate">
Expand Down
2 changes: 1 addition & 1 deletion src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
// it is not very performant.
export const link = PropTypes.shape({
displayName: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
url: PropTypes.string,
active: PropTypes.bool,
children: PropTypes.array,
});
104 changes: 104 additions & 0 deletions src/utils/nav-from-edges.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Convert an object used for generating the tree into an object
* to be used in the UI.
*
* @param {Object} link - A link object that includes directories.
* @param {string} link.url - The local path to the page.
* @param {string} link.displayName - The title for the page.
* @return {Object} A link object with just a url and displayName.
*/
const linkWithoutDirs = (link) => ({
url: link.url,
displayName: link.displayName,
});

/**
* Creates a link object (including a list of directories) from an
* edge (generated by GraphQL).
*
* @param {Object} edge - A markdown edge.
* @return {Object} A link object that includes directories.
*/
const linkFromEdge = (edge) => {
const { path, title } = edge?.node?.frontmatter;
return {
url: path,
displayName: title,
dirs: path.split('/').slice(1),
};
};

/**
* Converts a slug into a displayName.
*
* @param {string} str - A slug-like string.
* @return {string} A displayName for use in the UI.
*/
const makeDisplayName = (str) =>
str
.split('-')
.map((word) => word.replace(/\b\w/g, (l) => l.toUpperCase()))
.join(' ');

/**
* Function that compares two link objects and sorts by their displayNames.
*/
const sortByDisplayName = (a, b) => (a.displayName > b.displayName ? 1 : -1);

/**
* Generates an array of links for the UI. Each link can have an
* array of links below that. This function can call itself.
*
* @param {Object[]} links - An array of links that are >= this level.
* @param {number} [level=0] The nested level.
*/
const genTree = (links, level = 0) => {
const linksAtLevel = links.filter((link) => level === link.dirs.length - 1);

// if we have nothing below this, just return a flat list of links
if (linksAtLevel.length === links.length) {
return linksAtLevel.map(linkWithoutDirs);
}

// get all the directories at this level (sorted alphabetically for now)
const linksBelowLevel = links.filter((link) => level < link.dirs.length - 1);
const dirsAtLevel = linksBelowLevel.map((link) => link.dirs[level]);
const uniqueDirsAtLevel = [...new Set(dirsAtLevel)].sort();

return uniqueDirsAtLevel.reduce((acc, dir) => {
const linksUnderDir = links.filter((link) => link.dirs[level] === dir);

// find the index page, or make a non-link item for this
const index = linksUnderDir.find(
(link) => link.dirs[level + 1] === 'index'
);

// get the children for this node and sort them
const childLinks = linksUnderDir
.filter((link) => link !== index)
.sort(sortByDisplayName);

return [
...acc,
{
...(index
? linkWithoutDirs(index)
: { displayName: makeDisplayName(dir) }),
children: genTree(childLinks, level + 1),
},
];
}, []);
};

/**
* Given a list of edges, generates the navigation to be used in the UI.
*
* @param {Object[]} edges - An array of edge objects.
* @return {Object[]} An array of link objects.
*/
const getNavFromEdges = (edges) => {
const links = edges.map(linkFromEdge);
return genTree(links);
};

export default getNavFromEdges;