Skip to content

feat: DataTable internal scrolling with pinned chrome via Layout fill#350

Draft
interacsean wants to merge 2 commits into
mainfrom
feat/1388-datatable-internal-scroll
Draft

feat: DataTable internal scrolling with pinned chrome via Layout fill#350
interacsean wants to merge 2 commits into
mainfrom
feat/1388-datatable-internal-scroll

Conversation

@interacsean

@interacsean interacsean commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements DataTable internal scrolling (tailor-inc/platform-planning#1388): the page title, table toolbar, column header row, and pagination footer stay visible at every viewport height — only the rows region scrolls.

datatable-scrolling.mp4

How it works (height chain, top → bottom)

Layer Change
SidebarProvider wrapper min-h-svhh-svh — the shell is viewport-bounded; the document never scrolls
SidebarLayout content area overflow-y-auto — regular pages scroll here now
Layout New opt-in fill prop: flex-1 min-h-0 + grid-rows-[auto_minmax(0,1fr)] so the header row stays natural-height and the column row is bounded
DataTable.Root Shrinkable flex column (min-h-0, no flex-1 — short tables keep natural height)
Table container The vertical scroll region (overflow-auto min-h-0)
Column header row sticky top-0 bg-card with inset-shadow bottom border (Chrome drops row borders on sticky under border-collapse)

Also fixed along the way

  • Empty/error state reserved pageSize × 53px of height — at p=25 that was a 1325px blank region (previously a massively tall table; post-change a scrollable void). Now capped at 5 rows, with the message sticky so it stays in view in constrained containers.
  • Skeleton loader rows were hardcoded to 53px vs. real rows at 52.5px, causing a ~12px shift when data resolved. Skeletons now derive height from the same structure as real rows (icon-button footprint, text line box, badge pills) — measured delta is 0.

Examples & docs

  • Products page (vite-app) uses <Layout fill> with ~200 mock rows, default 25/page
  • New /dashboard/long-content example page (no fill) verifies content-area scrolling still works for normal pages
  • docs/components/layout.md Fill Mode section; list/dense-scan pattern updated (full <Layout fill> page composition, new "Page Layout & Internal Scrolling" guidance) and skill docs regenerated
  • Changeset: minor

Acceptance criteria (#1388)

  • Title, column header row, and footer remain visible regardless of row count or viewport height
  • Only the rows/body region scrolls vertically when content exceeds the available height
  • Behaviour works within the SidebarLayout content area of the AppShell
  • No regression when the table is short enough to fit — verified 0px gap last-row→footer, no scrollbar, no stretch (also verified empty and 3-row states)

⚠️ Behavior change to socialize

The AppShell layout is now viewport-bounded (h-svh), so page content scrolls inside the content area rather than on the document. Code relying on window/document scroll position should target the content area instead.

Test plan

  • 1220 unit tests, type-check, lint, fmt all green (includes 3 new Layout fill tests)
  • Browser-verified at 700px viewport: pinned chrome mid-scroll, sticky header opaque, footer visible
  • Long-content page scrolls in content area; breadcrumb bar pinned
  • Empty state (f.name:contains=xyzzy), 3-row state, p=10 short table — no scrollbar/stretch
  • Loading→resolved height delta measured at 0px

🤖 Generated with Claude Code

@interacsean interacsean force-pushed the feat/1388-datatable-internal-scroll branch from e59bede to 8b9e623 Compare July 2, 2026 06:07
interacsean and others added 2 commits July 2, 2026 16:16
Implements internal scrolling for DataTable inside the AppShell layout
(tailor-inc/platform-planning#1388):

- Shell is now viewport-bounded (h-svh) with the content area taking over
  scroll duty (overflow-y-auto, full-bleed so the scrollbar sits at the
  window edge), so scrolling never happens on the document
- New opt-in `fill` prop on Layout stretches the page to the available
  height and bounds the column row (grid minmax(0,1fr)) so children can
  scroll internally
- DataTable.Root becomes a shrinkable flex column; the table container is
  the vertical scroll region with a sticky column header row; Toolbar and
  Footer stay pinned
- Empty/error state height fixed at 3 rows (was pageSize-worth, creating a
  huge blank/scrollable region at large page sizes); message is sticky so
  it stays in view in height-constrained containers
- Skeleton loader rows derive their height from the same structure as real
  rows (icon-button footprint, text line box, badge pills) so the table
  no longer shifts when data resolves

Examples: products page uses <Layout fill> with ~200 mock rows and a
25-row default page size; new long-content page verifies content-area
scrolling for pages without fill. Pattern docs (list/dense-scan) and
Layout docs updated and regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Once the content area has scrolled, a mask fades its top edge to
transparent so content dissolves into the pinned breadcrumb rather than
cutting off abruptly. Masking (rather than overlaying a matching colour)
reveals the real page backdrop — which is a theme gradient with
background-attachment: fixed, so no single colour could match it — making
the fade seamless on every theme. Toggled by a lightweight scroll listener
(re-renders only when crossing scrollTop 0), so content isn't faded at
rest and pages whose content fits show nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@interacsean interacsean force-pushed the feat/1388-datatable-internal-scroll branch from 8b9e623 to f5eaf2f Compare July 2, 2026 06:17
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