Skip to content

Commit 45a1e0d

Browse files
committed
feat: support custom scroll container
1 parent d2739ec commit 45a1e0d

9 files changed

+91
-20
lines changed

src/ReactUnipika/ReactUnipika.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export interface ReactUnipikaProps {
2626
initiallyCollapsed?: boolean;
2727
caseInsensitiveSearch?: boolean;
2828
renderError?: (error: unknown) => React.ReactNode;
29+
scrollContainerRef?: React.RefObject<Element | null>;
30+
withScrollElement?: boolean;
2931
}
3032

3133
const defaultUnipikaSettings = {
@@ -53,6 +55,8 @@ export function ReactUnipika({
5355
initiallyCollapsed,
5456
caseInsensitiveSearch,
5557
renderError,
58+
scrollContainerRef,
59+
withScrollElement,
5660
}: ReactUnipikaProps) {
5761
const {convertedValue, error} = React.useMemo(() => {
5862
try {
@@ -117,6 +121,8 @@ export function ReactUnipika({
117121
showContainerSize={showContainerSize}
118122
initiallyCollapsed={initiallyCollapsed}
119123
caseInsensitiveSearch={caseInsensitiveSearch}
124+
scrollContainerRef={scrollContainerRef}
125+
withScrollElement={withScrollElement}
120126
/>
121127
) : (
122128
<pre
Loading
Loading
Loading
Loading

src/ReactUnipika/__stories__/ReactUnipika.stories.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,21 @@ export const WithError: StoryObj<ReactUnipikaProps> = {
7979
},
8080
},
8181
};
82+
83+
function WithScrollContainerComponent() {
84+
const scrollContainerRef = React.useRef<HTMLDivElement>(null);
85+
return (
86+
<div
87+
style={{height: 300, overflow: 'auto', border: '1px solid var(--g-color-line-generic)'}}
88+
ref={scrollContainerRef}
89+
>
90+
<ReactUnipika value={data} scrollContainerRef={scrollContainerRef} withScrollElement />
91+
</div>
92+
);
93+
}
94+
95+
export const WithScrollContainer: StoryObj<ReactUnipikaProps> = {
96+
render() {
97+
return <WithScrollContainerComponent />;
98+
},
99+
};

src/ReactUnipika/__tests__/ReactUnipika.visual.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,9 @@ test('ReactUnipika: with error', async ({mount, expectScreenshot, page}) => {
177177

178178
await expectScreenshot({component: page});
179179
});
180+
181+
test('ReactUnipika:with scroll container', async ({mount, expectScreenshot, page}) => {
182+
await mount(<Stories.WithScrollContainer />, {width: 1280});
183+
184+
await expectScreenshot({component: page});
185+
});

src/StructuredYson/StructuredYson.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ interface Props {
4242
showContainerSize?: boolean;
4343
initiallyCollapsed?: boolean;
4444
caseInsensitiveSearch?: boolean;
45+
scrollContainerRef?: React.RefObject<Element | null>;
46+
withScrollElement?: boolean;
4547
}
4648

4749
interface State {
@@ -208,7 +210,8 @@ export class StructuredYson extends React.PureComponent<Props, State> {
208210
settings,
209211
filter,
210212
} = this.state;
211-
const {collapseIconType, showContainerSize} = this.props;
213+
const {collapseIconType, showContainerSize, scrollContainerRef, withScrollElement} =
214+
this.props;
212215

213216
return (
214217
<Table
@@ -220,6 +223,8 @@ export class StructuredYson extends React.PureComponent<Props, State> {
220223
onToggleCollapse={this.onTogglePathCollapse}
221224
onShowFullText={this.onShowFullText}
222225
scrollToRef={this.tableRef}
226+
scrollContainerRef={scrollContainerRef}
227+
withScrollElement={withScrollElement}
223228
collapseIconType={collapseIconType}
224229
showContainerSize={showContainerSize}
225230
/>

src/StructuredYson/Table.tsx

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import React from 'react';
2-
import {Table as GravityTable, useTable, useWindowRowVirtualizer} from '@gravity-ui/table';
2+
import {
3+
Table as GravityTable,
4+
useRowVirtualizer,
5+
useTable,
6+
useWindowRowVirtualizer,
7+
} from '@gravity-ui/table';
38
import type {ColumnDef, Row} from '@gravity-ui/table/tanstack';
49
import {UnipikaFlattenTreeItem, SearchInfo} from '../utils/flattenUnipika';
510
import {CollapseIconType, UnipikaSettings} from '../StructuredYson/types';
@@ -19,6 +24,8 @@ export interface TableProps {
1924
onToggleCollapse: (path: string) => void;
2025
onShowFullText: (index: number) => void;
2126
scrollToRef: React.RefObject<null | {scrollToIndex(index: number): void}>;
27+
scrollContainerRef?: React.RefObject<Element | null>;
28+
withScrollElement?: boolean;
2229
collapseIconType?: CollapseIconType;
2330
showContainerSize?: boolean;
2431
}
@@ -32,6 +39,8 @@ export const Table: React.FC<TableProps> = ({
3239
onToggleCollapse,
3340
onShowFullText,
3441
scrollToRef,
42+
scrollContainerRef,
43+
withScrollElement,
3544
collapseIconType,
3645
showContainerSize,
3746
}) => {
@@ -62,32 +71,40 @@ export const Table: React.FC<TableProps> = ({
6271
data,
6372
});
6473

65-
const bodyRef = React.useRef<HTMLTableSectionElement>(null);
66-
67-
const rowVirtulization = useWindowRowVirtualizer({
68-
count: table.getRowModel().rows.length,
69-
estimateSize: () => 20,
70-
overscan: 5,
71-
scrollMargin: bodyRef.current?.offsetTop ?? 0,
72-
});
74+
const {rowVirtualizer, windowRowVirtualizer, bodyRef} = useRowVirtualization(
75+
table.getRowModel().rows.length,
76+
scrollContainerRef,
77+
);
7378

7479
React.useEffect(() => {
80+
const activeVirtualizer = withScrollElement ? rowVirtualizer : windowRowVirtualizer;
7581
scrollToRef.current = {
7682
scrollToIndex: (index: number) =>
77-
rowVirtulization.scrollToIndex(index, {align: 'center'}),
83+
activeVirtualizer.scrollToIndex(index, {align: 'center'}),
7884
};
79-
}, [scrollToRef, rowVirtulization]);
85+
}, [scrollToRef, withScrollElement, rowVirtualizer, windowRowVirtualizer]);
8086

8187
return (
8288
<div className={block()}>
83-
<GravityTable
84-
table={table}
85-
rowVirtualizer={rowVirtulization}
86-
rowClassName={rowClassName}
87-
cellClassName={block('cell')}
88-
headerCellClassName={block('header-cell')}
89-
bodyRef={bodyRef}
90-
/>
89+
{withScrollElement ? (
90+
<GravityTable
91+
table={table}
92+
rowVirtualizer={rowVirtualizer}
93+
rowClassName={rowClassName}
94+
cellClassName={block('cell')}
95+
headerCellClassName={block('header-cell')}
96+
bodyRef={bodyRef}
97+
/>
98+
) : (
99+
<GravityTable
100+
table={table}
101+
rowVirtualizer={windowRowVirtualizer}
102+
rowClassName={rowClassName}
103+
cellClassName={block('cell')}
104+
headerCellClassName={block('header-cell')}
105+
bodyRef={bodyRef}
106+
/>
107+
)}
91108
</div>
92109
);
93110
};
@@ -97,3 +114,22 @@ function rowClassName(row?: Row<UnipikaFlattenTreeItem>) {
97114
const k = key?.$decoded_value ?? '';
98115
return block('row', {key: asModifier(k)});
99116
}
117+
118+
function useRowVirtualization(count: number, scrollContainerRef?: React.RefObject<Element | null>) {
119+
const bodyRef = React.useRef<HTMLTableSectionElement>(null);
120+
const windowRowVirtualizer = useWindowRowVirtualizer({
121+
count,
122+
estimateSize: () => 20,
123+
overscan: 5,
124+
scrollMargin: bodyRef?.current?.offsetTop ?? 0,
125+
});
126+
127+
const rowVirtualizer = useRowVirtualizer({
128+
count,
129+
estimateSize: () => 20,
130+
overscan: 5,
131+
getScrollElement: () => scrollContainerRef?.current || null,
132+
});
133+
134+
return {rowVirtualizer, windowRowVirtualizer, bodyRef};
135+
}

0 commit comments

Comments
 (0)