From c8bdcc4df01fb3236ddeec97d6e268ce9080b33f Mon Sep 17 00:00:00 2001 From: Fredrik Burmester Date: Sun, 16 Nov 2025 16:40:42 +0100 Subject: [PATCH] feat: add snap scroll to horizontal lists --- components/home/InfiniteScrollingCollectionList.tsx | 8 ++++++++ components/medialists/MediaListSection.tsx | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/components/home/InfiniteScrollingCollectionList.tsx b/components/home/InfiniteScrollingCollectionList.tsx index 30464b63..73a1c21d 100644 --- a/components/home/InfiniteScrollingCollectionList.tsx +++ b/components/home/InfiniteScrollingCollectionList.tsx @@ -4,6 +4,7 @@ import { type QueryKey, useInfiniteQuery, } from "@tanstack/react-query"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, @@ -64,6 +65,11 @@ export const InfiniteScrollingCollectionList: React.FC = ({ // Flatten all pages into a single array const allItems = data?.pages.flat() || []; + const snapOffsets = useMemo(() => { + const itemWidth = orientation === "horizontal" ? 184 : 120; // w-44 (176px) + mr-2 (8px) or w-28 (112px) + mr-2 (8px) + return allItems.map((_, index) => index * itemWidth); + }, [allItems, orientation]); + if (hideIfEmpty === true && allItems.length === 0 && !isLoading) return null; if (disabled || !title) return null; @@ -126,6 +132,8 @@ export const InfiniteScrollingCollectionList: React.FC = ({ showsHorizontalScrollIndicator={false} onScroll={handleScroll} scrollEventThrottle={16} + snapToOffsets={snapOffsets} + decelerationRate='fast' > {allItems.map((item) => ( diff --git a/components/medialists/MediaListSection.tsx b/components/medialists/MediaListSection.tsx index 2c11c5ce..0ada9dc5 100644 --- a/components/medialists/MediaListSection.tsx +++ b/components/medialists/MediaListSection.tsx @@ -9,7 +9,7 @@ import { useQuery, } from "@tanstack/react-query"; import { useAtom } from "jotai"; -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { View, type ViewProps } from "react-native"; import { useInView } from "@/hooks/useInView"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; @@ -67,6 +67,12 @@ export const MediaListSection: React.FC = ({ [api, user?.Id, collection?.Id], ); + const snapOffsets = useMemo(() => { + const itemWidth = 120; // w-28 (112px) + mr-2 (8px) + // Generate offsets for a reasonable number of items + return Array.from({ length: 50 }, (_, index) => index * itemWidth); + }, []); + if (!collection) return null; return ( @@ -92,6 +98,8 @@ export const MediaListSection: React.FC = ({ )} queryFn={fetchItems} queryKey={["media-list", collection.Id!]} + snapToOffsets={snapOffsets} + decelerationRate='fast' /> );