Skip to content

Commit efbc791

Browse files
committed
Add frozen: 'start' | 'end' for end-edge column pinning
1 parent 0dbd4c2 commit efbc791

18 files changed

Lines changed: 743 additions & 106 deletions

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,11 +1377,23 @@ const columns: readonly Column<Row>[] = [
13771377
];
13781378
```
13791379

1380-
##### `frozen?: Maybe<boolean>`
1380+
##### `frozen?: Maybe<boolean | 'start' | 'end'>`
13811381

13821382
**Default**: `false`
13831383

1384-
Determines whether column is frozen. Frozen columns are pinned to the start edge (left in LTR, right in RTL). Per-column pinning to the end edge is not supported at the moment.
1384+
Determines whether the column is frozen, and on which edge. Frozen columns stay in place when the grid is scrolled horizontally.
1385+
1386+
- `'start'` (or `true` for backwards compatibility) — pins the column to the start edge (left in LTR, right in RTL).
1387+
- `'end'` — pins the column to the end edge (right in LTR, left in RTL).
1388+
- `false` (default) — the column scrolls with the rest of the grid.
1389+
1390+
```tsx
1391+
const columns: readonly Column<Row>[] = [
1392+
{ key: 'id', name: 'ID', frozen: 'start' },
1393+
{ key: 'name', name: 'Name' },
1394+
{ key: 'actions', name: 'Actions', frozen: 'end' }
1395+
];
1396+
```
13851397

13861398
##### `resizable?: Maybe<boolean>`
13871399

src/DataGrid.tsx

Lines changed: 77 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ import { cellDragHandleClassname, cellDragHandleFrozenClassname } from './style/
7575
import {
7676
rootClassname,
7777
frozenColumnShadowClassname,
78+
frozenColumnShadowEndClassname,
7879
viewportDraggingClassname,
79-
frozenColumnShadowTopClassname
80+
frozenColumnShadowTopClassname,
81+
frozenColumnShadowEndTopClassname
8082
} from './style/core';
8183
import SummaryRow from './SummaryRow';
8284

@@ -344,12 +346,14 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
344346
columns,
345347
colSpanColumns,
346348
lastFrozenColumnIndex,
349+
firstEndFrozenColumnIndex,
347350
headerRowsCount,
348351
colOverscanStartIdx,
349352
colOverscanEndIdx,
350353
templateColumns,
351354
layoutCssVars,
352-
totalFrozenColumnWidth
355+
totalFrozenColumnWidth,
356+
totalEndFrozenColumnWidth
353357
} = useCalculatedColumns({
354358
rawColumns,
355359
defaultColumnOptions,
@@ -382,6 +386,11 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
382386
gridColumnStart: lastFrozenColumnIndex + 2,
383387
insetInlineStart: totalFrozenColumnWidth
384388
};
389+
const frozenEndShadowStyles: React.CSSProperties = {
390+
gridColumnStart: firstEndFrozenColumnIndex + 1,
391+
gridColumnEnd: -1,
392+
insetInlineEnd: totalEndFrozenColumnWidth
393+
};
385394

386395
const {
387396
activePosition,
@@ -464,6 +473,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
464473
colOverscanStartIdx,
465474
colOverscanEndIdx,
466475
lastFrozenColumnIndex,
476+
firstEndFrozenColumnIndex,
467477
rowOverscanStartIdx,
468478
rowOverscanEndIdx,
469479
rows,
@@ -903,6 +913,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
903913
mainHeaderRowIdx,
904914
maxRowIdx,
905915
lastFrozenColumnIndex,
916+
firstEndFrozenColumnIndex,
906917
cellNavigationMode,
907918
activePosition,
908919
nextPosition,
@@ -967,6 +978,53 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
967978
);
968979
}
969980

981+
function renderFrozenShadow(
982+
shadowStyles: React.CSSProperties,
983+
bodyClassname: string,
984+
topClassname: string
985+
) {
986+
return (
987+
<>
988+
<div
989+
className={topClassname}
990+
style={{
991+
...shadowStyles,
992+
gridRowStart: 1,
993+
gridRowEnd: headerRowsCount + 1 + topSummaryRowsCount,
994+
insetBlockStart: 0
995+
}}
996+
/>
997+
998+
{rows.length > 0 && (
999+
<div
1000+
className={bodyClassname}
1001+
style={{
1002+
...shadowStyles,
1003+
gridRowStart: headerAndTopSummaryRowsCount + rowOverscanStartIdx + 1,
1004+
gridRowEnd: headerAndTopSummaryRowsCount + rowOverscanEndIdx + 2
1005+
}}
1006+
/>
1007+
)}
1008+
1009+
{bottomSummaryRows != null && bottomSummaryRowsCount > 0 && (
1010+
<div
1011+
className={topClassname}
1012+
style={{
1013+
...shadowStyles,
1014+
gridRowStart: headerAndTopSummaryRowsCount + rows.length + 1,
1015+
gridRowEnd: headerAndTopSummaryRowsCount + rows.length + 1 + bottomSummaryRowsCount,
1016+
insetBlockStart:
1017+
clientHeight > totalRowHeight
1018+
? gridHeight - summaryRowHeight * bottomSummaryRowsCount
1019+
: undefined,
1020+
insetBlockEnd: clientHeight > totalRowHeight ? undefined : 0
1021+
}}
1022+
/>
1023+
)}
1024+
</>
1025+
);
1026+
}
1027+
9701028
function getCellEditor(rowIdx: number) {
9711029
if (
9721030
!activePositionIsCellInViewport ||
@@ -978,7 +1036,10 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
9781036

9791037
const { row } = activePosition;
9801038
const column = getActiveColumn();
981-
const colSpan = getColSpan(column, lastFrozenColumnIndex, { type: 'ROW', row });
1039+
const colSpan = getColSpan(column, lastFrozenColumnIndex, firstEndFrozenColumnIndex, {
1040+
type: 'ROW',
1041+
row
1042+
});
9821043

9831044
function closeEditor(shouldFocus: boolean) {
9841045
const newPosition: ActivePosition = { idx: activePosition.idx, rowIdx, mode: 'ACTIVE' };
@@ -1114,6 +1175,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
11141175
...style,
11151176
// set scrollPadding to correctly scroll to non-sticky cells/rows
11161177
scrollPaddingInlineStart: totalFrozenColumnWidth,
1178+
scrollPaddingInlineEnd: totalEndFrozenColumnWidth,
11171179
scrollPaddingBlockStart: headerRowsHeight + topSummaryRowsCount * summaryRowHeight,
11181180
scrollPaddingBlockEnd: bottomSummaryRowsCount * summaryRowHeight,
11191181
gridTemplateColumns,
@@ -1227,46 +1289,19 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
12271289
)}
12281290
</DataGridDefaultRenderersContext>
12291291

1230-
{lastFrozenColumnIndex > -1 && (
1231-
<>
1232-
<div
1233-
className={frozenColumnShadowTopClassname}
1234-
style={{
1235-
...frozenShadowStyles,
1236-
gridRowStart: 1,
1237-
gridRowEnd: headerRowsCount + 1 + topSummaryRowsCount,
1238-
insetBlockStart: 0
1239-
}}
1240-
/>
1292+
{lastFrozenColumnIndex > -1 &&
1293+
renderFrozenShadow(
1294+
frozenShadowStyles,
1295+
frozenColumnShadowClassname,
1296+
frozenColumnShadowTopClassname
1297+
)}
12411298

1242-
{rows.length > 0 && (
1243-
<div
1244-
className={frozenColumnShadowClassname}
1245-
style={{
1246-
...frozenShadowStyles,
1247-
gridRowStart: headerAndTopSummaryRowsCount + rowOverscanStartIdx + 1,
1248-
gridRowEnd: headerAndTopSummaryRowsCount + rowOverscanEndIdx + 2
1249-
}}
1250-
/>
1251-
)}
1252-
1253-
{bottomSummaryRows != null && bottomSummaryRowsCount > 0 && (
1254-
<div
1255-
className={frozenColumnShadowTopClassname}
1256-
style={{
1257-
...frozenShadowStyles,
1258-
gridRowStart: headerAndTopSummaryRowsCount + rows.length + 1,
1259-
gridRowEnd: headerAndTopSummaryRowsCount + rows.length + 1 + bottomSummaryRowsCount,
1260-
insetBlockStart:
1261-
clientHeight > totalRowHeight
1262-
? gridHeight - summaryRowHeight * bottomSummaryRowsCount
1263-
: undefined,
1264-
insetBlockEnd: clientHeight > totalRowHeight ? undefined : 0
1265-
}}
1266-
/>
1267-
)}
1268-
</>
1269-
)}
1299+
{firstEndFrozenColumnIndex > -1 &&
1300+
renderFrozenShadow(
1301+
frozenEndShadowStyles,
1302+
frozenColumnShadowEndClassname,
1303+
frozenColumnShadowEndTopClassname
1304+
)}
12701305

12711306
{getDragHandle()}
12721307

src/GroupRow.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { classnames } from './utils';
66
import type { BaseRenderRowProps, GroupRow, Omit } from './types';
77
import { SELECT_COLUMN_KEY } from './Columns';
88
import GroupCell from './GroupCell';
9-
import { cell, cellFrozen } from './style/cell';
9+
import { cell, cellFrozen, cellFrozenEnd } from './style/cell';
1010
import { rowClassname, rowActiveClassname } from './style/row';
1111

1212
const groupRow = css`
@@ -15,8 +15,9 @@ const groupRow = css`
1515
background-color: var(--rdg-header-background-color);
1616
}
1717
18-
> .${cell}:not(:last-child, .${cellFrozen}),
19-
> :nth-last-child(n + 2 of .${cellFrozen}) {
18+
> .${cell}:not(:last-child, .${cellFrozen}, .${cellFrozenEnd}),
19+
> :nth-last-child(n + 2 of .${cellFrozen}),
20+
> :nth-child(n + 2 of .${cellFrozenEnd}) {
2021
border-inline-end: none;
2122
}
2223
}

src/HeaderRow.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
} from './types';
1313
import type { DataGridProps } from './DataGrid';
1414
import HeaderCell from './HeaderCell';
15-
import { cell, cellFrozen } from './style/cell';
15+
import { cell, cellFrozen, cellFrozenEnd } from './style/cell';
1616
import { rowActiveClassname } from './style/row';
1717

1818
type SharedDataGridProps<R, SR, K extends React.Key> = Pick<
@@ -44,7 +44,7 @@ const headerRow = css`
4444
position: sticky;
4545
}
4646
47-
& > .${cellFrozen} {
47+
& > .${cellFrozen}, & > .${cellFrozenEnd} {
4848
z-index: 3;
4949
}
5050
}

0 commit comments

Comments
 (0)