# WooNooW Page Editor - Enhanced UI/UX Brief (v4) ## Overview Redesign the Page Editor from a **card-list-based interface** to a **live-canvas-based interface** that shows actual rendered content in real-time. Leverage JSON-based rendering (no iframe/MutationObserver overhead) for faster, simpler implementation. --- ## Core Philosophy **One Mental Model: What You See Is What You Get (WYSIWYG)** - Canvas shows the **actual rendered output** merchants will see on the front-end - No iframe isolation → Direct component rendering → Instant visual feedback - Edit sections on the canvas → Settings panel updates → Preview updates in real-time - No context switching between "list view" and "edit mode" --- ## New Layout Structure ### Full-Screen Layout (No iframe, Direct React Rendering) ``` ┌──────────────────────────────────────────────────────────────────┐ │ [☰] WooNooW Appearance > Pages > [About Us] [D/M] [Save] │ ├─────────────────┬────────────────────────┬──────────────────────┤ │ │ │ │ │ LEFT SIDEBAR │ MAIN CANVAS │ RIGHT INSPECTOR │ │ (180px) │ (Dynamic, ~60-70%) │ (Collapse ◄) │ │ │ │ (200-280px) │ │ 📄 PAGES │ ╔════════════════════╗ │ │ │ ├─ Home │ ║ LIVE PAGE RENDER ║ │ ┌─────────────────┐ │ │ ├─ About ✓ │ ║ ║ │ │ Section Config │ │ │ └─ Contact │ ║ ┌──────────────┐ ║ │ └─────────────────┘ │ │ │ ║ │ HERO │ ║ │ │ │ 📋 TEMPLATES │ ║ │ Welcome │←──╋─┼─→ [Hero section selected] │ ├─ Post │ ║ │ [Image] │ ║ │ │ │ └─ Portfolio │ ║ │ Join us │ ║ │ Layout: │ │ │ ║ └──────────────┘ ║ │ [Hero-Left ▼] │ │ [+ Create] │ ║ ║ │ │ │ │ ║ ┌──────────────┐ ║ │ Title: │ │ │ ║ │ IMAGE+TEXT │ ║ │ [Welcome______] │ │ │ ║ │ [IMG] [TEXT] │ ║ │ │ │ │ ║ └──────────────┘ ║ │ Image: │ │ │ ║ ║ │ [Choose Image ▼] │ │ │ ║ ┌──────────────┐ ║ │ │ │ │ ║ │ CTA │ ║ │ [Delete Section] │ │ │ ║ │ "Shop Now" │ ║ │ │ │ │ ║ └──────────────┘ ║ │ [Hide Inspector] │ │ │ ║ ║ │ │ │ │ ║ [+ Add section] ║ │ │ │ │ ╚════════════════════╝ │ │ │ │ │ │ │ │ Responsive toggle: │ │ │ │ [Desktop] [Mobile] │ │ │ │ │ │ └─────────────────┴────────────────────────┴──────────────────────┘ │ [Discard Changes] [Save Changes] [Preview in New Tab] │ └──────────────────────────────────────────────────────────────────┘ ``` ### Key Differences from v3 | Aspect | v3 (Card List) | v4 (Live Canvas) | |--------|---|---| | **Main Column** | Card list (read-only) | Live rendered sections | | **Rendering** | Screenshot/preview | Actual React components | | **Interactivity** | Click card → settings | Click section → settings | | **Visual Feedback** | Static cards | Actual page appearance | | **Preview Size** | Small box on right | Full mobile/desktop | | **Edit Speed** | Click → settings → change | See change instantly | | **Context** | List + Settings + Preview | One coherent interface | --- ## Canvas Rendering System (JSON → React Components) ### Architecture: Zero-Iframe, Direct Component Rendering ``` ┌──────────────────────────────────────────┐ │ Page Editor State (Redux/Zustand) │ │ { │ │ sections: [ │ │ { type: "hero", props: {...} }, │ │ { type: "content", props: {...} } │ │ ], │ │ selectedSectionId: "sec-1" │ │ } │ └──────────────────────────────────────────┘ ↓ (Real-time) ┌──────────────────────────────────────────┐ │ CanvasRenderer Component │ │ (Renders JSON → React components) │ │ │ │ { sections.map((sec) => │ │ │ │ )} │ └──────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ Live Canvas Preview │ │ (Shows exact front-end appearance) │ │ │ │ ╔═══════════════════════════════════╗ │ │ ║ HERO SECTION ║ │ │ ║ (Selected, blue border) ║ │ │ ╚═══════════════════════════════════╝ │ │ │ │ ┌───────────────────────────────────┐ │ │ │ IMAGE + TEXT │ │ │ │ (Not selected, gray border) │ │ │ └───────────────────────────────────┘ │ └──────────────────────────────────────────┘ ``` ### Why This Is Simpler Than Page Builder Plugins **Page Builder Plugins (e.g., Elementor, Beaver):** - Use iframes to sandbox editing environment - Must observe DOM mutations to track changes - Heavy JavaScript overhead - Slow preview updates **WooNooW Approach:** - ✅ JSON state → React components (unidirectional) - ✅ No iframe = no sandbox overhead - ✅ No mutation observers = simple state management - ✅ Change state → re-render → instant preview - ✅ Single component tree (not isolated) --- ## Canvas Interaction Model ### Section States **At Rest (Not Selected):** ``` ┌─────────────────────────────────┐ │ Hero Section │ ← Subtle border (gray) │ [Actual rendered content] │ ← Hover shows subtle highlight │ "Welcome to our store" │ ← Cursor change on hover │ [Featured image] │ └─────────────────────────────────┘ ``` **On Hover:** ``` ╔════════════════════════════════╗ ║ Hero Section ║ ← Blue border appears ║ [Actual rendered content] ║ ← Handles show: [↑][↓][◆] ║ "Welcome to our store" ║ ← "Click to edit" hint ║ [Featured image] ║ ║ Drag handle: [:::] ║ ← Reorder handle visible ╚════════════════════════════════╝ ``` **On Click (Selected):** ``` ╔════════════════════════════════╗ ║ Hero Section ║ ← Bold blue border ║ [Actual rendered content] ║ ← Selection handles ║ "Welcome to our store" ║ ← Full edit mode ║ [Featured image] ║ ← Right panel populated ║ [≣ Move ↑ ↓] [⊕ Duplicate] [✕]║ ← Context menu ╚════════════════════════════════╝ ↓ Right panel updates: Section Config ├─ Layout: [Hero-Left ▼] ├─ Color: [Primary ▼] ├─ Title: [Welcome__________] └─ Image: [Choose Image ▼] ``` ### Interaction Handlers ```typescript // Canvas Section Component setSelectedSection(section.id)} onMouseEnter={() => setHoveredId(section.id)} onMouseLeave={() => setHoveredId(null)} // Reordering onDragStart={() => initDragReorder(section.id)} onDragOver={(e) => e.preventDefault()} onDrop={() => performDragReorder(section.id)} // Quick actions onDuplicate={() => duplicateSection(section.id)} onDelete={() => deleteSection(section.id)} onMoveUp={() => moveSection(section.id, 'up')} onMoveDown={() => moveSection(section.id, 'down')} /> ``` --- ## Inspector Panel (Right Side) ### Inspector Behavior **Collapsible:** ``` ┌──────────────────┐ │ [Inspector] [◄] │ ← Click to collapse (inspector slides out) │ │ │ Section Config │ │ ├─ Layout: [▼] │ │ ├─ Title: [____] │ │ └─ Image: [▼] │ └──────────────────┘ When collapsed: [Canvas takes 70% width instead of 60%] ``` **Responsive to Selection:** ``` No section selected: ┌──────────────────┐ │ Page Settings │ ├─ Title: [About] │ ├─ Slug: [/about] │ └─ SEO: [Manage→] │ └──────────────────┘ Hero section selected: ┌──────────────────┐ │ Section Config │ │ Type: Hero │ ├─ Layout: [▼] │ ├─ Title: [____] │ └─ Image: [▼] │ └──────────────────┘ Content section selected: ┌──────────────────┐ │ Section Config │ │ Type: Content │ ├─ Source: [▼] │ ← Dynamic: post_content │ ○ Static text │ │ ◉ Post Body │ └─ [Dynamic ◆] │ └──────────────────┘ ``` ### Inspector Field Types **For Static Fields:** ``` Title Field [Text input____________] Description [Large textarea___ _________________ _________________] Image [Choose from Media] or [Upload new] [Preview thumbnail] ``` **For Dynamic Fields (CPT Templates):** ``` Title Source ○ Static text: [__________] ◉ Dynamic: [Post Title ▼] • Post Title • Post Excerpt • Custom Field X [◆ Dynamic] indicator shown ``` **Layout Controls:** ``` Layout Variant [Hero-Left ▼] ├─ Hero-Left ├─ Hero-Right ├─ Hero-Center └─ Hero-Full Color Scheme [Primary ▼] ├─ Primary ├─ Secondary ├─ Accent └─ Dark ``` --- ## Canvas UI Features ### Section Card Appearance **In Canvas:** ``` Rendered like the actual front-end: ╔═══════════════════════════════════╗ ║ HERO SECTION ║ ← Looks like real page ║ ║ ║ Welcome to our store ║ ← Actual text ║ ┌─────────────────────────────┐ ║ ║ │ [Featured Image] │ ║ ← Actual image ║ │ (500x300px) │ ║ ║ └─────────────────────────────┘ ║ ║ ║ ║ Join our community today ║ ← Button ║ [SHOP NOW →] ║ ║ ║ ╚═══════════════════════════════════╝ ``` NOT like current cards: ``` ❌ WRONG (Current): ┌─────────────────────────┐ │ Hero [default] │ │ [↑] [↓] [x] │ │ ─────────────────────── │ ``` ### Add Section UI **Floating Button Below Sections:** ``` Canvas shows sections: ╔═══════════════════════════════════╗ ║ HERO SECTION ║ ╚═══════════════════════════════════╝ [+ Add section] ← Click to open section picker ├─ Hero ├─ Image + Text ├─ Feature Grid ├─ Related Items ├─ CTA Banner └─ Content ``` Or inline: ``` ╔═══════════════════════════════════╗ ║ CTA BANNER ║ ╚═══════════════════════════════════╝ [+ Add section below] ← Click here ``` ### Responsive Preview Toggle **Canvas shows device toggle:** ``` [Desktop] [Mobile] ← Toggle between previews Desktop (full width, e.g., 1024px) ╔═══════════════════════════════════╗ ║ Page at desktop width ║ ╚═══════════════════════════════════╝ Mobile (narrow width, e.g., 375px) ╔════════════╗ ║ Page at ║ ║ mobile ║ ║ width ║ ╚════════════╝ ``` --- ## Data Flow & State Management ### Single Source of Truth ``` ┌─────────────────────────────────┐ │ Editor State (Zustand/Redux) │ │ │ │ { │ │ pageId: "about", │ │ pageType: "page", │ │ title: "About Us", │ │ slug: "about", │ │ sections: [ │ │ { │ │ id: "sec-1", │ │ type: "hero", │ │ layoutVariant: "left", │ │ props: { │ │ title: { │ │ type: "static", │ │ value: "Welcome" │ │ }, │ │ image: { │ │ type: "static", │ │ value: "/img.jpg" │ │ } │ │ } │ │ } │ │ ], │ │ selectedSectionId: "sec-1", │ │ hasUnsavedChanges: true │ │ } │ └─────────────────────────────────┘ ↓ (Consumed by): ├─ CanvasRenderer │ └─ Renders sections to UI │ ├─ InspectorPanel │ └─ Shows/edits selected section │ ├─ PreviewToggle │ └─ Desktop/Mobile responsive │ └─ SaveButton └─ POST to /wp-json/woonoow/v1/pages/{slug} ``` ### Real-Time Updates ``` User changes field in Inspector: [Title input] → onChange event ↓ Store update: sections[0].props.title.value = "New Title" ↓ React re-renders: ↓ Canvas shows updated title instantly (No save needed, live preview) ``` --- ## Component Structure ### Main Components ``` ← Top container ├─ ← Left navigation │ ├─ ← Structural pages │ └─ ← CPT templates │ ├─ ← Main workspace │ ├─ ← Desktop/Mobile │ └─ ← Renders sections │ ├─ ← Each section │ │ ├─ ← Section-specific │ │ ├─ │ │ ├─ │ │ └─ ... │ └─ │ ├─ ← Right panel │ ├─ ← When no section selected │ ├─ ← When section selected │ └─ ← Collapse button │ └─ ← Actions ├─ ├─ └─ ``` ### Canvas Section Component Pattern ```tsx // Each section type follows same pattern: function HeroSectionRenderer({ section, isSelected, onSelect }) { return ( {/* Renders actual content */}

{section.props.title.value}

{section.props.subtitle.value}

{/* Selection UI overlays */} {isSelected && ( )}
); } // Same for Content, FeatureGrid, CTA, etc. ``` --- ## Selection & Interaction Feedback ### Visual Selection Indicators **Unselected:** ``` ┌─────────────────────────────┐ │ Hero Section │ ← Gray border (1px) │ [Content] │ ← Normal opacity └─────────────────────────────┘ ``` **Hovered:** ``` ┌─────────────────────────────┐ │ Hero Section │ ← Light blue border (1px) │ [Content] │ ← Subtle shadow │ [↑ ↓] │ ← Move handles appear └─────────────────────────────┘ ``` **Selected:** ``` ╔═════════════════════════════╗ ║ Hero Section ║ ← Bold blue border (2px) ║ [Content] ║ ← Highlight background ║ ║ ║ [≣] [↑↓] [⊕] [✕] ║ ← Full action menu ╚═════════════════════════════╝ ``` ### Drag & Drop Reordering ``` // User drags section up: Before: ╔═══════════════════╗ ║ HERO (drag me) ║ ← Dragging ║ [↑ ↓] ║ ╚═══════════════════╝ ╔═══════════════════╗ ║ IMAGE+TEXT ║ ← Drop zone appears ║ [↑ ↓] ║ ╚═══════════════════╝ After drop: ╔═══════════════════╗ ║ HERO ║ ╚═══════════════════╝ ╔═══════════════════╗ ║ IMAGE+TEXT ║ ╚═══════════════════╝ ``` --- ## Save & Persistence Flow ### Auto-Save vs Manual Save **Current approach: Manual Save** ``` User edits → Real-time canvas update (no server call) [Save Changes] button → POST to API → Save to wp_options/postmeta [Discard Changes] button → Reset to last saved state ``` **Optional: Auto-Save (Future)** ``` User edits → Real-time canvas update [Unsaved changes indicator] appears Auto-save after 3 seconds of inactivity Or manual save with [Save] button ``` ### Save Endpoints ``` POST /wp-json/woonoow/v1/pages/about { "sections": [...] } → Saves to wp_postmeta (_wn_page_structure) POST /wp-json/woonoow/v1/templates/post { "sections": [...] } → Saves to wp_options (wn_template_post) ``` --- ## Responsive Canvas Behavior ### Desktop View (Default) ``` Full-width canvas showing page at 1024px width ┌─────────────────────────────────┐ │ [Desktop] [Mobile] │ │ │ │ Canvas width: 1024px │ │ Shows full page layout │ └─────────────────────────────────┘ ``` ### Mobile View ``` Canvas showing page at 375px width ┌──────────┐ │[D][M] │ │ │ │ Canvas │ │ width: │ │ 375px │ │ │ │ Shows │ │ mobile │ │ layout │ │ │ └──────────┘ ``` ### Scroll Behavior - Canvas is scrollable (overflow-y: auto) - Sections stack vertically - Inspector panel stays fixed (top-right) - Bottom action bar always visible --- ## Merchant Experience Flow ### Typical Edit Session 1. **Select page from sidebar** ``` Sidebar: [About Us] ← Click Canvas loads page structure Inspector shows page settings ``` 2. **Click section to edit** ``` Canvas: [Hero section] ← Click Section highlights (blue border) Inspector updates: Section Config for Hero ``` 3. **Change fields in Inspector** ``` Inspector: Title = [Welcome_____] ← Type "Welcome to us" Canvas updates instantly No server call needed yet ``` 4. **Reorder sections (optional)** ``` Canvas: Drag [Hero] above [Image+Text] Sections reorder instantly ``` 5. **Add new section** ``` Canvas: [+ Add section] ← Click Section picker popup Select "CTA Banner" New section appears at bottom Inspector shows CTA settings ``` 6. **Save all changes** ``` Bottom bar: [Save Changes] ← Click POST to /wp-json/woonoow/v1/pages/about Sections saved to database "Page saved" toast notification ``` --- ## Implementation Phases (Enhanced) ### Phase 1: Canvas Rendering System (Priority) - [ ] Refactor state from Redux/Zustand (unified store) - [ ] Build `CanvasRenderer` component - Maps sections array to React components - Zero-iframe approach (direct rendering) - Responsive device simulation (CSS media queries) - [ ] Build section renderers (Hero, Content, Feature Grid, CTA, Related) - Render actual content, not placeholder cards - Show dynamic placeholders with [◆] badges - [ ] Add responsive toggle (Desktop/Mobile) - [ ] Test real-time re-rendering on state changes ### Phase 2: Canvas Interaction (Click, Hover, Select) - [ ] Click handler → Set selected section in state - [ ] Hover handler → Show selection handles - [ ] Visual feedback (borders, shadows, highlights) - [ ] Context menu (Move up, Down, Duplicate, Delete) - [ ] Add section button (opens section picker) ### Phase 3: Inspector Panel (Settings) - [ ] Collapse/expand button - [ ] Page settings vs Section settings - [ ] Dynamic field type detection - Static: text input, textarea, image picker - Dynamic: dropdown with available sources - [ ] Real-time sync (change field → canvas updates) ### Phase 4: Drag & Drop Reordering - [ ] Drag start/over/drop handlers - [ ] Visual drag feedback (opacity, shadow) - [ ] Reorder state updates - [ ] Test with multiple sections ### Phase 5: Polish & Launch - [ ] Save/Discard buttons - [ ] Unsaved changes warning - [ ] Toast notifications (saved, error) - [ ] Keyboard shortcuts (ESC to deselect, etc.) - [ ] Mobile-friendly inspector (for tablet editing) - [ ] Performance optimization (memoization, lazy rendering) --- ## Code Example: Canvas Component Structure ```tsx // pages/editor/PageEditor.tsx export function PageEditor() { const [pageState, dispatch] = useReducer(pageReducer, initialState); const [selectedSectionId, setSelectedSectionId] = useState(null); const [deviceMode, setDeviceMode] = useState('desktop'); const handleSectionSelect = (sectionId) => { setSelectedSectionId(sectionId); }; const handleSectionDelete = (sectionId) => { dispatch({ type: 'DELETE_SECTION', payload: { sectionId } }); }; const handleSectionDuplicate = (sectionId) => { dispatch({ type: 'DUPLICATE_SECTION', payload: { sectionId } }); }; const handleFieldChange = (sectionId, fieldPath, value) => { dispatch({ type: 'UPDATE_SECTION_PROP', payload: { sectionId, fieldPath, value } }); }; const handleSave = async () => { const response = await fetch(`/wp-json/woonoow/v1/pages/${pageState.slug}`, { method: 'POST', body: JSON.stringify({ sections: pageState.sections }) }); // Show toast notification }; return (
location.reload()} />
); } // components/Canvas.tsx export function Canvas({ sections, selectedSectionId, deviceMode, onSelectSection, onFieldChange, ...handlers }) { return (
{sections.map((section) => ( onSelectSection(section.id)} {...handlers} > onFieldChange(section.id, field, value) } /> ))}
); } // components/CanvasSection.tsx export function CanvasSection({ section, isSelected, children, ...handlers }) { return (
{/* Actual rendered content */} {children} {/* Selection overlay */} {isSelected && ( handlers.onDuplicate(section.id)} onDelete={() => handlers.onDelete(section.id)} /> )}
); } // components/Inspector.tsx export function Inspector({ pageState, selectedSectionId, onFieldChange }) { if (!selectedSectionId) { return ; } const section = pageState.sections.find(s => s.id === selectedSectionId); return (

{section.type}

{Object.entries(section.props).map(([fieldName, fieldData]) => ( onFieldChange(selectedSectionId, `props.${fieldName}.value`, value) } /> ))}
); } // components/InspectorField.tsx export function InspectorField({ fieldName, fieldData, onChange }) { if (fieldData.type === 'static') { return (
onChange(e.target.value)} />
); } if (fieldData.type === 'dynamic') { return (
); } } ``` --- ## CSS Utilities for Canvas ```css /* Canvas container */ .canvas-container { flex: 1; background: #f5f5f5; overflow-y: auto; padding: 20px; } /* Device modes */ .canvas-viewport { background: white; margin: 0 auto; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .canvas-desktop { max-width: 1024px; } .canvas-mobile { max-width: 375px; } /* Sections */ .canvas-section { position: relative; padding: 0; border: 1px solid #e0e0e0; border-radius: 8px; margin-bottom: 16px; transition: all 0.2s ease; } .canvas-section:hover { border-color: #90caf9; box-shadow: 0 2px 8px rgba(144, 202, 249, 0.3); } .canvas-section.selected { border-color: #1976d2; border-width: 2px; box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1); } /* Selection overlay */ .selection-overlay { position: absolute; top: 0; right: 0; display: flex; gap: 8px; padding: 8px; background: rgba(25, 118, 210, 0.05); border-radius: 4px; opacity: 0; } .canvas-section.selected .selection-overlay { opacity: 1; } /* Dynamic badge */ .dynamic-badge { display: inline-block; width: 14px; height: 14px; background: #ff9800; color: white; border-radius: 50%; text-align: center; font-size: 10px; line-height: 14px; margin-left: 4px; } ``` --- ## Benefits of This Approach | Benefit | Explanation | |---------|---| | **WYSIWYG** | See actual page appearance while editing | | **No iframe overhead** | Direct component rendering = faster updates | | **No MutationObserver** | JSON → React = clean state management | | **Instant feedback** | Change field → canvas updates in milliseconds | | **Simpler code** | No iframe communication, no sandbox complexity | | **Better mobile edit** | Responsive canvas shows mobile view directly | | **Easier debugging** | Single React component tree, standard dev tools | | **Faster iteration** | Less complexity = faster implementation | | **Merchant friendly** | See exactly what customers will see | --- ## Next Steps 1. ✅ Refactor state management (unified store) 2. ✅ Build CanvasRenderer component 3. ✅ Implement section click/hover interactions 4. ✅ Connect Inspector panel to canvas 5. ✅ Add drag-and-drop reordering 6. ✅ Test responsive device modes 7. ✅ Implement save/discard flow 8. ✅ Polish UX (animations, feedback)