mirror of
https://github.com/makeplane/plane.git
synced 2026-02-24 12:11:39 +01:00
chore: pagination components
This commit is contained in:
15
apps/web/core/components/pages/list/loader.tsx
Normal file
15
apps/web/core/components/pages/list/loader.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Loader } from "@plane/ui";
|
||||
|
||||
export function PageListLoader() {
|
||||
return (
|
||||
<Loader className="relative flex items-center gap-2 p-3 py-4 border-b border-custom-border-200">
|
||||
<Loader.Item width={`${250 + 10 * Math.floor(Math.random() * 10)}px`} height="22px" />
|
||||
<div className="ml-auto relative flex items-center gap-2">
|
||||
<Loader.Item width="60px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
<Loader.Item width="22px" height="22px" />
|
||||
</div>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,19 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// types
|
||||
import type { TPageNavigationTabs } from "@plane/types";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { ListLayout } from "@/components/core/list";
|
||||
// hooks
|
||||
import { useIntersectionObserver } from "@/hooks/use-intersection-observer";
|
||||
// plane web hooks
|
||||
import type { EPageStoreType } from "@/plane-web/hooks/store";
|
||||
import { usePageStore } from "@/plane-web/hooks/store";
|
||||
// local imports
|
||||
import { PageListBlock } from "./block";
|
||||
import { PageListLoader } from "./loader";
|
||||
|
||||
type TPagesListRoot = {
|
||||
pageType: TPageNavigationTabs;
|
||||
@@ -16,17 +22,58 @@ type TPagesListRoot = {
|
||||
|
||||
export const PagesListRoot = observer(function PagesListRoot(props: TPagesListRoot) {
|
||||
const { pageType, storeType } = props;
|
||||
// params
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
// store hooks
|
||||
const { getCurrentProjectFilteredPageIdsByTab } = usePageStore(storeType);
|
||||
const { getCurrentProjectFilteredPageIdsByTab, fetchPagesList, getPaginationInfo, getPaginationLoader } =
|
||||
usePageStore(storeType);
|
||||
|
||||
// pagination hooks
|
||||
const paginationInfo = getPaginationInfo(pageType);
|
||||
const paginationLoader = getPaginationLoader(pageType);
|
||||
const hasNextPage = paginationInfo.hasNextPage;
|
||||
const isFetchingNextPage = paginationLoader === "pagination";
|
||||
|
||||
// state for intersection observer element
|
||||
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
|
||||
// ref for container - we'll wrap ListLayout
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// derived values
|
||||
const filteredPageIds = getCurrentProjectFilteredPageIdsByTab(pageType);
|
||||
|
||||
// Function to fetch next page
|
||||
const fetchNextPage = useCallback(() => {
|
||||
if (!workspaceSlug || !projectId || !hasNextPage || isFetchingNextPage) {
|
||||
return;
|
||||
}
|
||||
// Use fetchPagesList with cursor from pagination info
|
||||
void fetchPagesList(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
pageType,
|
||||
paginationInfo.nextCursor ?? undefined
|
||||
);
|
||||
}, [workspaceSlug, projectId, hasNextPage, isFetchingNextPage, fetchPagesList, pageType, paginationInfo.nextCursor]);
|
||||
|
||||
// Set up intersection observer to trigger loading more pages
|
||||
useIntersectionObserver(
|
||||
containerRef,
|
||||
isFetchingNextPage ? null : intersectionElement,
|
||||
fetchNextPage,
|
||||
`100% 0% 100% 0%`
|
||||
);
|
||||
|
||||
if (!filteredPageIds) return <></>;
|
||||
|
||||
return (
|
||||
<ListLayout>
|
||||
{filteredPageIds.map((pageId) => (
|
||||
<PageListBlock key={pageId} pageId={pageId} storeType={storeType} />
|
||||
))}
|
||||
</ListLayout>
|
||||
<div ref={containerRef} className="size-full">
|
||||
<ListLayout>
|
||||
{filteredPageIds.map((pageId) => (
|
||||
<PageListBlock key={pageId} pageId={pageId} storeType={storeType} />
|
||||
))}
|
||||
{hasNextPage && <div ref={setIntersectionElement}>{isFetchingNextPage && <PageListLoader />}</div>}
|
||||
</ListLayout>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -156,6 +156,11 @@ export const PagesListMainContent = observer(function PagesListMainContent(props
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (loader === "mutation-loader" && (!filteredPageIds || filteredPageIds.length === 0)) {
|
||||
return <PageLoader />;
|
||||
}
|
||||
|
||||
// if no pages match the filter criteria
|
||||
if (filteredPageIds?.length === 0)
|
||||
return (
|
||||
|
||||
@@ -19,11 +19,20 @@ type TPageView = {
|
||||
export const PagesListView = observer(function PagesListView(props: TPageView) {
|
||||
const { children, pageType, projectId, storeType, workspaceSlug } = props;
|
||||
// store hooks
|
||||
const { isAnyPageAvailable, fetchPagesList } = usePageStore(storeType);
|
||||
// fetching pages list
|
||||
const { isAnyPageAvailable, fetchPagesList, filters } = usePageStore(storeType);
|
||||
|
||||
// fetching pages list - include filters and sorting in SWR key to refetch when they change
|
||||
useSWR(
|
||||
workspaceSlug && projectId && pageType ? `PROJECT_PAGES_${projectId}` : null,
|
||||
workspaceSlug && projectId && pageType ? () => fetchPagesList(workspaceSlug, projectId, pageType) : null
|
||||
workspaceSlug && projectId && pageType
|
||||
? `PROJECT_PAGES_${projectId}_${pageType}_${filters.searchQuery || ""}_${filters.sortKey || ""}_${
|
||||
filters.sortBy || ""
|
||||
}_${JSON.stringify(filters.filters || {})}`
|
||||
: null,
|
||||
workspaceSlug && projectId && pageType ? () => fetchPagesList(workspaceSlug, projectId, pageType) : null,
|
||||
{
|
||||
revalidateOnFocus: true,
|
||||
revalidateIfStale: true,
|
||||
}
|
||||
);
|
||||
|
||||
// pages loader
|
||||
|
||||
Reference in New Issue
Block a user