mirror of
https://github.com/zoriya/Kyoo.git
synced 2026-03-23 18:07:51 -04:00
127 lines
4.0 KiB
TypeScript
127 lines
4.0 KiB
TypeScript
import type {
|
|
LegendListComponent,
|
|
LegendListProps,
|
|
} from "@legendapp/list/react-native";
|
|
import { LegendList } from "@legendapp/list/react-native";
|
|
import { type ComponentType, type ReactElement, useMemo, useRef } from "react";
|
|
import type { ViewStyle } from "react-native";
|
|
import { createAnimatedComponent } from "react-native-reanimated";
|
|
import { type Breakpoint, HR, useBreakpointMap } from "~/primitives";
|
|
import { type QueryIdentifier, useInfiniteFetch } from "./query";
|
|
|
|
const AnimatedLegendList = createAnimatedComponent(
|
|
LegendList,
|
|
) as LegendListComponent;
|
|
|
|
export type Layout = {
|
|
numColumns: Breakpoint<number>;
|
|
size: Breakpoint<number>;
|
|
gap: Breakpoint<number>;
|
|
layout: "grid" | "horizontal" | "vertical";
|
|
};
|
|
|
|
export const InfiniteFetch = <Data, Type extends string = string>({
|
|
query,
|
|
placeholderCount = 4,
|
|
incremental = false,
|
|
getItemType,
|
|
getItemSizeMult,
|
|
getStickyIndices,
|
|
Render,
|
|
Loader,
|
|
layout,
|
|
Empty,
|
|
Divider,
|
|
Header,
|
|
Footer,
|
|
fetchMore = true,
|
|
contentContainerStyle,
|
|
columnWrapperStyle,
|
|
...props
|
|
}: {
|
|
query: QueryIdentifier<Data>;
|
|
placeholderCount?: number;
|
|
layout: Layout;
|
|
horizontal?: boolean;
|
|
getItemType?: (item: Data, index: number) => Type;
|
|
getItemSizeMult?: (item: Data, index: number, type: Type) => number;
|
|
getStickyIndices?: (items: Data[]) => number[];
|
|
stickyHeaderConfig?: LegendListProps["stickyHeaderConfig"];
|
|
Render: (props: { item: Data; index: number }) => ReactElement | null;
|
|
Loader: (props: { index: number }) => ReactElement | null;
|
|
Empty?: ReactElement;
|
|
incremental?: boolean;
|
|
Divider?: true | ComponentType;
|
|
Header?: ComponentType<{ children: ReactElement }> | ReactElement;
|
|
Footer?: ComponentType<{ children: ReactElement }> | ReactElement;
|
|
fetchMore?: boolean;
|
|
contentContainerStyle?: ViewStyle;
|
|
onScroll?: LegendListProps["onScroll"];
|
|
scrollEventThrottle?: LegendListProps["scrollEventThrottle"];
|
|
columnWrapperStyle?: Omit<ViewStyle, "gap" | "rowGap" | "columnGap">;
|
|
}): ReactElement | null => {
|
|
const { numColumns, size, gap } = useBreakpointMap(layout);
|
|
const oldItems = useRef<Data[] | undefined>(undefined);
|
|
let { items, fetchNextPage, hasNextPage, isFetching, refetch, isRefetching } =
|
|
useInfiniteFetch(query);
|
|
if (incremental && items) oldItems.current = items;
|
|
if (incremental) items ??= oldItems.current;
|
|
|
|
if (!query.infinite)
|
|
console.warn("A non infinite query was passed to an InfiniteFetch.");
|
|
|
|
const data = useMemo(() => {
|
|
const count = items
|
|
? numColumns - (items.length % numColumns)
|
|
: placeholderCount;
|
|
const placeholders = [...Array(count === 0 ? numColumns : count)].fill(0);
|
|
if (!items) return placeholders;
|
|
return isFetching && !isRefetching ? [...items, ...placeholders] : items;
|
|
}, [items, isFetching, isRefetching, placeholderCount, numColumns]);
|
|
|
|
return (
|
|
<AnimatedLegendList
|
|
data={data}
|
|
recycleItems
|
|
getItemType={getItemType}
|
|
estimatedItemSize={getItemSizeMult ? undefined : size}
|
|
stickyHeaderIndices={getStickyIndices?.(items ?? [])}
|
|
getEstimatedItemSize={
|
|
getItemSizeMult
|
|
? (item, idx, type) => getItemSizeMult(item, idx, type as Type) * size
|
|
: undefined
|
|
}
|
|
renderItem={({ item, index }) =>
|
|
item ? <Render index={index} item={item} /> : <Loader index={index} />
|
|
}
|
|
keyExtractor={(item: any, index) => (item ? item.id : index + 1)}
|
|
horizontal={layout.layout === "horizontal"}
|
|
numColumns={layout.layout === "horizontal" ? 1 : numColumns}
|
|
onEndReached={
|
|
fetchMore && hasNextPage && !isFetching
|
|
? () => fetchNextPage()
|
|
: undefined
|
|
}
|
|
onEndReachedThreshold={0.5}
|
|
onRefresh={layout.layout !== "horizontal" ? refetch : undefined}
|
|
refreshing={isRefetching}
|
|
ListHeaderComponent={Header}
|
|
ListEmptyComponent={Empty}
|
|
ListFooterComponent={Footer}
|
|
ItemSeparatorComponent={
|
|
Divider === true ? HR : (Divider as any) || undefined
|
|
}
|
|
showsHorizontalScrollIndicator={false}
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={contentContainerStyle}
|
|
columnWrapperStyle={{
|
|
gap,
|
|
marginLeft: gap,
|
|
marginRight: gap,
|
|
...columnWrapperStyle,
|
|
}}
|
|
{...props}
|
|
/>
|
|
);
|
|
};
|