UNPKG

@husam287/react-native-modern-datepicker

Version:

A customizable calendar, time & month picker for React Native (including Persian Jalaali calendar & locale)

291 lines (270 loc) 7.81 kB
import React, { useEffect, useRef, useState } from 'react'; import { View, StyleSheet, Text, Animated, FlatList, Easing, TouchableOpacity, I18nManager, Platform, } from 'react-native'; import { useCalendar } from '../DatePicker'; const AnimatedFlatList = Animated.createAnimatedComponent(FlatList); const TimeScroller = ({ title, data, onChange }) => { const { options, utils } = useCalendar(); const [itemSize, setItemSize] = useState(0); const style = styles(options); const scrollAnimatedValue = useRef(new Animated.Value(0)).current; const scrollListener = useRef(null); const active = useRef(0); const dataCopy = I18nManager.isRTL ? [...data].reverse() : data data = ['', '', ...dataCopy, '', '']; useEffect(() => { scrollListener.current && clearInterval(scrollListener.current); scrollListener.current = scrollAnimatedValue.addListener(({ value }) => (active.current = value)); return () => { clearInterval(scrollListener.current); }; }, [scrollAnimatedValue]); const changeItemWidth = ({ nativeEvent }) => { const { width } = nativeEvent.layout; !itemSize && setItemSize(width / 5); }; const renderItem = ({ item, index }) => { const makeAnimated = (a, b, c) => { return { inputRange: [...data.map((_, i) => i * itemSize)], outputRange: [ ...data.map((_, i) => { const center = i + 2; if (center === index) { return a; } else if (center + 1 === index || center - 1 === index) { return b; } else { return c; } }), ], }; }; return ( <Animated.View style={[ { width: itemSize, opacity: scrollAnimatedValue.interpolate(makeAnimated(1, 0.6, 0.3)), transform: [ { scale: scrollAnimatedValue.interpolate(makeAnimated(1.2, 0.9, 0.8)), }, ], }, style.listItem, ]}> <Text style={style.listItemText}> {utils.toPersianNumber(String(item).length === 1 ? '0' + item : item)} </Text> </Animated.View> ); }; return ( <View style={style.row} onLayout={changeItemWidth}> <Text style={style.title}>{title}</Text> <AnimatedFlatList pagingEnabled showsHorizontalScrollIndicator={false} horizontal snapToInterval={itemSize} decelerationRate={'fast'} onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: scrollAnimatedValue } } }], { useNativeDriver: true, })} data={data} onMomentumScrollEnd={() => { const index = Math.round(active.current / itemSize); onChange(data[index + 2]); }} keyExtractor={(_, i) => String(i)} renderItem={renderItem} contentContainerStyle={ I18nManager.isRTL && { transform: [ { scaleX: -1, }, ], flexDirection: 'row-reverse' } } /> </View> ); }; const SelectTime = () => { const { options, state, utils, minuteInterval, mode, onTimeChange, is12Times } = useCalendar(); const [mainState, setMainState] = state; const [show, setShow] = useState(false); const [time, setTime] = useState({ minute: 0, hour: 0, }); const style = styles(options); const openAnimation = useRef(new Animated.Value(0)).current; useEffect(() => { show && setTime({ minute: 0, hour: 0, }); }, [show]); useEffect(() => { mainState.timeOpen && setShow(true); Animated.timing(openAnimation, { toValue: mainState.timeOpen ? 1 : 0, duration: 350, useNativeDriver: true, easing: Easing.bezier(0.17, 0.67, 0.46, 1), }).start(() => { !mainState.timeOpen && setShow(false); }); }, [mainState.timeOpen, openAnimation]); const selectTime = () => { const newTime = utils.getDate(mainState.activeDate); const incHours = is12Times && time.a === utils.config.pm ? 12 : 0; const hour = is12Times ? time.hour + incHours : time.hour; newTime.hour(hour).minute(time.minute); setMainState({ type: 'set', activeDate: utils.getFormated(newTime), selectedDate: mainState.selectedDate ? utils.getFormated( utils .getDate(mainState.selectedDate) .hour(hour) .minute(time.minute), ) : '', }); onTimeChange(utils.getFormated(newTime, 'timeFormat')); mode !== 'time' && setMainState({ type: 'toggleTime', }); }; const containerStyle = [ style.container, { opacity: openAnimation, transform: [ { scale: openAnimation.interpolate({ inputRange: [0, 1], outputRange: [1.1, 1], }), }, ], }, ]; return show ? ( <Animated.View style={containerStyle}> <TimeScroller title={utils.config.hour} data={Array.from({ length: is12Times ? 12 : 24 }, (x, i) => i)} onChange={hour => setTime({ ...time, hour })} /> <TimeScroller title={utils.config.minute} data={Array.from({ length: 60 / minuteInterval }, (x, i) => i * minuteInterval)} onChange={minute => setTime({ ...time, minute })} /> {!!is12Times && <TimeScroller title={utils.config.a} data={Array.from({ length: 2 }, (x, i) => i === 0 ? utils.config.am : utils.config.pm)} onChange={a => setTime({ ...time, a })} /> } <View style={style.footer}> <TouchableOpacity style={style.button} activeOpacity={0.8} onPress={selectTime}> <Text style={style.btnText}>{utils.config.timeSelect}</Text> </TouchableOpacity> {mode !== 'time' && ( <TouchableOpacity style={[style.button, style.cancelButton]} onPress={() => setMainState({ type: 'toggleTime', }) } activeOpacity={0.8}> <Text style={style.btnText}>{utils.config.timeClose}</Text> </TouchableOpacity> )} </View> </Animated.View> ) : null; }; const styles = theme => StyleSheet.create({ container: { position: 'absolute', width: '100%', height: '100%', top: 0, right: 0, backgroundColor: theme.backgroundColor, borderRadius: 10, flexDirection: 'column', justifyContent: 'center', zIndex: 999, }, row: { flexDirection: 'column', alignItems: 'center', marginVertical: 5, }, title: { fontSize: theme.textHeaderFontSize, color: theme.mainColor, fontFamily: theme.headerFont, }, listItem: { height: 60, alignItems: 'center', justifyContent: 'center', transform: [ { scaleX: I18nManager.isRTL ? Platform.select({ android: -1, ios: 1 }) : 1, }, ] }, listItemText: { fontSize: theme.textHeaderFontSize, color: theme.textDefaultColor, fontFamily: theme.defaultFont, }, footer: { flexDirection: 'row', justifyContent: 'center', marginTop: 15, }, button: { paddingVertical: 10, paddingHorizontal: 25, borderRadius: 8, backgroundColor: theme.mainColor, margin: 8, }, btnText: { fontSize: theme.textFontSize, color: theme.selectedTextColor, fontFamily: theme.defaultFont, }, cancelButton: { backgroundColor: theme.textSecondaryColor, }, }); export { SelectTime };