Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions components/site-header/compact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { rem } from 'polished'
import { bool, func, string } from 'prop-types'
import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom'
import styled, { css } from 'styled-components'
import { usePrevious } from 'helpers/hooks'
import HamburgerMenu from './hamburger-menu'
import Links from './links'
import Logo from './logo'

const NAV_ID = 'site-navigation-small'

const NavOverlayRoot = styled.div`
position: fixed;
display: none;
width: 100%;
height: 0px;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000000;
color: #ffffff;
flex-direction: column;
align-items: center;
cursor: pointer;

${(p) =>
p.isOpen &&
css`
display: flex;
height: 100%;
`}

@media only screen and (min-width: 576px) {
display: none;
height: 0px;
}
`

const FauxHeader = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
padding: ${rem('32px')} ${rem('42px')} 0;
`

const Nav = styled.nav`
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
height: 100%;
width: 100%;

& a {
color: #fff;
text-decoration: none;
margin-top: ${rem('8px')};
font-size: ${rem('36px')};
line-height: ${rem('60px')};

&:first-of-type {
margin-top: 16px;
}
}
`

const NavOverlay = React.forwardRef(
({ id, isOpen, closeMenu, ...props }, ref) => {
// Must check for process.browser; Without this, document is undefined because document is not available when nextjs renders this server side
if (process.browser) {
return ReactDOM.createPortal(
<NavOverlayRoot id={id} isOpen={isOpen} {...props}>
<FauxHeader>
<Logo />
<HamburgerMenu
isOpen={isOpen}
handleClick={closeMenu}
ariaControlsId={id}
ref={ref}
className="close"
/>
</FauxHeader>
<Nav>
<Links />
</Nav>
</NavOverlayRoot>,
document.querySelector('body'),
)
}

return null
},
)

NavOverlay.propTypes = {
id: string,
isOpen: bool,
closeMenu: func,
}

const Compact = () => {
const [isOpen, setIsOpen] = useState(false)
const [isCompactScreen, setIsCompactScreen] = useState(false)
const OpenButtonRef = React.createRef()
const CloseButtonRef = React.createRef()
const prevIsOpen = usePrevious(isOpen)
const prevIsCompactScreen = usePrevious(isCompactScreen)

const openMenu = () => {
if (process.browser) {
const Root = document.getElementById('__next')
Root.setAttribute('hidden', 'true')
}
setIsOpen(true)
}

const closeMenu = () => {
if (process.browser) {
const Root = document.getElementById('__next')
Root.removeAttribute('hidden')
}
setIsOpen(false)
}

// handle focus transfer between buttons when opening and closing
useEffect(() => {
if (prevIsOpen === undefined || prevIsOpen === isOpen) {
// first render or update triggered that does not update isOpen state
return
}

if (isOpen) {
CloseButtonRef.current && CloseButtonRef.current.focus()
} else {
OpenButtonRef.current && OpenButtonRef.current.focus()
}
}, [isOpen])

// setup listener for screen size changes
useEffect(() => {
const mediaWatcher = window.matchMedia('(max-width: 575px)')
setIsCompactScreen(mediaWatcher.matches)

const handleSizeChange = (e) => {
setIsCompactScreen(e.matches)
}

if (mediaWatcher.addEventListener) {
mediaWatcher.addEventListener('change', handleSizeChange)
return function cleanup() {
mediaWatcher.removeEventListener('change', handleSizeChange)
}
}

// Backwards compatibility for Safari versions prior to 14. See
// https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener#browser_compatibility for details
mediaWatcher.addListener(handleSizeChange)
return function cleanup() {
mediaWatcher.removeListener(handleSizeChange)
}
})

// if screen size changes from compact to standard when overlay is open, close overlay
useEffect(() => {
if (
prevIsCompactScreen === undefined ||
prevIsCompactScreen === isCompactScreen
) {
return
}

if (!isCompactScreen && isOpen) {
closeMenu()
}
}, [isCompactScreen])

return (
<>
<HamburgerMenu
isOpen={isOpen}
handleClick={openMenu}
ariaControlsId={NAV_ID}
ref={OpenButtonRef}
className="open"
/>
<NavOverlay
id={NAV_ID}
isOpen={isOpen}
closeMenu={closeMenu}
ref={CloseButtonRef}
/>
</>
)
}

export default Compact
87 changes: 87 additions & 0 deletions components/site-header/hamburger-menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { bool, func, string } from 'prop-types'
import React from 'react'
import styled, { css } from 'styled-components'

const NOOP = () => {}

const HamburgerButton = styled.button`
display: none;
position: relative;
background: transparent;
border: none;
cursor: pointer;
width: 48px;
height: 48px;

@media only screen and (max-width: 575px) {
display: inline-block;
align-self: flex-end;
}
`

const HamburgerLines = styled.span`
margin: 4px;
top: 50%;
display: block;
margin-top: -1px;

&,
&:after,
&:before {
position: absolute;
width: 40px;
height: 3px;
border-radius: 3px;
background-color: #ffffff;
left: 0;
}

&:after {
bottom: -12px;
content: '';
}

&:before {
top: -12px;
content: '';
}

${(p) =>
p.active &&
css`
transform: rotate(45deg);

&:after {
bottom: 0;
transform: rotate(90deg);
}

&:before {
top: 0;
opacity: 0;
}
`}
`

const HamburgerMenu = React.forwardRef(
({ isOpen, handleClick = NOOP, ariaControlsId, ...props }, ref) => (
<HamburgerButton
type="button"
aria-label="menu"
aria-controls={ariaControlsId}
onClick={handleClick}
ref={ref}
{...props}
>
<HamburgerLines active={isOpen} />
</HamburgerButton>
),
)

HamburgerMenu.propTypes = {
isOpen: bool,
handleClick: func,
ariaControlsId: string,
}

export default HamburgerMenu
39 changes: 11 additions & 28 deletions components/site-header/index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,8 @@
import { rem } from 'polished'
import styled from 'styled-components'

const SiteHeader = () => (
<StyledHeader>
<img className="logo" src="/commit-logo.svg" alt="Commit Logo" />
<nav>
<a
className="nav-link"
href="https://blog.commit.dev/"
target="_blank"
rel="noreferrer"
>
Blog
</a>
</nav>
</StyledHeader>
)
import CompactMenu from './compact'
import Logo from './logo'
import StandardMenu from './standard'

const StyledHeader = styled.header`
position: sticky;
Expand All @@ -34,18 +21,14 @@ const StyledHeader = styled.header`
@media only screen and (max-width: 575px) {
margin: ${rem('32px')} ${rem('42px')} 0;
}

.logo {
width: ${rem('212px')};
@media only screen and (max-width: 575px) {
width: ${rem('148px')};
}
}

.nav-link {
color: #fff;
text-decoration: none;
}
`

const SiteHeader = () => (
<StyledHeader>
<Logo />
<StandardMenu />
<CompactMenu />
</StyledHeader>
)

export default SiteHeader
25 changes: 25 additions & 0 deletions components/site-header/links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { rem } from 'polished'
import styled from 'styled-components'

const Link = styled.a`
color: #fff;
text-decoration: none;
margin-left: ${rem('32px')};
`

// TODO: update links once pages exist
const Standard = () => (
<>
<Link href="https://blog.commit.dev/" target="_blank" rel="noreferrer">
About
</Link>
<Link href="https://blog.commit.dev/" target="_blank" rel="noreferrer">
Blog
</Link>
<Link href="https://blog.commit.dev/" target="_blank" rel="noreferrer">
Startups
</Link>
</>
)

export default Standard
25 changes: 25 additions & 0 deletions components/site-header/logo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { rem } from 'polished'
import styled from 'styled-components'

const LogoImg = styled.img`
width: ${rem('212px')};

@media only screen and (max-width: 575px) {
display: none;
}
`

const SmallLogoImg = styled.img`
@media only screen and (min-width: 576px) {
display: none;
}
`

const Logo = () => (
<>
<LogoImg src="/commit-logo.svg" alt="Commit Logo" />
<SmallLogoImg src="/commit-logo-small.svg" alt="Commit Logo" />
</>
)

export default Logo
Loading