Skip to content

Commit 847bacd

Browse files
committed
feat: support custom scroll container
1 parent d2739ec commit 847bacd

9 files changed

+91
-19
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 & 18 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,41 @@ export const Table: React.FC<TableProps> = ({
6271
data,
6372
});
6473

65-
const bodyRef = React.useRef<HTMLTableSectionElement>(null);
74+
const {rowVirtualizer, windowRowVirtualizer, bodyRef} = useRowVirtualization(
75+
table.getRowModel().rows.length,
76+
scrollContainerRef,
77+
);
6678

67-
const rowVirtulization = useWindowRowVirtualizer({
68-
count: table.getRowModel().rows.length,
69-
estimateSize: () => 20,
70-
overscan: 5,
71-
scrollMargin: bodyRef.current?.offsetTop ?? 0,
72-
});
79+
const activeVirtualizer = withScrollElement ? rowVirtualizer : windowRowVirtualizer;
7380

7481
React.useEffect(() => {
7582
scrollToRef.current = {
7683
scrollToIndex: (index: number) =>
77-
rowVirtulization.scrollToIndex(index, {align: 'center'}),
84+
activeVirtualizer.scrollToIndex(index, {align: 'center'}),
7885
};
79-
}, [scrollToRef, rowVirtulization]);
86+
}, [scrollToRef, activeVirtualizer]);
8087

8188
return (
8289
<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-
/>
90+
{withScrollElement ? (
91+
<GravityTable
92+
table={table}
93+
rowVirtualizer={rowVirtualizer}
94+
rowClassName={rowClassName}
95+
cellClassName={block('cell')}
96+
headerCellClassName={block('header-cell')}
97+
bodyRef={bodyRef}
98+
/>
99+
) : (
100+
<GravityTable
101+
table={table}
102+
rowVirtualizer={windowRowVirtualizer}
103+
rowClassName={rowClassName}
104+
cellClassName={block('cell')}
105+
headerCellClassName={block('header-cell')}
106+
bodyRef={bodyRef}
107+
/>
108+
)}
91109
</div>
92110
);
93111
};
@@ -97,3 +115,22 @@ function rowClassName(row?: Row<UnipikaFlattenTreeItem>) {
97115
const k = key?.$decoded_value ?? '';
98116
return block('row', {key: asModifier(k)});
99117
}
118+
119+
function useRowVirtualization(count: number, scrollContainerRef?: React.RefObject<Element | null>) {
120+
const bodyRef = React.useRef<HTMLTableSectionElement>(null);
121+
const windowRowVirtualizer = useWindowRowVirtualizer({
122+
count,
123+
estimateSize: () => 20,
124+
overscan: 5,
125+
scrollMargin: bodyRef?.current?.offsetTop ?? 0,
126+
});
127+
128+
const rowVirtualizer = useRowVirtualizer({
129+
count,
130+
estimateSize: () => 20,
131+
overscan: 5,
132+
getScrollElement: () => scrollContainerRef?.current || null,
133+
});
134+
135+
return {rowVirtualizer, windowRowVirtualizer, bodyRef};
136+
}

0 commit comments

Comments
 (0)