Skip to content
18 changes: 15 additions & 3 deletions README.md
Comment thread
amanmahajan7 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ Custom class name for the header row.
This property sets the text direction of the grid, it defaults to `'ltr'` (left-to-right). Setting `direction` to `'rtl'` has the following effects:

- Columns flow from right to left
- Frozen columns are pinned on the right
- Start-frozen columns are pinned on the right, and end-frozen columns are pinned on the left
- Column resize cursor is shown on the left edge of the column
- Scrollbar is moved to the left

Expand Down Expand Up @@ -1377,11 +1377,23 @@ const columns: readonly Column<Row>[] = [
];
```

##### `frozen?: Maybe<boolean>`
##### `frozen?: Maybe<boolean | 'start' | 'end'>`

**Default**: `false`

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.
Determines whether the column is frozen, and on which edge. Frozen columns stay in place when the grid is scrolled horizontally.

- `'start'` (or `true` for backwards compatibility) — pins the column to the start edge (left in LTR, right in RTL).
- `'end'` — pins the column to the end edge (right in LTR, left in RTL).
- `false` (default) — the column scrolls with the rest of the grid.

```tsx
const columns: readonly Column<Row>[] = [
{ key: 'id', name: 'ID', frozen: 'start' },
{ key: 'name', name: 'Name' },
{ key: 'actions', name: 'Actions', frozen: 'end' }
];
```

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

Expand Down
145 changes: 93 additions & 52 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ import { default as defaultRenderSortStatus } from './sortStatus';
import { cellDragHandleClassname, cellDragHandleFrozenClassname } from './style/cell';
import {
rootClassname,
frozenColumnShadowClassname,
viewportDraggingClassname,
frozenColumnShadowTopClassname
frozenColumnShadowEndClassname,
frozenColumnShadowEndTopClassname,
frozenColumnShadowStartClassname,
frozenColumnShadowStartTopClassname,
viewportDraggingClassname
} from './style/core';
import SummaryRow from './SummaryRow';

Expand Down Expand Up @@ -343,13 +345,15 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const {
columns,
colSpanColumns,
lastFrozenColumnIndex,
lastStartFrozenColumnIndex,
firstEndFrozenColumnIndex,
headerRowsCount,
colOverscanStartIdx,
colOverscanEndIdx,
templateColumns,
layoutCssVars,
totalFrozenColumnWidth
totalStartFrozenColumnWidth,
totalEndFrozenColumnWidth
} = useCalculatedColumns({
rawColumns,
defaultColumnOptions,
Expand Down Expand Up @@ -378,9 +382,14 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const isSelectable = selectedRows != null && onSelectedRowsChange != null;
const { leftKey, rightKey } = getLeftRightKey(direction);
const ariaRowCount = rawAriaRowCount ?? headerRowsCount + rows.length + summaryRowsCount;
const frozenShadowStyles: React.CSSProperties = {
gridColumnStart: lastFrozenColumnIndex + 2,
insetInlineStart: totalFrozenColumnWidth
const frozenStartShadowStyles: React.CSSProperties = {
gridColumnStart: lastStartFrozenColumnIndex + 2,
insetInlineStart: totalStartFrozenColumnWidth
};
const frozenEndShadowStyles: React.CSSProperties = {
gridColumnStart: firstEndFrozenColumnIndex + 1,
gridColumnEnd: -1,
insetInlineEnd: totalEndFrozenColumnWidth
};

const {
Expand Down Expand Up @@ -463,7 +472,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
colSpanColumns,
colOverscanStartIdx,
colOverscanEndIdx,
lastFrozenColumnIndex,
lastStartFrozenColumnIndex,
firstEndFrozenColumnIndex,
rowOverscanStartIdx,
rowOverscanEndIdx,
rows,
Expand Down Expand Up @@ -508,8 +518,14 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
(): DataGridHandle => ({
element: gridRef.current,
scrollToCell({ idx, rowIdx }) {
// frozen columns are always visible — scrolling to them is a no-op
const scrollToIdx =
idx != null && idx > lastFrozenColumnIndex && idx < columns.length ? idx : undefined;
idx != null &&
idx > lastStartFrozenColumnIndex &&
(firstEndFrozenColumnIndex === -1 || idx < firstEndFrozenColumnIndex) &&
idx < columns.length
? idx
: undefined;
const scrollToRowIdx =
rowIdx != null && validatePosition({ idx: 0, rowIdx }).isPositionInViewport
? rowIdx + headerAndTopSummaryRowsCount
Expand Down Expand Up @@ -902,7 +918,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
minRowIdx,
mainHeaderRowIdx,
maxRowIdx,
lastFrozenColumnIndex,
lastStartFrozenColumnIndex,
firstEndFrozenColumnIndex,
cellNavigationMode,
activePosition,
nextPosition,
Expand Down Expand Up @@ -967,6 +984,53 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
);
}

function renderFrozenShadow(
shadowStyles: React.CSSProperties,
bodyClassname: string,
topClassname: string
) {
return (
<>
<div
className={topClassname}
style={{
...shadowStyles,
gridRowStart: 1,
gridRowEnd: headerRowsCount + 1 + topSummaryRowsCount,
insetBlockStart: 0
}}
/>

{rows.length > 0 && (
<div
className={bodyClassname}
style={{
...shadowStyles,
gridRowStart: headerAndTopSummaryRowsCount + rowOverscanStartIdx + 1,
gridRowEnd: headerAndTopSummaryRowsCount + rowOverscanEndIdx + 2
}}
/>
)}

{bottomSummaryRows != null && bottomSummaryRowsCount > 0 && (
<div
className={topClassname}
style={{
...shadowStyles,
gridRowStart: headerAndTopSummaryRowsCount + rows.length + 1,
gridRowEnd: headerAndTopSummaryRowsCount + rows.length + 1 + bottomSummaryRowsCount,
insetBlockStart:
clientHeight > totalRowHeight
? gridHeight - summaryRowHeight * bottomSummaryRowsCount
: undefined,
insetBlockEnd: clientHeight > totalRowHeight ? undefined : 0
}}
/>
)}
</>
);
}

function getCellEditor(rowIdx: number) {
if (
!activePositionIsCellInViewport ||
Expand All @@ -978,7 +1042,10 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr

const { row } = activePosition;
const column = getActiveColumn();
const colSpan = getColSpan(column, lastFrozenColumnIndex, { type: 'ROW', row });
const colSpan = getColSpan(column, lastStartFrozenColumnIndex, firstEndFrozenColumnIndex, {
type: 'ROW',
row
});

function closeEditor(shouldFocus: boolean) {
const newPosition: ActivePosition = { idx: activePosition.idx, rowIdx, mode: 'ACTIVE' };
Expand Down Expand Up @@ -1113,7 +1180,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
style={{
...style,
// set scrollPadding to correctly scroll to non-sticky cells/rows
scrollPaddingInlineStart: totalFrozenColumnWidth,
scrollPaddingInlineStart: totalStartFrozenColumnWidth,
scrollPaddingInlineEnd: totalEndFrozenColumnWidth,
scrollPaddingBlockStart: headerRowsHeight + topSummaryRowsCount * summaryRowHeight,
scrollPaddingBlockEnd: bottomSummaryRowsCount * summaryRowHeight,
gridTemplateColumns,
Expand Down Expand Up @@ -1227,46 +1295,19 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
)}
</DataGridDefaultRenderersContext>

{lastFrozenColumnIndex > -1 && (
<>
<div
className={frozenColumnShadowTopClassname}
style={{
...frozenShadowStyles,
gridRowStart: 1,
gridRowEnd: headerRowsCount + 1 + topSummaryRowsCount,
insetBlockStart: 0
}}
/>
{lastStartFrozenColumnIndex > -1 &&
renderFrozenShadow(
frozenStartShadowStyles,
frozenColumnShadowStartClassname,
frozenColumnShadowStartTopClassname
)}

{rows.length > 0 && (
<div
className={frozenColumnShadowClassname}
style={{
...frozenShadowStyles,
gridRowStart: headerAndTopSummaryRowsCount + rowOverscanStartIdx + 1,
gridRowEnd: headerAndTopSummaryRowsCount + rowOverscanEndIdx + 2
}}
/>
)}

{bottomSummaryRows != null && bottomSummaryRowsCount > 0 && (
<div
className={frozenColumnShadowTopClassname}
style={{
...frozenShadowStyles,
gridRowStart: headerAndTopSummaryRowsCount + rows.length + 1,
gridRowEnd: headerAndTopSummaryRowsCount + rows.length + 1 + bottomSummaryRowsCount,
insetBlockStart:
clientHeight > totalRowHeight
? gridHeight - summaryRowHeight * bottomSummaryRowsCount
: undefined,
insetBlockEnd: clientHeight > totalRowHeight ? undefined : 0
}}
/>
)}
</>
)}
{firstEndFrozenColumnIndex > -1 &&
renderFrozenShadow(
frozenEndShadowStyles,
frozenColumnShadowEndClassname,
frozenColumnShadowEndTopClassname
)}

{getDragHandle()}

Expand Down
4 changes: 2 additions & 2 deletions src/GroupRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { classnames } from './utils';
import type { BaseRenderRowProps, GroupRow, Omit } from './types';
import { SELECT_COLUMN_KEY } from './Columns';
import GroupCell from './GroupCell';
import { cell, cellFrozen } from './style/cell';
import { cell, cellFrozenBase } from './style/cell';
import { rowClassname, rowActiveClassname } from './style/row';

const groupRow = css`
Expand All @@ -15,7 +15,7 @@ const groupRow = css`
background-color: var(--rdg-header-background-color);
}

> .${cell}:not(:last-child, .${cellFrozen}), > :nth-last-child(n + 2 of .${cellFrozen}) {
> .${cell}:not(:last-child, .${cellFrozenBase}), > :nth-last-child(n + 2 of .rdg-cell-frozen-start), > :nth-last-child(n + 2 of .rdg-cell-frozen-end) {
border-inline-end: none;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/HeaderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
} from './types';
import type { DataGridProps } from './DataGrid';
import HeaderCell from './HeaderCell';
import { cell, cellFrozen } from './style/cell';
import { cell, cellFrozenBase } from './style/cell';
import { rowActiveClassname } from './style/row';

type SharedDataGridProps<R, SR, K extends React.Key> = Pick<
Expand Down Expand Up @@ -44,7 +44,7 @@ const headerRow = css`
position: sticky;
}

& > .${cellFrozen} {
& > .${cellFrozenBase} {
z-index: 3;
}
}
Expand Down
Loading