"use client"; // One media query, read as external state. // // The app is mobile-first: layout decisions live in CSS, and JS only reads a // breakpoint when a *structure* has to change (which container holds the // filters, whether Split is offered, whether a `
` starts open). Every // such reader goes through this hook so they all agree within a single render — // `useSyncExternalStore` re-reads `matches` synchronously, so two components // asking the same question in the same commit can never disagree. // // The server snapshot is `false`: SSR has no viewport, so the narrow variant is // what gets prerendered. That is deliberate — the narrow shape is the one that // works everywhere — and it means callers must render layout *shells* // unconditionally and gate only their contents on the result, so the first // client render after hydration never shifts the main column. import { useCallback, useSyncExternalStore } from "react"; export function useMediaQuery(query: string): boolean { const subscribe = useCallback( (onChange: () => void) => { if (typeof window === "undefined" || !window.matchMedia) return () => {}; const mq = window.matchMedia(query); mq.addEventListener("change", onChange); return () => mq.removeEventListener("change", onChange); }, [query], ); const getSnapshot = useCallback(() => { if (typeof window === "undefined" || !window.matchMedia) return false; return window.matchMedia(query).matches; }, [query]); return useSyncExternalStore(subscribe, getSnapshot, () => false); } export default useMediaQuery;