1
1
mirror of https://github.com/primer/css.git synced 2024-11-27 17:52:45 +03:00
css/docs/SideNav.js

155 lines
4.5 KiB
JavaScript
Raw Normal View History

2018-12-01 03:20:19 +03:00
import React from 'react'
2018-12-05 02:49:02 +03:00
import {join} from 'path'
2018-12-05 08:46:36 +03:00
import {withRouter} from 'next/router'
2018-12-05 02:59:11 +03:00
import {Box, BorderBox, Flex, Relative} from '@primer/components'
2018-12-05 08:46:36 +03:00
import NodeLink from './NodeLink'
2018-12-05 02:59:11 +03:00
import {rootPage} from './utils'
2018-12-05 03:20:57 +03:00
export default function SideNav(props) {
return (
<Relative is="nav">
2018-12-21 01:28:22 +03:00
<Box id="sidenav" {...props}>
2018-12-05 03:20:57 +03:00
<Flex flexDirection="column" alignItems="start">
2018-12-05 08:30:20 +03:00
<Router>
2018-12-05 12:46:04 +03:00
<Section path="/css/getting-started" />
<Section path="/css/principles" />
<Section path="/css/tools" />
<Section path="/css/whats-new" />
2018-12-05 08:46:36 +03:00
<RouteMatch path="/css">
<Section>
2019-01-08 23:16:50 +03:00
<SectionLink href="status-key" />
</Section>
<Section path="support" />
<Section path="utilities" />
<Section path="objects" />
<Section path="components" />
2018-12-05 08:30:20 +03:00
</RouteMatch>
</Router>
2018-12-05 03:20:57 +03:00
</Flex>
2018-12-21 01:28:22 +03:00
</Box>
2018-12-05 03:20:57 +03:00
</Relative>
)
}
2018-12-08 01:54:35 +03:00
/**
* A <Section> gets a `path` and optional children. If it has children it will
* render those and prepend each child's `href` prop with the provided `path`.
* This means that you can do:
*
* ```jsx
* <Section path="/section">
* <Link href="foo">Links to /section/foo</Link>
* </Section>
* ```
*
* If no children are provided, it renders a <NavList> with the provided
* `path`.
*/
2018-12-05 03:20:57 +03:00
const Section = ({path, children}) => (
<BorderBox p={5} border={0} borderBottom={1} borderRadius={0} width="100%">
{children && path ? React.Children.map(children, child => addPath(child, path)) : <NavList path={path} />}
2018-12-05 02:49:02 +03:00
</BorderBox>
)
2018-12-08 01:54:35 +03:00
/**
* A <NavList> renders a <SectionLink> for the given `path` and looks up the
* path in the page tree. If a node is found, it renders a <NavLink> for each
* of the node's children.
*/
2018-12-05 08:30:20 +03:00
function NavList({path}) {
const node = rootPage.first(node => node.path === path)
const children = node ? node.children.sort(nodeSort) : []
2018-12-05 08:30:20 +03:00
return (
<>
<SectionLink href={path} mb={3} />
2018-12-21 01:29:18 +03:00
{children.map(child => (
<NavLink href={child.path} key={child.path} />
))}
2018-12-05 08:30:20 +03:00
</>
)
}
2018-12-05 02:49:02 +03:00
2018-12-08 01:54:35 +03:00
/**
* A <SectionLink> is really just a <NodeLink> that's bold when its `href`
* matches the current path, wrapped in a <Box> for whitespace.
*/
2018-12-05 02:49:02 +03:00
const SectionLink = withRouter(({href, router, ...rest}) => (
<Box {...rest}>
2019-01-17 09:46:10 +03:00
<NodeLink href={href} color="gray.9" fontSize={2} fontWeight={router.pathname.startsWith(href) ? 'bold' : null} />
2018-12-05 02:49:02 +03:00
</Box>
))
2018-12-08 01:54:35 +03:00
/**
* A <NavLink> is a <NodeLink> that turns black when its `href` matches the
* current path, wrapped in a <Box> for whitespace.
*/
2018-12-05 02:49:02 +03:00
const NavLink = withRouter(({href, router, ...rest}) => {
return (
2019-01-09 22:40:39 +03:00
<Box mt={2}>
2018-12-05 03:20:57 +03:00
<NodeLink href={href} color={router.pathname === href ? 'black' : undefined} fontSize={1} {...rest} />
2018-12-05 02:49:02 +03:00
</Box>
)
})
2018-12-08 01:54:35 +03:00
/**
* This inspired React Router's <Router> component, in that it looks for
* children with the `path` prop and only renders the _first_ one that matches
* the beginning of the current path. Children without a `path` prop are always
* rendered.
*/
2018-12-05 08:30:20 +03:00
const Router = withRouter(({router, children}) => {
let matched = false
return React.Children.toArray(children).map(child => {
if (child.props.path) {
2018-12-05 08:46:36 +03:00
if (!matched && router.pathname.indexOf(child.props.path) === 0) {
2018-12-05 09:02:00 +03:00
return (matched = child)
2018-12-05 08:30:20 +03:00
}
} else {
return child
}
})
})
2018-12-08 02:08:52 +03:00
/**
2019-01-22 08:53:14 +03:00
* <RouteMatch> is just a way to conditionally render content without a wrapper
2018-12-08 02:08:52 +03:00
* element when contained directly in a <Router>:
*
* ```jsx
* <Router>
* <RouteMatch path="/some/dir">
* this will only show up on pages whose path begins with "/some/dir"
* </RouteMatch>
* </Router>
* ```
*/
function RouteMatch({path, children}) {
return path ? React.Children.map(children, child => addPath(child, path)) : children
2018-12-05 08:30:20 +03:00
}
function sortCompare(a, b, get) {
const aa = get(a)
const bb = get(b)
return typeof aa === 'string' && typeof bb === 'string' ? aa.localeCompare(bb) : undefined
}
function nodeSort(a, b) {
return sortCompare(a, b, node => node.meta.sort_title || node.meta.title)
}
function addPath(el, path) {
// if there's no path, just return the element
if (!path) return el
// if this is a link it'll have an "href"; otherwise, add "path"
const prop = el.props.href ? 'href' : 'path'
const value = el.props[prop]
const props = {}
// if there's a value and it's not absolute, prefix it with the path
if (value && !value.match(/^(\/|https?:)/)) {
props[prop] = join(path, value)
} else {
props[prop] = path
}
return React.cloneElement(el, props)
}