feat(datatable): sticky table header row

Make the DataTable's <thead> sticky so the column headers stay put at the
top of the page scroller while the rows scroll under them — the core of
'the table header should be fixed, only the rows scroll'. Benefits every
DataTable page (Channels manager, discovery, Audit log).

Each header cell gets bg-bg so rows don't show through; the header's
bottom border rides an inset box-shadow because a sticky <tr>'s own
border can scroll away with the collapsed table box. Verified on the
Audit log: 500 rows scroll under a header stuck at the scroller top; the
scroller's top fade is unobtrusive behind the solid header. Suite 15/15.
This commit is contained in:
2026-07-18 05:11:10 +02:00
parent 02bd0d433b
commit 7df9bc0035
+7 -3
View File
@@ -350,8 +350,12 @@ export default function DataTable<T>({
{/* Wide screens: table. The wrapper isn't clipped so header filter popovers can overflow. */} {/* Wide screens: table. The wrapper isn't clipped so header filter popovers can overflow. */}
<div className="hidden md:block"> <div className="hidden md:block">
<table className="w-full border-collapse text-sm"> <table className="w-full border-collapse text-sm">
<thead> {/* Sticky header row: the column headers stay put at the top of the page scroller while
<tr className="text-muted border-b border-border"> the rows scroll under them. `bg-bg` on each cell keeps rows from showing through; the
border rides a pseudo-element (box-shadow) since a sticky <tr>'s own border scrolls
away with the collapsed table box in some browsers. */}
<thead className="sticky top-0 z-10">
<tr className="text-muted [&>th]:shadow-[inset_0_-1px_0_var(--border)]">
{columns.map((col) => { {columns.map((col) => {
const sorted_ = sort?.key === col.key; const sorted_ = sort?.key === col.key;
const filterOn = isActive(filters[col.key]); const filterOn = isActive(filters[col.key]);
@@ -359,7 +363,7 @@ export default function DataTable<T>({
<th <th
key={col.key} key={col.key}
style={col.width ? { width: col.width } : undefined} style={col.width ? { width: col.width } : undefined}
className={`relative font-medium px-2 py-2 whitespace-nowrap ${align(col.align)}`} className={`relative font-medium px-2 py-2 whitespace-nowrap bg-bg ${align(col.align)}`}
> >
<span className="inline-flex items-center gap-1"> <span className="inline-flex items-center gap-1">
<button <button