UNPKG

react-native-flexi-datepicker

Version:

A highly customizable and flexible date picker component for React Native

545 lines (544 loc) 26.3 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var react_1 = __importStar(require("react")); var react_native_1 = require("react-native"); var react_native_calendars_1 = require("react-native-calendars"); var moment_1 = __importDefault(require("moment")); var localeUtils_1 = require("../utils/localeUtils"); var iosDatePicker_1 = __importDefault(require("./iosDatePicker")); var SCREEN_WIDTH = react_native_1.Dimensions.get("window").width; var defaultTheme = { primary: "#018577", background: "#FFFFFF", text: "#000000", selectedText: "#FFFFFF", disabledText: "#D9E1E8", headerBackground: "#018577", yearText: "#FFFFFF", monthYearText: "#000000", buttonText: "#018577", todayText: "#000000", dayText: "#2D4150", dotColor: "#018577", selectedDotColor: "#FFFFFF", arrowColor: "#018577", monthTextColor: "#018577", indicatorColor: "#018577", }; var createStyles = function (colors) { return react_native_1.StyleSheet.create({ modalOverlay: { flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "rgba(0, 0, 0, 0.5)", }, datePickerContainer: { backgroundColor: colors.background, borderRadius: 8, overflow: "hidden", width: "90%", height: 550, }, headerContainer: { padding: 16, backgroundColor: colors.headerBackground, }, yearSelector: { alignSelf: "flex-start", }, yearText: { fontSize: 14, color: colors.yearText, }, dateText: { fontSize: 28, fontWeight: "bold", color: colors.selectedText, marginTop: 8, }, yearPickerOverlay: { flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "rgba(0, 0, 0, 0.4)", }, yearPickerContainer: { backgroundColor: colors.background, borderRadius: 8, width: SCREEN_WIDTH * 0.5, height: 300, overflow: "hidden", }, yearItem: { height: 52, justifyContent: "center", alignItems: "center", }, selectedYearItem: { backgroundColor: "rgba(0, 0, 0, 0.1)", }, yearTextSelector: { fontSize: 22, color: colors.text, }, selectedYearText: { color: colors.primary, fontWeight: "bold", }, chevron: { width: 12, height: 12, justifyContent: "center", alignItems: "center", }, chevronLeft: { transform: [{ rotate: "180deg" }], }, chevronInner: { width: 8, height: 8, borderTopWidth: 2, borderRightWidth: 2, borderColor: colors.text, transform: [{ rotate: "45deg" }], }, monthYearSelectorContainer: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", padding: 16, backgroundColor: colors.background, }, monthYearText: { fontSize: 18, color: colors.monthYearText, }, arrowButton: { padding: 8, zIndex: 1, }, calendarContainer: { flex: 1, overflow: "hidden", }, calendarWrapper: { flex: 1, }, buttonContainer: { flexDirection: "row", justifyContent: "flex-end", padding: 16, borderTopWidth: 1, borderTopColor: "rgba(0, 0, 0, 0.1)", }, button: { padding: 8, marginLeft: 8, }, buttonText: { color: colors.buttonText, fontWeight: "bold", }, }); }; var MonthYearSelector = function (_a) { var date = _a.date, onPressArrow = _a.onPressArrow, localeData = _a.localeData, animatedTextStyle = _a.animatedTextStyle, monthYearFormat = _a.monthYearFormat, styles = _a.styles, colors = _a.colors; var localeCode = (localeData === null || localeData === void 0 ? void 0 : localeData.abbr) || "en"; var monthYear = date.locale(localeCode).format(monthYearFormat); var ChevronIcon = (0, react_1.useCallback)(function (_a) { var direction = _a.direction; return (<react_native_1.View style={[styles.chevron, direction === "left" && styles.chevronLeft]}> <react_native_1.View style={styles.chevronInner}/> </react_native_1.View>); }, [styles]); return (<react_native_1.View style={styles.monthYearSelectorContainer}> <react_native_1.TouchableOpacity onPress={function () { return onPressArrow("left"); }} style={styles.arrowButton}> <ChevronIcon direction="left"/> </react_native_1.TouchableOpacity> <react_native_1.Animated.Text style={[ styles.monthYearText, animatedTextStyle, { color: colors.monthYearText }, ]}> {monthYear} </react_native_1.Animated.Text> <react_native_1.TouchableOpacity onPress={function () { return onPressArrow("right"); }} style={styles.arrowButton}> <ChevronIcon direction="right"/> </react_native_1.TouchableOpacity> </react_native_1.View>); }; var CustomHeader = function (_a) { var selectedDate = _a.selectedDate, currentDate = _a.currentDate, onPressYear = _a.onPressYear, onPressDate = _a.onPressDate, localeData = _a.localeData, headerFormat = _a.headerFormat, styles = _a.styles, colors = _a.colors; var localeCode = (localeData === null || localeData === void 0 ? void 0 : localeData.abbr) || "en"; var formattedDate = selectedDate.locale(localeCode).format(headerFormat); var year = currentDate.year(); return (<react_native_1.View style={[ styles.headerContainer, { backgroundColor: colors.headerBackground }, ]}> <react_native_1.TouchableOpacity onPress={onPressYear} style={styles.yearSelector}> <react_native_1.Text style={[styles.yearText, { color: colors.yearText }]}> {year} </react_native_1.Text> </react_native_1.TouchableOpacity> <react_native_1.TouchableOpacity onPress={onPressDate}> <react_native_1.Text style={[styles.dateText, { color: colors.selectedText }]}> {formattedDate} </react_native_1.Text> </react_native_1.TouchableOpacity> </react_native_1.View>); }; var DatePicker = function (_a) { var isVisible = _a.isVisible, onClose = _a.onClose, onDateChange = _a.onDateChange, _b = _a.initialDate, initialDate = _b === void 0 ? new Date() : _b, _c = _a.minDate, minDate = _c === void 0 ? "1900-01-01" : _c, _d = _a.maxDate, maxDate = _d === void 0 ? "2700-12-31" : _d, localeData = _a.localeData, _e = _a.cancelButton, cancelButton = _e === void 0 ? "Cancel" : _e, _f = _a.okButton, okButton = _f === void 0 ? "OK" : _f, _g = _a.animationEnabled, animationEnabled = _g === void 0 ? true : _g, _h = _a.theme, theme = _h === void 0 ? {} : _h, _j = _a.headerFormat, headerFormat = _j === void 0 ? "ddd, D MMM" : _j, _k = _a.monthYearFormat, monthYearFormat = _k === void 0 ? "MMMM YYYY" : _k, _l = _a.customStyles, customStyles = _l === void 0 ? {} : _l, _m = _a.pickerStyle, pickerStyle = _m === void 0 ? 'auto' : _m; var _o = (0, react_1.useState)((0, moment_1.default)(initialDate)), selectedDate = _o[0], setSelectedDate = _o[1]; var _p = (0, react_1.useState)((0, moment_1.default)(initialDate).startOf("month")), currentDate = _p[0], setCurrentDate = _p[1]; var _q = (0, react_1.useState)(false), isYearPickerVisible = _q[0], setYearPickerVisible = _q[1]; var _r = (0, react_1.useState)({}), markedDates = _r[0], setMarkedDates = _r[1]; var _s = (0, react_1.useState)(0), calendarKey = _s[0], setCalendarKey = _s[1]; var minMoment = (0, moment_1.default)(minDate); var maxMoment = (0, moment_1.default)(maxDate); var minYear = minMoment.year(); var maxYear = maxMoment.year(); var fadeAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current; var slideAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current; var animatedTextStyle = { opacity: fadeAnim, transform: [{ translateX: slideAnim }], }; (0, react_1.useEffect)(function () { react_native_calendars_1.LocaleConfig.locales['default'] = { monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], }; react_native_calendars_1.LocaleConfig.defaultLocale = 'default'; }, []); var colors = (0, react_1.useMemo)(function () { return (__assign(__assign({}, defaultTheme), theme)); }, [theme]); var baseStyles = (0, react_1.useMemo)(function () { return createStyles(colors); }, [colors]); var mergeStyles = (0, react_1.useCallback)(function (baseStyles, customStyles) { var mergedStyles = __assign({}, baseStyles); for (var key in customStyles) { if (key in mergedStyles) { mergedStyles[key] = __assign(__assign({}, mergedStyles[key]), customStyles[key]); } } return mergedStyles; }, []); var styles = (0, react_1.useMemo)(function () { return mergeStyles(baseStyles, customStyles); }, [baseStyles, customStyles, mergeStyles]); var animate = (0, react_1.useCallback)(function (direction) { if (!animationEnabled) { return Promise.resolve(); } return new Promise(function (resolve) { react_native_1.Animated.parallel([ react_native_1.Animated.timing(fadeAnim, { toValue: 0, duration: 100, useNativeDriver: true, }), react_native_1.Animated.timing(slideAnim, { toValue: direction === "right" ? -SCREEN_WIDTH / 3 : SCREEN_WIDTH / 3, duration: 100, useNativeDriver: true, }), ]).start(function () { slideAnim.setValue(0); fadeAnim.setValue(1); resolve(); }); }); }, [animationEnabled, fadeAnim, slideAnim]); var fadeOut = (0, react_1.useCallback)(function () { react_native_1.Animated.timing(fadeAnim, { toValue: 0, duration: 50, useNativeDriver: true, }).start(); }, [fadeAnim]); var fadeIn = (0, react_1.useCallback)(function () { react_native_1.Animated.timing(fadeAnim, { toValue: 1, duration: 150, useNativeDriver: true, }).start(); }, [fadeAnim]); var years = (0, react_1.useMemo)(function () { return Array.from({ length: maxYear - minYear + 1 }, function (_, index) { return minYear + index; }); }, [minYear, maxYear]); var isIOS = react_native_1.Platform.OS === 'ios'; var useIOSPicker = pickerStyle === 'ios' || (pickerStyle === 'auto' && isIOS); var loadMomentLocale = (0, react_1.useCallback)(function (localeCode) { if (localeCode === "en") { return; } var localeLoader = localeUtils_1.localeRequires[localeCode]; if (localeLoader) { try { localeLoader(); console.log("Loaded locale: ".concat(localeCode)); } catch (error) { console.warn("Failed to load locale: ".concat(localeCode), error); moment_1.default.locale("en"); } } else { console.warn("Locale not found: ".concat(localeCode)); moment_1.default.locale("en"); } }, []); (0, react_1.useEffect)(function () { var initialMoment = (0, moment_1.default)(initialDate); setSelectedDate(initialMoment); setCurrentDate(initialMoment.clone().startOf("month")); updateMarkedDates(initialMoment); if (localeData) { var abbr = localeData.abbr, calendar = localeData.calendar; loadMomentLocale(abbr); moment_1.default.locale(abbr); react_native_calendars_1.LocaleConfig.locales[abbr] = { monthNames: calendar.monthNames, monthNamesShort: calendar.monthNamesShort, dayNames: calendar.dayNames, dayNamesShort: calendar.dayNamesShort, }; react_native_calendars_1.LocaleConfig.defaultLocale = abbr; console.log("Locale updated to: ".concat(abbr)); } else { moment_1.default.locale("en"); react_native_calendars_1.LocaleConfig.defaultLocale = "default"; console.log("Locale set to default: en"); } setCalendarKey(function (prevKey) { return prevKey + 1; }); }, [initialDate, localeData, loadMomentLocale]); var jumpToSelectedDate = (0, react_1.useCallback)(function () { fadeOut(); setTimeout(function () { setCurrentDate(selectedDate.clone().startOf("month")); updateMarkedDates(selectedDate); setCalendarKey(function (prevKey) { return prevKey + 1; }); fadeIn(); }, 30); }, [selectedDate, fadeOut, fadeIn]); var updateMarkedDates = (0, react_1.useCallback)(function (date) { var _a; var formattedDate = formatDate(date); setMarkedDates((_a = {}, _a[formattedDate] = { selected: true, selectedColor: colors.primary }, _a)); }, [colors.primary]); var jumpToYear = (0, react_1.useCallback)(function (year) { var newDate = selectedDate.clone().year(year); if (newDate.isBetween(minMoment, maxMoment, "day", "[]")) { setSelectedDate(newDate); setCurrentDate(newDate.clone().startOf("month")); updateMarkedDates(newDate); setCalendarKey(function (prevKey) { return prevKey + 1; }); } setYearPickerVisible(false); }, [selectedDate, minMoment, maxMoment, updateMarkedDates]); var onPressArrow = (0, react_1.useCallback)(function (direction) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, animate(direction)]; case 1: _a.sent(); setCurrentDate(function (prevDate) { var newDate = prevDate .clone() .add(direction === "left" ? -1 : 1, "month") .startOf("month"); return newDate; }); setCalendarKey(function (prevKey) { return prevKey + 1; }); return [2 /*return*/]; } }); }); }, [animate]); var formatDate = (0, react_1.useCallback)(function (date) { return date.format("YYYY-MM-DD"); }, []); var YearPicker = (0, react_1.useCallback)(function () { return (<react_native_1.Modal visible={isYearPickerVisible} transparent={true} animationType="fade"> <react_native_1.TouchableWithoutFeedback onPress={function () { return setYearPickerVisible(false); }}> <react_native_1.View style={styles.yearPickerOverlay}> <react_native_1.TouchableWithoutFeedback> <react_native_1.View style={styles.yearPickerContainer}> <react_native_1.FlatList data={years} renderItem={function (_a) { var item = _a.item; return (<react_native_1.TouchableOpacity onPress={function () { return jumpToYear(item); }} style={[ styles.yearItem, item === currentDate.year() && styles.selectedYearItem, ]}> <react_native_1.Text style={[ styles.yearTextSelector, { color: colors.text }, item === currentDate.year() && { color: colors.primary, fontWeight: "bold", }, ]}> {item} </react_native_1.Text> </react_native_1.TouchableOpacity>); }} keyExtractor={function (item) { return item.toString(); }} showsVerticalScrollIndicator={false} initialScrollIndex={years.findIndex(function (year) { return year === currentDate.year(); }) - 3} getItemLayout={function (data, index) { return ({ length: 52, offset: 52 * index, index: index, }); }}/> </react_native_1.View> </react_native_1.TouchableWithoutFeedback> </react_native_1.View> </react_native_1.TouchableWithoutFeedback> </react_native_1.Modal>); }, [isYearPickerVisible, years, currentDate, jumpToYear, styles, colors]); var renderAndroidPicker = function () { return (<react_native_1.View style={styles.calendarContainer}> <MonthYearSelector date={currentDate} onPressArrow={onPressArrow} localeData={localeData} animatedTextStyle={animatedTextStyle} monthYearFormat={monthYearFormat} styles={styles} colors={colors}/> <react_native_1.Animated.View style={[ styles.calendarWrapper, animationEnabled ? { opacity: fadeAnim, transform: [{ translateX: slideAnim }], } : {}, ]}> <react_native_calendars_1.Calendar key={calendarKey} locale={(localeData === null || localeData === void 0 ? void 0 : localeData.abbr) || "en"} current={formatDate(currentDate)} onMonthChange={function (month) { setCurrentDate((0, moment_1.default)(month.dateString).startOf("month")); }} onDayPress={function (day) { var newSelectedDate = (0, moment_1.default)(day.dateString); if (newSelectedDate.isBetween(minMoment, maxMoment, "day", "[]")) { setSelectedDate(newSelectedDate); updateMarkedDates(newSelectedDate); } }} markedDates={markedDates} hideArrows={true} hideExtraDays={false} enableSwipeMonths={true} disableMonthChange={false} firstDay={1} hideDayNames={false} showWeekNumbers={false} disableArrowLeft={true} onSwipeLeft={function () { return onPressArrow("right"); }} onSwipeRight={function () { return onPressArrow("left"); }} disableArrowRight={true} disableAllTouchEventsForDisabledDays={true} renderHeader={function () { return null; }} minDate={formatDate(minMoment)} maxDate={formatDate(maxMoment)} theme={{ backgroundColor: colors.background, calendarBackground: colors.background, textSectionTitleColor: colors.text, selectedDayBackgroundColor: colors.primary, selectedDayTextColor: colors.selectedText, todayTextColor: colors.todayText, dayTextColor: colors.dayText, textDisabledColor: colors.disabledText, dotColor: colors.dotColor, selectedDotColor: colors.selectedDotColor, arrowColor: colors.arrowColor, monthTextColor: colors.monthTextColor, indicatorColor: colors.indicatorColor, textDayFontWeight: "300", textMonthFontWeight: "bold", textDayHeaderFontWeight: "300", textDayFontSize: 16, textMonthFontSize: 16, textDayHeaderFontSize: 16, textDayStyle: { fontSize: 16, fontWeight: "300", }, }}/> </react_native_1.Animated.View> </react_native_1.View>); }; var renderIOSPicker = function () { return (<iosDatePicker_1.default selectedDate={selectedDate} onDateChange={function (date) { setSelectedDate(date); updateMarkedDates(date); }} minDate={minMoment} maxDate={maxMoment} localeData={localeData} theme={colors} customStyles={customStyles}/>); }; return (<react_native_1.Modal visible={isVisible} transparent={true} animationType="fade" onRequestClose={onClose}> <react_native_1.View style={styles.modalOverlay}> <react_native_1.View style={styles.datePickerContainer}> <CustomHeader selectedDate={selectedDate} currentDate={currentDate} onPressYear={function () { return setYearPickerVisible(true); }} onPressDate={jumpToSelectedDate} localeData={localeData} headerFormat={headerFormat} styles={styles} colors={colors}/> {useIOSPicker ? renderIOSPicker() : renderAndroidPicker()} <react_native_1.View style={styles.buttonContainer}> <react_native_1.TouchableOpacity onPress={onClose} style={styles.button}> <react_native_1.Text style={[styles.buttonText, { color: colors.buttonText }]}> {cancelButton} </react_native_1.Text> </react_native_1.TouchableOpacity> <react_native_1.TouchableOpacity onPress={function () { onDateChange(formatDate(selectedDate)); onClose(); }} style={styles.button}> <react_native_1.Text style={[styles.buttonText, { color: colors.buttonText }]}> {okButton} </react_native_1.Text> </react_native_1.TouchableOpacity> </react_native_1.View> <YearPicker /> </react_native_1.View> </react_native_1.View> </react_native_1.Modal>); }; exports.default = DatePicker;