87 lines · 2.6 KB · TypeScript
RawDownload
import { useCallback, useSyncExternalStore } from "react"; const BREAKPOINTS = {  "2xl": 1536,  "3xl": 1600,  "4xl": 2000,  lg: 1024,  md: 768,  sm: 640,  xl: 1280,} as const; type Breakpoint = keyof typeof BREAKPOINTS; type BreakpointQuery = Breakpoint | `max-${Breakpoint}` | `${Breakpoint}:max-${Breakpoint}`; function resolveMin(value: Breakpoint | number): string {  const px = typeof value === "number" ? value : BREAKPOINTS[value];  return `(min-width: ${px}px)`;} function resolveMax(value: Breakpoint | number): string {  const px = typeof value === "number" ? value : BREAKPOINTS[value];  return `(max-width: ${px - 1}px)`;} function parseQuery(query: BreakpointQuery | MediaQueryInput | (string & {})): string {  if (typeof query !== "string") {    const parts: string[] = [];    if (query.min != null) parts.push(resolveMin(query.min));    if (query.max != null) parts.push(resolveMax(query.max));    if (query.pointer === "coarse") parts.push("(pointer: coarse)");    if (query.pointer === "fine") parts.push("(pointer: fine)");    if (parts.length === 0) return "(min-width: 0px)";    return parts.join(" and ");  }   if (query.startsWith("(")) return query;   const parts: string[] = [];  for (const segment of query.split(":")) {    if (segment.startsWith("max-")) {      const bp = segment.slice(4);      if (bp in BREAKPOINTS) parts.push(resolveMax(bp as Breakpoint));    } else if (segment in BREAKPOINTS) {      parts.push(resolveMin(segment as Breakpoint));    }  }   return parts.length > 0 ? parts.join(" and ") : query;} function getServerSnapshot(): boolean {  return false;} export type MediaQueryInput = {  min?: Breakpoint | number;  max?: Breakpoint | number;  /** Touch-like input (finger). Use "fine" for mouse/trackpad. */  pointer?: "coarse" | "fine";}; export function useMediaQuery(query: BreakpointQuery | MediaQueryInput | (string & {})): boolean {  const mediaQuery = parseQuery(query);   const subscribe = useCallback(    (callback: () => void) => {      if (typeof window === "undefined") return () => {};      const mql = window.matchMedia(mediaQuery);      mql.addEventListener("change", callback);      return () => mql.removeEventListener("change", callback);    },    [mediaQuery],  );   const getSnapshot = useCallback(() => {    if (typeof window === "undefined") return false;    return window.matchMedia(mediaQuery).matches;  }, [mediaQuery]);   return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);} export function useIsMobile(): boolean {  return useMediaQuery("max-md");}