Skip to content

Instantly share code, notes, and snippets.

@kosciolek
Last active July 18, 2021 11:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kosciolek/5129026e7452e1faede72d4231c4d1d5 to your computer and use it in GitHub Desktop.
Save kosciolek/5129026e7452e1faede72d4231c4d1d5 to your computer and use it in GitHub Desktop.
Fancy media query hooks.
/* 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