From e5fa6ef45816361bbc4f68851fd70b14fcc15345 Mon Sep 17 00:00:00 2001 From: jerryzhou196 Date: Fri, 21 Nov 2025 12:19:15 -0500 Subject: [PATCH 1/7] feat(replayTable): Add sticky header support to replay table and header components --- static/app/components/replays/table/replayTable.tsx | 5 +++++ .../components/replays/table/replayTableHeader.tsx | 11 +++++++++-- static/app/views/replays/detail/playlist/index.tsx | 1 + 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/static/app/components/replays/table/replayTable.tsx b/static/app/components/replays/table/replayTable.tsx index 68cb9c5360c5d8..6fce48c135aa81 100644 --- a/static/app/components/replays/table/replayTable.tsx +++ b/static/app/components/replays/table/replayTable.tsx @@ -30,6 +30,7 @@ type Props = SortProps & { highlightedRowIndex?: number; query?: Query; ref?: RefObject; + stickyHeader?: boolean; }; export default function ReplayTable({ @@ -43,6 +44,7 @@ export default function ReplayTable({ showDropdownFilters, highlightedRowIndex = -1, sort, + stickyHeader = false, }: Props) { const gridTemplateColumns = columns.map(col => col.width ?? 'max-content').join(' '); const hasInteractiveColumn = columns.some(col => col.interactive); @@ -59,6 +61,7 @@ export default function ReplayTable({ replays={replays} onSortClick={onSortClick} sort={sort} + stickyHeader={stickyHeader} /> @@ -79,6 +82,7 @@ export default function ReplayTable({ onSortClick={onSortClick} replays={replays} sort={sort} + stickyHeader={stickyHeader} /> @@ -102,6 +106,7 @@ export default function ReplayTable({ onSortClick={onSortClick} replays={replays} sort={sort} + stickyHeader={stickyHeader} /> {replays.length === 0 && ( {t('No replays found')} diff --git a/static/app/components/replays/table/replayTableHeader.tsx b/static/app/components/replays/table/replayTableHeader.tsx index 225c233ce8ddb3..74f036971e4ac1 100644 --- a/static/app/components/replays/table/replayTableHeader.tsx +++ b/static/app/components/replays/table/replayTableHeader.tsx @@ -20,9 +20,16 @@ type Props = { replays: ReplayListRecord[]; onSortClick?: (key: string) => void; sort?: Sort; + stickyHeader?: boolean; }; -export default function ReplayTableHeader({columns, replays, onSortClick, sort}: Props) { +export default function ReplayTableHeader({ + columns, + replays, + onSortClick, + sort, + stickyHeader, +}: Props) { const listItemCheckboxState = useListItemCheckboxContext(); const {countSelected, isAllSelected, isAnySelected, queryKey, selectAll, selectedIds} = listItemCheckboxState; @@ -31,7 +38,7 @@ export default function ReplayTableHeader({columns, replays, onSortClick, sort}: return ( - + {columns.map(({Header, sortKey}, columnIndex) => ( ); } From 6376b8e266835da2f41fd35b151436dcb84b1230 Mon Sep 17 00:00:00 2001 From: jerryzhou196 Date: Fri, 21 Nov 2025 12:21:27 -0500 Subject: [PATCH 2/7] fix(replayTableHeader): Update TableHeader component to support sticky positioning --- static/app/components/replays/table/replayTableHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/components/replays/table/replayTableHeader.tsx b/static/app/components/replays/table/replayTableHeader.tsx index 74f036971e4ac1..7c32187ec65813 100644 --- a/static/app/components/replays/table/replayTableHeader.tsx +++ b/static/app/components/replays/table/replayTableHeader.tsx @@ -55,7 +55,7 @@ export default function ReplayTableHeader({ {isAnySelected ? ( - + Date: Fri, 21 Nov 2025 13:24:41 -0500 Subject: [PATCH 3/7] ref(replayTableHeader): Simplify sticky header style implementation in TableHeader component --- static/app/components/replays/table/replayTableHeader.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/app/components/replays/table/replayTableHeader.tsx b/static/app/components/replays/table/replayTableHeader.tsx index 7c32187ec65813..ad9b341715c530 100644 --- a/static/app/components/replays/table/replayTableHeader.tsx +++ b/static/app/components/replays/table/replayTableHeader.tsx @@ -36,9 +36,11 @@ export default function ReplayTableHeader({ const queryOptions = parseQueryKey(queryKey).options; const queryString = queryOptions?.query?.query; + const headerStyle = stickyHeader ? ({position: 'sticky', top: 0} as const) : {}; + return ( - + {columns.map(({Header, sortKey}, columnIndex) => ( {isAnySelected ? ( - + Date: Mon, 24 Nov 2025 09:59:44 -0500 Subject: [PATCH 4/7] ref(replayTableHeader): Update header style type for improved type safety --- static/app/components/replays/table/replayTableHeader.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/static/app/components/replays/table/replayTableHeader.tsx b/static/app/components/replays/table/replayTableHeader.tsx index ad9b341715c530..a45c7a4b935213 100644 --- a/static/app/components/replays/table/replayTableHeader.tsx +++ b/static/app/components/replays/table/replayTableHeader.tsx @@ -36,11 +36,13 @@ export default function ReplayTableHeader({ const queryOptions = parseQueryKey(queryKey).options; const queryString = queryOptions?.query?.query; - const headerStyle = stickyHeader ? ({position: 'sticky', top: 0} as const) : {}; + const headerStyle = stickyHeader + ? ({position: 'sticky', top: 0} as React.CSSProperties) + : {}; return ( - + {columns.map(({Header, sortKey}, columnIndex) => ( Date: Mon, 24 Nov 2025 10:02:17 -0500 Subject: [PATCH 5/7] docs(replayTable): Add comment clarifying stickyHeader functionality in ReplayTable component --- static/app/components/replays/table/replayTable.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/static/app/components/replays/table/replayTable.tsx b/static/app/components/replays/table/replayTable.tsx index 6fce48c135aa81..150f7d04b7bb72 100644 --- a/static/app/components/replays/table/replayTable.tsx +++ b/static/app/components/replays/table/replayTable.tsx @@ -44,6 +44,7 @@ export default function ReplayTable({ showDropdownFilters, highlightedRowIndex = -1, sort, + // stickyHeader only works if the table is inside a scrollable container stickyHeader = false, }: Props) { const gridTemplateColumns = columns.map(col => col.width ?? 'max-content').join(' '); From 3d6a3cefcec1a7006bf14a28038d94a69b941d62 Mon Sep 17 00:00:00 2001 From: jerryzhou196 Date: Mon, 24 Nov 2025 10:23:24 -0500 Subject: [PATCH 6/7] ref(replayTableHeader): Remove inline style from TableHeader for cleaner implementation --- static/app/components/replays/table/replayTableHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/components/replays/table/replayTableHeader.tsx b/static/app/components/replays/table/replayTableHeader.tsx index a45c7a4b935213..65cec80d6689b5 100644 --- a/static/app/components/replays/table/replayTableHeader.tsx +++ b/static/app/components/replays/table/replayTableHeader.tsx @@ -59,7 +59,7 @@ export default function ReplayTableHeader({ {isAnySelected ? ( - + Date: Mon, 24 Nov 2025 13:08:02 -0500 Subject: [PATCH 7/7] ref(replayTableHeader): Explicitly define headerStyle type for better clarity --- static/app/components/replays/table/replayTableHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/components/replays/table/replayTableHeader.tsx b/static/app/components/replays/table/replayTableHeader.tsx index 65cec80d6689b5..9a5192d37e3794 100644 --- a/static/app/components/replays/table/replayTableHeader.tsx +++ b/static/app/components/replays/table/replayTableHeader.tsx @@ -36,8 +36,8 @@ export default function ReplayTableHeader({ const queryOptions = parseQueryKey(queryKey).options; const queryString = queryOptions?.query?.query; - const headerStyle = stickyHeader - ? ({position: 'sticky', top: 0} as React.CSSProperties) + const headerStyle: React.CSSProperties = stickyHeader + ? {position: 'sticky', top: 0} : {}; return (