fix(builder): mobile-A2 hardening -- 16px inputs, shared sheet/modal chrome, z-scale + portal
- Force font-size:16px !important on Styles-sheet/topbar/Sitesmith inputs inside the mobile media query so inline 12px/14px styles stop triggering iOS zoom-on-focus. - Lift sheet-open + Templates/Head Code modal-open state out of private useState into a shared MobileChromeContext (EditorShell), so Phase B can open/close sheets from outside MobilePanelBar. - Add an explicit z-index layer scale, portal TemplateModal to document.body (was trapped under the tab bar inside .topbar's stacking context), align Sitesmith to the same --z-modal layer, and make opening a sheet close any open modal. Also fix modal backdrops swallowing tab bar taps (mirrors the sheet backdrop's existing tab-bar cutout). - Drop BottomSheet's incorrect aria-modal; mobile-aware AssetsPanel empty state copy. - Tests: useIsMobile (matchMedia mock incl. legacy fallback + cleanup), MobileChromeContext invariants (one sheet open, sheet closes modals), MobilePanelBar wiring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { useSitesmithModal } from './SitesmithContext';
|
||||
|
||||
/** Mobile bottom-sheet tabs (Phase A: Blocks/Pages/Layers/Assets/Styles).
|
||||
* Exported so Phase B can open a specific sheet from outside
|
||||
* `MobilePanelBar` -- e.g. the selection toolbar's "Edit styles" action
|
||||
* opening 'styles', or tap-to-add closing 'blocks' after inserting a
|
||||
* block on the canvas. */
|
||||
export type MobileSheetTab = 'blocks' | 'pages' | 'layers' | 'assets' | 'styles';
|
||||
|
||||
interface MobileChromeContextValue {
|
||||
/** Which bottom sheet (if any) is open. Only one at a time -- this is a
|
||||
* single value by construction, not a set, so "one sheet open" is an
|
||||
* invariant of the type, not something callers have to maintain. */
|
||||
activeSheet: MobileSheetTab | null;
|
||||
/** Opens `tab`'s sheet (replacing whatever sheet, if any, was open).
|
||||
* Per item 3 (no accidental overlay stacking), this also closes any
|
||||
* open full-screen modal -- Templates, Head Code, Sitesmith -- so a
|
||||
* sheet is never shown stacked underneath one. Toggle-to-close is the
|
||||
* CALLER's job (see MobilePanelBar's tab click handler): this function
|
||||
* always opens, so Phase B can call it unconditionally. */
|
||||
openSheet: (tab: MobileSheetTab) => void;
|
||||
/** Closes the open sheet, if any. No-op otherwise. */
|
||||
closeSheet: () => void;
|
||||
|
||||
/** Mobile topbar "..." overflow menu open state -- lifted here too
|
||||
* ("+ the overflow-menu open state if convenient" per the brief) so
|
||||
* Phase B can close it alongside sheets/modals from one place. */
|
||||
overflowOpen: boolean;
|
||||
setOverflowOpen: (open: boolean) => void;
|
||||
|
||||
/** Templates / Head Code modal open state, lifted out of TopBar so
|
||||
* `openSheet` can close them directly (see above). Behavior is
|
||||
* otherwise identical to the private useState these replaced. */
|
||||
templateModalOpen: boolean;
|
||||
setTemplateModalOpen: (open: boolean) => void;
|
||||
|
||||
headCodeModalOpen: boolean;
|
||||
setHeadCodeModalOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
/** Default value mirrors the other state contexts in this codebase
|
||||
* (EditorConfigContext, PageContext, SiteDesignContext): a harmless no-op
|
||||
* stub rather than throwing, so a consumer rendered outside the provider
|
||||
* (e.g. in a narrower test) degrades quietly instead of crashing. */
|
||||
const MobileChromeContext = createContext<MobileChromeContextValue>({
|
||||
activeSheet: null,
|
||||
openSheet: noop,
|
||||
closeSheet: noop,
|
||||
overflowOpen: false,
|
||||
setOverflowOpen: noop,
|
||||
templateModalOpen: false,
|
||||
setTemplateModalOpen: noop,
|
||||
headCodeModalOpen: false,
|
||||
setHeadCodeModalOpen: noop,
|
||||
});
|
||||
|
||||
export const useMobileChrome = () => useContext(MobileChromeContext);
|
||||
|
||||
/**
|
||||
* Shared mobile editor-chrome state (Phase A hardening / Phase B prep).
|
||||
* Previously `activeTab`/sheet-open state lived as a private `useState`
|
||||
* inside `MobilePanelBar`, which meant only a tap on the tab bar itself
|
||||
* could open or close a sheet. Phase B needs to drive this from elsewhere
|
||||
* (a canvas selection toolbar, tap-to-add, etc.), so it's lifted into this
|
||||
* context, provided once in `EditorShell.tsx` around the whole mobile+
|
||||
* desktop tree (harmless on desktop -- nothing there consumes it except
|
||||
* `TopBar`'s Templates/Head Code modal state, which behaves identically to
|
||||
* the private `useState` it replaced).
|
||||
*/
|
||||
export const MobileChromeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [activeSheet, setActiveSheet] = useState<MobileSheetTab | null>(null);
|
||||
const [overflowOpen, setOverflowOpen] = useState(false);
|
||||
const [templateModalOpen, setTemplateModalOpen] = useState(false);
|
||||
const [headCodeModalOpen, setHeadCodeModalOpen] = useState(false);
|
||||
const { close: closeSitesmith } = useSitesmithModal();
|
||||
|
||||
const openSheet = useCallback((tab: MobileSheetTab) => {
|
||||
setActiveSheet(tab);
|
||||
// Item 3: a sheet and a full-screen modal must never stack -- opening
|
||||
// a sheet always wins over whatever modal was open.
|
||||
setTemplateModalOpen(false);
|
||||
setHeadCodeModalOpen(false);
|
||||
closeSitesmith();
|
||||
}, [closeSitesmith]);
|
||||
|
||||
const closeSheet = useCallback(() => setActiveSheet(null), []);
|
||||
|
||||
const value = useMemo<MobileChromeContextValue>(() => ({
|
||||
activeSheet,
|
||||
openSheet,
|
||||
closeSheet,
|
||||
overflowOpen,
|
||||
setOverflowOpen,
|
||||
templateModalOpen,
|
||||
setTemplateModalOpen,
|
||||
headCodeModalOpen,
|
||||
setHeadCodeModalOpen,
|
||||
}), [activeSheet, openSheet, closeSheet, overflowOpen, templateModalOpen, headCodeModalOpen]);
|
||||
|
||||
return <MobileChromeContext.Provider value={value}>{children}</MobileChromeContext.Provider>;
|
||||
};
|
||||
Reference in New Issue
Block a user