import { createContext, useContext, useState, ReactNode, useEffect, useMemo, } from "react"; import { throttle } from "lodash"; const breakpoints = { sm: "640px", md: "768px", lg: "1024px", xl: "1280px", } type ViewportContextType = { width: number; height: number; isMobile: boolean; isTablet: boolean; isDesktop: boolean; isLessThanMedium: boolean; }; const ViewportContext = createContext({ width: 0, height: 0, isMobile: true, isTablet: false, isDesktop: false, isLessThanMedium: false, }); // The result of this function will only be useful if it's in pixels. It would // not be a useful value if the unit is e.g. `%`, `em`, etc. function stripUnit(value: string | number): number { if (typeof value === "number") { return value; } return parseFloat(value); } function getIsMobile(width: number) { return width <= stripUnit(breakpoints.sm); } function getIsTablet(width: number) { return ( width > stripUnit(breakpoints.sm) && width <= stripUnit(breakpoints.lg) ); } function getIsDesktop(width: number) { return width > stripUnit(breakpoints.lg); } function getIsLessThanMedium(width: number) { return width < stripUnit(breakpoints.md); } export const ViewportProvider = ({ children }: { children: ReactNode }) => { const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); const [isMobile, setIsMobile] = useState(getIsMobile(width)); const [isDesktop, setIsDesktop] = useState(getIsDesktop(width)); const [isTablet, setIsTablet] = useState(getIsTablet(width)); const [isLessThanMedium, setIsLessThanMedium] = useState( getIsLessThanMedium(width) ); useEffect(() => { // Set the size on the initial load. We need to do this here because we only // want it to run on the client. setWidth(window.innerWidth); setHeight(window.innerHeight); const handleWindowResize = throttle( () => { setWidth(window.innerWidth); setHeight(window.innerHeight); }, 250, { trailing: true } ); window.addEventListener("resize", handleWindowResize); return () => window.removeEventListener("resize", handleWindowResize); }, [setWidth, setHeight]); useEffect(() => { setIsMobile(getIsMobile(width)); setIsTablet(getIsTablet(width)); setIsDesktop(getIsDesktop(width)); setIsLessThanMedium(getIsLessThanMedium(width)); }, [width]); return ( {children} ); }; export const useViewport = () => { const { width, height, isMobile, isTablet, isDesktop, isLessThanMedium } = useContext(ViewportContext); return useMemo( () => ({ width, height, isMobile, isTablet, isDesktop, isLessThanMedium }), [width, height, isMobile, isTablet, isDesktop, isLessThanMedium] ); };