Last active
July 18, 2021 11:54
-
-
Save kosciolek/5129026e7452e1faede72d4231c4d1d5 to your computer and use it in GitHub Desktop.
Fancy media query hooks.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable react-hooks/rules-of-hooks */ | |
import {useLayoutEffect, useState} from "react"; | |
export const firstToUppercase = (string: string) => | |
string.charAt(0).toUpperCase() + string.slice(1); | |
export const breakpoints = { | |
xs: 0, | |
sm: 600, | |
md: 960, | |
lg: 1280, | |
xl: 1920, | |
} as const; | |
export type Breakpoints = typeof breakpoints; | |
export type BreakpointKeys = keyof Breakpoints; | |
export type QueryObject<T extends "max" | "min"> = { | |
[K in BreakpointKeys]: `@media screen and (${T}-width: ${typeof breakpoints[K]})`; | |
} & | |
{ | |
[K in BreakpointKeys as `use${Capitalize<K>}`]: () => boolean; | |
} & { | |
useQuery: (width: number) => boolean | |
}; | |
export const useQuery = (width: number, type: "min" | "max") => { | |
const [matches, setMatches] = useState(true); | |
useLayoutEffect(() => { | |
const mediaQuery = window.matchMedia( | |
`screen and (${type}-width: ${width}px)` | |
); | |
setMatches(mediaQuery.matches); | |
const listener = (ev) => setMatches(ev.matches); | |
mediaQuery.addEventListener("change", listener); | |
return () => mediaQuery.removeEventListener("change", listener); | |
}, []); | |
return matches; | |
}; | |
const createQueryObject = <T extends "max" | "min">(type: T) => | |
Object.entries(breakpoints).reduce((acc, [breakpoint, value]) => { | |
acc[breakpoint] = `@media screen and (${type}-width: ${value}px)`; | |
acc[`use${firstToUppercase(breakpoint)}`] = () => useQuery(value, type); | |
return acc; | |
}, {} as any) as QueryObject<T>; | |
export type Query = (<T extends number>( | |
minWidth: T | |
) => `@media screen and (min-width: ${T}px)`) & { | |
down: (<T extends number>( | |
maxWidth: T | |
) => `@media screen and (max-width: ${T}px)`) & | |
QueryObject<"max">; | |
} & QueryObject<"min">; | |
/* Min func */ | |
export const query: Query = (<T extends number>(minWidth: T) => | |
`@media screen and (min-width: ${minWidth}px)`) as any; | |
/* Max breakpoint funcs & hooks */ | |
Object.assign(query, createQueryObject("min")); | |
/* Min any hook */ | |
query.useQuery = (minWidth: number) => useQuery(minWidth, "min"); | |
/* Max func */ | |
query.down = (<T extends number>(minWidth: T) => | |
`@media screen and (min-width: ${minWidth}px)`) as any; | |
/* Max breakpoint funcs & hooks */ | |
Object.assign(query.down, createQueryObject("max")); | |
/* Max any hook */ | |
query.down.useQuery = (minWidth: number) => useQuery(minWidth, "max"); | |
export type Subquery = { | |
useQuery: () => boolean, | |
query: string | |
} | |
export function createSubquery (breakpoint: BreakpointKeys, type?: "max" | "min"):Subquery; | |
export function createSubquery (width: number, type?: "max" | "min"):Subquery; | |
export function createSubquery (breakpointOrWidth: BreakpointKeys | number, type?: "max" | "min"):Subquery { | |
type = type || "min"; | |
const source = type === "min" ? query : query.down; | |
return typeof breakpointOrWidth === "string" ? { | |
useQuery: source[`use${firstToUppercase(breakpointOrWidth)}`], | |
query: source[breakpointOrWidth] | |
} as const : { | |
useQuery: () => source.useQuery(breakpointOrWidth), | |
query: source(breakpointOrWidth) | |
} as const; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment