@confluenza/component-navigator-react
@confluenza/component-navigator-react
is an experiment. We do not know yet how useful it is, but we thought it may be interesting to have
a confluenza-like side navigation which instead of navigating through various Markdown documents, renders a different React component
on each menu item selection.
And so here it is. A version made specifically for React and tested with a React app created with create-react-app
.
@confluenza/component-navigator-react
package exposes only one React component: DocumentationLayout
. You can use as follows:
import { DocumentationLayout } from '@confluenza/component-navigator-react'
const Layout = ({ children, location, data }) => {
return (
<DocumentationLayout location={location} data={data}>
{children}
</DocumentationLayout>
)
}
export { Layout }
With New JSX Transform introduced in React v17, you no longer need to import React just to use JSX.
You need to provide a location
object and navigation config in data
. First, location. With React Router v6 you can use useLocation
hook to get the current location: :
import { Routes, Route, useLocation } from 'react-router-dom'
import { Global } from '@emotion/react'
import { Helmet } from 'react-helmet'
import { Layout } from './Layout'
import { globalStyles } from './globalStyles'
import { navigationData } from './navigationData'
const Group1 = () => <div>Group1</div>
const Group1Heading1 = () => <div>Group1Heading1</div>
const Group1Heading2 = () => <div>Group1Heading2</div>
const Group2 = () => <div>Group2</div>
const Group3 = () => <div>Group3</div>
const Group3Heading1 = () => <div>Group3Heading1</div>
const Group3Heading2 = () => <div>Group3Heading2</div>
const App = () => {
const location = useLocation()
return (
<div>
<Global styles={globalStyles} />
<Helmet>
<title>{navigationData.site.siteMetadata.title}</title>
<link href='https://fonts.googleapis.com/css?family=Roboto+Mono:100,100i,300,300i,400,400i,500,500i&display=swap' rel='stylesheet' />
</Helmet>
<Layout location={location} data={navigationData}>
<Routes>
<Route path='/' element={<Group1 />} />
<Route path='/heading1' element={<Group1Heading1 />} />
<Route path='/heading2' element={<Group1Heading2 />} />
<Route path='/group-2' element={<Group2 />} />
<Route path='/group-3' element={<Group3 />} />
<Route path='/group-3/heading1' element={<Group3Heading1 />} />
<Route path='/group-3/heading2' element={<Group3Heading2 />} />
</Routes>
</Layout>
</div>
)
}
In the example above, we see a number of routes and we also see the corresponding intended paths associated with them.
Now we need to connect these with our navigation component. We use navigationData
to achieve this:
const navigationData = {
site: {
siteMetadata: {
title: 'Component Navigator'
}
},
config: {
nodes: [
{ title: 'Nav Group 1', tag: 'tag1' },
{ title: 'Nav Group 2', tag: 'tag2' }
]
},
navigation: {
docs: [
{
node: {
frontmatter: {
title: 'Group 1',
path: '/',
tag: 'tag1',
sortIndex: 1
},
headings: [
{ value: 'Heading 1', path: '/heading1' },
{ value: 'Heading 2', path: '/heading2' }
]
}
},
{
node: {
frontmatter: {
title: 'Group 2',
path: '/group-2',
tag: 'tag1',
sortIndex: 2
}
}
},
{
node: {
frontmatter: {
title: 'Group 3',
path: '/group-3',
tag: 'tag2'
},
headings: [
{ value: 'Heading 1', path: '/group-3/heading1' },
{ value: 'Heading 2', path: '/group-3/heading2' }
]
}
}
]
}
}
export { navigationData }
It may feel a bit verbose at first, but this in currently intentional. Confluenza comes from Gatsby world and this structure stays close to the structure we use in Gatsby. We may decide to simplify it, but for the time being it works pretty well for us. What do we have here?
site
In the site
section we we only provide the title of our application. This what you see on the title bar of the navigation menu (see Figure 1) and we also use it as the
title of our whole site.
config
config
provides the names of the navigation groups. Take a look at Figure 1 below.
Figure 1 Confluenza Navigation Menu
There are two navigation groups: Nav Group 1, and Nav Group 2. This is reflected in the config section:
config: {
nodes: [
{ title: 'Nav Group 1', tag: 'tag1' },
{ title: 'Nav Group 2', tag: 'tag2' }
]
}
Each navigation group holds one document node. A document node has a frontmatter
, which is required, and a list of headings
(optional). A frontmatter describes one document node and corresponds to a single document under the navigation group. In Figure 1, Group 1, and Group 2 correspond to two document nodes. Let's take a look at Group 1:
node: {
frontmatter: {
title: 'Group 1',
path: '/',
tag: 'tag1',
sortIndex: 1
},
headings: [
{ value: 'Heading 1', path: '/heading1' },
{ value: 'Heading 2', path: '/heading2' }
]
}
The frontmatter describes the document node: its title
(Group 1), a path
where the site should navigate when selecting the menu item corresponding to the document (so when clicking directly on Group 1 in the figure above), and finally a tag
. The tag
in the frontmatter
indicates under which navigation group given document should be placed. In the case of Group 1 above, the tag
in the frontmatter
says tag1. Now, in the config
section above, we see that a tag
with value tag1 is associated with navigation group Nav Group 1. This is why Group 1 is under Nav Group 1 in Figure 1.
The headings
section allows us to add one more level to the navigation. Each heading has value
and path
. The value
gives the name displayed under the document node, while the path
indicates where the site should navigate when the given heading is selected.
As mentioned above, headings are optional. If omitted, only the document node will be displayed under the navigation group (e.g. Group 2 under Nav Group 1 in the figure above).
Finally, the sortIndex
attribute indicate the order of groups inside their navigation group, when more than one group is present. This is reflected in our example: Group 1 is listed before Group 2. If we change the sortIndex
in Group 2 to be 0
, then Group 2 will be listed as the first one. If no sortIndex
is provided, it is assumed to have value 10
.
Using tags it is easy to relocate document nodes to another navigation group. If, for instance, we would like Group 1 document node to be placed under navigation group Nav Group 2 instead of Nav Group 1 (where it is placed at the moment), we just need to change the value of the tag
in the frontmatter
corresponding to the given document node, so in our case we need to change from:
frontmatter: {
title: 'Group 1',
path: '/',
tag: 'tag1',
sortIndex: 1
}
to:
frontmatter: {
title: 'Group 1',
path: '/',
tag: 'tag2', // <== changed from 'tag1'
sortIndex: 1
}
After this change, Group 1 document node will be placed under Nav Group 2 as shown in Figure 2 below:
Figure 2 Group 1 relocated to Nav Group 2
Group 1 will be shown as the first on the list. This is because its sortIndex
has value 1
while Group 3 does not have sortIndex
property explicitly defined in the header, which means its sortIndex
value will be set to 10
(which is greater then 1
that is the value of sortIndex
of Group 1).
Workspace demo-component-navigator
contains a working example React app with component navigator as described in this document.
Path Prefix
Sometimes you want to deploy your site in a subfolder. For instance, instead of having your app served from the root of your site (/
),
you may want to serve it from /my-app
. To have that working you need to provide pathPrefix
prop to the DocumentationLayout
:
<DocumentationLayout location={location} data={data} pathPrefix='/my-app'>
{children}
</DocumentationLayout>
You also have to set the basepath
prop of your router correspondingly:
<Router basepath='/my-app'>
<Group1 path='/' />
<Group1Heading1 path='/heading1' />
<Group1Heading2 path='/heading2' />
<Group2 path='/group-2' />
<Group3 path='/group-3' />
<Group3Heading1 path='/group-3/heading1' />
<Group3Heading2 path='/group-3/heading2' />
</Router>
<style scoped> .scrollable { width: 100%; overflow-x: auto; } .flex-wrap { display:flex; flex-flow:column; justify-content:center; align-items: center; } @media (max-width: 650px) { .responsive { align-items: flex-start; } } .figure-title { font-size: 0.8em } .bordered-content-600 { width: 600px; border: 1px solid black; } .bordered-content-300 { width: 300px; border: 1px solid black; } </style>Notice that if you do not use path prefix (i.e. you deploy to the root of your site) then you need to either remove the
pathPrefix
andbasePath
from your setup or set them to/
and''
respectively.