From b0c4e38d3758bc3d456a29da660141d4f26b1cc9 Mon Sep 17 00:00:00 2001 From: Nayan Date: Fri, 14 Aug 2026 13:23:17 +0530 Subject: [PATCH] feat(overlay): give long menus collapsible groups and one shared device list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The device menu had outgrown a flat list, and two menus were improvising a delete glyph the icon set did not have. `MenuGroup` is a single-open accordion entry; `MenuNode` takes an arbitrary row. `items()` filters rows inside collapsed groups, so keyboard navigation does not walk through entries nobody can see, and `isMenuItem` is exported — `font-menu.test.ts` was hand-rolling a `"label" in e` predicate that never tested `run`. Two placement fixes, found once the menus were tall enough to show them. `onReflow` ignores scroll events originating inside the popover, which had been snapping a tall menu back to the top on every wheel tick. `placePopover` resets `left` to `0px` before measuring, because a stale value from the previous open shrank the width available to a content-sized box. `frame-chrome.ts` is mostly extraction. The frame verbs move to `frame-verbs.ts` and the custom W×H form to `device-menu.ts`, carrying their toasts and the delete-undo with them, so the frame bar, the frame list and the keyboard are the same act rather than three implementations of it. `deviceGroups()` seeds open on the frame's own family, and the zoom menu is rebuilt per open. Delete's glyph moves from `minus` to `trash`. The imported set has no delete mark, so the two call sites had improvised `−` and `✕` — one of which reads as half of an add. Eleventh hand-drawn local mark; ICONS.md carries the rationale. --- packages/editor-icons/ICONS.md | 14 +- packages/editor-icons/assets/local/trash.svg | 5 + packages/editor-icons/src/generated/icons.ts | 1 + packages/overlay/src/canvas/device-menu.ts | 131 ++++++++++ packages/overlay/src/canvas/frame-chrome.ts | 246 ++++++------------ packages/overlay/src/canvas/frame-verbs.ts | 101 +++++++ .../src/inspector/sections/font-menu.test.ts | 13 +- packages/overlay/src/popover-host.test.ts | 208 +++++++++++++++ packages/overlay/src/popover-host.ts | 230 ++++++++++++++-- packages/overlay/src/popover.ts | 11 + 10 files changed, 771 insertions(+), 189 deletions(-) create mode 100644 packages/editor-icons/assets/local/trash.svg create mode 100644 packages/overlay/src/canvas/device-menu.ts create mode 100644 packages/overlay/src/canvas/frame-verbs.ts diff --git a/packages/editor-icons/ICONS.md b/packages/editor-icons/ICONS.md index 57a2eea..cf36f33 100644 --- a/packages/editor-icons/ICONS.md +++ b/packages/editor-icons/ICONS.md @@ -447,6 +447,7 @@ icons: gutter: local/gutter.svg lock: local/lock.svg unlock: local/unlock.svg + trash: local/trash.svg claude: local/claude.svg codex: local/codex.svg opencode: local/opencode.svg @@ -601,7 +602,7 @@ is the control, not the bundler. ## Marks we draw ourselves -Ten slugs under `assets/local/` are not from the imported set. They go through the same +Eleven slugs under `assets/local/` are not from the imported set. They go through the same manifest and the same normalisation as everything else — which is the point. They previously lived as hand-authored strings in the overlay's `icons.ts`, inset "roughly 4..20" (a **16-unit** span) to match the set by eye. The set's @@ -656,6 +657,17 @@ conventions with nothing reconciling them. and sits next to the eye and the layer-kind glyphs, which are filled. These are the mask's own silhouette painted directly, with the shackle as an even-odd counter, so the pair matches the family it lives in. +- **`trash`** — a bin, because the imported set has no delete glyph. It publishes + `general/remove.svg` (a bare horizontal rule → `minus`) and `general/x.svg` + (→ `close`), and both were pressed into service: frame delete drew a `−` and + the same action in the frame list drew a `✕`, so one verb had two glyphs and + neither said "destroy". `−` is still right where it pairs with a `+` over a + list — remove a fill, remove a shadow — and that grammar is deliberately left + alone; this is for the deletes that are not the other half of an add. + + Three parts on the family's terms: a staple handle, a lid bar, and a body whose + two slots are even-odd counters rather than drawn strokes, so nothing here is a + hairline that has to survive being scaled to `xs`. - **`claude` / `codex` / `opencode`** — third-party product marks, for the agent picker. Unlike the rest these are not ours to redraw; they name a backend in the UI. Path data from `@lobehub/icons-static-svg`; the marks remain the diff --git a/packages/editor-icons/assets/local/trash.svg b/packages/editor-icons/assets/local/trash.svg new file mode 100644 index 0000000..943d325 --- /dev/null +++ b/packages/editor-icons/assets/local/trash.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/editor-icons/src/generated/icons.ts b/packages/editor-icons/src/generated/icons.ts index 65be116..e4679af 100644 --- a/packages/editor-icons/src/generated/icons.ts +++ b/packages/editor-icons/src/generated/icons.ts @@ -361,6 +361,7 @@ export const EDITOR_ICONS = { "tool-move": { box: 24, body: `` }, "tool-scale": { box: 24, body: `` }, "tool-text": { box: 24, body: `` }, + "trash": { box: 24, body: `` }, "unlock": { box: 24, body: `` }, "var-add": { box: 24, body: `` }, "var-apply": { box: 24, body: `` }, diff --git a/packages/overlay/src/canvas/device-menu.ts b/packages/overlay/src/canvas/device-menu.ts new file mode 100644 index 0000000..63d61d7 --- /dev/null +++ b/packages/overlay/src/canvas/device-menu.ts @@ -0,0 +1,131 @@ +/** + * "Which viewport?", as menu content. + * + * Four surfaces ask that question — the bar's `+`, the bar's dimensions button, + * the frame list's `+`, and a row's `⋯` — and until now the answer was written + * twice: `frame-chrome.ts` built an accordion of `