react-native-flexi-datepicker
Version:
A highly customizable and flexible date picker component for React Native
545 lines (544 loc) • 26.3 kB
JavaScript
;
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;