diff --git a/HERO_EDITOR.md b/HERO_EDITOR.md index 8dbc36c..0a3f50e 100644 --- a/HERO_EDITOR.md +++ b/HERO_EDITOR.md @@ -108,9 +108,13 @@ layout adapts across viewports without breakpoint data. `version` is validated reads the draft (admin) with a banner; revert. Verified: overlay/position update the canvas, auto-save writes the draft, publish writes live, preview shows the draft while the normal portal shows live. -- **Phase 3 — Elements: select / drag / text_block / buttons.** Add/select/move - (native pointer)/delete/z-order; text_block + buttons property panels. *Exit:* - add a heading + CTA row, drag to place, publish, see it live. +- **Phase 3 — Elements: select / drag / text_block / buttons** ✅ (verified + 2026-06-28). Element tray (+ Text / + Buttons); click-to-select with outline; + native Pointer Events drag (% of canvas); Delete key + panel delete; z-order + (send back / bring forward); text_block line editor (text/tag/size/color/bold, + add/remove lines, align) and buttons editor (label/path/variant, add/remove). + Verified: select shows the line editor, editing a line updates the canvas live, + drag moved 50%→65%, add→3/delete→2 elements, empty-canvas click deselects. - **Phase 4 — moon + badge + image + resize + snap grid.** Remaining element types, resize handles, 8px snap. *Exit:* place a moon and an uploaded image, resize, publish. diff --git a/client/src/components/HeroElement.jsx b/client/src/components/HeroElement.jsx index 0352dfc..9dfa88e 100644 --- a/client/src/components/HeroElement.jsx +++ b/client/src/components/HeroElement.jsx @@ -105,9 +105,17 @@ function content(element) { } } -// Absolute-positioned wrapper + type-specific content. `wrapperStyle` lets the -// editor layer on selection affordances without changing positioning logic. -export default function HeroElement({ element, wrapperStyle, children }) { +// Absolute-positioned wrapper + type-specific content. In `editor` mode the inner +// content is made non-interactive (so clicks select/drag the wrapper) and the +// wrapper takes selection styling + an onPointerDown handler. +export default function HeroElement({ + element, + wrapperStyle, + editor = false, + selected = false, + onPointerDown, + children, +}) { const anchor = element.anchor || 'center' const transform = anchor === 'center' @@ -115,13 +123,17 @@ export default function HeroElement({ element, wrapperStyle, children }) { : anchor === 'top-right' ? 'translateX(-100%)' : undefined - // text_block/buttons may set a box width (px); kept within the viewport on mobile. + // text_block/buttons may set a box width (px); kept within the containing block + // (the hero section live, or the editor canvas) with small side gutters. const boxWidth = (element.type === 'text_block' || element.type === 'buttons') && element.props?.width - ? `min(${element.props.width}px, calc(100vw - 36px))` + ? `min(${element.props.width}px, calc(100% - 36px))` : undefined + const cls = [editor ? 'hero-el-editable' : '', selected ? 'is-selected' : ''].filter(Boolean).join(' ') return (
Compose the portal hero. {status && · {status}}
- Live preview of the draft. Element drag & properties arrive in the next phase. + Click to select · drag to move · Delete key removes the selected element.
Background & overlay
+Using the default hero image.
+ )} + +{element.type.replace('_', ' ')}
+ Done +Editing controls for this element type arrive in the next phase.
+ )} + +