feat(Board): fixed cols × rows matrix, exported placement helpers, hover-ring opt-out - #1348
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 1e75548 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📦 NPM canary releaseDeployed canary version 0.0.0-canary-ca2791e. |
🏋️ Size limit report
Compared against main at 3e5e3fe — run 32739310694, 2026-08-24T14:32:16Z.To see which modules changed, download the size-limit-statoscope-report artifact from this run and open report.html. |
🧪 Storybook is successfully deployed!
|
…ver-ring opt-out `rows` renders exactly that many rows whatever the content needs, and implies a `maxRows` of the same value so `gridBounds` keeps every drag and resize inside the matrix. `rowHeight="stretch"` divides the board's own measured height into those rows. Together they express "a grid of a declared size that fills the box it is given": the board takes its parent's height and each cell an equal share, so resizing the container resizes the cells instead of adding or removing them. An app wanting that previously had to measure the box itself, divide by the row count and feed the result back in as `rowHeight` -- which meant rendering nothing until the first measurement landed, and a plain div alongside the board to hang the ResizeObserver on. `placeInFreeSlot` and `distributeEvenly` are now exported from grid-core. The first is the rule the board already applies when a dragged widget cannot stay where it was dropped; exporting it means a widget an app adds programmatically lands where a drag would have put it, rather than wherever the app's own re-implementation of the scan decided. The second tiles items evenly across both axes while keeping the row structure the layout already has, and is the one operation that deliberately grows items -- which is why it is a call the app makes, not a mode the board is in. `hoverRing` turns off the resting ring a widget shows on hover. It is the affordance that says a widget can be picked up, and on scenery -- a chromeless layout container, a spacer -- it advertises an interaction the widget does not offer. Turning it off used to mean overriding `styles.shadow`, which replaces the whole modifier map, so the `selected` ring and the drag lift had to be re-authored by hand to drop one state. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b2a7bf6 to
1e75548
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1e75548. Configure here.
| ? undefined | ||
| : isStretching | ||
| ? { height: '100%' } | ||
| : { minHeight: `${containerHeight}px` } |
There was a problem hiding this comment.
Stretch board collapses without parent height
Medium Severity
When rows and rowHeight="stretch" are set, the board always applies height: 100% via isStretching, even if the parent has no definite height. Percentage height then resolves to auto, absolutely positioned widgets contribute no flow size, and with min-height: 0 the board collapses to empty. Row-height math falls back to DEFAULT_ROW_HEIGHT when measuredHeight is 0, but that never restores a content-hugging minHeight, so the documented non-collapsing fallback does not work.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1e75548. Configure here.


Three additions to
Board, all driven by what Cube Cloud's Grid container widget (cubedevinc/cubejs-enterprise#13919) had to hand-roll around the current API.A fixed
cols × rowsmatrixrowsrenders exactly that many rows whatever the content needs — a matrix rather than a grid that hugs its widgets — and implies amaxRowsof the same value, sogridBoundskeeps every drag and resize inside it without a per-widgetmaxH.rowHeight="stretch"divides the board's own measured height into those rows.Together they express "a grid of a declared size that fills the box it is given": the board takes its parent's height and each cell an equal share, so resizing the container resizes the cells instead of adding or removing them.
An app wanting that previously had to measure the box itself, divide by the row count, and feed the result back in as
rowHeight— which meant a plaindivalongside the board to hang aResizeObserveron, and rendering nothing at all until the first measurement landed. A stretch board takesheight: 100%rather than a min-height derived from its own rows; a self-derived height would be a fixed point (it would measure back exactly what it just asked for) and the cells could never track the container. That is the bug the browser test below caught in the first attempt.Falls back to the default pixel height when it lacks either a bounded row count or a parent with a height, rather than collapsing.
Two exported placement helpers
placeInFreeSlotanddistributeEvenlynow ship fromgrid-coreand the package root.placeInFreeSlotis the rule the board already applies when a dragged widget cannot stay where it was dropped. It was private, so an app adding a widget programmatically — a toolbar drop, a cross-board move made by code — had to re-implement the scan, and the same drop landed in two different cells depending on whether a pointer or a code path served it.distributeEvenlytiles items across both axes while keeping the row structure the layout already has (grouped into visual rows byy,rowsdivided across the groups andcolsacross each group's members). It is the one operation that deliberately grows items — every gesture-driven path refuses to, because growing on a move is a surprise — which is why it is a call the app makes rather than a mode the board is in.hoverRingTurns off the resting ring a widget shows on hover, per widget or for the whole board via
widgetProps. The ring is the affordance that says a widget can be picked up; on scenery — a chromeless layout container, a spacer — it advertises an interaction the widget does not offer.Turning it off used to mean overriding
styles.shadow, which replaces the whole modifier map, so theselectedring and the drag lift had to be re-authored by hand just to drop one state.Verification
FixedMatrixstory, and a changeset are included; the defaults registry is regenerated forhoverRing.🤖 Generated with Claude Code
Note
Medium Risk
Touches Board layout geometry (height measurement, stretch sizing, implied maxRows) and drag/resize bounds. Behavior is additive and covered by unit plus browser tests, but mis-measurement could still affect nested dashboards.
Overview
Boardcan now be a declaredcols × rowsmatrix that fills its parent instead of hugging content.rowspaints that many rows regardless of widgets and impliesmaxRows, so drags and resizes stay inside the matrix.rowHeight="stretch"(only withrows) splits the board’s measured height across those rows so cells grow with the container; withoutrowsor a measured height it keeps the default content-hugging pixel height. Stretch boards useheight: 100%so they do not lock to a self-derived min-height.placeInFreeSlot(the existing drop-scan, now public) anddistributeEvenly(even tiling that may grow items, keeping visual rows) are exported from the package so programmatic adds match drag placement.hoverRing(per widget or viawidgetProps) turns off the resting hover ring without replacing the wholeshadowmap. Docs, aFixedMatrixstory, and tests cover the new geometry and helpers.Reviewed by Cursor Bugbot for commit 1e75548. Bugbot is set up for automated code reviews on this repo. Configure here.