Skip to content

Commit

Permalink
feat: added new component for infinite scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
Ayushi Sharma committed Apr 19, 2024
1 parent ec7457d commit 6ec1c85
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 3 deletions.
103 changes: 103 additions & 0 deletions packages/apsara-ui/src/Listing/InfiniteScroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { debounce } from "lodash";
import React, { useState, useEffect } from "react";

interface InfiniteScrollProps<T> {
fetchMoreData: (page: number, pageSize: number, filters: any) => Promise<T[]>;
contentRef: React.RefObject<HTMLDivElement>;
pageSize?: number;
filters?: any;
renderItem: (item: T) => React.ReactNode;
loadingComponent?: React.ReactNode;
noMoreDataComponent?: React.ReactNode;
}

const InfiniteScroll: React.FC<InfiniteScrollProps<any>> = ({
fetchMoreData,
contentRef,
pageSize = 10,
filters,
renderItem,
loadingComponent,
noMoreDataComponent,
}) => {
const [data, setData] = useState<any[]>([]);
const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(false);
const [hasMoreData, setHasMoreData] = useState(true);
const [isFirstFetch, setIsFirstFetch] = useState(true);

const onBottomHit = debounce(async () => {
if (!isLoading && hasMoreData && isBottom(contentRef)) {
setIsLoading(true);
try {
const newData = await fetchMoreData(page, pageSize, filters);
setData((prevData) => prevData.concat(newData));
setPage((prevPage) => prevPage + 1);
setHasMoreData(newData.length > 0);
} finally {
setIsLoading(false);
}
}
}, 200);

useEffect(() => {
setPage(1);
setHasMoreData(true);
setData([]);
}, [filters]);

useEffect(() => {
if (isFirstFetch) {
onBottomHit();
}
setIsFirstFetch(false);

const onScroll = () => {
onBottomHit();
};

const currentContentRef = contentRef.current;

if (currentContentRef) {
currentContentRef.addEventListener("scroll", onScroll);
}

return () => {
if (currentContentRef) {
currentContentRef.removeEventListener("scroll", onScroll);
}
};
}, [
contentRef,
isLoading,
hasMoreData,
page,
filters,
fetchMoreData,
setData,
setPage,
setHasMoreData,
isFirstFetch,
onBottomHit,
]);

const isBottom = (ref: React.RefObject<HTMLDivElement>) => {
if (!ref.current) {
return false;
}

const { scrollTop, scrollHeight, clientHeight } = ref.current;

return scrollTop + clientHeight >= scrollHeight;
};

return (
<div className="infinite-list">
{data.map(renderItem)}
{isLoading ? loadingComponent : null}
{!hasMoreData && noMoreDataComponent}
</div>
);
};

export default InfiniteScroll;
55 changes: 53 additions & 2 deletions packages/apsara-ui/src/Listing/Listing.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, { useState } from "react";
import React, { useRef, useState } from "react";
import Listing from "./Listing";
import InfiniteListing from "./InfiniteListing";
import { ScrollableList, UserCard } from "./Listing.styles";
import InfiniteScroll from "./InfiniteScroll";

export default {
title: "Data Display/Listing",
Expand Down Expand Up @@ -28,7 +30,7 @@ function getData(page = 1): User[] {
}

export const listing = () => (
<div style={{ height: '720px' }}>
<div style={{ height: "720px" }}>
<h4>Listing</h4>
<Listing<User>
loading={false}
Expand Down Expand Up @@ -188,3 +190,52 @@ export const infiniteListingWithApply = () => {
/>
);
};

export const infiniteListWithCustomComponent = () => {
const pageSize = 10;
const contentRef = useRef<HTMLDivElement>(null);
const fetchRecords = (page: number, pageSize: number) => {
return Array.from({ length: pageSize }, (_, i) => ({
name: `user ${page * pageSize + i}`,
index: page * pageSize + i,
age: Math.floor(Math.random() * 41) + 20, // Random age between 20 and 60
}));
};

const fetchMore = async (page: number, pageSize: number, filter: string) => {
console.log(filter);
const records = fetchRecords(page, pageSize);

return records;
};

const Card = ({ user }: { user: User }) => {
return (
<UserCard>
<div className="body">
<div className="body-left">
<div className="description">{user.name}</div>
</div>
<div className="body-right">
<div>Age {user.age}</div>
</div>
</div>
</UserCard>
);
};

return (
<div style={{ display: "flex", justifyContent: "center" }}>
<ScrollableList className="results-list" ref={contentRef}>
<InfiniteScroll
fetchMoreData={fetchMore}
pageSize={pageSize}
contentRef={contentRef}
renderItem={(user: User) => <Card user={user} />}
loadingComponent={<div>Loading...</div>}
noMoreDataComponent={<div>No more data to fetch!</div>}
></InfiniteScroll>
</ScrollableList>
</div>
);
};
86 changes: 86 additions & 0 deletions packages/apsara-ui/src/Listing/Listing.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,89 @@ export const FilterActions = styled.span`
display: flex;
align-items: center;
`;

export const ScrollableList = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
height: 70%;
background-color: ${({ theme }) => theme.backgroundColor || "white"};
overflow-y: auto;
box-shadow: rgb(179 179 179 / 31%) 0px 1px 5px;
`;

export const UserCard = styled.div`
margin-bottom: 16px;
border-radius: 2px;
box-shadow: rgb(179 179 179 / 31%) 0px 1px 5px;
.title {
color: #4d85f4;
font-size: 16px;
}
.description {
margin-top: 20px;
word-break: break-all;
}
.label-box {
display: flex;
flex-direction: row;
position: relative;
> :first-child {
font-weight: bold;
::after {
content: ":";
margin-right: 3px;
}
}
}
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 12px 20px 0px 20px;
.title {
font-size: 16px;
font-weight: 500;
line-height: 1;
letter-spacing: 0.4px;
word-break: break-word;
}
}
.body {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0px 20px 12px 20px;
> .body-left {
margin-top: 16px;
flex: 1;
}
> .body-right {
width: 240px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-between;
> div {
display: flex;
align-items: flex-end;
flex-direction: column;
}
.tag_content {
text-transform: uppercase;
}
}
}
`;
1 change: 1 addition & 0 deletions packages/apsara-ui/src/Listing/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Listing from "./Listing";
export { default as InfiniteScroll } from "./InfiniteScroll";
export { default as InfiniteListing } from "./InfiniteListing";
export { default as useSearchFilter } from "./hooks/useSearchFilter";

Expand Down
3 changes: 2 additions & 1 deletion packages/apsara-ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Button from "./Button";
import Icon, { IconImage } from "./Icon";
import Listing, { useSearchFilter, InfiniteListing } from "./Listing";
import Listing, { useSearchFilter, InfiniteListing, InfiniteScroll } from "./Listing";
import Search from "./Search";
import Table from "./Table";
import VTable from "./Table/VirtualisedTable";
Expand Down Expand Up @@ -103,6 +103,7 @@ export {
Slider,
Themes,
InfoModal,
InfiniteScroll,
DiffTimeline,
InputNumber,
DatePicker,
Expand Down

0 comments on commit 6ec1c85

Please sign in to comment.