Repository navigation
feat(builder): add a floating toolbar to the selected block - #1025
Conversation
The structural verbs existed only as keystrokes, so an author who had not read a shortcut list had no way to discover that the editor could duplicate a block at all. The bar puts select-parent, move up, move down, duplicate and delete at the block they act on. It presses the same verbs the keys press. BlockKeyboardActions now publishes them to what it wraps, so a button and a keystroke cannot drift apart and both announce into the one live region. Unavailable actions stay in place and stay focusable through aria-disabled, because the reason they are unavailable is exactly what a keyboard author needs and 'disabled' would take it out of the tab sequence with them. The canvas learns that chrome drawn over the page is not the page: without it a press on any overlay control resolves to 'no node' and clears the very selection that control acts on.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex please review this PR |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
Next review available in: 25 minutes Limit details: You’ve used all 1 included review currently available under your plan. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (10)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@nextlyhq/adapter-drizzle
@nextlyhq/adapter-mysql
@nextlyhq/adapter-postgres
@nextlyhq/adapter-sqlite
@nextlyhq/admin
@nextlyhq/admin-css
@nextlyhq/blocks-engine
@nextlyhq/blocks-react
@nextlyhq/builder
create-nextly-app
@nextlyhq/eslint-plugin
nextly
@nextlyhq/plugin-form-builder
@nextlyhq/plugin-page-builder
@nextlyhq/plugin-sdk
@nextlyhq/plugin-seo
@nextlyhq/storage-s3
@nextlyhq/storage-uploadthing
@nextlyhq/storage-vercel-blob
@nextlyhq/ui
commit: |
Fallow audit reportFound 40 findings. Details
Generated by fallow. |
|
@codex please review this PR |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@codex please review this PR |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Completes B-16. The breadcrumb half shipped in #1013; this is the toolbar half.
Why
Select parent, move up, move down, duplicate and delete all existed already — and every one of them was reachable only by keystroke. An author who has not read a shortcut list has no way to discover that this editor can duplicate a block at all, so for most of the people using it those verbs were effectively absent.
The design decisions worth arguing with
It presses the same verbs the keys press.
BlockKeyboardActionsnow publishes them to whatever it wraps, and the toolbar consumes them. Nothing in the toolbar applies an op. A bar with its own ops would be a second answer to "what does duplicate do", and the two would drift — the author meets that as a button and a keystroke that disagree, long after either was written. It is also why the announcements are right without the toolbar containing one: the verbs announce into the single live region that component already owns.Unavailable actions stay in place, and stay focusable.
aria-disabled, never thedisabledattribute. A disabled button leaves the tab sequence, and the reason it is disabled — "Caption inside this block is locked" — is precisely what a keyboard or screen-reader author needs and would then never receive. Hiding them instead would change the bar's width and order as the selection moves, so the control an author is aiming at has moved by the time they arrive.A dimmed press is passed on rather than swallowed. This started as a guard in the toolbar. Stub-verification showed removing the guard moved no test result — every verb already refuses what it cannot do — so the guard was dead code that could only ever disagree with the rule beside it. Removing it is also strictly better behaviour: a lock refusal announces, so a dimmed Delete pressed by a keyboard author now says "Caption is locked. Unlock it to delete it." instead of nothing.
A lock stops a move and a delete but not a duplicate, and a lock inside a block stops its delete without stopping its move. Both come from
lockBlockingMove/lockBlockingDeleterather than from readingnode.locked, and the second case is the one that can tell the two apart.The insertion point is a line, the toolbar is a rectangle — but both live in canvas content coordinates. Scrolling carries the bar with its block at no cost; there is no scroll listener and no frame on which the two disagree.
One change outside the toolbar
canvas.tsxlearns that editor chrome drawn over the page is not the page (data-nx-chrome). Without it, a press on any overlay control resolves to "no node", which the canvas reads as a background click and uses to clear the very selection that control acts on — the toolbar would run its action and then watch itself disappear. An attribute rather than each overlay callingstopPropagation, because the rule belongs to what chrome is, and the version where every new overlay has to remember it is the version where the next one does not.Accessibility
role="toolbar", one tab stop, arrows within, Home/End, wrapping.--nx-builder-*token aliasing the vetted admin palette. Zero literals, zero opacity-modified colours —packages/builder/srcis outsidelint-design.mjs's roots and outside the contrast suite, so nothing would have caught a bad one.Verified
In a browser (playground,
:3123, auth control asserted first —h1reads "Welcome, Dev"):Namereads "Kid copy", and the selection is not cleared.In tests: 682 in
packages/builder(up from 652), 61 inplugin-page-builder, types and lint clean on both. Every new test stub-verified in both directions.Known, not fixed here
For a block near the canvas top the bar flips below it and therefore overlaps the following block. That is the standard trade for this pattern and the flip is the correct half of it — clamping to the canvas top instead would draw the bar on the selected block.
A pre-existing console error in the autosave-recovery lane (
["autosave-recovery","collection","pages",…]returning undefined) is visible in the editor and is untouched by this change.