Skip to content

ref(tables): Share one sortable header cell across table shells#120587

Draft
JoshuaKGoldberg wants to merge 1 commit into
fix/aria-sort-sortable-table-headersfrom
joshgoldberg/de-1399-shared-sortable-header-cell-aria-sort
Draft

ref(tables): Share one sortable header cell across table shells#120587
JoshuaKGoldberg wants to merge 1 commit into
fix/aria-sort-sortable-table-headersfrom
joshgoldberg/de-1399-shared-sortable-header-cell-aria-sort

Conversation

@JoshuaKGoldberg

@JoshuaKGoldberg JoshuaKGoldberg commented Jul 24, 2026

Copy link
Copy Markdown
Member

Stacked on #120560.

SimpleTable.HeaderCell, the explore table kit, and the replay grids each drew their own sortable header — a label, an optional tooltip, and an arrow whose direction was recomputed from a different sort shape every time. This consolidates them into one SortableHeaderCell in components/tables. Each shell keeps its own padding, border, background, and typography; the shared cell owns the interactive element, the label, truncation, and the sort indicator.

Sortable headers now behave the same everywhere:

  • a <button> when the column can be sorted, so columns are keyboard-sortable — the explore kit previously sorted from a click-handled <div>
  • aria-sort on the columnheader, including the whole gridEditable lineage (discover, performance, insights, profiling), threaded from the columnSortBy prop GridEditable already accepted but never used
  • an aria-hidden arrow for the active direction
  • a label that clips with an ellipsis and reveals its full text on hover — previously only the explore kit truncated, so a long or translated label spilled into the neighbouring column elsewhere

Deleted along the way: the duplicate Directions union, TableHeadCellContent and its isFrozen prop, SimpleTable's isSorted prop and SortIndicator, and dead ref props on the replay header cells.

Screenshots

Row content is blanked with visibility: hidden (layout preserved, so columns are identical) because these are production tables and the rows carry customer data. The change is confined to the header row.

Table Before After
Explore › Traces — span samples (explore kit)
Header children go divbutton; sorted column gains aria-sort="descending". No visual delta.
spans before spans after
Explore › Logs (explore kit)
Same as above, plus the empty first head cell no longer renders a placeholder div.
logs before logs after
Replay detail › Network (replay virtualized grid)
Two fixes. The sort arrow and the ⓘ tooltip sat at the column's right edge, far from the label they describe — they now sit beside it, and unsorted columns no longer reserve an invisible arrow. Status was 76px, too narrow for its label plus the ⓘ, so it is now 104px and fits.
replay-network before replay-network after
Replay detail › Errors (replay virtualized grid)
Every column reserved an invisible arrow before (5 svgs); now only the sorted column draws one, and it sits beside its label.
replay-errors before replay-errors after
Monitors list (SimpleTable)
Sortable headers stay buttons and the sorted Last Issue column gains aria-sort="descending"; Assignee stays a non-interactive div. Production renders fewer columns here than the local build (flag difference, unrelated to this change).
monitors before monitors after

@linear-code

linear-code Bot commented Jul 24, 2026

Copy link
Copy Markdown

DE-1399

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

sentry Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Sentry Snapshot Testing

Name Added Removed Changed Renamed Unchanged Skipped Status
sentry-frontend
sentry-frontend
- - - - - - ✅ 451 uploaded

No base snapshots found to compare against. Make sure snapshots are uploaded from your main branch.

⚙️ sentry-frontend Snapshot Settings

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Story previews

Preview the stories changed in this PR on the Vercel deployment:

Preview deployment: https://sentry-a2va0imzy.sentry.dev

SimpleTable.HeaderCell, the explore table kit, and the replay grids each drew
their own sortable header: a label, an optional tooltip, and an arrow whose
direction was recomputed from a different sort shape every time. Consolidate
them into one SortableHeaderCell in components/tables. Each shell keeps its own
padding, border, background, and typography; the shared cell owns the
interactive element, the label, truncation, and the sort indicator.

Sortable headers now behave the same everywhere: a <button> when the column can
be sorted, an aria-hidden arrow for the active direction beside its label, and a
label that clips with an ellipsis and reveals its full text on hover. Previously
only the explore kit truncated, only SimpleTable hid the arrow from screen
readers, the explore kit sorted from a click-handled <div> that keyboard users
could not reach, and the replay grids pushed the arrow to the far edge of the
column, away from the label it describes.

Also thread aria-sort through the gridEditable lineage from columnSortBy, which
GridEditable accepted but never used, so the discover, performance, insights,
and profiling tables announce their sort state.

The replay network grid's Status column was 76px, too narrow for its label
alongside the info tooltip, so widen it to fit rather than routing a special
case through the shared cell.

Deletes the duplicate Directions union, TableHeadCellContent and its isFrozen
prop, SimpleTable's isSorted prop and SortIndicator, and dead ref props on the
replay header cells.

Fixes DE-1399
@JoshuaKGoldberg
JoshuaKGoldberg force-pushed the joshgoldberg/de-1399-shared-sortable-header-cell-aria-sort branch from 1ad9061 to f41a886 Compare July 24, 2026 21:33
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