Skip to content

Commit

Permalink
feat(InfiniteScroll): added InfiniteScroll component (#35)
Browse files Browse the repository at this point in the history
* feat: added new component for infinite scroll

* feat: fixed filter functionality

* feat(InfiniteScroll): added useCallback in fetchMore func

* feat(InfiniteScroll): make contentRef prop optional if not present take reference of infinite-list div

* feat(InfiniteScroll): make InfiniteScroll a separate componet

* feat(infinite-scroll): change props contract

* feat: overload component height if provided in props

* feat: make default height (100%-0px)

* feat: move containerElem inside useEffect

---------

Co-authored-by: Ayushi Sharma <[email protected]>
Co-authored-by: Stewart Jingga <[email protected]>
  • Loading branch information
3 people authored Apr 25, 2024
1 parent 6565493 commit 1e83602
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 0 deletions.
134 changes: 134 additions & 0 deletions packages/apsara-ui/src/InfiniteScroll/InfiniteScroll.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { useCallback, useEffect, useState } from "react";
import InfiniteScroll from "./InfiniteScroll";
import styled from "styled-components";

export default {
title: "Data Display/InfiniteScroll",
component: InfiniteScroll,
};

interface User {
key: number;
name: string;
status: "active" | "inactive";
age: number;
address: string;
}

function getData(page = 1, page_size = 10): User[] {
return new Array(page * page_size).fill(0).map((_, index) => {
return {
key: index,
name: `name ${index}`,
status: index % 2 ? "active" : "inactive",
age: index,
address: `A${index} Downing Street`,
};
});
}

function getDataAsync(page = 1, page_size = 10): Promise<User[]> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(getData(page, page_size));
}, 1000);
});
}

export const basic = () => {
const [data, setData] = useState<User[]>([]);
const [isFetching, setIsFetching] = useState<boolean>(false);

const fetchData = useCallback(() => {
setIsFetching(true);
getDataAsync()
.then((d) => setData((curr) => curr.concat(d)))
.finally(() => setIsFetching(false));
}, []);

useEffect(() => {
fetchData();
}, [fetchData]);

const onScroll = () => {
if (isFetching) return;

fetchData();
};

return (
<div style={{ height: "80vh" }}>
<InfiniteScroll<User>
items={data}
renderItem={(item) => <Card user={item} />}
onBottomScroll={onScroll}
isLoading={isFetching}
/>
</div>
);
};

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

const UserCard = styled.div`
margin-bottom: 16px;
border-radius: 2px;
box-shadow: rgb(179 179 179 / 31%) 0px 1px 5px;
.title {
margin: 10px;
color: #4d85f4;
font-size: 16px;
}
.description {
margin-top: 20px;
word-break: break-all;
}
.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;
}
}
}
`;
82 changes: 82 additions & 0 deletions packages/apsara-ui/src/InfiniteScroll/InfiniteScroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useEffect, useRef } from "react";

interface InfiniteScrollProps<T> {
renderItem: (item: T) => React.ReactNode;
onBottomScroll: () => void;
items: T[];
height?: string;
style?: React.CSSProperties;
isLoading?: boolean;
noMoreData?: boolean;
containerRef?: React.RefObject<HTMLElement>;
threshold?: number;
loadingComponent?: React.ReactNode;
noMoreDataComponent?: React.ReactNode;
}

const InfiniteScroll = <T,>({
renderItem,
onBottomScroll,
items,
style,
height,
isLoading,
noMoreData,
containerRef,
threshold = 1,
loadingComponent,
noMoreDataComponent,
}: InfiniteScrollProps<T>) => {
const defaultContainerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const containerElem = containerRef ? containerRef.current : defaultContainerRef.current;
if (!containerElem) return;

const onScroll = () => {
if (isBottom(containerElem, threshold)) {
onBottomScroll();
}
};

containerElem.addEventListener("scroll", onScroll);

return () => {
containerElem.removeEventListener("scroll", onScroll);
};
}, [containerRef, defaultContainerRef, onBottomScroll, threshold]);

const loadingComp = loadingComponent || <DefaultLoading />;

const scrollStyle: React.CSSProperties = !containerRef
? {
height: height || "calc(100%-0px)",
overflow: "scroll",
position: "relative",
...style,
}
: {};

return (
<div className="apsara-infinite-scroll-wrapper" style={scrollStyle} ref={defaultContainerRef}>
{items?.map(renderItem)}
{isLoading && loadingComp}
{!noMoreData && noMoreDataComponent}
</div>
);
};

const DefaultLoading = () => <div>Loading...</div>;

const isBottom = (elem: HTMLElement, threshold: number): boolean => {
if (!elem) return false;

const { scrollTop, scrollHeight, clientHeight } = elem;

const a = scrollTop + clientHeight;
const b = scrollHeight - threshold;

return a >= b;
};

export default InfiniteScroll;
1 change: 1 addition & 0 deletions packages/apsara-ui/src/InfiniteScroll/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "./InfiniteScroll";
2 changes: 2 additions & 0 deletions packages/apsara-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { TooltipPlacement } from "./Tooltip/Tooltip";
import Modal from "./Modal";
import Alert from "./Alert";
import { DotBadge } from "./Badge";
import InfiniteScroll from "./InfiniteScroll";
export { DynamicList } from "./DynamicList";

export * from "./Notification";
Expand Down Expand Up @@ -103,6 +104,7 @@ export {
Slider,
Themes,
InfoModal,
InfiniteScroll,
DiffTimeline,
InputNumber,
DatePicker,
Expand Down

0 comments on commit 1e83602

Please sign in to comment.