feat: infinite scroll friend list

This commit is contained in:
Zamitto 2024-08-16 13:55:52 -03:00
parent b1e263814c
commit 515a08a3a6
3 changed files with 72 additions and 38 deletions

View File

@ -4,7 +4,6 @@ import {
XCircleIcon, XCircleIcon,
} from "@primer/octicons-react"; } from "@primer/octicons-react";
import * as styles from "./user-friend-modal.css"; import * as styles from "./user-friend-modal.css";
import cn from "classnames";
import { SPACING_UNIT } from "@renderer/theme.css"; import { SPACING_UNIT } from "@renderer/theme.css";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -108,7 +107,7 @@ export const UserFriendItem = (props: UserFriendItemProps) => {
if (type === "BLOCKED") { if (type === "BLOCKED") {
return ( return (
<div className={cn(styles.friendListContainer, styles.profileContentBox)}> <div className={styles.friendListContainer}>
<div className={styles.friendListButton} style={{ cursor: "inherit" }}> <div className={styles.friendListButton} style={{ cursor: "inherit" }}>
<div className={styles.friendAvatarContainer}> <div className={styles.friendAvatarContainer}>
{profileImageUrl ? ( {profileImageUrl ? (
@ -149,7 +148,7 @@ export const UserFriendItem = (props: UserFriendItemProps) => {
} }
return ( return (
<div className={cn(styles.friendListContainer, styles.profileContentBox)}> <div className={styles.friendListContainer}>
<button <button
type="button" type="button"
className={styles.friendListButton} className={styles.friendListButton}

View File

@ -1,10 +1,11 @@
import { SPACING_UNIT } from "@renderer/theme.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css";
import { UserFriend } from "@types"; import { UserFriend } from "@types";
import { useEffect, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { UserFriendItem } from "./user-friend-item"; import { UserFriendItem } from "./user-friend-item";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useToast, useUserDetails } from "@renderer/hooks"; import { useToast, useUserDetails } from "@renderer/hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
export interface UserFriendModalListProps { export interface UserFriendModalListProps {
userId: string; userId: string;
@ -22,14 +23,18 @@ export const UserFriendModalList = ({
const navigate = useNavigate(); const navigate = useNavigate();
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [maxPage, setMaxPage] = useState(0); const [maxPage, setMaxPage] = useState(0);
const [friends, setFriends] = useState<UserFriend[]>([]); const [friends, setFriends] = useState<UserFriend[]>([]);
const listContainer = useRef<HTMLDivElement>(null);
const { userDetails, undoFriendship } = useUserDetails(); const { userDetails, undoFriendship } = useUserDetails();
const isMe = userDetails?.id == userId; const isMe = userDetails?.id == userId;
const loadNextPage = () => { const loadNextPage = () => {
if (page > maxPage) return; if (page > maxPage) return;
setIsLoading(true);
console.log("loading next page");
window.electron window.electron
.getUserFriends(userId, pageSize, page * pageSize) .getUserFriends(userId, pageSize, page * pageSize)
.then((newPage) => { .then((newPage) => {
@ -40,9 +45,29 @@ export const UserFriendModalList = ({
setFriends([...friends, ...newPage.friends]); setFriends([...friends, ...newPage.friends]);
setPage(page + 1); setPage(page + 1);
}) })
.catch(() => {}); .catch(() => {})
.finally(() => setIsLoading(false));
}; };
const handleScroll = () => {
const scrollTop = listContainer.current?.scrollTop || 0;
const scrollHeight = listContainer.current?.scrollHeight || 0;
const clientHeight = listContainer.current?.clientHeight || 0;
const maxScrollTop = scrollHeight - clientHeight;
if (scrollTop < maxScrollTop * 0.9 || isLoading) {
return;
}
loadNextPage();
};
useEffect(() => {
listContainer.current?.addEventListener("scroll", handleScroll);
return () =>
listContainer.current?.removeEventListener("scroll", handleScroll);
}, [isLoading]);
const reloadList = () => { const reloadList = () => {
setPage(0); setPage(0);
setMaxPage(0); setMaxPage(0);
@ -70,27 +95,42 @@ export const UserFriendModalList = ({
}; };
return ( return (
<div <SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
style={{ <div
display: "flex", ref={listContainer}
flexDirection: "column", style={{
gap: `${SPACING_UNIT * 2}px`, display: "flex",
}} flexDirection: "column",
> gap: `${SPACING_UNIT * 2}px`,
{friends.length === 0 && <p>{t("no_friends_added")}</p>} maxHeight: "400px",
{friends.map((friend) => { overflowY: "scroll",
return ( }}
<UserFriendItem >
userId={friend.id} {friends.length === 0 && <p>{t("no_friends_added")}</p>}
displayName={friend.displayName} {friends.map((friend) => {
profileImageUrl={friend.profileImageUrl} return (
onClickItem={handleClickFriend} <UserFriendItem
onClickUndoFriendship={handleUndoFriendship} userId={friend.id}
type={isMe ? "ACCEPTED" : null} displayName={friend.displayName}
key={friend.id} profileImageUrl={friend.profileImageUrl}
onClickItem={handleClickFriend}
onClickUndoFriendship={handleUndoFriendship}
type={isMe ? "ACCEPTED" : null}
key={"modal" + friend.id}
/>
);
})}
{isLoading && (
<Skeleton
style={{
width: "100%",
height: "54px",
overflow: "hidden",
borderRadius: "4px",
}}
/> />
); )}
})} </div>
</div> </SkeletonTheme>
); );
}; };

View File

@ -1,17 +1,6 @@
import { SPACING_UNIT, vars } from "../../../theme.css"; import { SPACING_UNIT, vars } from "../../../theme.css";
import { style } from "@vanilla-extract/css"; import { style } from "@vanilla-extract/css";
export const profileContentBox = style({
display: "flex",
gap: `${SPACING_UNIT * 3}px`,
alignItems: "center",
borderRadius: "4px",
border: `solid 1px ${vars.color.border}`,
width: "100%",
boxShadow: "0px 0px 15px 0px rgba(0, 0, 0, 0.7)",
transition: "all ease 0.3s",
});
export const friendAvatarContainer = style({ export const friendAvatarContainer = style({
width: "35px", width: "35px",
minWidth: "35px", minWidth: "35px",
@ -42,8 +31,14 @@ export const profileAvatar = style({
}); });
export const friendListContainer = style({ export const friendListContainer = style({
display: "flex",
gap: `${SPACING_UNIT * 3}px`,
alignItems: "center",
borderRadius: "4px",
border: `solid 1px ${vars.color.border}`,
width: "100%", width: "100%",
height: "54px", height: "54px",
minHeight: "54px",
transition: "all ease 0.2s", transition: "all ease 0.2s",
position: "relative", position: "relative",
":hover": { ":hover": {