Skip to content

ref(tables): unify table components onto one shared Table shell - #120745

Draft
JoshuaKGoldberg wants to merge 2 commits into
joshgoldberg/de-1397-consolidate-column-resize-into-usecolumnresizefrom
de-1392-unified-table
Draft

ref(tables): unify table components onto one shared Table shell#120745
JoshuaKGoldberg wants to merge 2 commits into
joshgoldberg/de-1397-consolidate-column-resize-into-usecolumnresizefrom
de-1392-unified-table

Conversation

@JoshuaKGoldberg

Copy link
Copy Markdown
Member

Stacked on #120563base is that branch, not master, so this diff is only the new work. #120563 needs to land first.

What

Sentry had two sanctioned table components sharing no code, a third de-facto shell leaking out of gridEditable/styles.tsx, two column-resize implementations, and two competing column-track models.

This adds components/tables/table/ — a dumb shell that owns only table geometry:

  • real <table>/<thead>/<tbody>/<th>/<td> on display: grid with grid-template-columns: subgrid
  • column tracks, and the resize drag (delegated to useColumnResize from ref(tables): consolidate column-resize handling into useColumnResize #120563)
  • columns declared as data, because a resize needs stable identity and a known column count; cell contents stay as children
  • widths either controlled (onColumnResize) or kept internally

GridEditable, SimpleTable, the explore table kit and InfiniteTable become facades over it. useTableStyles is deleted, the second resize drag loop and second track builder are gone, and InfiniteTable no longer writes grid-template-columns onto every row — so there is now one resize implementation and one track model.

Net effect on the touched files is −215 lines, and gridEditable/styles.tsx drops from a structural layer to a thin theming layer.

Reviewer notes

Three intentional behaviour changes worth a look:

  1. Explicit role attributes are kept alongside the real elements. display: grid drops implicit table/row/cell semantics in Chrome and Safari, so real <td> alone would have been an a11y regression. Keeping the roles also means existing getByRole queries and [role='cell'] CSS keep working.
  2. Sortable headers are now <th aria-sort><button> instead of a div[role=columnheader] that was itself the button. That's the standard accessible pattern, but it changes the DOM contract — the button is stretched to fill the cell so the click target is unchanged.
  3. InfiniteTable's virtualization strategy changed from absolute-transform to spacer-padding (what logsInfiniteTable already uses). Forced, not incidental: an absolutely positioned element is not a grid item, so subgrid on it computes to none, which would reintroduce the per-row track model this PR removes.

Not visually verified. Everything here is type-, lint- and DOM-semantics-checked; this changes the rendered element type of most tables in the app, and CSS behaves differently on table elements. Worth eyeballing Discover, Explore, Logs, Issues and a settings page.

Not in this PR

  • PanelTable — split out deliberately. It has no Row/Cell abstraction, so its ~41 consumers hand-roll <div>s, and the wrapper and cells are coupled per consumer (any intermediate state is invalid DOM). It gets its own earlier PR that introduces the abstraction first.
  • resultGrid (app + gsAdmin) and the three key/value tables — separately tracked.

Follow-ups

  • Extract useVirtualRows: infiniteTable and logsInfiniteTable now use identical spacer-padding math, and replays useVirtualizedGrid is a third copy (reopens DE-1398)
  • InfiniteTable's scrollMargin is unset, so the virtual window is offset by the sticky header (~40px, absorbed by overscan today)
  • GridEditable and PanelTable can't use the shell's divider row prop yet — their rules are entangled with head-row exclusion and radius
  • disableHeaders / disableHeaderBorderBottom on PanelTable have zero consumers

Ref DE-1392, DE-1405

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

Sentry had two sanctioned table components that shared no code, plus a third
de-facto shell leaking out of `gridEditable/styles.tsx`, two column-resize
implementations, and two competing column-track models.

Adds `components/tables/table/`: a dumb shell that owns only table geometry —
real `<table>/<thead>/<tbody>/<th>/<td>` on `display: grid` with
`grid-template-columns: subgrid`, column tracks, and the resize drag (delegated
to `useColumnResize`). Columns are declared as data because a resize needs
stable identity and a known column count; cell contents stay as children.
Widths are controlled or uncontrolled. Explicit `role` attributes are kept
because `display: grid` drops implicit table semantics in Chrome and Safari.

GridEditable, SimpleTable, the explore table kit and InfiniteTable are now
facades over it:

- `useTableStyles` is deleted; its callers go through the shell
- the second resize drag loop and second track builder are gone
- InfiniteTable no longer overrides `grid-template-columns` per row, so there
  is one track model
- SimpleTable renders real cells instead of `div[role=cell]`

Sortable headers become `<th aria-sort><button>` rather than a
`div[role=columnheader]` that was itself the button, which is the standard
accessible pattern.

Ref DE-1392
Ref DE-1405
@linear-code

linear-code Bot commented Jul 28, 2026

Copy link
Copy Markdown

DE-1392

DE-1405

@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

📊 Type Coverage Diff

Metric Before After Delta
Coverage 94.04% 94.03% 🔴 -0.01%
Typed 136,590 136,631 🟢 +41
Untyped 8,663 8,669 🔴 +6
🔍 6 new type safety issues introduced

any-typed symbols (5 new)

File Line Detail
static/app/components/tables/gridEditable/styles.tsx 195 GridResizer (var)
static/app/components/tables/table/index.tsx 255 Head (var)
static/app/components/tables/table/index.tsx 256 Body (var)
static/app/components/tables/table/index.tsx 257 Row (var)
static/app/components/tables/table/index.tsx 258 Cell (var)

Non-null assertions (!) (1 new)

File Line Detail
static/app/components/tables/gridEditable/index.tsx 137 props.columnOrder[columnIndex]!

This is informational only and does not block the PR.

Follow-ups from the shell PR.

`infiniteTable` and `logsInfiniteTable` now share one spacer-padding
virtualization hook. Its padding formula accounts for `scrollMargin`, which
fixes the offset InfiniteTable had after its scroll element moved to the table.
Replays' `useVirtualizedGrid` is left alone: it measures column widths rather
than spacing rows, so it shares only `useVirtualizer` boilerplate.

Removes shell surface that turned out to be unreachable or unused:

- `lastColumnFlexible` was only read by the default track rule, which its one
  caller bypasses by supplying `getColumnTrack`
- `dataRows` sized a resize handle that is invisible until hover, long after the
  `--table-resizer-height` var is written
- `stickyHeader` and `writeResizerHeightVar` had no callers

Collapses the remaining duplicated styles: the two `grid-column` header-cell
variants and the full-span row cell move into SimpleTable, and the two teams
tables stop declaring byte-identical table and link styles.

Ref DE-1392
Ref DE-1398
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant