feat(Table): make row expansion behave like its sibling plugins - #5995
feat(Table): make row expansion behave like its sibling plugins#5995ernestt wants to merge 7 commits into
Conversation
…el to the first column The chevron's rotation was on the <button>, which is the hit target and carries the hover chip, so opening a row swung that rounded rectangle and its highlight around with the arrow. Moves the transform onto the glyph. The detail panel spanned the row with a flat inline padding, leaving its content under the chevron — a column to the left of every label it describes. It now indents by the chevron column's width plus the density's own cell padding, read off the table context. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: Cursor <cursoragent@cursor.com>
|
The red
Fix is up as #5996 (one line). Once that lands I'll rebase this and the checks should clear. |
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR Modified ComponentsTable (@astryxdesign/core) · View in Storybook
Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Visual RegressionStatus: No visual change across 2 compared shot(s). Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
|
Correction to the note above: I've closed #5996 rather than landing it, since the break is not mine to fix. The diagnosis is preserved on that PR for whoever owns it. Nothing here changes — the red |
useTableRowExpansion accepts hasRowClickExpansion, matching the flag useTableTreeData has carried since it shipped. The two plugins are halves of one pair — expand into child rows, expand into a panel — and moving between them silently lost whole-row clicking. It could not be added from outside either: the handler has to know not to fire on a checkbox, a link, or the end of a text drag. Off by default and pointer-only; the chevron stays the accessible control. Collapsed rows are wired up too, which is most of the point — the row worth clicking is the one that has not opened yet. Co-authored-by: Cursor <cursoragent@cursor.com>
Added:
|
The panel row painted --color-background-muted unconditionally, and being a <tr> the plugin builds itself, nothing a caller rendered could reach it. That wash is the right default: in a bare table it is the only thing telling a detail panel apart from another row of data. It is wrong for a table already on a Card, where it reads as a third surface rather than as a distinction. panelVariant: 'muted' | 'transparent', matching Card's vocabulary. Default unchanged. Worth noting the wash is a low-alpha near-black, so over a dark card it is nearly invisible — dark themes have been rendering 'transparent' all along. Co-authored-by: Cursor <cursoragent@cursor.com>
Added:
|
A row and its detail panel are one unit, but the divider was landing between them: the row drew its own bottom border, putting a line between the row and the detail it had just opened, and the panel drew none, so it ran flush into the next row. The pair was split down the middle and fused to the row below. The expanded row now gives up its border and the panel takes one. With no row dividers the suppression removes a border that was never there and the panel's is never applied, so only the panel consults the divider mode, to know whether to draw at all. The panel row carries tableRowMarker, which is how TableCell scopes its "no trailing line under the last row" rule, so an expanded last row still ends the table cleanly. Co-authored-by: Cursor <cursoragent@cursor.com>
…5995 Co-authored-by: Cursor <cursoragent@cursor.com>
What
Four changes to
useTableRowExpansion. Two are fixes to where things sit, two close gaps against the plugin's siblings. Grouped because they are one read of the same file, and each one is small enough that splitting them costs more in review overhead than it saves.fixfixfixfeathasRowClickExpansion— the whole row toggles, as inuseTableTreeDatafeatpanelVariant— opt the panel's wash outfix — the divider split a row from its own detail
A row and its panel are one unit, but the line was landing between them. The row drew its own bottom border, putting a divider between the row and the detail it had just opened; the panel drew none, so it ran flush into the next row. The pair was cut down the middle and fused to the row below — the reverse of the grouping it should express.
The expanded row now gives up its border and the panel takes one.
Before — the line sits under
Operating, and its detail runs straight intoPayroll:After — the row and its detail read as one block, and the line closes underneath:
Only the panel consults the divider mode, and only to know whether to draw at all. On a table with no row dividers the suppression removes a border that was never there, and the panel's is never applied. The panel row carries
tableRowMarker— the same markerTableCellscopes its "no trailing line under the last row" rule to — so an expanded last row still ends the table cleanly.fix — the chevron turned the button, not the arrow
transform: rotate(90deg)was on the<button>, which is the hit target and carries the ghost hover chip, so expanding a row swung that rounded rectangle and its highlight through a quarter turn along with the glyph. A finished 90° turn on a 24px rounded square lands back on itself, so this only shows up in motion, where the chip passes through a diamond. The transform now sits on the glyph; the button stays put.Frames are mid-animation, transition stretched from 150ms to 3s so the middle is photographable.
Before — the chip is a diamond, dragged round by the arrow:
After — the chip is square and still; only the arrow moves:
fix — the panel started at the row edge
The panel is one cell spanning the whole row with a flat
20pxinline padding, so its content began under the chevron — a column to the left of every label it describes. It now indents to the first real column:The start padding is the chevron column's fixed width plus the inline padding a cell of that density gives its own content —
calc(40px + var(--spacing-3))atbalanced. Density comes off the table context, which is why the panel had to become a small component (ExpansionPanelCell) rather than a bare<td>: the plugin builds this row insidetransformBodyRow, outside the Table's own render, so the context is not otherwise in hand.It is a logical property, so RTL mirrors it, and it is not configurable — a panel starting anywhere else reads as a misalignment rather than as a choice.
feat —
hasRowClickExpansionuseTableTreeDataalready lets the row body toggle its own disclosure; this plugin only accepted the chevron, so two tables with the same affordance behaved differently depending on which plugin drew it. Same prop name, same default offalse, so nothing changes for existing callers.The handler steps aside for anything that has its own answer to a click —
button,a,input,select,textarea,[role="button"],[role="checkbox"],[contenteditable]— and for a live text selection, so dragging across a cell to copy a figure does not also collapse what you were reading. The chevron is abutton, so it is covered by the same guard rather than by a special case, and does not double-toggle. Rows the predicate says are not expandable stay inert and keep the default cursor.feat —
panelVariantThe panel's muted wash was hardcoded. It reads well on a table sitting on the page, but inside a card the panel becomes a third surface between the card and the row and the band works against the grouping.
panelVariant: 'transparent'drops it and lets the panel inherit whatever is behind the table.'muted'(default)'transparent'Worth knowing before you reach for it: the default is close to a no-op in dark mode.
--color-background-mutedand--color-background-cardresolve to the same value in the dark theme, so a panel inside a card already looks transparent there. The prop is really about light mode; in dark mode it mostly changes what happens when the theme later separates those two tokens. Kept'muted'as the default anyway, since flipping it would move every existing panel.Two things worth a reviewer's eye
The indent lands 1px short, and that 1px is not mine. Measured in the browser: panel content at x=184, first column's text at x=185. The formula is exact; the gap is the UA stylesheet's
td { padding: 1px }leaking into cells that own a context menu.TableCellrelocates density padding onto the right-click trigger for those cells and leaves the<td>withfont/box-sizing only — no padding, which is true of the authored rules but not of the computed result. This plugin puts a context menu on every cell, so every one of them carries the stray pixel. The fix is a one-linepadding: 0inTableCell, but it shifts every context-menu cell in the system by 1px in both axes, so it wants its own PR and its own visual pass rather than riding along here.The indent assumes the chevron column is the leading one. It is, for every current caller, and the plugin already makes exactly this assumption for the panel's
colSpan(columnCountRefis captured intransformColumns, so a plugin that prepended a column later would throw both off). No regression, but it is the same latent limit in a second place.Test plan
vitest run packages/core/src/Table— 517 passed across 23 files, up from 503.useTableRowExpansion.test.tsx:calc(40px + var(--spacing-3))at the default density; it followsdensityto--spacing-4atspacious.cursor: pointeronly when wired up.transparent.griddividers too.tsc --noEmitandeslintclean.Four changesets included, one per change.
Made with Cursor