Files
fusion/packages/dashboard/app/hooks/useViewportMode.ts
gsxdsm 1aef3c9899 FN-5817: prevent blank dashboard after auto-merge toggle
Ensure viewport mode updates stay in sync so toggling auto-merge no longer blanks the dashboard.

- update useViewportMode to initialize safely and react consistently to viewport/media-query changes
- add focused hook coverage for initial mode detection and transition behavior
- add dashboard/app regression tests to confirm the board remains rendered after auto-merge toggles
- add a changeset for @runfusion/fusion patch release

Files changed:
 .changeset/fn-5817-auto-merge-toggle-mobile.md     |  5 ++
 .../app/components/__tests__/App.test.tsx          | 66 ++++++++++++++++++++++
 .../__tests__/board-mobile-initial-render.test.tsx | 30 ++++++++++
 .../app/hooks/__tests__/useViewportMode.test.ts    | 34 +++++++++++
 packages/dashboard/app/hooks/useViewportMode.ts    | 28 +++++++--
 5 files changed, 159 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-5817

Fusion-Task-Lineage: 2fc044b1-5f37-44de-b70e-c60364a3d69c
2026-06-01 01:30:34 -07:00

67 lines
2.1 KiB
TypeScript

import { useState, useEffect } from "react";
export type ViewportMode = "mobile" | "tablet" | "desktop";
// `(max-height: 480px)` catches phones held in landscape, which can exceed
// 768 CSS px wide but stay short. Without it, landscape phones fall out of
// mobile mode and lose the bottom nav bar + get the desktop horizontally-
// scrollable board.
export const MOBILE_MEDIA_QUERY = "(max-width: 768px)";
export function getViewportMode(): ViewportMode {
if (typeof window === "undefined") return "desktop";
if (window.matchMedia(MOBILE_MEDIA_QUERY).matches) return "mobile";
if (window.matchMedia("(min-width: 769px) and (max-width: 1024px)").matches) return "tablet";
return "desktop";
}
export function useViewportMode(): ViewportMode {
const [mode, setMode] = useState<ViewportMode>(getViewportMode);
useEffect(() => {
if (typeof window === "undefined") return;
const mobileQuery = window.matchMedia(MOBILE_MEDIA_QUERY);
const tabletQuery = window.matchMedia("(min-width: 769px) and (max-width: 1024px)");
const updateMode = () => {
if (mobileQuery.matches) {
setMode("mobile");
} else if (tabletQuery.matches) {
setMode("tablet");
} else {
setMode("desktop");
}
};
const addChangeListener = (query: MediaQueryList, listener: () => void) => {
if (typeof query.addEventListener === "function") {
query.addEventListener("change", listener);
return;
}
if (typeof query.addListener === "function") {
query.addListener(listener);
}
};
const removeChangeListener = (query: MediaQueryList, listener: () => void) => {
if (typeof query.removeEventListener === "function") {
query.removeEventListener("change", listener);
return;
}
if (typeof query.removeListener === "function") {
query.removeListener(listener);
}
};
addChangeListener(mobileQuery, updateMode);
addChangeListener(tabletQuery, updateMode);
return () => {
removeChangeListener(mobileQuery, updateMode);
removeChangeListener(tabletQuery, updateMode);
};
}, []);
return mode;
}