Skip to content

Commit d9c2256

Browse files
committed
Merge branch 'feature/table-auto-scroll-to-selected' into q/1.0
2 parents e35774d + 7506203 commit d9c2256

File tree

3 files changed

+103
-11
lines changed

3 files changed

+103
-11
lines changed

src/lib/components/tablev2/SingleSelectableContent.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { memo, useEffect } from 'react';
2-
import { areEqual } from 'react-window';
1+
import { memo, useEffect, useRef } from 'react';
2+
import { areEqual, FixedSizeList } from 'react-window';
33
import { Row } from 'react-table';
44
import { useTableContext } from './Tablev2.component';
55
import {
@@ -33,6 +33,7 @@ export type SingleSelectableContentProps<
3333
hasScrollbar?: boolean;
3434
isLoadingMoreItems?: boolean;
3535
children?: (rows: JSX.Element) => JSX.Element;
36+
autoScrollToSelected?: boolean;
3637
};
3738

3839
export function SingleSelectableContent<
@@ -47,19 +48,35 @@ export function SingleSelectableContent<
4748
onRowSelected,
4849
customItemKey,
4950
children,
51+
autoScrollToSelected = false,
5052
}: SingleSelectableContentProps<DATA_ROW>) {
5153
if (selectedId && !onRowSelected) {
5254
console.error('Please specify the onRowSelected function.');
5355
}
5456

5557
const { headerRef } = useSyncedScroll<DATA_ROW>();
58+
const listRef = useRef<FixedSizeList<Row<DATA_ROW>[]>>(null);
5659
const { headerGroups, prepareRow, rows, setRowHeight } =
5760
useTableContext<DATA_ROW>();
5861

5962
useEffect(() => {
6063
setRowHeight(rowHeight);
6164
}, [rowHeight, setRowHeight]);
6265

66+
useEffect(() => {
67+
if (!autoScrollToSelected || !selectedId || !listRef.current) return;
68+
69+
const selectedIndex = rows.findIndex((row) => row.id === selectedId);
70+
if (selectedIndex < 0) return;
71+
72+
const timer = setTimeout(() => {
73+
if (!listRef.current) return;
74+
listRef.current.scrollToItem(selectedIndex, 'center');
75+
}, 100);
76+
77+
return () => clearTimeout(timer);
78+
}, [autoScrollToSelected, selectedId, rows]);
79+
6380
const RenderRow = memo(({ index, style }: RenderRowType) => {
6481
const row = rows[index];
6582
prepareRow(row);
@@ -180,6 +197,7 @@ export function SingleSelectableContent<
180197
children={children}
181198
customItemKey={customItemKey}
182199
RenderRow={RenderRow}
200+
listRef={listRef}
183201
/>
184202
</TableBody>
185203
{isLoadingMoreItems && (

src/lib/components/tablev2/TableCommon.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ComponentType, LegacyRef, useCallback, useState } from 'react';
1+
import { ComponentType, Ref, useCallback, useState, forwardRef } from 'react';
22
import { Row } from 'react-table';
33
import AutoSizer from 'react-virtualized-auto-sizer';
44
import {
@@ -18,6 +18,14 @@ import useSyncedScroll from './useSyncedScroll';
1818
import { CSSProperties } from 'styled-components';
1919
import { UnsuccessfulResult } from '../UnsuccessfulResult.component';
2020

21+
const SmoothScrollDiv = forwardRef<HTMLDivElement, any>((props, ref) => (
22+
<div
23+
ref={ref}
24+
{...props}
25+
style={{ ...props.style, scrollBehavior: 'smooth' }}
26+
/>
27+
));
28+
2129
type VirtualizedRowsType<
2230
DATA_ROW extends Record<string, unknown> = Record<string, unknown>,
2331
> = {
@@ -31,7 +39,7 @@ type VirtualizedRowsType<
3139
itemKey?: ListItemKeySelector<Row<DATA_ROW>[]>;
3240
onBottom?: (rowLength: number) => void;
3341
onBottomOffset?: number;
34-
listRef?: LegacyRef<FixedSizeList<Row<DATA_ROW>[]>>;
42+
listRef?: Ref<FixedSizeList<Row<DATA_ROW>[]>>;
3543
};
3644

3745
export const VirtualizedRows = <
@@ -57,6 +65,7 @@ export const VirtualizedRows = <
5765
itemKey={itemKey}
5866
itemData={rows}
5967
ref={listRef}
68+
outerElementType={SmoothScrollDiv}
6069
onItemsRendered={({
6170
visibleStartIndex,
6271
visibleStopIndex,
@@ -122,14 +131,23 @@ type TableRowsProps<
122131
RenderRow: React.MemoExoticComponent<
123132
({ index, style }: RenderRowType) => JSX.Element
124133
>;
134+
listRef?: Ref<FixedSizeList<Row<DATA_ROW>[]>>;
125135
};
126136
export function TableRows<
127137
DATA_ROW extends Record<string, unknown> = Record<string, unknown>,
128-
>({ locale, children, customItemKey, RenderRow }: TableRowsProps<DATA_ROW>) {
138+
>({
139+
locale,
140+
children,
141+
customItemKey,
142+
RenderRow,
143+
listRef: externalListRef,
144+
}: TableRowsProps<DATA_ROW>) {
129145
const { setHasScrollbar } = useTableScrollbar();
130146
const { rows, status, entityName, rowHeight, onBottom, onBottomOffset } =
131-
useTableContext();
132-
const { bodyRef } = useSyncedScroll();
147+
useTableContext<DATA_ROW>();
148+
const { bodyRef } = useSyncedScroll<DATA_ROW>();
149+
const listRef: Ref<FixedSizeList<Row<DATA_ROW>[]>> =
150+
externalListRef || bodyRef;
133151

134152
function itemKey(index, data) {
135153
if (typeof customItemKey === 'function') {
@@ -153,9 +171,9 @@ export function TableRows<
153171
if (typeof children === 'function') {
154172
if (rows.length) {
155173
return children(
156-
<VirtualizedRows
174+
<VirtualizedRows<DATA_ROW>
157175
rows={rows}
158-
listRef={bodyRef}
176+
listRef={listRef}
159177
itemKey={itemKey}
160178
rowHeight={rowHeight}
161179
setHasScrollbar={setHasScrollbar}
@@ -175,9 +193,9 @@ export function TableRows<
175193
}
176194
} else if (rows.length) {
177195
return (
178-
<VirtualizedRows
196+
<VirtualizedRows<DATA_ROW>
179197
rows={rows}
180-
listRef={bodyRef}
198+
listRef={listRef}
181199
setHasScrollbar={setHasScrollbar}
182200
onBottom={onBottom}
183201
onBottomOffset={onBottomOffset}

stories/tablev2.stories.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,3 +676,59 @@ export const TableWithSyncButton = {
676676
);
677677
},
678678
};
679+
680+
export const AutoScrollToSelected = {
681+
render: () => {
682+
const largeData: Entry[] = Array.from({ length: 100 }, (_, index) => ({
683+
id: index + 1,
684+
firstName: `FirstName${index + 1}`,
685+
lastName: `LastName${index + 1}`,
686+
health: ['healthy', 'warning', 'critical'][index % 3],
687+
}));
688+
689+
const [selectedId, setSelectedId] = useState<string>(
690+
'LastName80 FirstName80',
691+
);
692+
693+
const handleRowSelected = (row: Row<Entry>) => {
694+
const rowId = `${row.original.lastName} ${row.original.firstName}`;
695+
setSelectedId(rowId);
696+
};
697+
698+
const handleSelectRandom = () => {
699+
const randomIndex = Math.floor(Math.random() * largeData.length);
700+
const randomRow = largeData[randomIndex];
701+
setSelectedId(`${randomRow.lastName} ${randomRow.firstName}`);
702+
};
703+
704+
return (
705+
<>
706+
<Title>Auto Scroll to Selected Row</Title>
707+
<Box mb={2}>
708+
<Button
709+
variant="secondary"
710+
label="Select Random Row"
711+
onClick={handleSelectRandom}
712+
/>
713+
<Box mt={1}>Currently selected: {selectedId || 'None'}</Box>
714+
</Box>
715+
<div style={{ height: '400px' }}>
716+
<Table
717+
columns={columns}
718+
data={largeData}
719+
defaultSortingKey="firstName"
720+
getRowId={getRowId}
721+
>
722+
<Table.SingleSelectableContent
723+
rowHeight="h40"
724+
separationLineVariant="backgroundLevel3"
725+
selectedId={selectedId}
726+
onRowSelected={handleRowSelected}
727+
autoScrollToSelected
728+
/>
729+
</Table>
730+
</div>
731+
</>
732+
);
733+
},
734+
};

0 commit comments

Comments
 (0)