import { useEffect, useState } from 'react'; /** Mobile breakpoint for the editor chrome (Phase A). Keep in sync with the * `@media (max-width: 768px)` block in `src/styles/editor.css` -- both the * CSS and this hook must agree on where mobile chrome kicks in. */ export const MOBILE_BREAKPOINT_PX = 768; const MOBILE_QUERY = `(max-width: ${MOBILE_BREAKPOINT_PX}px)`; function computeIsMobile(): boolean { if (typeof window === 'undefined') return false; if (typeof window.matchMedia === 'function') { try { return window.matchMedia(MOBILE_QUERY).matches; } catch { // Fall through to the width check below (e.g. some older/embedded // WebViews expose a matchMedia that throws instead of omitting it). } } return window.innerWidth <= MOBILE_BREAKPOINT_PX; } /** * Reports whether the viewport is at or below the mobile editor breakpoint. * Drives every mobile-only branch introduced in Phase A (bottom tab bar, * collapsed topbar, bottom sheets) -- desktop rendering must stay identical * above the breakpoint, so this is the single source of truth both React * and (via the matching CSS media query) plain CSS use to agree on "mobile". * * Falls back to a `resize` listener + `window.innerWidth` when * `matchMedia` isn't available (e.g. some test/JSDOM environments), so the * hook degrades gracefully rather than throwing. */ export function useIsMobile(): boolean { const [isMobile, setIsMobile] = useState(computeIsMobile); useEffect(() => { if (typeof window === 'undefined') return; if (typeof window.matchMedia === 'function') { let mql: MediaQueryList | null = null; try { mql = window.matchMedia(MOBILE_QUERY); } catch { mql = null; } if (mql) { const handleChange = () => setIsMobile(mql!.matches); handleChange(); if (typeof mql.addEventListener === 'function') { mql.addEventListener('change', handleChange); return () => mql!.removeEventListener('change', handleChange); } // Safari < 14 only supports the deprecated addListener/removeListener pair. const legacyMql = mql as MediaQueryList & { addListener?: (listener: () => void) => void; removeListener?: (listener: () => void) => void; }; legacyMql.addListener?.(handleChange); return () => legacyMql.removeListener?.(handleChange); } } const handleResize = () => setIsMobile(window.innerWidth <= MOBILE_BREAKPOINT_PX); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return isMobile; }