Skip to content
This repository was archived by the owner on Dec 16, 2022. It is now read-only.

Commit 4584d06

Browse files
authored
[WINDOW]: pinned column support for left columns (#158)
1 parent 7c9c550 commit 4584d06

File tree

15 files changed

+435
-247
lines changed

15 files changed

+435
-247
lines changed

.changeset/violet-moles-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@resembli/react-virtualized-window": minor
3+
---
4+
5+
Added support for pinned left columns
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as React from "react"
2+
3+
import { RenderItem } from "./RenderItem"
4+
import type { GridProps } from "./types"
5+
6+
interface PinnedColumnsProps<T> {
7+
totalWidth: number
8+
topOffset: number
9+
left?: number
10+
right?: number
11+
rtl?: boolean
12+
columns: T[][]
13+
widths: number[]
14+
heights: number[]
15+
runningHeight: number
16+
vertStart: number
17+
vertEnd: number
18+
verticalGap: number
19+
horizontalGap: number
20+
Component: GridProps<T>["children"]
21+
}
22+
23+
export function PinnedColumn<T>({
24+
totalWidth,
25+
left,
26+
right,
27+
topOffset,
28+
columns,
29+
widths,
30+
heights,
31+
rtl,
32+
runningHeight,
33+
Component,
34+
vertStart,
35+
vertEnd,
36+
verticalGap,
37+
horizontalGap,
38+
}: PinnedColumnsProps<T>) {
39+
return (
40+
<div
41+
style={{
42+
width: totalWidth,
43+
position: "sticky",
44+
left,
45+
right,
46+
transform: `translate3d(0px, ${topOffset}px, 0px)`,
47+
height: innerHeight,
48+
display: "flex",
49+
}}
50+
>
51+
{columns.map((pinnedColumn, colIndex) => {
52+
const columnWidth = widths[colIndex]
53+
54+
const marginLeft = !rtl && colIndex !== 0 ? horizontalGap : 0
55+
const marginRight = rtl && colIndex !== 0 ? horizontalGap : 0
56+
57+
return (
58+
<div key={colIndex}>
59+
<div style={{ height: runningHeight }} />
60+
{pinnedColumn.slice(vertStart, vertEnd).map((row, rowIndex) => {
61+
const height = heights[rowIndex + vertStart]
62+
return (
63+
<div
64+
key={rowIndex + vertStart}
65+
style={{
66+
height,
67+
marginTop: rowIndex + vertStart === 0 ? 0 : verticalGap,
68+
marginBottom: rowIndex + vertStart === heights.length - 1 ? 0 : verticalGap,
69+
}}
70+
>
71+
<RenderItem
72+
Component={Component}
73+
marginLeft={marginLeft}
74+
marginRight={marginRight}
75+
itemProps={row}
76+
pinned="left"
77+
column={colIndex}
78+
itemWidth={columnWidth}
79+
row={rowIndex + vertStart}
80+
/>
81+
</div>
82+
)
83+
})}
84+
</div>
85+
)
86+
})}
87+
</div>
88+
)
89+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import * as React from "react"
2+
3+
import type { CellMeta, GridProps } from "./types"
4+
5+
type RenderItemsProps<T> = {
6+
Component: GridProps<T>["children"]
7+
itemProps: T
8+
itemWidth: number
9+
column: number
10+
row: number
11+
marginLeft: number
12+
marginRight: number
13+
pinned?: "left" | "right"
14+
}
15+
16+
export const RenderItem = function <T>({
17+
Component,
18+
itemProps,
19+
itemWidth,
20+
column,
21+
row,
22+
marginRight,
23+
marginLeft,
24+
pinned,
25+
}: RenderItemsProps<T>) {
26+
const itemStyles = React.useMemo(() => {
27+
return {
28+
width: itemWidth,
29+
minWidth: itemWidth,
30+
maxWidth: itemWidth,
31+
height: "100%",
32+
marginLeft,
33+
marginRight,
34+
}
35+
}, [itemWidth, marginLeft, marginRight])
36+
37+
const cellMeta = React.useMemo<CellMeta>(() => ({ row, column, pinned }), [column, pinned, row])
38+
39+
return <Component data={itemProps} style={itemStyles} cellMeta={cellMeta} />
40+
}
Lines changed: 50 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,19 @@
11
import * as React from "react"
22

3+
import { PinnedColumn } from "../PinnedColumn"
34
import { SizingDiv } from "../SizingDiv"
45
import { StickyDiv } from "../StickyDiv"
56
import { getHorizontalGap, getVerticalGap } from "../itemGapUtilities"
6-
import type { NumberOrPercent, VirtualWindowBaseProps } from "../types"
7+
import type { GridProps } from "../types"
78
import { useDataDimension } from "../useDataDimension"
89
import { useIndicesForDimensions } from "../useDimensionIndices"
910
import { useScrollAdjustWindowDims } from "../useScrollAdjustedDim"
11+
import { useScrollItems } from "../useScrollItems"
1012
import { useSmartSticky } from "../useSmartSticky"
1113
import { useWindowApi } from "../useWindowApi"
1214
import { useWindowDimensions } from "../useWindowDimensions"
1315
import { useWindowScroll } from "../useWindowScroll"
1416

15-
export interface CellMeta {
16-
column: number
17-
row: number
18-
}
19-
20-
export interface GridProps<T> extends VirtualWindowBaseProps<T> {
21-
data: T[][]
22-
children: <B extends T>(props: {
23-
data: B
24-
style: React.CSSProperties
25-
cellMeta: CellMeta
26-
}) => JSX.Element
27-
defaultRowHeight: NumberOrPercent
28-
rowHeights?: NumberOrPercent[]
29-
defaultColumnWidth: NumberOrPercent
30-
columnWidths?: NumberOrPercent[]
31-
}
32-
33-
export type RenderItem<T> = GridProps<T>["children"]
34-
3517
export function Grid<T>({
3618
data,
3719
children,
@@ -57,6 +39,9 @@ export function Grid<T>({
5739
height: sizingHeight,
5840

5941
onScroll: userOnScroll,
42+
43+
pinnedLeft,
44+
leftWidths,
6045
}: GridProps<T>) {
6146
const windowRef = React.useRef<HTMLDivElement>(null)
6247
const transRef = React.useRef<HTMLDivElement>(null)
@@ -120,70 +105,30 @@ export function Grid<T>({
120105
overscan: overscan ?? 0,
121106
})
122107

123-
const scrollableItems = React.useMemo(
124-
function Items() {
125-
return (
126-
<>
127-
<div style={{ height: runningHeight }}></div>
128-
{data.slice(vertStart, vertEnd).map((row, i) => {
129-
const rowKey = i + vertStart
130-
const itemHeight = dataHeights[vertStart + i]
131-
132-
const rowChildren = row.slice(horiStart, horiEnd).map((cell, j) => {
133-
const cellKey = getKey?.(cell) ?? horiStart + j
134-
const itemWidth = dataWidths[horiStart + j]
135-
136-
return (
137-
<RenderItem
138-
key={cellKey}
139-
itemWidth={itemWidth}
140-
Component={children}
141-
marginLeft={rtl || j + horiStart === 0 ? 0 : horizontalGap}
142-
marginRight={!rtl || j + horiStart === 0 ? 0 : horizontalGap}
143-
itemProps={cell}
144-
column={horiStart + j}
145-
row={vertStart + i}
146-
/>
147-
)
148-
})
108+
const [lWidths, leftTotalWidth] = useDataDimension({
109+
count: pinnedLeft?.length ?? 0,
110+
defaultDimension: defaultColumnWidth,
111+
windowDim: adjustedWidth,
112+
gap: horizontalGap,
113+
dimensions: leftWidths,
114+
})
149115

150-
return (
151-
<div
152-
key={rowKey}
153-
style={{
154-
display: "flex",
155-
height: itemHeight,
156-
minHeight: itemHeight,
157-
maxHeight: itemHeight,
158-
marginTop: i + vertStart === 0 ? 0 : verticalGap,
159-
marginBottom: i + vertStart === data.length - 1 ? 0 : verticalGap,
160-
}}
161-
>
162-
<div style={{ width: runningWidth }} />
163-
{rowChildren}
164-
</div>
165-
)
166-
})}
167-
</>
168-
)
169-
},
170-
[
171-
children,
172-
data,
173-
dataHeights,
174-
dataWidths,
175-
getKey,
176-
horiEnd,
177-
horiStart,
178-
horizontalGap,
179-
rtl,
180-
runningHeight,
181-
runningWidth,
182-
vertEnd,
183-
vertStart,
184-
verticalGap,
185-
],
186-
)
116+
const scrollableItems = useScrollItems({
117+
children,
118+
data,
119+
dataHeights,
120+
dataWidths,
121+
getKey,
122+
horiEnd,
123+
horiStart,
124+
horizontalGap,
125+
rtl,
126+
runningHeight,
127+
runningWidth,
128+
vertEnd,
129+
vertStart,
130+
verticalGap,
131+
})
187132

188133
return (
189134
<SizingDiv
@@ -206,7 +151,7 @@ export function Grid<T>({
206151
direction: rtl ? "rtl" : "ltr",
207152
}}
208153
>
209-
<div style={{ width: innerWidth, height: innerHeight, contain: "strict" }}>
154+
<div style={{ width: innerWidth + leftTotalWidth - horizontalGap, height: innerHeight }}>
210155
<StickyDiv
211156
disabled={disableSticky ?? false}
212157
rtl={rtl ?? false}
@@ -221,51 +166,34 @@ export function Grid<T>({
221166
disableSticky ? 0 : -topOffset
222167
}px, 0px)`,
223168
top: 0,
224-
left: rtl ? undefined : 0,
225-
right: rtl ? 0 : undefined,
169+
left: rtl ? undefined : leftTotalWidth,
170+
right: rtl ? leftTotalWidth : undefined,
226171
willChange: "transform",
227172
}}
228173
>
229174
{scrollableItems}
230175
</div>
176+
{pinnedLeft && (
177+
<PinnedColumn
178+
Component={children}
179+
totalWidth={leftTotalWidth}
180+
left={rtl ? undefined : 0}
181+
right={rtl ? 0 : undefined}
182+
topOffset={disableSticky ? 0 : -topOffset}
183+
columns={pinnedLeft}
184+
widths={lWidths}
185+
heights={dataHeights}
186+
vertStart={vertStart}
187+
vertEnd={vertEnd}
188+
verticalGap={verticalGap}
189+
horizontalGap={horizontalGap}
190+
runningHeight={runningHeight}
191+
rtl={rtl}
192+
/>
193+
)}
231194
</StickyDiv>
232195
</div>
233196
</div>
234197
</SizingDiv>
235198
)
236199
}
237-
238-
type RenderItemsProps<T> = {
239-
Component: GridProps<T>["children"]
240-
itemProps: T
241-
itemWidth: number
242-
column: number
243-
row: number
244-
marginLeft: number
245-
marginRight: number
246-
}
247-
248-
const RenderItem = function <T>({
249-
Component,
250-
itemProps,
251-
itemWidth,
252-
column,
253-
row,
254-
marginRight,
255-
marginLeft,
256-
}: RenderItemsProps<T>) {
257-
const itemStyles = React.useMemo(() => {
258-
return {
259-
width: itemWidth,
260-
minWidth: itemWidth,
261-
maxWidth: itemWidth,
262-
height: "100%",
263-
marginLeft,
264-
marginRight,
265-
}
266-
}, [itemWidth, marginLeft, marginRight])
267-
268-
const cellMeta = React.useMemo<CellMeta>(() => ({ row, column }), [column, row])
269-
270-
return <Component data={itemProps} style={itemStyles} cellMeta={cellMeta} />
271-
}

packages/react-virtualized-window/src/types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,27 @@ export interface VirtualWindowBaseProps<T> {
2424

2525
"data-testid"?: string
2626
}
27+
28+
export interface CellMeta {
29+
column: number
30+
row: number
31+
pinned?: "left" | "right"
32+
}
33+
34+
export interface GridProps<T, L = unknown> extends VirtualWindowBaseProps<T> {
35+
data: T[][]
36+
children: <B extends T>(props: {
37+
data: B
38+
style: React.CSSProperties
39+
cellMeta: CellMeta
40+
}) => JSX.Element
41+
defaultRowHeight: NumberOrPercent
42+
rowHeights?: NumberOrPercent[]
43+
defaultColumnWidth: NumberOrPercent
44+
columnWidths?: NumberOrPercent[]
45+
46+
pinnedLeft?: L[][]
47+
leftWidths?: NumberOrPercent[]
48+
}
49+
50+
export type RenderItem<T> = GridProps<T>["children"]

0 commit comments

Comments
 (0)