Untitled
unknown
plain_text
11 days ago
10 kB
3
Indexable
import React, { useState, useRef, useEffect } from 'react'; import { View, Text, TouchableOpacity, FlatList, StyleSheet, Dimensions, Modal } from 'react-native'; import moment from 'moment'; import { Calendar } from 'react-native-calendars'; const SCREEN_WIDTH = Dimensions.get('window').width; const ITEM_WIDTH = 30; // Base width for date items const SELECTED_ITEM_WIDTH = 121; // Width of selected date item const ITEM_MARGIN = 10; // Total horizontal margin (5 on each side) const HorizontalDatePicker = ({ onDateSelect, initialDate, themeColor = 'black', accentColor = '#00E5FF', selectedItemWidth = 121, itemWidth = 30, showCalendarOnSelectedDatePress = true, confirmButtonText = 'Confirm', calendarHeaderText = 'Select a Date' }) => { // Generate dates centered around today const generateDatesAroundToday = () => { const today = moment(); let dates = []; // Add dates before today (past 7 days) for (let i = 7; i > 0; i--) { dates.push(moment(today).subtract(i, 'days').format('YYYY-MM-DD')); } // Add today dates.push(today.format('YYYY-MM-DD')); // Add dates after today (next 14 days) for (let i = 1; i <= 14; i++) { dates.push(moment(today).add(i, 'days').format('YYYY-MM-DD')); } return dates; }; const [dates, setDates] = useState(generateDatesAroundToday()); const [selectedDate, setSelectedDate] = useState(initialDate || moment().format('YYYY-MM-DD')); const [calendarVisible, setCalendarVisible] = useState(false); const [markedDates, setMarkedDates] = useState({}); const flatListRef = useRef(null); const initialScrollDone = useRef(false); // More precise centering calculation const centerSelectedDate = (dateString) => { if (!flatListRef.current) return; const index = dates.indexOf(dateString); if (index === -1) return; // Calculate the middle of the screen const screenCenter = SCREEN_WIDTH / 2; // For dates before the selected date, calculate their total width let widthBeforeSelected = 0; for (let i = 0; i < index; i++) { widthBeforeSelected += (ITEM_WIDTH + ITEM_MARGIN); } // Calculate the position that would center the selected item const offset = widthBeforeSelected - screenCenter + (SELECTED_ITEM_WIDTH / 2); // Apply the scroll with a safe offset (not less than 0) flatListRef.current.scrollToOffset({ offset: Math.max(0, offset), animated: true, }); }; const handleDateSelection = (date) => { setSelectedDate(date); centerSelectedDate(date); // Call parent callback if provided if (onDateSelect) { onDateSelect(date); } // Add more dates if we're approaching the end const lastVisibleIndex = dates.length - 5; if (dates.indexOf(date) >= lastVisibleIndex) { const lastDate = dates[dates.length - 1]; const nextDates = Array.from({ length: 7 }, (_, i) => moment(lastDate).add(i + 1, 'days').format('YYYY-MM-DD') ); setDates(prevDates => [...prevDates, ...nextDates]); } }; // Function to open calendar when selected date is clicked const handleSelectedDateClick = () => { // Update marked dates to highlight the currently selected date const updatedMarkedDates = { [selectedDate]: { selected: true, selectedColor: themeColor, } }; setMarkedDates(updatedMarkedDates); setCalendarVisible(true); }; // Handle date selection from the calendar const handleCalendarDateSelect = (day) => { const dateString = day.dateString; // Close the calendar setCalendarVisible(false); // Update selected date and center it setSelectedDate(dateString); // Check if the date exists in our horizontal list const dateIndex = dates.indexOf(dateString); // If the date is not in our list, add it and surrounding dates if (dateIndex === -1) { const selectedMoment = moment(dateString); const newDates = [...dates]; // Add 7 days before and 7 days after the selected date for (let i = -7; i <= 7; i++) { const newDate = selectedMoment.clone().add(i, 'days').format('YYYY-MM-DD'); if (!newDates.includes(newDate)) { newDates.push(newDate); } } // Sort the dates newDates.sort(); setDates(newDates); // Center after a brief delay to ensure rendering setTimeout(() => centerSelectedDate(dateString), 100); } else { centerSelectedDate(dateString); } // Call parent callback if provided if (onDateSelect) { onDateSelect(dateString); } }; // Center today's date when component mounts useEffect(() => { const todayIndex = dates.indexOf(moment().format('YYYY-MM-DD')); // Use a longer timeout to ensure rendering is complete const timer = setTimeout(() => { if (!initialScrollDone.current) { centerSelectedDate(selectedDate); initialScrollDone.current = true; } }, 500); return () => clearTimeout(timer); }, []); // Pre-scroll to approximately center the view when FlatList renders const getItemLayout = (data, index) => { const itemSize = index === dates.indexOf(selectedDate) ? SELECTED_ITEM_WIDTH : ITEM_WIDTH; // Calculate offset based on item sizes let offset = 0; for (let i = 0; i < index; i++) { offset += (i === dates.indexOf(selectedDate) ? SELECTED_ITEM_WIDTH : ITEM_WIDTH) + ITEM_MARGIN; } return { length: itemSize + ITEM_MARGIN, offset, index }; }; const renderDateItem = ({ item }) => { const isSelected = selectedDate === item; const dateNum = moment(item).format('D'); const isDoubleDigit = dateNum.length > 1; return ( <TouchableOpacity onPress={() => { if (isSelected && showCalendarOnSelectedDatePress) { // If clicking the already selected date and the feature is enabled, open the calendar handleSelectedDateClick(); } else { // Otherwise just select the date handleDateSelection(item); } }} style={[ styles.dateContainer, isSelected && styles.selectedDate, ]}> <Text style={[ styles.dateText, isSelected && styles.selectedDateText, isDoubleDigit && !isSelected && styles.doubleDigitDate ]}> {isSelected ? moment(item).format('ddd, D MMM') : dateNum} </Text> </TouchableOpacity> ); }; return ( <View style={styles.container}> <FlatList ref={flatListRef} data={dates} horizontal keyExtractor={(item) => item} showsHorizontalScrollIndicator={false} initialNumToRender={dates.length} getItemLayout={getItemLayout} initialScrollIndex={dates.indexOf(selectedDate) - 3} // Start a bit before today maxToRenderPerBatch={dates.length} windowSize={21} onScrollToIndexFailed={(info) => { // Handle scroll failure gracefully setTimeout(() => { if (flatListRef.current) { flatListRef.current.scrollToOffset({ offset: info.averageItemLength * info.index, animated: false, }); } }, 100); }} renderItem={renderDateItem} /> {/* Calendar Modal */} <Modal transparent={true} visible={calendarVisible} animationType="slide" onRequestClose={() => setCalendarVisible(false)} > <View style={styles.modalContainer}> <View style={styles.calendarContainer}> <View style={styles.calendarHeader}> <Text style={styles.headerText}>{calendarHeaderText}</Text> <TouchableOpacity onPress={() => setCalendarVisible(false)}> <Text style={styles.closeButton}>✕</Text> </TouchableOpacity> </View> <Calendar current={selectedDate} onDayPress={handleCalendarDateSelect} markedDates={markedDates} theme={{ selectedDayBackgroundColor: themeColor, todayTextColor: accentColor, arrowColor: themeColor, dotColor: themeColor, selectedDotColor: 'white', }} /> <TouchableOpacity style={styles.confirmButton} onPress={() => setCalendarVisible(false)} > <Text style={styles.confirmButtonText}>{confirmButtonText}</Text> </TouchableOpacity> </View> </View> </Modal> </View> ); }; const styles = StyleSheet.create({ container: { paddingVertical: 10, }, dateContainer: { width: ITEM_WIDTH, height: 40, alignItems: 'center', justifyContent: 'center', paddingVertical: 10, borderRadius: 20, marginHorizontal: 5, }, selectedDate: { backgroundColor: 'black', borderRadius: 20, paddingHorizontal: 10, width: SELECTED_ITEM_WIDTH, // Wider width for selected date }, dateText: { fontSize: 16, color: 'black', textAlign: 'center', }, doubleDigitDate: { fontSize: 16, }, selectedDateText: { color: 'white', fontWeight: 'bold', }, // Calendar Modal Styles modalContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0, 0, 0, 0.5)', }, calendarContainer: { width: '90%', backgroundColor: 'white', borderRadius: 12, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, }, calendarHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16, }, headerText: { fontSize: 18, fontWeight: 'bold', color: 'black', }, closeButton: { fontSize: 22, color: '#666', }, confirmButton: { backgroundColor: 'black', padding: 12, borderRadius: 8, alignItems: 'center', marginTop: 16, }, confirmButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, }, }); export default HorizontalDatePicker;
Editor is loading...
Leave a Comment