Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 2x 49x 49x 49x 49x 49x 49x 49x 49x 37x 37x 37x 3x 3x 3x 3x 37x 36x 1x 37x 37x 36x 1x 49x 37x 37x 37x 13x 24x 37x 37x 49x 22x 22x 23x 23x 23x 17x 17x 122x 122x 122x 122x 122x 23x 23x 23x 6x 6x 6x 23x 23x 23x 6x 6x 6x 23x 23x 23x 22x 22x 22x 22x 4x 4x 4x 4x 4x 18x 18x 49x | import { useState, useEffect } from "react";
import { Logger } from "@shared/logger";
const logger = new Logger("MobileLayout");
export function useMobileLayout() {
const isClient = typeof window !== "undefined";
const mqQuery = "(max-width: 768px)";
const initialIsMobile = isClient ? window.matchMedia(mqQuery).matches : false;
const [isMobile, setIsMobile] = useState<boolean>(initialIsMobile);
const [mobilePanel, setMobilePanel] = useState<"code" | "compile" | "serial" | "board" | null>(
initialIsMobile ? "code" : null,
);
const [headerHeight, setHeaderHeight] = useState<number>(40);
const [overlayZ, setOverlayZ] = useState<number>(30);
// Media query listener for responsive layout
useEffect(() => {
Iif (!isClient) return;
const mq = window.matchMedia(mqQuery);
const onChange = (e: MediaQueryListEvent | MediaQueryList) => {
const matches = "matches" in e ? e.matches : mq.matches;
setIsMobile(matches);
// If switching into mobile mode, open code panel immediately
if (matches && !mobilePanel) setMobilePanel("code");
// If switching out of mobile, close any mobile panel
if (!matches) setMobilePanel(null);
};
// Modern browsers: addEventListener; fallback to addListener
if (typeof mq.addEventListener === "function")
mq.addEventListener("change", onChange as any);
else mq.addListener(onChange as any);
return () => {
if (typeof mq.removeEventListener === "function")
mq.removeEventListener("change", onChange as any);
else mq.removeListener(onChange as any);
};
}, [isClient, mobilePanel]);
// Prevent body scroll when mobile panel is open
useEffect(() => {
Iif (!isClient) return;
const prev = document.body.style.overflow;
if (mobilePanel) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = prev || "";
}
return () => {
document.body.style.overflow = prev || "";
};
}, [mobilePanel, isClient]);
// Compute header height and overlay z-index
useEffect(() => {
Iif (!isClient) return;
const measure = () => {
// First try to find our mobile header by data attribute
let hdr: Element | null = document.querySelector("[data-mobile-header]");
// Fallback to <header> tag
if (!hdr) hdr = document.querySelector("header");
if (!hdr) {
const all = Array.from(
document.body.querySelectorAll("*"),
) as HTMLElement[];
hdr =
all.find((el) => {
Iif (!el) return false;
// ignore html/body
Iif (el === document.body || el === document.documentElement)
return false;
const style = getComputedStyle(el);
Eif (
style.display === "none" ||
style.visibility === "hidden" ||
Number(style.opacity) === 0
)
return false;
const r = el.getBoundingClientRect();
// must be near the top and reasonably small (not full-page)
if (r.top < -5 || r.top > 48) return false;
if (r.height < 24 || r.height > window.innerHeight / 2)
return false;
return true;
}) || null;
}
Iif (hdr === document.body || hdr === document.documentElement) hdr = null;
let h = 40;
if (hdr) {
const rect = (hdr as HTMLElement).getBoundingClientRect();
Eif (rect.height > 0 && rect.height < window.innerHeight / 2)
h = Math.ceil(rect.height);
}
setHeaderHeight(h);
let z = 0;
if (hdr) {
const zStr = getComputedStyle(hdr as HTMLElement).zIndex;
const zNum = parseInt(zStr || "", 10);
z = Number.isFinite(zNum) ? zNum : 0;
}
const chosenZ = z > 0 ? Math.max(z - 1, 5) : 30;
setOverlayZ(chosenZ);
logger.debug(
`[mobile overlay] header detect: ${hdr} headerHeight=${h} overlayZ=${chosenZ}`,
);
};
measure();
window.addEventListener("resize", measure);
const hdr = document.querySelector("header");
if (hdr) {
const obs = new MutationObserver(measure);
obs.observe(hdr, { attributes: true, childList: true, subtree: true });
return () => {
window.removeEventListener("resize", measure);
obs.disconnect();
};
}
return () => {
window.removeEventListener("resize", measure);
};
}, [isClient]);
return {
isMobile,
mobilePanel,
setMobilePanel,
headerHeight,
overlayZ,
};
}
|