mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-15 23:59:08 +00:00
Some checks failed
🤖 Android APK Build (Phone + TV) / 🏗️ Build Android APK (phone) (push) Has been cancelled
🤖 Android APK Build (Phone + TV) / 🏗️ Build Android APK (tv) (push) Has been cancelled
🤖 iOS IPA Build (Phone + TV) / 🏗️ Build iOS IPA (phone) (push) Has been cancelled
🤖 iOS IPA Build (Phone + TV) / 🏗️ Build iOS IPA (tv) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🕒 Handle Stale Issues / 🗑️ Cleanup Stale Issues (push) Has been cancelled
Co-authored-by: Alex Kim <alexkim@Alexs-MacBook-Pro.local>
99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
import { FlashList, type FlashListProps } from "@shopify/flash-list";
|
|
import React, { useImperativeHandle, useRef } from "react";
|
|
import { View, type ViewStyle } from "react-native";
|
|
import { Text } from "./Text";
|
|
|
|
type PartialExcept<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
|
|
|
export interface HorizontalScrollRef {
|
|
scrollToIndex: (index: number, viewOffset: number) => void;
|
|
}
|
|
|
|
interface HorizontalScrollProps<T>
|
|
extends PartialExcept<
|
|
Omit<FlashListProps<T>, "renderItem">,
|
|
"estimatedItemSize"
|
|
> {
|
|
data?: T[] | null;
|
|
renderItem: (item: T, index: number) => React.ReactNode;
|
|
keyExtractor?: (item: T, index: number) => string;
|
|
containerStyle?: ViewStyle;
|
|
contentContainerStyle?: ViewStyle;
|
|
height?: number;
|
|
loading?: boolean;
|
|
extraData?: any;
|
|
noItemsText?: string;
|
|
}
|
|
|
|
export const HorizontalScroll = <T,>(
|
|
props: HorizontalScrollProps<T> & {
|
|
ref?: React.ForwardedRef<HorizontalScrollRef>;
|
|
},
|
|
) => {
|
|
const {
|
|
data = [],
|
|
keyExtractor,
|
|
renderItem,
|
|
containerStyle,
|
|
contentContainerStyle,
|
|
loading = false,
|
|
height = 164,
|
|
extraData,
|
|
noItemsText,
|
|
ref,
|
|
...restProps
|
|
} = props;
|
|
const flashListRef = useRef<FlashList<T>>(null);
|
|
|
|
useImperativeHandle(ref!, () => ({
|
|
scrollToIndex: (index: number, viewOffset: number) => {
|
|
flashListRef.current?.scrollToIndex({
|
|
index,
|
|
animated: true,
|
|
viewPosition: 0,
|
|
viewOffset,
|
|
});
|
|
},
|
|
}));
|
|
|
|
const renderFlashListItem = ({ item, index }: { item: T; index: number }) => (
|
|
<View className='mr-2'>{renderItem(item, index)}</View>
|
|
);
|
|
|
|
if (!data || loading) {
|
|
return (
|
|
<View className='px-4 mb-2'>
|
|
<View className='bg-neutral-950 h-24 w-full rounded-md mb-2' />
|
|
<View className='bg-neutral-950 h-10 w-full rounded-md mb-1' />
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={containerStyle}>
|
|
<FlashList<T>
|
|
ref={flashListRef}
|
|
data={data}
|
|
extraData={extraData}
|
|
renderItem={renderFlashListItem}
|
|
horizontal
|
|
estimatedItemSize={200}
|
|
showsHorizontalScrollIndicator={false}
|
|
contentContainerStyle={{
|
|
paddingHorizontal: 16,
|
|
...contentContainerStyle,
|
|
}}
|
|
keyExtractor={keyExtractor}
|
|
ListEmptyComponent={() => (
|
|
<View className='flex-1 justify-center items-center'>
|
|
<Text className='text-center text-gray-500'>
|
|
{noItemsText || "No data available"}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
{...restProps}
|
|
/>
|
|
</View>
|
|
);
|
|
};
|