import React, { useRef } from "react";
import {
Animated,
FlatList,
FlatListProps,
NativeScrollEvent,
NativeSyntheticEvent,
useWindowDimensions,
} from "react-native";
import { Box } from "@/components/ui/box";
type CarouselFlatListProps<T> = FlatListProps<T> & {
innerRef?: React.RefObject<any> | null;
className?: string;
withPagination?: boolean;
dotActiveColor?: string;
dotColor?: string;
dotClassName?: string;
};
function CarouselPagination<ItemT>({
data,
scrollX,
dotActiveColor = "#5852a5",
dotColor = "#ccc",
className,
}: {
data: ArrayLike<ItemT> | null | undefined;
scrollX: Animated.Value;
dotActiveColor?: string;
dotColor?: string;
className?: string;
}) {
const { width } = useWindowDimensions();
if (!data || !Array.isArray(data)) {
return null;
}
return (
<Box
className={`flex flex-row ${className || "mt-6 w-full justify-center items-center"}`}
>
{data.map((_, idx) => {
const inputRange = [(idx - 1) * width, idx * width, (idx + 1) * width];
const dotWidth = scrollX.interpolate({
inputRange,
outputRange: [12, 30, 12],
extrapolate: "clamp",
});
const backgroundColor = scrollX.interpolate({
inputRange,
outputRange: [dotColor, dotActiveColor, dotColor],
extrapolate: "clamp",
});
return (
<Animated.View
key={idx.toString()}
className={"w-2 h-2"}
style={[
{
borderRadius: 6,
marginHorizontal: 3,
backgroundColor: dotColor,
},
{ width: dotWidth, backgroundColor },
]}
/>
);
})}
</Box>
);
}
export default function Carousel<ItemT>({
dotActiveColor = "#fff",
dotColor = "#ddd",
dotClassName = "",
withPagination = true,
innerRef,
...props
}: CarouselFlatListProps<ItemT>) {
const scrollX = useRef(new Animated.Value(0)).current;
const handleOnScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
Animated.event(
[
{
nativeEvent: {
contentOffset: {
x: scrollX,
},
},
},
],
{
useNativeDriver: false,
},
)(event);
};
const viewabilityConfig = useRef({
itemVisiblePercentThreshold: 50,
}).current;
return (
<>
<FlatList
ref={innerRef}
{...props}
horizontal={true}
decelerationRate={"fast"}
pagingEnabled={withPagination}
snapToAlignment="center"
showsHorizontalScrollIndicator={false}
onScroll={(e) => {
props.onScroll?.(e);
handleOnScroll(e);
}}
viewabilityConfig={viewabilityConfig}
/>
{withPagination && (
<CarouselPagination
data={props.data}
scrollX={scrollX}
dotActiveColor={dotActiveColor}
dotColor={dotColor}
className={dotClassName}
/>
)}
</>
);
}
import Carousel from "@/nativestack-ui/carousel/free/carousel";
import { Box } from "@/components/ui/box";
import { Text } from "@/components/ui/text";
import { SafeAreaView } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import { useWindowDimensions } from "react-native";
export default function TabHomeScreen() {
const items = [1, 2, 3];
const { width, height } = useWindowDimensions();
return (
<SafeAreaView className={"bg-white w-full h-full"}>
<StatusBar style={"light"} backgroundColor={"#fff"}></StatusBar>
<Box>
<Carousel
dotActiveColor={"#434343"}
data={items}
renderItem={({ item }) => {
return (
<Box>
<Box
style={{
width,
height: height / 2,
}}
className={`${item % 2 === 0 ? "bg-indigo-500" : "bg-indigo-300"} flex justify-center items-center`}
>
<Text className={"text-typography-900"} size={"3xl"}>
{item}
</Text>
</Box>
</Box>
);
}}
/>
</Box>
</SafeAreaView>
);
}