Skip to content

Commit 601f8db

Browse files
authored
Fix issue where next page loaded even when table wasn't scrolled (#6476)
- on table, scrollbar sits on top of the scrollable content, taking up no space. - Fixed an issue where next page loaded even when table wasn't scrolled in some case.
1 parent 1136ff6 commit 601f8db

File tree

9 files changed

+92
-110
lines changed

9 files changed

+92
-110
lines changed

.vale/styles/spelling-exceptions.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ REST
118118
resources
119119
schema's
120120
schema_mapping
121+
scrollbar
122+
scrollable
121123
sdk
122124
subcommand
123125
subnet

changelog/infinite-scroll.fixed.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- on table, scrollbar sits on top of the scrollable content, taking up no space.
2+
- Fixed an issue where next page loaded even when table wasn't scrolled in some case.

frontend/app/src/entities/nodes/object/ui/object-table/object-table-skeleton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function ObjectTableSkeleton({ headerCount }: ObjectsTableSkeletonProps)
1313
{[...Array(headerCount)].map((_, colIndex) => (
1414
<TableCell
1515
key={`skeleton-${rowIndex}-${colIndex}`}
16-
className={classNames(colIndex === 0 && "sticky left-0")}
16+
className={classNames(colIndex === 0 && "sticky left-0 bg-white z-1")}
1717
>
1818
<Skeleton className="h-4 w-full" />
1919
</TableCell>

frontend/app/src/entities/nodes/object/ui/object-table/object-table.tsx

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { getObjectActionsColumn } from "@/entities/nodes/object/ui/object-table/
33
import { ObjectTableEmpty } from "@/entities/nodes/object/ui/object-table/object-table-empty";
44
import { Permission } from "@/entities/permission/types";
55
import { ModelSchema } from "@/entities/schema/types";
6-
import { InfiniteDataTable } from "@/shared/components/table/infinite-data-table";
6+
import { DataTable } from "@/shared/components/table/data-table";
7+
import { InfiniteScroll } from "@/shared/components/utils/infinite-scroll";
78
import useFilters from "@/shared/hooks/useFilters";
89
import React from "react";
910
import { getObjectTableColumns } from "./get-object-table-columns";
@@ -15,25 +16,22 @@ export interface ObjectsTableProps {
1516

1617
export const ObjectTable = ({ schema, permission }: ObjectsTableProps) => {
1718
const [filters] = useFilters();
18-
const { isPending, data, fetchNextPage, hasNextPage, isFetchingNextPage } = useObjects({
19-
schema,
20-
filters,
21-
});
19+
const { isFetching, data, fetchNextPage, hasNextPage } = useObjects({ schema, filters });
2220

2321
const columns = React.useMemo(() => {
2422
return [...getObjectTableColumns(schema), getObjectActionsColumn(permission)];
2523
}, [schema.hash]);
2624
const flatData = React.useMemo(() => data?.pages?.flat() ?? [], [data]);
2725

2826
return (
29-
<InfiniteDataTable
30-
columns={columns}
31-
data={flatData}
32-
isLoading={isPending || isFetchingNextPage}
33-
hasNextPage={hasNextPage}
34-
fetchNextPage={fetchNextPage}
35-
renderEmpty={() => <ObjectTableEmpty schema={schema} />}
36-
data-testid="object-items"
37-
/>
27+
<InfiniteScroll scrollX hasNextPage={hasNextPage} onLoadMore={fetchNextPage}>
28+
<DataTable
29+
columns={columns}
30+
data={flatData}
31+
isLoading={isFetching}
32+
renderEmpty={() => <ObjectTableEmpty schema={schema} />}
33+
data-testid="object-items"
34+
/>
35+
</InfiniteScroll>
3836
);
3937
};

frontend/app/src/entities/nodes/relationships/ui/relationship-table/relationship-table.tsx

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
} from "@/entities/nodes/relationships/domain/get-object-relationships/get-object-relationships.query";
77
import { getRelationshipActionsColumn } from "@/entities/nodes/relationships/ui/relationship-table/get-relationship-actions-column";
88
import { PERMISSION_ALLOW_ALL } from "@/entities/permission/constants";
9-
import { InfiniteDataTable } from "@/shared/components/table/infinite-data-table";
9+
import { DataTable } from "@/shared/components/table/data-table";
10+
import { InfiniteScroll } from "@/shared/components/utils/infinite-scroll";
1011
import useFilters from "@/shared/hooks/useFilters";
1112
import React from "react";
1213

@@ -20,15 +21,14 @@ export function RelationshipTable({
2021
...props
2122
}: RelationshipTableProps) {
2223
const [filters] = useFilters();
23-
const { data, isPending, isFetchingNextPage, fetchNextPage, hasNextPage } =
24-
useObjectRelationships({
25-
relationshipSchema,
26-
parentId,
27-
parentKind,
28-
relationshipName,
29-
filters,
30-
...props,
31-
});
24+
const { data, isFetching, fetchNextPage, hasNextPage } = useObjectRelationships({
25+
relationshipSchema,
26+
parentId,
27+
parentKind,
28+
relationshipName,
29+
filters,
30+
...props,
31+
});
3232

3333
const flatData = React.useMemo(() => data?.pages?.flat() ?? [], [data]);
3434

@@ -46,13 +46,13 @@ export function RelationshipTable({
4646
}, [relationshipSchema.hash, flatData.length]);
4747

4848
return (
49-
<InfiniteDataTable
50-
columns={columns}
51-
data={flatData}
52-
isLoading={isPending || isFetchingNextPage}
53-
renderEmpty={() => <ObjectTableEmpty schema={relationshipSchema} />}
54-
hasNextPage={hasNextPage}
55-
fetchNextPage={fetchNextPage}
56-
/>
49+
<InfiniteScroll scrollX hasNextPage={hasNextPage} onLoadMore={fetchNextPage}>
50+
<DataTable
51+
columns={columns}
52+
data={flatData}
53+
isLoading={isFetching}
54+
renderEmpty={() => <ObjectTableEmpty schema={relationshipSchema} />}
55+
/>
56+
</InfiniteScroll>
5757
);
5858
}

frontend/app/src/pages/objects/layout.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,8 @@ const ObjectPageLayout = () => {
5959
</>
6060
)}
6161

62-
<ResizablePanel>
63-
<div className="overflow-auto h-full">
64-
<Outlet />
65-
</div>
62+
<ResizablePanel className="h-full flex flex-col">
63+
<Outlet />
6664
</ResizablePanel>
6765
</ResizablePanelGroup>
6866
</Content.Card>

frontend/app/src/shared/components/table/infinite-data-table.tsx renamed to frontend/app/src/shared/components/table/data-table.tsx

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,27 @@ import { ObjectTableSkeleton } from "@/entities/nodes/object/ui/object-table/obj
22
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
33
import React from "react";
44

5-
export interface InfiniteDataTableProps<T> extends React.HTMLAttributes<HTMLDivElement> {
5+
export interface DataTableProps<T> extends React.HTMLAttributes<HTMLDivElement> {
66
columns: ColumnDef<T>[];
77
data: Array<T>;
88
isLoading?: boolean;
99
renderEmpty?: () => React.ReactNode;
10-
hasNextPage: boolean;
11-
fetchNextPage: () => void;
1210
}
1311

14-
export function InfiniteDataTable<T extends object>({
12+
export function DataTable<T extends object>({
1513
columns,
1614
data,
1715
isLoading,
1816
renderEmpty,
19-
hasNextPage,
20-
fetchNextPage,
2117
...props
22-
}: InfiniteDataTableProps<T>) {
23-
const tableContainerRef = React.useRef<HTMLTableElement>(null);
18+
}: DataTableProps<T>) {
2419
const table = useReactTable({
2520
columns,
2621
data,
2722
getCoreRowModel: getCoreRowModel(),
2823
manualSorting: true,
2924
});
3025

31-
const fetchMoreOnBottomReached = React.useCallback(
32-
(containerRefElement?: HTMLDivElement | null) => {
33-
if (containerRefElement) {
34-
const { scrollHeight, scrollTop, clientHeight } = containerRefElement;
35-
//once the user has scrolled within 250px of the bottom of the table, fetch more data if we can
36-
if (scrollHeight - scrollTop - clientHeight < 250 && !isLoading && hasNextPage) {
37-
fetchNextPage();
38-
}
39-
}
40-
},
41-
[fetchNextPage, isLoading]
42-
);
43-
44-
React.useEffect(() => {
45-
fetchMoreOnBottomReached(tableContainerRef.current);
46-
}, [fetchMoreOnBottomReached]);
47-
4826
const allHeaders = table.getFlatHeaders();
4927
const allRows = table.getRowModel().rows;
5028
const style = React.useMemo<React.CSSProperties>(
@@ -55,13 +33,7 @@ export function InfiniteDataTable<T extends object>({
5533
);
5634

5735
return (
58-
<div
59-
className="grid content-start overflow-auto"
60-
style={style}
61-
onScroll={(e) => fetchMoreOnBottomReached(e.currentTarget)}
62-
ref={tableContainerRef}
63-
{...props}
64-
>
36+
<div className="grid content-start" style={style} {...props}>
6537
{allHeaders.map((header) => {
6638
return flexRender(header.column.columnDef.header, {
6739
...header.getContext(),

frontend/app/src/shared/components/ui/scroll-area.tsx

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,54 @@ import { classNames } from "@/shared/utils/common";
22
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
33
import * as React from "react";
44

5-
export const ScrollArea = React.forwardRef<
6-
React.ElementRef<typeof ScrollAreaPrimitive.Viewport>,
7-
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & {
8-
scrollX?: boolean;
9-
scrollY?: boolean;
10-
scrollBarClassName?: string;
11-
}
12-
>(({ className, children, scrollX = false, scrollY = true, scrollBarClassName, ...props }, ref) => (
13-
<ScrollAreaPrimitive.Root
14-
scrollHideDelay={0}
15-
className={classNames("relative overflow-hidden", className)}
16-
{...props}
17-
>
18-
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]" ref={ref}>
19-
{children}
20-
</ScrollAreaPrimitive.Viewport>
21-
{scrollX && <ScrollBar orientation="horizontal" className={scrollBarClassName} />}
22-
{scrollY && <ScrollBar orientation="vertical" className={scrollBarClassName} />}
23-
<ScrollAreaPrimitive.Corner />
24-
</ScrollAreaPrimitive.Root>
25-
));
5+
export interface ScrollAreaProps
6+
extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
7+
scrollX?: boolean;
8+
scrollY?: boolean;
9+
scrollBarClassName?: string;
10+
ref?: React.Ref<HTMLDivElement>;
11+
}
2612

27-
export const ScrollBar = React.forwardRef<
28-
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
29-
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
30-
>(({ className, orientation = "vertical", ...props }, ref) => (
31-
<ScrollAreaPrimitive.ScrollAreaScrollbar
32-
ref={ref}
33-
orientation={orientation}
34-
className={classNames(
35-
"flex touch-none select-none transition-colors bg-gray-50",
36-
orientation === "vertical" && "h-full w-2 border-l border-l-transparent p-px",
37-
orientation === "horizontal" && "h-2 flex-col border-t border-t-transparent p-px",
38-
className
39-
)}
40-
{...props}
41-
>
42-
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-gray-200 hover:bg-gray-400" />
43-
</ScrollAreaPrimitive.ScrollAreaScrollbar>
44-
));
13+
export function ScrollArea({
14+
className,
15+
children,
16+
scrollX = false,
17+
scrollY = true,
18+
scrollBarClassName,
19+
ref,
20+
...props
21+
}: ScrollAreaProps) {
22+
return (
23+
<ScrollAreaPrimitive.Root
24+
scrollHideDelay={0}
25+
className={classNames("relative overflow-hidden", className)}
26+
{...props}
27+
>
28+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]" ref={ref}>
29+
{children}
30+
</ScrollAreaPrimitive.Viewport>
31+
{scrollX && <ScrollBar orientation="horizontal" className={scrollBarClassName} />}
32+
{scrollY && <ScrollBar orientation="vertical" className={scrollBarClassName} />}
33+
<ScrollAreaPrimitive.Corner />
34+
</ScrollAreaPrimitive.Root>
35+
);
36+
}
37+
38+
export interface ScrollBarProps extends ScrollAreaPrimitive.ScrollAreaScrollbarProps {}
39+
40+
export function ScrollBar({ className, orientation = "vertical", ...props }: ScrollBarProps) {
41+
return (
42+
<ScrollAreaPrimitive.ScrollAreaScrollbar
43+
orientation={orientation}
44+
className={classNames(
45+
"flex touch-none select-none transition-colors bg-gray-50",
46+
orientation === "vertical" && "h-full w-2 border-l border-l-transparent p-px",
47+
orientation === "horizontal" && "h-2 flex-col border-t border-t-transparent p-px",
48+
className
49+
)}
50+
{...props}
51+
>
52+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-gray-200 hover:bg-gray-400" />
53+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
54+
);
55+
}

frontend/app/src/shared/components/utils/infinite-scroll.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ScrollArea } from "@/shared/components/ui/scroll-area";
2-
import { ScrollAreaProps } from "@radix-ui/react-scroll-area";
1+
import { ScrollArea, ScrollAreaProps } from "@/shared/components/ui/scroll-area";
32
import React from "react";
43

54
export interface InfiniteScrollProps extends ScrollAreaProps {

0 commit comments

Comments
 (0)