feat(FN-2927): merge fusion/fn-2927
- test(FN-2927): complete Step 3 — cover keyboardOpen mobile nav behavior - feat(FN-2927): complete Step 2 — wire keyboard state into mobile nav - feat(FN-2927): complete Step 1 — add keyboardOpen hide behavior - feat(FN-2916): streamline Cloudflare quick tunnel settings UX - feat(FN-2913): merge fusion/fn-2913 Fusion-Task-Id: FN-2927
This commit is contained in:
@@ -38,6 +38,7 @@ import { useAppSettings } from "./hooks/useAppSettings";
|
||||
import { useDeepLink } from "./hooks/useDeepLink";
|
||||
import { useFavorites } from "./hooks/useFavorites";
|
||||
import { useAuthOnboarding } from "./hooks/useAuthOnboarding";
|
||||
import { useMobileKeyboard } from "./hooks/useMobileKeyboard";
|
||||
import { useSetupReadiness } from "./hooks/useSetupReadiness";
|
||||
import { useUpdateCheck } from "./hooks/useUpdateCheck";
|
||||
import { useViewState, type TaskView } from "./hooks/useViewState";
|
||||
@@ -272,6 +273,10 @@ function AppInner() {
|
||||
|
||||
const viewportMode = useViewportMode();
|
||||
const isMobile = viewportMode === "mobile";
|
||||
const { keyboardOverlap } = useMobileKeyboard({ enabled: isMobile });
|
||||
// Keyboard visibility controls both MobileNavBar rendering and whether
|
||||
// the project content reserves bottom padding for the mobile nav bar.
|
||||
const mobileKeyboardOpen = isMobile && keyboardOverlap > 0;
|
||||
|
||||
// App-level mailbox unread count state (used for header/mobile nav badges)
|
||||
const [mailboxUnreadCount, setMailboxUnreadCount] = useState(0);
|
||||
@@ -902,7 +907,7 @@ function AppInner() {
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`project-content${viewMode === "project" && currentProject ? " project-content--with-footer" : ""}${isMobile ? " project-content--with-mobile-nav" : ""}`}
|
||||
className={`project-content${viewMode === "project" && currentProject ? " project-content--with-footer" : ""}${isMobile && !mobileKeyboardOpen ? " project-content--with-mobile-nav" : ""}`}
|
||||
>
|
||||
{renderMainContent()}
|
||||
</div>
|
||||
@@ -926,6 +931,7 @@ function AppInner() {
|
||||
onChangeView={viewMode === "project" && currentProject ? handleTaskViewChange : () => {}}
|
||||
footerVisible={viewMode === "project" && !!currentProject}
|
||||
modalOpen={modalManager.anyModalOpen}
|
||||
keyboardOpen={mobileKeyboardOpen}
|
||||
onOpenSettings={handleOpenSettings}
|
||||
onOpenActivityLog={modalManager.openActivityLog}
|
||||
onOpenSystemStats={modalManager.openSystemStats}
|
||||
|
||||
@@ -40,6 +40,8 @@ export interface MobileNavBarProps {
|
||||
footerVisible: boolean;
|
||||
/** Whether any full-screen modal is currently open (hides the tab bar) */
|
||||
modalOpen?: boolean;
|
||||
/** Whether the on-screen mobile keyboard is open (hides the tab bar) */
|
||||
keyboardOpen?: boolean;
|
||||
// Navigation handlers
|
||||
onOpenSettings?: () => void;
|
||||
onOpenActivityLog?: () => void;
|
||||
@@ -89,6 +91,7 @@ export function MobileNavBar({
|
||||
onChangeView,
|
||||
footerVisible,
|
||||
modalOpen = false,
|
||||
keyboardOpen = false,
|
||||
onOpenSettings,
|
||||
onOpenActivityLog,
|
||||
onOpenSystemStats,
|
||||
@@ -168,7 +171,7 @@ export function MobileNavBar({
|
||||
return () => document.removeEventListener("keydown", onKeyDown);
|
||||
}, [isMoreOpen]);
|
||||
|
||||
if (mode !== "mobile" || modalOpen) {
|
||||
if (mode !== "mobile" || modalOpen || keyboardOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -367,6 +367,16 @@ describe("MobileNavBar", () => {
|
||||
expect(container.querySelector(".mobile-nav-bar")).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null when keyboardOpen is true on mobile", () => {
|
||||
const { container } = render(<MobileNavBar {...createDefaultProps()} keyboardOpen={true} />);
|
||||
expect(container.querySelector(".mobile-nav-bar")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders nav bar when keyboardOpen is false on mobile", () => {
|
||||
const { container } = render(<MobileNavBar {...createDefaultProps()} keyboardOpen={false} />);
|
||||
expect(container.querySelector(".mobile-nav-bar")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("applies footer-visible class when footer is shown", () => {
|
||||
const { container } = render(<MobileNavBar {...createDefaultProps()} footerVisible={true} />);
|
||||
expect(container.querySelector(".mobile-nav-bar--with-footer")).not.toBeNull();
|
||||
|
||||
Reference in New Issue
Block a user