react-static
Version:
A progressive static site generator for React
219 lines (196 loc) • 4.78 kB
JavaScript
import React from 'react'
import styled, { css } from 'styled-components'
import { SiteData, Link } from 'react-static'
//
import ClickOutside from 'components/ClickOutside'
const breakpoint = 800
const sidebarBackground = '#f7f7f7'
const SidebarStyles = styled.div`
position: relative;
width: 100%;
max-width: 100%;
padding-left: 300px;
margin: 0 auto;
transition: all 0.2s ease-out;
@media screen and (max-width: ${breakpoint}px) {
padding-left: 0px;
}
.sidebar {
z-index: 1;
position: fixed;
display: flex;
flex-direction: column;
top: 0;
left: 0;
height: 100%;
width: 300px;
max-width: calc(100% - 45px);
border-right: 1px solid rgba(0, 0, 0, 0.1);
-webkit-overflow-scrolling: touch;
background: ${sidebarBackground};
transition: all 0.2s ease-out;
@media screen and (max-width: ${breakpoint}px) {
transform: translateX(-100%);
${props =>
props.isOpen &&
css`
box-shadow: 0 0 40px rgba(0, 0, 0, 0.3);
transform: translateX(0%);
`};
}
.toggle {
appearance: none;
position: absolute;
top: 5px;
right: -6px;
transform: translateX(100%);
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: ${sidebarBackground};
border: 1px solid rgba(0, 0, 0, 0.1);
color: #555;
font-size: 1.2rem;
border-radius: 3px;
cursor: pointer;
opacity: 0;
pointer-events: none;
transition: all 0.2s ease-out;
transform-origin: center;
outline: none;
@media screen and (max-width: ${breakpoint}px) {
opacity: 1;
pointer-events: all;
}
${props =>
!props.isOpen &&
css`
transform: translateX(100%) rotate(180deg);
`};
}
.header {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 0.7rem;
border-bottom: 3px solid rgba(0, 0, 0, 0.1);
.back {
font-weight: bold;
}
.version {
font-size: 0.9rem;
font-weight: bold;
opacity: 0.3;
}
}
.scroll {
flex: 1 1 auto;
overflow-y: auto;
padding-bottom: 5rem;
}
.list {
margin: 0;
padding: 0;
list-style-type: none;
.list {
padding-left: 1rem;
}
}
.item {
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
.name,
a {
display: block;
padding: 0.5rem 0.7rem;
}
a {
color: rgba(0, 0, 0, 0.8);
&.active {
font-weight: bold;
}
}
.name {
font-size: 0.8rem;
text-transform: uppercase;
font-weight: bold;
color: rgba(0, 0, 0, 0.5);
}
}
}
.content {
position: relative;
z-index: 0;
padding: 1rem 2.5rem;
}
`
const Menu = ({ items }) => (
<div className="list">
{items.map(({ name, link, children }) => (
<div key={name + link} className="item">
{link ? (
<Link to={link} exact activeClassName="active">
{name}
</Link>
) : (
<div className="name">{name}</div>
)}
{children ? <Menu items={children} /> : null}
</div>
))}
</div>
)
class Sidebar extends React.Component {
state = {
isOpen: false,
}
toggle = isOpen =>
this.setState({
isOpen,
})
render () {
const { children } = this.props
const { isOpen } = this.state
return (
<SiteData
render={({ menu }) => (
<SidebarStyles className="sidebar" isOpen={isOpen}>
<ClickOutside
onClickOutside={() => {
if (isOpen) {
this.setState({
isOpen: false,
})
}
}}
>
<div className="sidebar">
<button
className="toggle"
onClick={() => {
this.toggle(!isOpen)
}}
>
⇤
</button>
<div className="header">
<Link to="/" className="back">
← Back to Site
</Link>
<div className="version">v{process.env.REPO_VERSION}</div>
</div>
<div className="scroll">
<Menu items={menu} />
</div>
</div>
</ClickOutside>
<div className="content">{children}</div>
</SidebarStyles>
)}
/>
)
}
}
export default Sidebar