```
### 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)