react-native-flexi-datepicker
Version:
A highly customizable and flexible date picker component for React Native
442 lines (441 loc) • 19 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 __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 moment_1 = __importDefault(require("moment"));
var localeUtils_1 = require("../utils/localeUtils"); // Add this import
var SCREEN_WIDTH = react_native_1.Dimensions.get("window").width;
var SCREEN_HEIGHT = react_native_1.Dimensions.get("window").height;
var ITEM_HEIGHT = 40;
var VISIBLE_ITEMS = 5;
var IOSDatePicker = 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 ? "3000-12-31" : _d, _e = _a.theme, theme = _e === void 0 ? {} : _e, _f = _a.cancelButton, cancelButton = _f === void 0 ? "Reset" : _f, _g = _a.okButton, okButton = _g === void 0 ? "Done" : _g, localeData = _a.localeData;
var _h = (0, react_1.useState)(function () {
var date = (0, moment_1.default)(initialDate);
if (localeData) {
date.locale(localeData.abbr);
}
return date;
}), selectedDate = _h[0], setSelectedDate = _h[1];
var _j = (0, react_1.useState)(false), isMonthYearPickerVisible = _j[0], setMonthYearPickerVisible = _j[1];
var _k = (0, react_1.useState)(selectedDate.month()), selectedMonthIndex = _k[0], setSelectedMonthIndex = _k[1];
(0, react_1.useEffect)(function () {
if (localeData) {
var abbr = localeData.abbr;
var localeLoader = localeUtils_1.localeRequires[abbr];
if (localeLoader) {
try {
localeLoader();
moment_1.default.locale(abbr);
console.log("Loaded locale: ".concat(abbr));
}
catch (error) {
console.warn("Failed to load locale: ".concat(abbr), error);
moment_1.default.locale("en");
}
}
else {
console.warn("Locale not found: ".concat(abbr));
moment_1.default.locale("en");
}
}
else {
moment_1.default.locale("en");
}
}, [localeData]);
var years = (0, react_1.useMemo)(function () {
return Array.from({ length: (0, moment_1.default)(maxDate).year() - (0, moment_1.default)(minDate).year() + 1 }, function (_, i) { return (0, moment_1.default)(minDate).year() + i; });
}, [minDate, maxDate]);
var months = (0, react_1.useMemo)(function () {
if (localeData) {
return localeData.calendar.monthNames;
}
return moment_1.default.months();
}, [localeData]);
var _l = (0, react_1.useState)(years.findIndex(function (year) { return year === selectedDate.year(); })), selectedYearIndex = _l[0], setSelectedYearIndex = _l[1];
var monthScrollY = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
var yearScrollY = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
var colors = __assign({ primary: "#007AFF", background: "#FFFFFF", text: "#000000", border: "#E5E5EA" }, theme);
var styles = react_native_1.StyleSheet.create({
modalOverlay: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "rgba(0, 0, 0, 0.4)",
},
pickerContainer: {
backgroundColor: colors.background,
borderRadius: 12,
overflow: "hidden",
width: SCREEN_WIDTH * 0.9,
maxHeight: SCREEN_HEIGHT * 0.7,
},
header: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
padding: 16,
borderBottomWidth: 1,
borderBottomColor: colors.border,
},
monthYearButton: {
flexDirection: "row",
alignItems: "center",
},
monthYearText: {
fontSize: 18,
fontWeight: "bold",
color: colors.text,
marginRight: 4,
},
arrowContainer: {
flexDirection: "row",
},
arrowButton: {
padding: 8,
},
arrowText: {
fontSize: 20,
color: colors.primary,
},
calendarContainer: {
padding: 16,
},
weekDaysContainer: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 8,
},
weekDayText: {
color: colors.text,
opacity: 0.5,
},
daysContainer: {
flexDirection: "row",
flexWrap: "wrap",
},
dayButton: {
width: (SCREEN_WIDTH * 0.9 - 32) / 7,
height: 40,
justifyContent: "center",
alignItems: "center",
},
dayText: {
color: colors.text,
},
selectedDayText: {
color: colors.primary,
fontWeight: "bold",
},
disabledDayText: {
color: colors.text,
opacity: 0.3,
},
bottomContainer: {
flexDirection: "row",
justifyContent: "space-between",
padding: 16,
borderTopWidth: 1,
borderTopColor: colors.border,
},
bottomButton: {
fontSize: 16,
color: colors.primary,
},
monthYearPickerContainer: {
flexDirection: "row",
height: ITEM_HEIGHT * VISIBLE_ITEMS,
alignItems: "center",
},
pickerColumn: {
flex: 1,
height: ITEM_HEIGHT * VISIBLE_ITEMS,
},
pickerItem: {
height: ITEM_HEIGHT,
justifyContent: "center",
alignItems: "center",
},
pickerItemText: {
fontSize: 20,
color: colors.text,
},
pickerHighlight: {
position: "absolute",
top: ((VISIBLE_ITEMS - 1) / 2) * ITEM_HEIGHT,
left: 0,
right: 0,
height: ITEM_HEIGHT,
borderTopWidth: 1,
borderBottomWidth: 1,
borderColor: colors.border,
},
});
var renderWeekDays = (0, react_1.useCallback)(function () {
var weekDays = localeData
? localeData.calendar.dayNamesShort
: moment_1.default.weekdaysShort(true);
return (<react_native_1.View style={styles.weekDaysContainer}>
{weekDays.map(function (day) { return (<react_native_1.Text key={day} style={styles.weekDayText}>
{day}
</react_native_1.Text>); })}
</react_native_1.View>);
}, [styles, localeData]);
var renderDays = (0, react_1.useCallback)(function () {
var days = [];
var daysInMonth = selectedDate.daysInMonth();
var firstDayOfMonth = selectedDate.clone().startOf("month").day();
for (var i = 0; i < firstDayOfMonth; i++) {
days.push(<react_native_1.View key={"empty-".concat(i)} style={styles.dayButton}/>);
}
var _loop_1 = function (i) {
var date = selectedDate.clone().date(i);
var isSelected = date.isSame(selectedDate, "day");
var isDisabled = date.isBefore((0, moment_1.default)(minDate)) || date.isAfter((0, moment_1.default)(maxDate));
days.push(<react_native_1.TouchableOpacity key={i} style={styles.dayButton} onPress={function () { return !isDisabled && setSelectedDate(date); }} disabled={isDisabled}>
<react_native_1.Text style={[
styles.dayText,
isSelected && styles.selectedDayText,
isDisabled && styles.disabledDayText,
]}>
{i}
</react_native_1.Text>
</react_native_1.TouchableOpacity>);
};
for (var i = 1; i <= daysInMonth; i++) {
_loop_1(i);
}
return <react_native_1.View style={styles.daysContainer}>{days}</react_native_1.View>;
}, [selectedDate, minDate, maxDate, styles]);
var changeMonth = (0, react_1.useCallback)(function (increment) {
setSelectedDate(function (prev) { return prev.clone().add(increment, "month"); });
}, []);
var renderPickerItems = (0, react_1.useCallback)(function (_a) {
var items = _a.items, scrollY = _a.scrollY, onMomentumScrollEnd = _a.onMomentumScrollEnd, itemType = _a.itemType, initialScrollIndex = _a.initialScrollIndex;
var AnimatedFlatList = react_native_1.Animated.createAnimatedComponent(react_native_1.FlatList);
var handleScroll = function (event) {
var index = Math.round(event.nativeEvent.contentOffset.y / ITEM_HEIGHT);
console.log("Scrolling ".concat(itemType, ". Current index:"), index);
};
var handleMomentumScrollEnd = function (event) {
var index = Math.round(event.nativeEvent.contentOffset.y / ITEM_HEIGHT);
onMomentumScrollEnd(index);
};
return (<AnimatedFlatList data={items} keyExtractor={function (item) { return item.toString(); }} showsVerticalScrollIndicator={false} snapToInterval={ITEM_HEIGHT} decelerationRate="fast" bounces={false} onScroll={react_native_1.Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
useNativeDriver: true,
listener: handleScroll,
})} onMomentumScrollEnd={handleMomentumScrollEnd} renderItem={function (_a) {
var item = _a.item, index = _a.index;
var position = react_native_1.Animated.subtract(index * ITEM_HEIGHT, scrollY);
var opacity = position.interpolate({
inputRange: [
-ITEM_HEIGHT * 2,
-ITEM_HEIGHT,
0,
ITEM_HEIGHT,
ITEM_HEIGHT * 2,
],
outputRange: [0.3, 0.6, 1, 0.6, 0.3],
extrapolate: "clamp",
});
var scale = position.interpolate({
inputRange: [-ITEM_HEIGHT, 0, ITEM_HEIGHT],
outputRange: [0.8, 1, 0.8],
extrapolate: "clamp",
});
return (<react_native_1.Animated.View style={[
styles.pickerItem,
{
opacity: opacity,
transform: [{ scale: scale }],
},
]}>
<react_native_1.Text style={styles.pickerItemText}>
{item.toString()}
</react_native_1.Text>
</react_native_1.Animated.View>);
}} getItemLayout={function (_, index) { return ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index: index,
}); }} initialScrollIndex={initialScrollIndex} contentContainerStyle={{
paddingVertical: ((VISIBLE_ITEMS - 1) / 2) * ITEM_HEIGHT,
}}/>);
}, [styles]);
(0, react_1.useEffect)(function () {
if (isMonthYearPickerVisible) {
monthScrollY.setValue(-selectedMonthIndex * ITEM_HEIGHT);
yearScrollY.setValue(-selectedYearIndex * ITEM_HEIGHT);
}
}, [
isMonthYearPickerVisible,
selectedMonthIndex,
selectedYearIndex,
monthScrollY,
yearScrollY,
]);
var renderMonthYearPicker = (0, react_1.useCallback)(function () { return (<react_native_1.View style={styles.monthYearPickerContainer}>
<react_native_1.View style={styles.pickerHighlight}/>
<react_native_1.View style={styles.pickerColumn}>
{renderPickerItems({
items: months,
scrollY: monthScrollY,
onMomentumScrollEnd: function (index) {
setSelectedMonthIndex(index);
console.log("Month scroll ended. Selected index:", index);
},
itemType: "month",
initialScrollIndex: selectedMonthIndex,
})}
</react_native_1.View>
<react_native_1.View style={styles.pickerColumn}>
{renderPickerItems({
items: years,
scrollY: yearScrollY,
onMomentumScrollEnd: function (index) {
setSelectedYearIndex(index);
console.log("Year scroll ended. Selected index:", index);
},
itemType: "year",
initialScrollIndex: selectedYearIndex,
})}
</react_native_1.View>
</react_native_1.View>); }, [
months,
years,
monthScrollY,
yearScrollY,
renderPickerItems,
styles,
selectedMonthIndex,
selectedYearIndex,
]);
var handleMonthYearSelection = (0, react_1.useCallback)(function () {
var selectedYear = years[selectedYearIndex];
var newDate = (0, moment_1.default)()
.locale(localeData ? localeData.abbr : "en")
.year(selectedYear)
.month(selectedMonthIndex)
.date(1);
if (newDate.isBefore((0, moment_1.default)(minDate))) {
setSelectedDate((0, moment_1.default)(minDate).locale(localeData ? localeData.abbr : "en"));
}
else if (newDate.isAfter((0, moment_1.default)(maxDate))) {
setSelectedDate((0, moment_1.default)(maxDate).locale(localeData ? localeData.abbr : "en"));
}
else {
setSelectedDate(newDate);
}
console.log("Final selected date:", newDate.format("YYYY-MM-DD"));
setMonthYearPickerVisible(false);
}, [
selectedMonthIndex,
selectedYearIndex,
years,
minDate,
maxDate,
localeData,
]);
(0, react_1.useEffect)(function () {
if (localeData) {
setSelectedDate(function (prevDate) { return prevDate.clone().locale(localeData.abbr); });
}
}, [localeData]);
return (<react_native_1.Modal visible={isVisible} transparent animationType="fade" onRequestClose={onClose}>
<react_native_1.View style={styles.modalOverlay}>
<react_native_1.View style={styles.pickerContainer}>
<react_native_1.View style={styles.header}>
<react_native_1.TouchableOpacity style={styles.monthYearButton} onPress={function () {
setMonthYearPickerVisible(!isMonthYearPickerVisible);
if (!isMonthYearPickerVisible) {
monthScrollY.setValue(-selectedDate.month() * ITEM_HEIGHT);
yearScrollY.setValue(-years.indexOf(selectedDate.year()) * ITEM_HEIGHT);
}
}}>
<react_native_1.Text style={styles.monthYearText}>
{localeData
? "".concat(localeData.calendar.monthNames[selectedDate.month()], " ").concat(selectedDate.format("YYYY"))
: selectedDate.format("MMMM YYYY")}
</react_native_1.Text>
<react_native_1.Text style={[styles.arrowText, { fontSize: 12 }]}>
{isMonthYearPickerVisible ? "▲" : "▼"}
</react_native_1.Text>
</react_native_1.TouchableOpacity>
{!isMonthYearPickerVisible && (<react_native_1.View style={styles.arrowContainer}>
<react_native_1.TouchableOpacity style={styles.arrowButton} onPress={function () { return changeMonth(-1); }}>
<react_native_1.Text style={styles.arrowText}>{"<"}</react_native_1.Text>
</react_native_1.TouchableOpacity>
<react_native_1.TouchableOpacity style={styles.arrowButton} onPress={function () { return changeMonth(1); }}>
<react_native_1.Text style={styles.arrowText}>{">"}</react_native_1.Text>
</react_native_1.TouchableOpacity>
</react_native_1.View>)}
</react_native_1.View>
{isMonthYearPickerVisible ? (renderMonthYearPicker()) : (<react_native_1.View style={styles.calendarContainer}>
{renderWeekDays()}
{renderDays()}
</react_native_1.View>)}
<react_native_1.View style={styles.bottomContainer}>
<react_native_1.TouchableOpacity onPress={function () {
if (isMonthYearPickerVisible) {
setMonthYearPickerVisible(false);
}
else {
onClose();
}
}}>
<react_native_1.Text style={styles.bottomButton}>{cancelButton}</react_native_1.Text>
</react_native_1.TouchableOpacity>
<react_native_1.TouchableOpacity onPress={function () {
if (isMonthYearPickerVisible) {
handleMonthYearSelection();
}
else {
onDateChange(selectedDate.format("YYYY-MM-DD"));
onClose();
}
}}>
<react_native_1.Text style={styles.bottomButton}>{okButton}</react_native_1.Text>
</react_native_1.TouchableOpacity>
</react_native_1.View>
</react_native_1.View>
</react_native_1.View>
</react_native_1.Modal>);
};
exports.default = react_1.default.memo(IOSDatePicker);