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({ 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(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(() => ({ activeSheet, openSheet, closeSheet, overflowOpen, setOverflowOpen, templateModalOpen, setTemplateModalOpen, headCodeModalOpen, setHeadCodeModalOpen, }), [activeSheet, openSheet, closeSheet, overflowOpen, templateModalOpen, headCodeModalOpen]); return {children}; };