Skip to content

Instantly share code, notes, and snippets.

@ndpniraj
Last active October 7, 2024 10:30
Show Gist options
  • Select an option

  • Save ndpniraj/bd28b45f26ad1cfebb70fba5582de755 to your computer and use it in GitHub Desktop.

Select an option

Save ndpniraj/bd28b45f26ad1cfebb70fba5582de755 to your computer and use it in GitHub Desktop.
React Native Scrollable Selector Component using Flatlist
import ScrollableSelector from "@/ui/ScrollableSelector";
import { useState } from "react";
import { StyleSheet, SafeAreaView, Text, View } from "react-native";
const generateHrsList = (qty: number) => {
return Array(qty)
.fill("")
.map((_, index) => (index + 1 <= 9 ? `0${index + 1}` : `${index + 1}`));
};
const generateMinList = (qty: number) => {
return Array(qty)
.fill("")
.map((_, index) => (index <= 9 ? `0${index}` : `${index}`));
};
export default function HomeScreen() {
const [time, setTime] = useState({ hrs: "--", min: "--", period: "--" });
return (
<SafeAreaView style={styles.container}>
<View style={{ flexDirection: "row", gap: 10, padding: 10 }}>
<ScrollableSelector
value="11"
style={{ flex: 1 }}
onChange={(hrs) => setTime({ ...time, hrs })}
visibleItems={3}
data={generateHrsList(12)}
renderItem={(item) => {
return (
<Text style={{ fontSize: 20, fontWeight: "700" }}>{item}</Text>
);
}}
/>
<ScrollableSelector
value="46"
style={{ flex: 1 }}
onChange={(min) => setTime({ ...time, min })}
visibleItems={3}
data={generateMinList(60)}
renderItem={(item) => {
return (
<Text style={{ fontSize: 20, fontWeight: "700" }}>{item}</Text>
);
}}
/>
<ScrollableSelector
value="PM"
style={{ flex: 1 }}
onChange={(period) => setTime({ ...time, period })}
visibleItems={3}
data={["AM", "PM"]}
renderItem={(item) => {
return (
<Text style={{ fontSize: 20, fontWeight: "700" }}>{item}</Text>
);
}}
/>
</View>
<Text
style={{
fontSize: 20,
fontWeight: "700",
padding: 10,
textAlign: "center",
color: "blue",
}}
>{`${time.hrs}:${time.min}:${time.period}`}</Text>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
import React, { ReactNode, useEffect, useRef } from "react";
import {
View,
FlatList,
StyleSheet,
NativeSyntheticEvent,
NativeScrollEvent,
ViewStyle,
StyleProp,
} from "react-native";
const ITEM_HEIGHT = 60; // Define the height of each item
const VISIBLE_ITEMS = 3; // The number of visible items in the FlatList
interface Props<T> {
value?: T;
itemHeight?: number;
visibleItems?: number;
data: T[];
style?: StyleProp<ViewStyle>;
onChange?(item: T): void;
renderItem(item: T): ReactNode;
}
const ScrollableSelector = <T extends unknown>({
data = [],
value,
itemHeight = ITEM_HEIGHT,
visibleItems = VISIBLE_ITEMS,
style,
onChange,
renderItem,
}: Props<T>) => {
const FLATLIST_HEIGHT = itemHeight * visibleItems;
const flatListRef = useRef<FlatList>(null);
// Handler to update selected item when scrolling stops
const handleScrollEnd = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const offsetY = event.nativeEvent.contentOffset.y;
const index = Math.round(offsetY / ITEM_HEIGHT);
onChange && onChange(data[index]);
};
useEffect(() => {
// Find the index of the default time in the times array
if (flatListRef.current && value) {
const defaultIndex = data.indexOf(value);
// Scroll to the index of the default time when the component mounts
if (defaultIndex !== -1) {
setTimeout(() => {
flatListRef.current?.scrollToIndex({
index: defaultIndex,
animated: true,
});
}, 100);
}
}
}, [flatListRef, value]);
if (!renderItem)
throw new Error("ScrollableSelector: renderItem function is missing!");
return (
<View style={style}>
{/* Highlighted view in the center of the FlatList */}
<View
style={[
styles.highlightView,
{
height: itemHeight,
top: FLATLIST_HEIGHT / 2,
transform: [{ translateY: -(itemHeight / 2) }],
},
]}
></View>
{/* Fixed height FlatList */}
<FlatList
ref={flatListRef}
data={data}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item }) => (
<View style={[styles.itemContainer, { height: itemHeight }]}>
{renderItem(item)}
</View>
)}
getItemLayout={(data, index) => ({
length: itemHeight,
offset: itemHeight * index,
index,
})}
snapToInterval={itemHeight} // Snap effect to each item height
showsVerticalScrollIndicator={false} // Hide scroll indicator
decelerationRate="fast" // Smooth scrolling
contentContainerStyle={{
paddingVertical: (FLATLIST_HEIGHT - itemHeight) / 2, // Center first item
}}
onMomentumScrollEnd={handleScrollEnd} // Detect scrolling end to find the selected item
style={[{ height: FLATLIST_HEIGHT }, styles.flatListStyle]}
/>
</View>
);
};
const styles = StyleSheet.create({
flatListStyle: {
// height: FLATLIST_HEIGHT, // Set FlatList height
backgroundColor: "transparent",
},
itemContainer: {
// height: ITEM_HEIGHT,
justifyContent: "center",
alignItems: "center",
},
itemText: {
fontSize: 24,
fontWeight: "bold",
},
highlightView: {
position: "absolute",
// height: ITEM_HEIGHT,
// top: FLATLIST_HEIGHT / 2,
// transform: [{ translateY: -(ITEM_HEIGHT / 2) }],
width: "100%",
backgroundColor: "#F0F0F0",
paddingHorizontal: 2,
zIndex: 0, // Ensure this is above the FlatList but below the flatlist items
},
});
export default ScrollableSelector;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment