feat(builder): mobile-responsive editor chrome (Phase A)

Makes the Craft.js editor usable on phones (≤768px) without touching desktop
layout/behavior: a useIsMobile() hook gates a bottom tab bar + sheets (hosting
the existing Blocks/Pages/Layers/Assets/Styles panels unchanged) in place of
the side panels, a collapsed TopBar with a "..." overflow menu, 44px touch
targets, 16px inputs, dvh/safe-area-aware sizing, and small copy/overflow
fixes (empty-canvas hint, Templates modal tabs + close button).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 06:54:15 -07:00
parent 329a782052
commit 979331b12d
10 changed files with 852 additions and 64 deletions
+20 -3
View File
@@ -1,12 +1,14 @@
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { useEditor } from '@craftjs/core';
import { TopBar } from '../panels/topbar/TopBar';
import { LeftPanel } from '../panels/left/LeftPanel';
import { RightPanel } from '../panels/right/RightPanel';
import { MobilePanelBar } from '../panels/mobile/MobilePanelBar';
import { Canvas } from './Canvas';
import { ContextMenu } from '../panels/context-menu/ContextMenu';
import { useContextMenu } from '../hooks/useContextMenu';
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
import { useIsMobile } from '../hooks/useIsMobile';
import { DeviceMode } from '../types';
const SHOW_GUIDES_STORAGE_KEY = 'craft-show-guides';
@@ -21,7 +23,21 @@ function loadShowGuides(): boolean {
}
export const EditorShell: React.FC = () => {
const isMobile = useIsMobile();
const [device, setDevice] = useState<DeviceMode>('desktop');
// Phase A: default the canvas to the "mobile" preview width the first
// time we detect a mobile viewport, so the frame fits without the user
// having to reach for the device switcher (now tucked in TopBar's
// overflow menu on mobile). Only fires once, and only if the device is
// still at its initial default -- it must not fight a device the user
// has already picked (e.g. from the overflow menu) on a later re-render.
const mobileDeviceAppliedRef = useRef(false);
useEffect(() => {
if (isMobile && !mobileDeviceAppliedRef.current) {
mobileDeviceAppliedRef.current = true;
setDevice((current) => (current === 'desktop' ? 'mobile' : current));
}
}, [isMobile]);
// Item 10: canvas dashed "guide" outlines toggle -- default ON, persisted
// so the choice survives a reload. Lifted here (rather than owned by
// TopBar or Canvas alone) because the toggle button lives in TopBar but
@@ -67,12 +83,13 @@ export const EditorShell: React.FC = () => {
onToggleGuides={() => setShowGuides(!showGuides)}
/>
<div className="editor-container">
<LeftPanel />
{!isMobile && <LeftPanel />}
<div onContextMenu={handleContextMenu} style={{ flex: 1, display: 'flex', minWidth: 0 }}>
<Canvas device={device} showGuides={showGuides} />
</div>
<RightPanel />
{!isMobile && <RightPanel />}
</div>
{isMobile && <MobilePanelBar />}
<ContextMenu
visible={menuState.visible}
x={menuState.x}