UNPKG

react-native-taqweem

Version:

**A dual calendar component (Hijri + Gregorian) for React Native** — minimal, customizable, and theme-ready. Perfect for apps needing culturally-aware calendars, Islamic date pickers, or just modern UX flexibility.

314 lines (311 loc) 10 kB
"use strict"; import { useEffect, useState } from 'react'; import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; import moment from 'moment-hijri'; import { useCalendarDays, useCalendarInfo, useWeekdays } from "../hooks.js"; import { getCalendarTitle, getCalendarTitleFormat, getFullDateFormat, getNextMonth, getPreviousMonth, getYearMonthFormat } from "../utils.js"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; export function DualCalendar({ initialSelectedDate, currentYearMonth, minDate, maxDate, renderHeader, calendarTheme = {}, showAdjacentMonths = true, calendarMode = 'gregorian', allowFutureDates = true, onDateChange = () => {} }) { const [isHijri, setIsHijri] = useState(calendarMode === 'hijri'); const fullDateFormat = getFullDateFormat(isHijri); const yearMonthFormat = getYearMonthFormat(isHijri); const titleFormat = getCalendarTitleFormat(isHijri); const defaultMinDate = isHijri ? '1400-1' : '1900-1'; const defaultMaxDate = isHijri ? moment().add(10, 'iYear').format('iYYYY-iM') : moment().add(10, 'year').format('YYYY-M'); const effectiveMinDate = moment(minDate || defaultMinDate, yearMonthFormat); const effectiveMaxDate = moment(maxDate || defaultMaxDate, yearMonthFormat); const todaysDate = moment().format(fullDateFormat); const initialYearMonth = currentYearMonth ? moment(currentYearMonth, yearMonthFormat) : initialSelectedDate ? moment(initialSelectedDate, fullDateFormat) : moment(); const [currentDate, setCurrentDate] = useState(initialYearMonth); const [selectedDateStr, setSelectedDateStr] = useState(initialSelectedDate ?? null); const _previousMonth = getPreviousMonth(isHijri, currentDate); const _nextMonth = getNextMonth(isHijri, currentDate); const canGoPreviousMonth = !_previousMonth.isBefore(effectiveMinDate, 'month'); const canGoNextMonth = !_nextMonth.isAfter(effectiveMaxDate, 'month') && (allowFutureDates || !_nextMonth.isAfter(moment(), 'month')); useEffect(() => { setIsHijri(calendarMode === 'hijri'); }, [calendarMode]); useEffect(() => { if (!currentYearMonth) { return; } const parsedKey = moment(currentYearMonth, yearMonthFormat); const min = effectiveMinDate; const max = effectiveMaxDate; const clamped = parsedKey.isBefore(min) ? min : parsedKey.isAfter(max) ? max : parsedKey; setCurrentDate(clamped); }, [currentYearMonth, yearMonthFormat, effectiveMaxDate, effectiveMinDate]); const { year, month, daysInMonth } = useCalendarInfo(isHijri, currentDate); const days = useCalendarDays(isHijri, year, month, daysInMonth, showAdjacentMonths); const handleSelectDay = dayObj => { const selectedKey = dayObj.date.format(fullDateFormat); const gregorianDate = dayObj.date.format('YYYY-MM-DD'); const hijriDate = dayObj.date.format('iYYYY-iMM-iDD'); setSelectedDateStr(prev => { const isToggling = prev === selectedKey; onDateChange(isToggling ? { calendarDate: '', gregorianDate: '', hijriDate: '', momentObj: null } : { calendarDate: selectedKey, gregorianDate, hijriDate, momentObj: dayObj.date }); return isToggling ? null : selectedKey; }); if (!dayObj.isCurrentMonth) { setCurrentDate(dayObj.date.clone()); } }; const goToPrevMonth = () => { let prev = getPreviousMonth(isHijri, currentDate); if (prev.isBefore(effectiveMinDate, 'month')) { return; } setCurrentDate(prev); }; const goToNextMonth = () => { let next = getNextMonth(isHijri, currentDate); // 🔒 Block navigation if allowFutureDates is false if (!allowFutureDates && next.isAfter(moment(), 'month')) { return; } // 🔒 Still respect maxDate limit if (next.isAfter(effectiveMaxDate, 'month')) { return; } setCurrentDate(next); }; const resetSelection = () => { onDateChange({ calendarDate: '', gregorianDate: '', hijriDate: '', momentObj: null }); setSelectedDateStr(null); }; const toggleCalendarMode = () => { setIsHijri(prev => { resetSelection(); if (prev === true) { return false; } return true; }); }; const monthYearTitle = getCalendarTitle(currentDate, titleFormat); const headerProps = { calendarTitle: monthYearTitle, canGoNextMonth, canGoPreviousMonth, goToNextMonth, goToPrevMonth, toggleCalendarMode, isHijri, dateObj: currentDate }; const dayGridProps = { days, allowFutureDates, fullDateFormat, handleSelectDay, todaysDate, selectedDateStr }; return /*#__PURE__*/_jsxs(View, { style: [styles.calendarView, calendarTheme.calendarView], children: [renderHeader ? renderHeader(headerProps) : /*#__PURE__*/_jsx(CalendarHeader, { ...headerProps, headerTheme: { header: calendarTheme.header, headerTextStyle: calendarTheme.headerTextStyle } }), /*#__PURE__*/_jsx(MonthView, { ...dayGridProps, dayGridTheme: { calendarGrid: calendarTheme.calendarGrid, dayCell: calendarTheme.dayCell, dayNameCell: calendarTheme.dayNameCell, dayTextStyle: calendarTheme.dayTextStyle, nonCurrentMonthTextStyle: calendarTheme.nonCurrentMonthTextStyle, selectedDayStyle: calendarTheme.selectedDayStyle, todayCellStyle: calendarTheme.todayCellStyle, todayTextStyle: calendarTheme.todayTextStyle, selectedDayTextStyle: calendarTheme.selectedDayTextStyle } })] }); } function CalendarHeader({ calendarTitle = '', canGoPreviousMonth = false, canGoNextMonth = false, headerTheme = {}, goToNextMonth = () => {}, goToPrevMonth = () => {} }) { return /*#__PURE__*/_jsxs(View, { style: [styles.header, headerTheme.header], children: [canGoPreviousMonth && /*#__PURE__*/_jsx(TouchableOpacity, { style: styles.arrow, onPress: goToPrevMonth, children: /*#__PURE__*/_jsx(Text, { style: [styles.nav, headerTheme.headerTextStyle], children: "\u2039" }) }), /*#__PURE__*/_jsx(View, { style: [styles.titleWrapper], children: /*#__PURE__*/_jsx(Text, { style: [styles.monthTitleStyle, headerTheme.headerTextStyle], children: calendarTitle }) }), canGoNextMonth && /*#__PURE__*/_jsx(TouchableOpacity, { style: styles.arrow, onPress: goToNextMonth, children: /*#__PURE__*/_jsx(Text, { style: [styles.nav, headerTheme.headerTextStyle], children: "\u203A" }) })] }); } function WeekDayNames({ weekDayTheme = {} }) { const weekDayNames = useWeekdays(); return weekDayNames.map((dayName, i) => /*#__PURE__*/_jsx(Text, { style: [styles.dayNameCell, weekDayTheme.dayNameCell], children: dayName }, i)); } function MonthView(props) { return /*#__PURE__*/_jsxs(View, { style: [styles.calendarGrid, props.dayGridTheme?.calendarGrid], children: [/*#__PURE__*/_jsx(WeekDayNames, { weekDayTheme: { dayNameCell: props.dayGridTheme?.dayNameCell } }), /*#__PURE__*/_jsx(DayGrid, { ...props })] }); } function DayGrid({ allowFutureDates, fullDateFormat, selectedDateStr, todaysDate, dayGridTheme = {}, days = [], handleSelectDay = () => {} }) { return /*#__PURE__*/_jsx(_Fragment, { children: days.map((dayObj, idx) => { if (!dayObj) { return /*#__PURE__*/_jsx(View, { style: [styles.dayCell, dayGridTheme.dayCell] }, idx); } if (!allowFutureDates && dayObj.date.isAfter(moment(), 'day')) { return /*#__PURE__*/_jsx(View, { style: [styles.dayCell, dayGridTheme.dayCell], children: /*#__PURE__*/_jsx(Text, { style: [styles.nonCurrentMonthTextStyle, dayGridTheme.nonCurrentMonthTextStyle], children: dayObj.label }) }, idx); } const key = dayObj.date.format(fullDateFormat); const isSelected = key === selectedDateStr; const isToday = dayObj.date.format(fullDateFormat) === todaysDate; return /*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.dayCell, dayGridTheme.dayCell, isToday && styles.todayCellStyle, isToday && dayGridTheme.todayCellStyle, isSelected && styles.selectedDayStyle, isSelected && dayGridTheme.selectedDayStyle], onPress: () => handleSelectDay(dayObj), children: /*#__PURE__*/_jsx(Text, { style: [styles.dayTextStyle, dayGridTheme.dayTextStyle, isToday && dayGridTheme.todayTextStyle, isSelected && dayGridTheme.selectedDayTextStyle, !dayObj.isCurrentMonth && styles.nonCurrentMonthTextStyle, !dayObj.isCurrentMonth && dayGridTheme.nonCurrentMonthTextStyle], children: dayObj.label }) }, idx); }) }); } const styles = StyleSheet.create({ calendarView: { padding: 16, backgroundColor: '#f8f9fa' }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }, arrow: { width: 40, alignItems: 'center' }, titleWrapper: { flex: 1, alignItems: 'center' }, nav: { fontSize: 24 }, monthTitleStyle: { fontSize: 18, fontWeight: 'bold' }, calendarGrid: { flexDirection: 'row', flexWrap: 'wrap' }, dayNameCell: { width: '14.28%', textAlign: 'center', textAlignVertical: 'center', height: 35, fontWeight: 'bold' }, dayCell: { width: '14.28%', height: 50, alignItems: 'center', justifyContent: 'center', borderRadius: 50 }, selectedDayStyle: { backgroundColor: '#f7ede2', borderWidth: 1 }, todayCellStyle: { borderWidth: 1, borderColor: '#415a77', borderStyle: 'dashed' }, dayTextStyle: { color: '#000' }, nonCurrentMonthTextStyle: { color: '#aaa' } }); //# sourceMappingURL=index.js.map