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:
@@ -5,6 +5,7 @@ import { usePages } from '../state/PageContext';
|
||||
import { DeviceMode } from '../types';
|
||||
import { DEVICE_WIDTHS } from '../constants/presets';
|
||||
import { exportBodyHtml } from '../utils/html-export';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
|
||||
interface CanvasProps {
|
||||
device: DeviceMode;
|
||||
@@ -112,13 +113,18 @@ export const EmptyCanvasHint: React.FC = () => {
|
||||
isDragging: state.events.dragged.size > 0,
|
||||
};
|
||||
});
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
if (!isEmpty || isDragging) return null;
|
||||
|
||||
return (
|
||||
<div className="empty-canvas-hint">
|
||||
<i className="fa fa-cubes" aria-hidden />
|
||||
<span>Drag blocks from the left panel, or pick a Template to start.</span>
|
||||
<span>
|
||||
{isMobile
|
||||
? <>Tap <strong>Blocks</strong> below to add content, or pick a Template to start.</>
|
||||
: 'Drag blocks from the left panel, or pick a Template to start.'}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user