feat(utils-shared): add hooks for media query

This commit is contained in:
unix
2020-05-13 00:35:52 +08:00
parent b8a23c3247
commit 4631fd5b27
2 changed files with 94 additions and 0 deletions

View File

@@ -2,10 +2,12 @@ import { default as useBodyScroll } from './use-body-scroll'
import { default as useClipboard } from './use-clipboard'
import { default as useCurrentState } from './use-current-state'
import { default as useClickAway } from './use-click-away'
import { default as useMediaQuery } from './use-media-query'
export default {
useBodyScroll,
useClipboard,
useCurrentState,
useClickAway,
useMediaQuery,
}

View File

@@ -0,0 +1,92 @@
import { useEffect, useMemo, useState } from 'react'
import useTheme from '../styles/use-theme'
import { tuple } from '../utils/prop-types'
import { BreakpointsItem, ZeitUIThemesBreakpoints } from '../styles/themes'
const breakpoints = tuple('xs', 'sm', 'md', 'lg', 'xl', 'mobile')
export type ResponsiveBreakpoint = typeof breakpoints[number]
const matchType = tuple('up', 'down', 'default')
export type ResponsiveMatchType = typeof matchType[number]
export type ResponsiveOptions = {
match?: ResponsiveMatchType
ssrMatchMedia?: (query: string) => { matches: boolean }
}
const defaultResponsiveOptions = {
match: 'default' as ResponsiveMatchType,
}
const makeQueries = (
bp: ZeitUIThemesBreakpoints,
up: boolean,
down: boolean,
): {
[key in ResponsiveBreakpoint]: string
} => {
const queryString = (item: BreakpointsItem) => {
const upQuery = `(min-width: ${item.min})`
const downQuery = `(max-width: ${item.max})`
return up ? upQuery : down ? downQuery : `${upQuery} and ${downQuery}`
}
const xs = queryString(bp.xs)
return {
xs: xs,
mobile: xs,
sm: queryString(bp.sm),
md: queryString(bp.md),
lg: queryString(bp.lg),
xl: queryString(bp.xl),
}
}
const useMediaQuery = (
breakpoint: ResponsiveBreakpoint,
options: ResponsiveOptions = defaultResponsiveOptions,
) => {
const { match: matchType = 'default', ssrMatchMedia = null } = options
const supportMedia = typeof window !== 'undefined' && typeof window.matchMedia !== 'undefined'
const theme = useTheme()
const mediaQueries: {
[key in ResponsiveBreakpoint]: string
} = useMemo(() => {
const up = matchType === 'up'
const down = matchType === 'down'
return makeQueries(theme.breakpoints, up, down)
}, [theme.breakpoints, options])
const query = useMemo(() => mediaQueries[breakpoint], [mediaQueries, breakpoint])
const matchQuery = (q: string) => window.matchMedia(q)
/**
* Do nothing in the server-side rendering.
* If server match query fucntion is simulated, return user-defined value first.
*/
const [state, setState] = useState<boolean>(() => {
if (supportMedia) return matchQuery(query).matches
if (ssrMatchMedia && typeof ssrMatchMedia === 'function') {
return ssrMatchMedia(query).matches
}
return false
})
useEffect(() => {
if (!supportMedia) return
const queryList = matchQuery(query)
const update = () => setState(matchQuery(query).matches)
update()
/**
* addListener is deprecated. EventTarget.addEventListener is recommended.
* But in some old browsers, MediaQueryList does not inherit from EventTarget.
*/
queryList.addListener(update)
return () => {
queryList.removeListener(update)
}
}, [supportMedia])
return state
}
export default useMediaQuery