Last active
October 7, 2024 10:30
-
-
Save ndpniraj/bd28b45f26ad1cfebb70fba5582de755 to your computer and use it in GitHub Desktop.
React Native Scrollable Selector Component using Flatlist
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | |
| }, | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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