Skip to content

Commit

Permalink
fix: cleanup hooks to use useBreakpoint for screen width changes
Browse files Browse the repository at this point in the history
  • Loading branch information
lukekarrys committed Sep 26, 2023
1 parent b53dce6 commit 9b1c5db
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 92 deletions.
22 changes: 4 additions & 18 deletions theme/src/components/header.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import {Box, Flex, Link, Sticky} from '@primer/components'
import {ThreeBarsIcon} from '@primer/octicons-react'
import {Link as GatsbyLink} from 'gatsby'
import React from 'react'
import styled, {ThemeContext} from 'styled-components'
import styled from 'styled-components'
import headerNavItems from '../header-nav.yml'
import useSiteMetadata from '../use-site-metadata'
import DarkButton from './dark-button'
import MobileSearch from './mobile-search'
import NavDrawer, {useNavDrawerState} from './nav-drawer'
import NavDrawer from './nav-drawer'
import NavDropdown, {NavDropdownItem} from './nav-dropdown'
import Search from './search'
import NpmLogo from './npm-logo'
import useBreakpoint from '../use-breakpoint'
import useSearch from '../use-search'

export const HEADER_HEIGHT = 66
Expand All @@ -22,11 +19,8 @@ const NpmHeaderBar = styled(Box)`
`

function Header({location, isSearchEnabled = true}) {
const theme = React.useContext(ThemeContext)
const [isNavDrawerOpen, setIsNavDrawerOpen] = useNavDrawerState(theme.breakpoints[2])
const siteMetadata = useSiteMetadata()
const isMobile = useBreakpoint(theme.breakpoints[2], 'max')
const search = useSearch({isMobile})
const search = useSearch()

const logoStyle = {color: '#cb0000', marginRight: '16px'}
const titleStyle = {color: '#dddddd', fontWeight: '600', display: 'flex', alignItems: 'center'}
Expand Down Expand Up @@ -58,15 +52,7 @@ function Header({location, isSearchEnabled = true}) {
</Box>
<Flex display={['flex', null, null, 'none']}>
{isSearchEnabled ? <MobileSearch {...search} /> : null}
<DarkButton
aria-label="Menu"
aria-expanded={isNavDrawerOpen}
onClick={() => setIsNavDrawerOpen(true)}
ml={3}
>
<ThreeBarsIcon />
</DarkButton>
<NavDrawer location={location} isOpen={isNavDrawerOpen} onDismiss={() => setIsNavDrawerOpen(false)} />
<NavDrawer location={location} />
</Flex>
</Flex>
</Flex>
Expand Down
103 changes: 47 additions & 56 deletions theme/src/components/nav-drawer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {BorderBox, Flex, Link, Text} from '@primer/components'
import {ChevronDownIcon, ChevronUpIcon, XIcon} from '@primer/octicons-react'
import {ChevronDownIcon, ChevronUpIcon, XIcon, ThreeBarsIcon} from '@primer/octicons-react'
import {Link as GatsbyLink} from 'gatsby'
import debounce from 'lodash.debounce'
import React from 'react'
import navItems from '../nav.yml'
import headerNavItems from '../header-nav.yml'
Expand All @@ -10,72 +9,64 @@ import DarkButton from './dark-button'
import Details from './details'
import Drawer from './drawer'
import NavItems from './nav-items'
import {useIsMobile} from '../use-breakpoint'

export function useNavDrawerState(breakpoint) {
// Handle string values from themes with units at the end
if (typeof breakpoint === 'string') {
breakpoint = parseInt(breakpoint, 10)
}
const [isOpen, setOpen] = React.useState(false)

const debouncedOnResize = React.useMemo(
() =>
debounce(() => {
if (window.innerWidth >= breakpoint) {
setOpen(false)
}
}, 250),
[breakpoint],
)
const useDrawerIsOpen = () => {
const isMobile = useIsMobile()
const [isOpen, setIsOpen] = React.useState(false)
const setOpen = React.useCallback(() => setIsOpen(true), [])
const setClose = React.useCallback(() => setIsOpen(false), [])

React.useEffect(() => {
if (isOpen) {
window.addEventListener('resize', debouncedOnResize)
return () => {
// cancel any debounced invocation of the resize handler
debouncedOnResize.cancel()
window.removeEventListener('resize', debouncedOnResize)
}
if (!isMobile && isOpen) {
setIsOpen(false)
}
}, [isOpen, debouncedOnResize])
}, [isMobile, isOpen])

return [isOpen, setOpen]
return [isOpen, {setOpen, setClose}]
}

function NavDrawer({location, isOpen, onDismiss}) {
function NavDrawer({location}) {
const siteMetadata = useSiteMetadata()
const [isOpen, {setOpen, setClose}] = useDrawerIsOpen()

return (
<Drawer isOpen={isOpen} onDismiss={onDismiss}>
<Flex
flexDirection="column"
height="100%"
bg="gray.0"
style={{overflow: 'auto', WebkitOverflowScrolling: 'touch'}}
>
<Flex flexDirection="column" flex="1 0 auto" color="gray.7" bg="gray.0">
<BorderBox borderWidth={0} borderRadius={0} borderBottomWidth={1} borderColor="gray.7">
<Flex py={3} pl={4} pr={3} alignItems="center" justifyContent="space-between" color="gray.1" bg="gray.9">
<Link as={GatsbyLink} to="/" display="inline-block" color="inherit">
{siteMetadata.title}
</Link>
<DarkButton aria-label="Close" onClick={onDismiss}>
<XIcon />
</DarkButton>
</Flex>
</BorderBox>
{navItems.length > 0 ? (
<Flex flexDirection="column">
<NavItems location={location} items={navItems} editOnGitHub={false} />
<>
<DarkButton aria-label="Menu" aria-expanded={isOpen} onClick={setOpen} ml={3}>
<ThreeBarsIcon />
</DarkButton>
<Drawer isOpen={isOpen} onDismiss={setClose}>
<Flex
flexDirection="column"
height="100%"
bg="gray.0"
style={{overflow: 'auto', WebkitOverflowScrolling: 'touch'}}
>
<Flex flexDirection="column" flex="1 0 auto" color="gray.7" bg="gray.0">
<BorderBox borderWidth={0} borderRadius={0} borderBottomWidth={1} borderColor="gray.7">
<Flex py={3} pl={4} pr={3} alignItems="center" justifyContent="space-between" color="gray.1" bg="gray.9">
<Link as={GatsbyLink} to="/" display="inline-block" color="inherit">
{siteMetadata.title}
</Link>
<DarkButton aria-label="Close" onClick={setClose}>
<XIcon />
</DarkButton>
</Flex>
</BorderBox>
{navItems.length > 0 ? (
<Flex flexDirection="column">
<NavItems location={location} items={navItems} editOnGitHub={false} />
</Flex>
) : null}
</Flex>
{headerNavItems.length > 0 ? (
<Flex flexDirection="column" flex="1 0 auto" color="gray.1" bg="gray.9">
<HeaderNavItems items={headerNavItems} />
</Flex>
) : null}
</Flex>
{headerNavItems.length > 0 ? (
<Flex flexDirection="column" flex="1 0 auto" color="gray.1" bg="gray.9">
<HeaderNavItems items={headerNavItems} />
</Flex>
) : null}
</Flex>
</Drawer>
</Drawer>
</>
)
}

Expand Down
40 changes: 23 additions & 17 deletions theme/src/use-breakpoint.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import React from 'react'
import {ThemeContext} from 'styled-components'

function useBreakpoint(breakpoint, minMax = 'min') {
// Handle string values from themes with units at the end
if (typeof breakpoint === 'string') {
breakpoint = parseInt(breakpoint, 10)
}

const matchMedia = React.useMemo(() => {
if (typeof window === 'undefined') {
const eventTarget = new EventTarget()
eventTarget.matches = false
return eventTarget
}
return window.matchMedia(`(${minMax}-width: ${breakpoint - (minMax === 'min' ? 0 : 1)}px)`)
}, [breakpoint, minMax])
const getMatches = query => (typeof window !== 'undefined' ? window.matchMedia(query).matches : false)

const [matches, setMatches] = React.useState(matchMedia.matches)
const handleChange = React.useCallback(() => setMatches(matchMedia.matches), [matchMedia])
// The MIT License (MIT)
// Copyright (c) 2020 Julien CARON
// https://github.com/juliencrn/usehooks-ts/blob/master/packages/usehooks-ts/src/useMediaQuery/useMediaQuery.ts
export function useMediaQuery(query) {
const [matches, setMatches] = React.useState(getMatches(query))
const handleChange = React.useCallback(() => setMatches(getMatches(query)), [query])

React.useEffect(() => {
handleChange()
const matchMedia = window.matchMedia(query)
matchMedia.addEventListener('change', handleChange)
return () => matchMedia.removeEventListener('change', handleChange)
}, [matchMedia, handleChange])
}, [query, handleChange])

return matches
}

export function useBreakpoint(breakpoint, minMax = 'min') {
// Handle string values from themes with units at the end
const px = typeof breakpoint === 'string' ? parseInt(breakpoint, 10) : breakpoint
return useMediaQuery(`(${minMax}-width: ${px - (minMax === 'min' ? 0 : 1)}px)`)
}

// a common breakpoint where things change on mobile
export function useIsMobile() {
const theme = React.useContext(ThemeContext)
return useBreakpoint(theme.breakpoints[2], 'max')
}

export default useBreakpoint
5 changes: 4 additions & 1 deletion theme/src/use-search.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React from 'react'
import {useCombobox} from 'downshift'
import {navigate, graphql, useStaticQuery} from 'gatsby'
import {useIsMobile} from './use-breakpoint'

function useSearch() {
const isMobile = useIsMobile()

function useSearch({isMobile = false} = {}) {
const queryRef = React.useRef()
const workerRef = React.useRef()

Expand Down

0 comments on commit 9b1c5db

Please sign in to comment.