Skip to content

refactor(table): add grouped and pinned column headers to table component#8392

Draft
LWS49 wants to merge 1 commit into
masterfrom
lws49/feat-table-grouped-headers
Draft

refactor(table): add grouped and pinned column headers to table component#8392
LWS49 wants to merge 1 commit into
masterfrom
lws49/feat-table-grouped-headers

Conversation

@LWS49
Copy link
Copy Markdown
Collaborator

@LWS49 LWS49 commented May 19, 2026

Summary

Extends the shared TanStack/MUI table component to support multi-level grouped column headers and sticky pinned columns, including full support for combining pinned columns with row selectors and row indices.

Implementation

Builder (buildHeaderRows)

A new pure function converts a flat ColumnTemplate[] into a row-major HeaderRow[] grid for the renderer. Columns are partitioned into leftPin | middle | rightPin; pinned columns always appear first and last in visual order regardless of declaration order. Pinned columns span all header rows via rowSpan = depth + 1 and are placed only in row 0. Group cells are built by a run-length scan over middle columns, collapsing contiguous same-id segments into a single GroupHeaderCell with the appropriate colSpan.

Development-time validation catches constraints TypeScript cannot express: duplicate column IDs (corrupt React reconciliation), inconsistent groupPath depths across columns, and non-contiguous same-id group segments within the same parent. In development, depth errors throw; in test, they log only.

CSS (gridSxStyles)

  • Uses borderSpacing: 0 + border-separate instead of border-collapse: collapse. Collapsed borders are shared between cells — this breaks sticky columns, which need independent borders on their own stacking layer to stay visible over scrolled content.
  • Uses borderLeft not borderRight for cell separators. In sticky contexts, borderRight on cell N is painted over by cell N+1's background; borderLeft is owned by the cell's own layer.
  • Header row separators use borderTop on non-first rows rather than borderBottom on non-last rows. Blink composites sticky layers differently when activated — borderBottom gets covered by the next row's background; borderTop is always visible.
  • Row-spanning pinned header cells restore the header/body separator via a dedicated grid-pin-rowspan class.

Sticky header offsets (useStickyHeaderOffsets)

A new hook measures each header row's rendered height with ResizeObserver (falling back to window.resize) and writes the cumulative top offset back onto rows 2+, correcting MUI's stickyHeader behaviour which sets top: 0 on every row.

Pin + indexing support

Indexing columns (row selector, row indices) are now represented as synthetic left-pinned ColumnTemplates prepended to allColumnsForHeader before buildHeaderRows is called. Their indices align directly with TanStack's header array, removing the tsOffset addition from the leaf callback entirely. In forEachCell, cells at index < tsOffset return pin: 'left' with the correct fixed width so MuiTableRow's existing offset accumulation handles them without special cases.

What it supports

  • Depth-N group header hierarchies (arbitrary nesting)
  • Left and right sticky pinned columns with correct pixel offset accumulation
  • Multi-row sticky headers that stack correctly under scroll
  • Pinned columns combined with row selectors and row index columns
  • Flat tables (no groups, no pins) are unaffected — zero overhead path

Test plan

  • buildHeaderRows unit tests cover flat, depth-1, depth-2, pin-only, and mixed layouts
  • computePinOffsets unit tests cover left/right accumulation for single, multi, and empty inputs
  • Table integration tests cover flat regression, grouped+pinned+scroll, pin+indexing (row selector), pin+indexing (indices), and sort UI placement
  • All existing consumer table tests (UsersTable, InvitationResultUsersTable, InstanceUserRoleRequestsTable, InstancesTable) continue to pass

@LWS49 LWS49 changed the title Lws49/feat table grouped headers refactor(table): add grouped and pinned column headers to table component May 19, 2026
@LWS49 LWS49 force-pushed the lws49/feat-table-grouped-headers branch 9 times, most recently from 09a480d to 26e50f1 Compare May 20, 2026 06:58
@LWS49 LWS49 force-pushed the lws49/feat-table-grouped-headers branch from 26e50f1 to 714bb53 Compare May 20, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant