feat(builder): items 10+12+13 — scope canvas guides, Show Guides toggle, Layers panel hover/icons, topbar unification

Item 10: dashed canvas "guide" outlines were applied via blanket tag
selectors (div/section/header/...), so a finished section showed 3-4
nested dashed boxes. RenderNode.tsx (the <Editor onRender> override) now
tags each Craft.js droppable container's real DOM node with a
`data-craft-node` attribute (node.data.isCanvas, excluding ROOT), and
editor.css's guide rules target that attribute instead — a component's own
internal wrapper markup is no longer mistaken for a drop target. Added a
"Show guides" topbar toggle (default ON, persisted to localStorage),
state lifted in EditorShell.tsx (mirrors how `device` is already lifted),
flips `.guides-off` on Canvas.tsx's `.canvas-device-frame`.

Item 12: Layers panel rows get per-type FA icons (keyed off the same
craft.displayName used for the row label, seeded from BlocksPanel's
choices), indent-guide lines connecting nested rows, and row hover
highlights the matching canvas element (`data-layer-hovered`, written
directly to the node's DOM via `query.node(id).get().dom` — NOT via
`actions.setNodeEvent`, which is stripped from useEditor()'s public
`actions` at runtime, not just in its TS type, and threw when called).

Along the way, Craft.js's own connect() was found to already wire mouse
hover to the same `hovered` node event internally (previously invisible
because the matching CSS was dead) — RenderNode now also mirrors that
onto `data-craft-hovered` for a real-mouse-hover canvas highlight, and
both hover attributes plus the guide attribute are suppressed under
`.guides-off` so the toggle stays airtight.

Item 13: unified the topbar to one button radius (`.topbar-btn` 20px pill
-> var(--radius-md)), demoted Code/Preview to icon-only with
`data-tooltip`. Fixed a latent bug the new tooltips exposed: `[data-tooltip]`
tooltips render above their trigger, but the topbar has no room above it
(`body { overflow: hidden }`) — added a `.topbar` override to render
those below instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 20:55:52 -07:00
co-authored by Claude Opus 4.8
parent 5685670895
commit 613a44c4d4
7 changed files with 394 additions and 78 deletions
+74 -41
View File
@@ -136,7 +136,7 @@ body {
color: var(--color-text);
background: transparent;
border: 1px solid var(--color-border);
border-radius: 20px;
border-radius: var(--radius-md);
cursor: pointer;
white-space: nowrap;
transition: background var(--transition-fast), border-color var(--transition-fast),
@@ -198,6 +198,19 @@ body {
margin: 0 4px;
}
/* Icon-only topbar buttons (Show Guides, Code, Preview) -- square-ish
footprint instead of the label+icon pill, tooltip via [data-tooltip]. */
.topbar-btn.icon-only {
gap: 0;
padding: 6px 8px;
}
.topbar-btn.guides-toggle.active {
color: var(--color-accent);
background: var(--color-accent-subtle);
border-color: var(--color-accent);
}
/* --------------------------------------------------------------------------
Device Switcher
-------------------------------------------------------------------------- */
@@ -1002,63 +1015,63 @@ body {
}
/* --------------------------------------------------------------------------
Editor Visual Guides — dashed outlines for containers/columns/sections
Editor Visual Guides — dashed outlines for real drop targets only
-------------------------------------------------------------------------- */
/* All block-level elements in the canvas get a subtle dashed border */
.canvas-device-frame div,
.canvas-device-frame section,
.canvas-device-frame header,
.canvas-device-frame footer,
.canvas-device-frame main,
.canvas-device-frame article,
.canvas-device-frame nav,
.canvas-device-frame form {
/* Only actual Craft.js droppable containers get a guide outline -- these are
tagged with `data-craft-node` in RenderNode.tsx (the `<Editor onRender>`
override) based on `node.data.isCanvas`, not a blanket tag selector. A
component's own internal wrapper `<div>`s (e.g. Section's inner max-width
container) are real DOM elements but not Craft nodes, so they never get
this attribute and never get a guide outline -- this is what stops
finished sections from showing 3-4 nested dashed boxes. */
.canvas-device-frame [data-craft-node] {
outline: 1px dashed rgba(148, 163, 184, 0.2);
outline-offset: -1px;
transition: outline-color 0.15s;
}
/* Don't outline the outermost root wrapper or tiny utility divs */
.canvas-device-frame > div {
outline: none;
}
/* Rows (flex containers) get a slightly more visible border */
.canvas-device-frame div[style*="display: flex"],
.canvas-device-frame div[style*="display:flex"] {
.canvas-device-frame [data-craft-node][style*="display: flex"],
.canvas-device-frame [data-craft-node][style*="display:flex"] {
outline: 1px dashed rgba(148, 163, 184, 0.35);
}
/* Empty containers: more visible + minimum height so they're clickable */
.canvas-device-frame div:empty,
.canvas-device-frame section:empty,
.canvas-device-frame main:empty,
.canvas-device-frame header:empty,
.canvas-device-frame footer:empty {
.canvas-device-frame [data-craft-node]:empty {
outline: 2px dashed rgba(148, 163, 184, 0.4);
min-height: 60px;
}
/* Hover highlights */
.canvas-device-frame div:hover,
.canvas-device-frame section:hover,
.canvas-device-frame header:hover,
.canvas-device-frame footer:hover,
.canvas-device-frame main:hover,
.canvas-device-frame nav:hover {
.canvas-device-frame [data-craft-node]:hover {
outline-color: rgba(59, 130, 246, 0.5);
}
/* Header/footer zones get a distinct amber color */
.canvas-device-frame [data-zone="header"],
.canvas-device-frame [data-zone="footer"] {
outline: 1px dashed rgba(245, 158, 11, 0.4);
/* `data-craft-hovered` is set (presence, no value) in RenderNode.tsx,
mirroring Craft's own `hovered` node event. Craft.js's `connectors.connect()`
(called by every component) wires that event to a native
mouseover/mouseleave listener on each node's dom internally, so this
rule is a real mouse-hover highlight for any node (container or leaf).
`data-layer-hovered` is set directly by the Layers panel (LayersPanel.tsx)
on its own row hover/focus -- a separate producer (writing straight to the
target's dom rather than through Craft's `hovered` event, since the
action that drives it is stripped from the public `useEditor()` API at
runtime) that drives the identical highlight when hovering a layer row.
Both are scoped under `.canvas-device-frame` and suppressed by
`.guides-off` so "Show guides: off" stays airtight either way. */
.canvas-device-frame [data-craft-hovered],
.canvas-device-frame [data-layer-hovered] {
outline: 2px dashed var(--color-accent) !important;
outline-offset: -1px;
}
.canvas-device-frame [data-zone="header"]:hover,
.canvas-device-frame [data-zone="footer"]:hover {
outline-color: rgba(245, 158, 11, 0.7);
/* "Show guides" topbar toggle (TopBar.tsx) -- flips this class on
.canvas-device-frame to hide every guide/hover outline at once. Default ON. */
.canvas-device-frame.guides-off [data-craft-node],
.canvas-device-frame.guides-off [data-craft-hovered],
.canvas-device-frame.guides-off [data-layer-hovered] {
outline: none !important;
}
/* --------------------------------------------------------------------------
@@ -1069,11 +1082,6 @@ body {
outline-offset: -1px;
}
.craftjs-renderer [data-hovered="true"] {
outline: 1px dashed var(--color-accent) !important;
outline-offset: -1px;
}
/* Component indicator badge (floats over the selected node via a portal --
position/top/left are set inline per-node, see RenderNode.tsx) */
.component-indicator {
@@ -1153,6 +1161,21 @@ body {
scrollbar-color: var(--color-border) transparent;
}
/* --------------------------------------------------------------------------
Pages Panel — Header/Footer zone rows (Item 11)
Compact rows in the same neutral surface language as the page list, with
a pencil hint that only appears on hover instead of a permanently-loud
"Editing" pill.
-------------------------------------------------------------------------- */
.zone-row-pencil {
opacity: 0;
transition: opacity var(--transition-fast);
}
.zone-row:hover .zone-row-pencil {
opacity: 1;
}
/* --------------------------------------------------------------------------
Tooltip
-------------------------------------------------------------------------- */
@@ -1183,6 +1206,16 @@ body {
opacity: 1;
}
/* Topbar sits flush against the top of the viewport (`body { overflow:
hidden }`), so the default "tooltip above the trigger" placement gets
clipped off-screen for topbar buttons -- flip those to render below
instead. Left-panel/canvas tooltips keep the default above placement,
which has room to render there. */
.topbar [data-tooltip]::after {
bottom: auto;
top: calc(100% + 6px);
}
/* --------------------------------------------------------------------------
Notifications / Toasts
-------------------------------------------------------------------------- */