Skip to content

Commit cdc72db

Browse files
authored
fix(replay): Fix row-clicks inside replay table (#95691)
**Purpose** We were inconsistent with adding analytics to "view replay details" links in the rows and in the `ReplaySessionColumn`. So I wanted to simplify that and aim to have one link in the mix instead of 3 (the row, the displayName, and the shortLink). Also, there is a problem with the existing `onRowClick` prop: it doesn't care about modifier keys. So CMD+clicking a row (to open a new browser tab) will also make the original browser tab navigate to the replay :( **Solution** So I spent a bunch of time, and looked at how issues does it, to make it so that rows within a `<ReplayTable>` can be clickable, and accessible at the same time. Basically what we have now is: - If any column is self-declared as `interactive` then we'll add the `<InteractionStateLayer>` to the row - There is no row-level interaction that can be defined within the table... but `position: relative` was removed from the RowCell, it only exists on the Row now. - So cells can be relative to the row by implementing the `:before { position: absolute; ... }` pattern. - If a cell does make itself absolute positioned, then z-index can control if that cell sits above, or below, it's neighbors. There's 4 columns that each set some combination of those properties. Together these properties create a stack/layer of columns across the whole row. | Column | `interactive: true` | `z-index: 1` | `:before{postition: absolute}` | | --- | --- | --- | --- | | ReplayDetailsLinkColumn | true | true | - | ReplayPlayPauseColumn | true | true | true | ReplaySelectColumn | true | true | - | ReplaySessionColumn | true | - | true **Layers in action** In the **main replay table** we have the columns: `ReplaySelectColumn | ReplaySessionColumn | ...rest` 1. The Select column uses `z-index:1` to appear on top of the Session column 2. The Session column uses the `:before` selector to make itself spread across the whole row. It's clickable everywhere, except under the select column Something like this: <img width="988" height="124" alt="SCR-20250716-swuc" src="https://github.com/user-attachments/assets/e03b9094-93a0-4baa-b52b-04923c94281b" /> In the **Issues>Replay** table are the columns: `ReplayPlayPauseColumn | ReplaySessionColumn | ...rest | ReplayDetailsLinkColumn` 1. The PlayPause column uses `:before` and `z-index:1` to spread across the whole row. 2. The Session column also uses `:before` to spread across the whole row, but it's not clickable because PlayPause is above it with it's z-index 3. The Details column sets it's own z-index, which puts it above the other two columns. But it's only clickable in it's own column. Something like this: <img width="990" height="121" alt="SCR-20250716-swwd" src="https://github.com/user-attachments/assets/c2b55f16-4443-4fa4-9f1f-114d2b148a6f" />
1 parent 5aadc9d commit cdc72db

File tree

6 files changed

+189
-127
lines changed

6 files changed

+189
-127
lines changed

static/app/components/replays/table/replayTable.tsx

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,19 @@ type Props = SortProps & {
2525
isPending: boolean;
2626
replays: ReplayListRecord[];
2727
showDropdownFilters: boolean;
28-
onClickRow?: (props: {replay: ReplayListRecord; rowIndex: number}) => void;
2928
};
3029

3130
export default function ReplayTable({
3231
columns,
3332
error,
3433
isPending,
35-
onClickRow,
3634
onSortClick,
3735
replays,
3836
showDropdownFilters,
3937
sort,
4038
}: Props) {
4139
const gridTemplateColumns = columns.map(col => col.width ?? 'max-content').join(' ');
40+
const hasInteractiveColumn = columns.some(col => col.interactive);
4241

4342
if (isPending) {
4443
return (
@@ -93,33 +92,24 @@ export default function ReplayTable({
9392
{replays.length === 0 && (
9493
<SimpleTable.Empty>{t('No replays found')}</SimpleTable.Empty>
9594
)}
96-
{replays.map((replay, rowIndex) => {
97-
const rows = columns.map((column, columnIndex) => (
98-
<RowCell key={`${replay.id}-${column.sortKey}`}>
99-
<column.Component
100-
columnIndex={columnIndex}
101-
replay={replay}
102-
rowIndex={rowIndex}
103-
showDropdownFilters={showDropdownFilters}
104-
/>
105-
</RowCell>
106-
));
107-
return (
108-
<SimpleTable.Row
109-
key={replay.id}
110-
variant={replay.is_archived ? 'faded' : 'default'}
111-
>
112-
{onClickRow ? (
113-
<RowContentButton as="div" onClick={() => onClickRow({replay, rowIndex})}>
114-
<InteractionStateLayer />
115-
{rows}
116-
</RowContentButton>
117-
) : (
118-
rows
119-
)}
120-
</SimpleTable.Row>
121-
);
122-
})}
95+
{replays.map((replay, rowIndex) => (
96+
<SimpleTable.Row
97+
key={replay.id}
98+
variant={replay.is_archived ? 'faded' : 'default'}
99+
>
100+
{hasInteractiveColumn ? <InteractionStateLayer /> : null}
101+
{columns.map((column, columnIndex) => (
102+
<RowCell key={`${replay.id}-${columnIndex}-${column.sortKey}`}>
103+
<column.Component
104+
columnIndex={columnIndex}
105+
replay={replay}
106+
rowIndex={rowIndex}
107+
showDropdownFilters={showDropdownFilters}
108+
/>
109+
</RowCell>
110+
))}
111+
</SimpleTable.Row>
112+
))}
123113
</StyledSimpleTable>
124114
);
125115
}
@@ -150,18 +140,7 @@ function getErrorMessage(fetchError: RequestError) {
150140
);
151141
}
152142

153-
const RowContentButton = styled('button')`
154-
display: contents;
155-
cursor: pointer;
156-
157-
border: none;
158-
background: transparent;
159-
margin: 0;
160-
padding: 0;
161-
`;
162-
163143
const RowCell = styled(SimpleTable.RowCell)`
164-
position: relative;
165144
overflow: auto;
166145
167146
/* Used for cell menu items that are hidden by default */

0 commit comments

Comments
 (0)