From 43f11935d32bd03c4368bf1f7c0629d1600d5e79 Mon Sep 17 00:00:00 2001 From: Rajesh Singh Date: Tue, 9 Sep 2025 11:52:28 +1000 Subject: [PATCH] First pass of a working month-year only selector based on flag --- src/Date/Calendar.tsx | 146 ++++-- src/Date/CalendarHeader.tsx | 18 +- src/Date/DatePickerInput.shared.tsx | 1 + src/Date/DatePickerInput.tsx | 4 +- src/Date/DatePickerInputWithoutModal.tsx | 5 + src/Date/DatePickerModal.tsx | 6 + src/Date/DatePickerModalContent.tsx | 16 + src/Date/DatePickerModalContentHeader.tsx | 38 ++ src/Date/Mon.tsx | 158 +++++++ src/Date/Month.tsx | 2 +- src/Date/Swiper.native.tsx | 5 +- src/Date/Swiper.tsx | 5 +- src/Date/SwiperUtils.ts | 1 + src/Date/Year.tsx | 446 ++++++++++++++++++ src/Date/dateUtils.tsx | 18 +- src/Date/inputUtils.ts | 4 +- src/__tests__/Date/Calendar.test.tsx | 5 +- src/__tests__/Date/CalendarEdit.test.tsx | 11 +- src/__tests__/Date/CalendarHeader.test.tsx | 1 + src/__tests__/Date/DatePickerInput.test.tsx | 5 +- .../Date/DatePickerInputWithoutModal.test.tsx | 5 +- .../Date/DatePickerModalContent.test.tsx | 5 +- src/index.tsx | 6 +- src/translations/ar.ts | 1 + src/translations/ca.ts | 1 + src/translations/cs.ts | 1 + src/translations/de.ts | 1 + src/translations/el.ts | 1 + src/translations/en.ts | 1 + src/translations/enGB.ts | 1 + src/translations/es.ts | 1 + src/translations/fr.ts | 1 + src/translations/he.ts | 1 + src/translations/hi.ts | 1 + src/translations/id.ts | 1 + src/translations/it.ts | 1 + src/translations/ja.ts | 1 + src/translations/ko.ts | 1 + src/translations/nl.ts | 1 + src/translations/noNO.ts | 1 + src/translations/pl.ts | 1 + src/translations/pt.ts | 1 + src/translations/ro.ts | 1 + src/translations/ru.ts | 1 + src/translations/sv.ts | 1 + src/translations/th.ts | 1 + src/translations/tr.ts | 1 + src/translations/ukUA.ts | 1 + src/translations/utils.ts | 1 + src/translations/zh.ts | 1 + src/translations/zhTW.ts | 1 + 51 files changed, 865 insertions(+), 74 deletions(-) create mode 100644 src/Date/Mon.tsx create mode 100644 src/Date/Year.tsx diff --git a/src/Date/Calendar.tsx b/src/Date/Calendar.tsx index d333c762..b9518125 100644 --- a/src/Date/Calendar.tsx +++ b/src/Date/Calendar.tsx @@ -1,6 +1,7 @@ import { View } from 'react-native' import Swiper from './Swiper' import Month from './Month' +import Year from './Year' import { areDatesOnSameDay, dateToUnix, @@ -18,7 +19,7 @@ import { darkenBy, lightenBy, useLatest } from '../shared/utils' import { sharedStyles } from '../shared/styles' import { defaultStartYear, defaultEndYear } from './dateUtils' -export type ModeType = 'single' | 'range' | 'multiple' +export type ModeType = 'single' | 'range' | 'multiple' | 'month' export type ScrollModeType = 'horizontal' | 'vertical' @@ -80,8 +81,14 @@ export interface CalendarMultiProps extends BaseCalendarProps { onChange: MultiChange } +export interface CalendarMonthProps extends BaseCalendarProps { + mode: 'month' + date: CalendarDate + onChange: SingleChange +} + function Calendar( - props: CalendarSingleProps | CalendarRangeProps | CalendarMultiProps + props: CalendarSingleProps | CalendarRangeProps | CalendarMultiProps | CalendarMonthProps ) { const { locale, @@ -128,7 +135,7 @@ function Calendar( const onPressDate = useCallback( (d: Date) => { - if (mode === 'single') { + if (mode === 'single' || mode === 'month') { ;(onChangeRef.current as SingleChange)({ date: dateMode === 'start' ? d : getEndOfDay(d), }) @@ -174,48 +181,97 @@ function Calendar( return ( - ( - - )} - renderHeader={({ onPrev, onNext }) => ( - - )} - /> + {mode === 'month' ? ( + ( + + )} + renderHeader={({ onPrev, onNext }) => ( + + )} + /> + ) : ( + ( + + )} + renderHeader={({ onPrev, onNext }) => ( + + )} + /> + )} {scrollMode === 'horizontal' ? ( any onNext: () => any disableWeekDays?: DisableWeekDaysType + hideDays: boolean startWeekOnMonday: boolean }) { const isHorizontal = scrollMode === 'horizontal' const theme = useTheme() - return ( + return isHorizontal || !hideDays ? ( {isHorizontal ? ( @@ -62,13 +64,15 @@ function CalendarHeader({ ) : null} - + {hideDays ? null : ( + + )} - ) + ) : null } const styles = StyleSheet.create({ diff --git a/src/Date/DatePickerInput.shared.tsx b/src/Date/DatePickerInput.shared.tsx index 890ab2d5..f5b9d009 100644 --- a/src/Date/DatePickerInput.shared.tsx +++ b/src/Date/DatePickerInput.shared.tsx @@ -23,6 +23,7 @@ export type DatePickerInputProps = { uppercase?: boolean startYear?: number endYear?: number + monthOnly?: boolean onChangeText?: (text: string | undefined) => void inputEnabled?: boolean disableStatusBarPadding?: boolean diff --git a/src/Date/DatePickerInput.tsx b/src/Date/DatePickerInput.tsx index a9cefe6c..6610bdcb 100644 --- a/src/Date/DatePickerInput.tsx +++ b/src/Date/DatePickerInput.tsx @@ -14,6 +14,7 @@ function DatePickerInput( disableCalendarIcon = false, animationType = Platform.select({ web: 'none', default: 'slide' }), presentationStyle = 'overFullScreen', + monthOnly = false, ...rest }: DatePickerInputProps, ref: any @@ -37,6 +38,7 @@ function DatePickerInput( return ( ) : null} + {mode === 'month' ? ( + + ) : null} @@ -153,6 +159,38 @@ export function HeaderContentSingle({ ) } +export function HeaderContentMonth({ + state, + emptyLabel = ' ', + color, + locale, +}: HeaderContentProps & { color: string }) { + const theme = useTheme() + + const lighterColor = Color(color).fade(0.5).rgb().toString() + const dateColor = state.date + ? theme.isV3 + ? theme.colors.onSurface + : color + : lighterColor + + const formatter = useMemo(() => { + return new Intl.DateTimeFormat(locale, { + year: 'numeric', + month: 'short', + }) + }, [locale]) + + return ( + + {state.date ? formatter.format(state.date) : emptyLabel} + + ) +} + export function HeaderContentMulti({ state, emptyLabel = ' ', diff --git a/src/Date/Mon.tsx b/src/Date/Mon.tsx new file mode 100644 index 00000000..8529ea67 --- /dev/null +++ b/src/Date/Mon.tsx @@ -0,0 +1,158 @@ +import { MD2Theme, Text, TouchableRipple } from 'react-native-paper' +import { StyleSheet, View } from 'react-native' +import { daySize } from './dateUtils' + +import type { PaperTheme } from '../shared/utils' +import { memo, useCallback } from 'react' + +function Mon(props: { + theme: PaperTheme + textColorOnPrimary: string + month: number + label: string + year: number + selected: boolean + inRange: boolean + leftCrop: boolean + rightCrop: boolean + primaryColor: string + selectColor: string + thisMonth: boolean + disabled: boolean + onPressDate: (date: Date) => any +}) { + const { + month, + label, + year, + selected, + inRange, + onPressDate, + primaryColor, + selectColor, + thisMonth, + disabled, + textColorOnPrimary, + theme, + } = props + const borderColorFallback = theme.dark ? '#fff' : '#000' + const selectedOrInRangeDarkMode = selected || (inRange && theme.dark) + const v2BorderColor = selectedOrInRangeDarkMode + ? textColorOnPrimary + : borderColorFallback + const borderColor = theme.isV3 ? theme.colors.primary : v2BorderColor + + const onPress = useCallback(() => { + onPressDate(new Date(year, month, 1)) + }, [onPressDate, year, month, 1]) + + // TODO: check if this can be simplified + // converted with Chat-GPT for now from enormous conditional to if-else + let baseTextColor + let finalTextColor + + if (theme.isV3) { + // Theme V3 specific logic for base text color + if (selected) { + baseTextColor = theme.colors.onPrimary + } else if (inRange && theme.dark) { + baseTextColor = theme.colors.onPrimaryContainer + } else { + baseTextColor = theme.colors.onSurface + } + + // Theme V3 specific logic for final text color + if (thisMonth) { + finalTextColor = selected ? baseTextColor : theme.colors.primary + } else { + finalTextColor = baseTextColor + } + } else { + // Logic for themes other than V3 + if (selected || (inRange && theme.dark)) { + baseTextColor = textColorOnPrimary + } + // Since there's no additional logic provided for non-V3 themes in the step 2, + // the final text color for non-V3 themes will simply be the base text color. + finalTextColor = baseTextColor + } + + let textFont = theme?.isV3 + ? theme.fonts.bodySmall + : (theme as any as MD2Theme).fonts.medium + + return ( + + + + + {label || month} + + + + + ) +} + +const styles = StyleSheet.create({ + button: { + width: daySize, + height: daySize, + overflow: 'hidden', + borderRadius: daySize / 2, + }, + month: { + flexBasis: 0, + flex: 1, + borderRadius: daySize / 2, + width: daySize, + height: daySize, + justifyContent: 'center', + alignItems: 'center', + borderWidth: 1, + borderColor: 'transparent', + }, + disabled: { + opacity: 0.3, + }, + empty: { + flex: 1, + flexBasis: 0, + }, + root: { + flexBasis: 0, + flex: 1, + justifyContent: 'center', + alignItems: 'center', + position: 'relative', + }, +}) + +export default memo(Mon) diff --git a/src/Date/Month.tsx b/src/Date/Month.tsx index 1724d7b3..7140eb3d 100644 --- a/src/Date/Month.tsx +++ b/src/Date/Month.tsx @@ -76,7 +76,7 @@ interface MonthMultiProps extends BaseMonthProps { dates: CalendarDates } -function Month(props: MonthSingleProps | MonthRangeProps | MonthMultiProps) { +function Month(props: MonthSingleProps | MonthRangeProps | MonthMultiProps ) { const { locale, mode, diff --git a/src/Date/Swiper.native.tsx b/src/Date/Swiper.native.tsx index 2fa6f699..1751abfc 100644 --- a/src/Date/Swiper.native.tsx +++ b/src/Date/Swiper.native.tsx @@ -53,6 +53,7 @@ function SwiperInner({ width, height, startWeekOnMonday, + yearOnly, startYear, endYear, }: SwiperProps & { width: number; height: number }) { @@ -111,14 +112,14 @@ function SwiperInner({ ) const onPrev = useCallback(() => { - const newIndex = idx.current - 1 + const newIndex = idx.current - (yearOnly ? 12 : 1) if (isIndexWithinRange(newIndex, startYear, endYear)) { scrollTo(newIndex, true) } }, [scrollTo, idx, startYear, endYear]) const onNext = useCallback(() => { - const newIndex = idx.current + 1 + const newIndex = idx.current + (yearOnly ? 12 : 1) if (isIndexWithinRange(newIndex, startYear, endYear)) { scrollTo(newIndex, true) } diff --git a/src/Date/Swiper.tsx b/src/Date/Swiper.tsx index a71e0f2b..a303efdb 100644 --- a/src/Date/Swiper.tsx +++ b/src/Date/Swiper.tsx @@ -42,6 +42,7 @@ function Swiper({ selectedYear, initialIndex, startWeekOnMonday, + yearOnly, startYear, endYear, }: SwiperProps) { @@ -51,7 +52,7 @@ function Swiper({ const onPrev = useCallback(() => { setIndex((prev) => { - const newIndex = prev - 1 + const newIndex = prev - (yearOnly ? 12 : 1) // Check if the new index is within allowed range if (isIndexWithinRange(newIndex, startYear, endYear)) { return newIndex @@ -62,7 +63,7 @@ function Swiper({ const onNext = useCallback(() => { setIndex((prev) => { - const newIndex = prev + 1 + const newIndex = prev + (yearOnly ? 12 : 1) // Check if the new index is within allowed range if (isIndexWithinRange(newIndex, startYear, endYear)) { return newIndex diff --git a/src/Date/SwiperUtils.ts b/src/Date/SwiperUtils.ts index cb111703..45e73a89 100644 --- a/src/Date/SwiperUtils.ts +++ b/src/Date/SwiperUtils.ts @@ -23,6 +23,7 @@ export type SwiperProps = { renderFooter?: (renderProps: RenderProps) => any selectedYear: number | undefined startWeekOnMonday: boolean + yearOnly: boolean startYear?: number endYear?: number } diff --git a/src/Date/Year.tsx b/src/Date/Year.tsx new file mode 100644 index 00000000..bf9e9218 --- /dev/null +++ b/src/Date/Year.tsx @@ -0,0 +1,446 @@ +import { StyleSheet, View } from 'react-native' +import { + Icon, + MD2Theme, + Text, + TouchableRipple, + useTheme, +} from 'react-native-paper' +import Mon from './Mon' + +import { + addMonths, + areDatesInSameMonth, + daySize, + estimatedMonthHeight, + getGridCount, + getRealIndex, + getStartAtIndex, + getTotalMonths, + createGridCounts, + DisableWeekDaysType, + useRangeChecker, +} from './dateUtils' +import { getCalendarHeaderHeight } from './CalendarHeader' +import type { + CalendarDate, + CalendarDates, + ModeType, + ValidRangeType, +} from './Calendar' +import { dayNamesHeight } from './DayNames' +import { useTextColorOnPrimary } from '../shared/utils' +import { memo, useMemo } from 'react' +import { sharedStyles } from '../shared/styles' + +interface BaseYearProps { + locale: undefined | string + scrollMode: 'horizontal' | 'vertical' + disableWeekDays?: DisableWeekDaysType + mode: ModeType + index: number + onPressYear: (year: number) => any + selectingYear: boolean + onPressDate: (date: Date) => any + primaryColor: string + selectColor: string + roundness: number + validRange?: ValidRangeType + startWeekOnMonday: boolean + startYear?: number + endYear?: number + // some of these should be required in final implementation + startDate?: CalendarDate + endDate?: CalendarDate + date?: CalendarDate + dates?: CalendarDates +} + +interface YearMonthProps extends BaseYearProps { + mode: 'month' + date: CalendarDate +} + +function Year(props: YearMonthProps ) { + const { + locale, + mode, + index, + onPressYear, + selectingYear, + onPressDate, + primaryColor, + selectColor, + roundness, + validRange, + scrollMode, + startDate, + endDate, + date, + dates, + startWeekOnMonday, + startYear, + endYear, + } = props + const isHorizontal = scrollMode === 'horizontal' + + const theme = useTheme() + const textColorOnPrimary = useTextColorOnPrimary() + const realIndex = getRealIndex(index, startYear, endYear) + const { isDisabled, isWithinValidRange } = useRangeChecker(validRange) + + const { month, year } = useMemo(() => { + const md = addMonths(new Date(), realIndex) + const y = md.getFullYear() + const m = md.getMonth() + return { month: m, year: y } + }, [realIndex]) + + const grid = useMemo(() => { + const today = new Date() + + const formatter = new Intl.DateTimeFormat(locale, { + month: 'short', + }) + + return yearGrid().map( + ({ months, monthGrid }) => { + return { + monthIndex: monthGrid, + generatedMonths: months.map((_, index) => { + const monthOfYear = monthGrid * 3 + index + + const day = new Date(year, monthOfYear, 1) + const thisMonth = areDatesInSameMonth(day, today) + + const monthLabel = formatter.format(day) + + let inRange = false + let disabled = isDisabled(day) + let selected = false + + let leftCrop = monthOfYear === 1 + let rightCrop = monthOfYear === 12 + + selected = areDatesInSameMonth(day, date) + + const isWithinOptionalValidRange = isWithinValidRange(day) + + if (inRange && !disabled) { + disabled = false + } + + if (!isWithinOptionalValidRange) { + disabled = true + } + + return { + year, + monthOfYear, + monthLabel, + index, + mode, + selected, + inRange, + leftCrop, + rightCrop, + thisMonth, + disabled, + } + }), + } + } + ) + }, [ + year, + month, + index, + isDisabled, + mode, + isWithinValidRange, + startDate, + endDate, + dates, + date, + startWeekOnMonday, + startYear, + endYear, + locale, + ]) + + let textFont = theme?.isV3 + ? theme.fonts.titleSmall + : (theme as any as MD2Theme).fonts.medium + + const iconColor = theme.isV3 + ? theme.colors.onSurfaceVariant + : theme.colors.onSurface + + const iconSourceV3 = selectingYear ? 'menu-up' : 'menu-down' + const iconSourceV2 = selectingYear ? 'chevron-up' : 'chevron-down' + const iconSource = theme.isV3 ? iconSourceV3 : iconSourceV2 + + return ( + + + onPressYear(year) : undefined} + accessibilityRole="button" + accessibilityLabel={`${year}`} + style={[ + styles.yearButton, + { + borderRadius: roundness, + }, + ]} + > + + + {year} + + + + + + + + {grid.map(({ monthIndex, generatedMonths }) => ( + + {generatedMonths + .map((gd) => ( + + ) + )} + + ))} + + ) +} + +export const weekMargin = 6 +export const weekSize = daySize + weekMargin +export const montHeaderHeight = 56 +export const monthHeaderSingleMarginTop = 4 +export const monthHeaderSingleMarginBottom = 8 + 44 + 12 +export const monthHeaderSingleHeight = + monthHeaderSingleMarginTop + monthHeaderSingleMarginBottom + +const styles = StyleSheet.create({ + iconWrapper: { + padding: 8, + }, + monthHeader: { + height: montHeaderHeight, + justifyContent: 'center', + overflow: 'hidden', + }, + monthLabel: { + fontSize: 14, + opacity: 0.7, + }, + month: { + flexDirection: 'row', + marginBottom: weekMargin, + height: daySize, + }, + yearButton: { + alignSelf: 'flex-start', + marginLeft: 6, + }, + yearButtonInner: { + paddingLeft: 16, + flexDirection: 'row', + alignItems: 'center', + }, +}) + +const yearGrid = () => { + return Array(4) + .fill(null) + .map((_, monthGrid) => { + const months = Array(3).fill(null) + return { monthGrid, months } + }) +} + +function getIndexCount( + index: number, + startYear?: number, + endYear?: number +): number { + const dynamicStartAtIndex = getStartAtIndex(startYear, endYear) + if (index > dynamicStartAtIndex) { + return index - dynamicStartAtIndex + } + + return -(dynamicStartAtIndex - index) +} + +function weeksOffset( + index: number, + startWeekOnMonday: boolean, + startYear?: number, + endYear?: number +): number { + const dynamicStartAtIndex = getStartAtIndex(startYear, endYear) + const dynamicGridCounts = createGridCounts(getTotalMonths(startYear, endYear)) + + if (index === dynamicStartAtIndex) { + return 0 + } + let off = 0 + if (index > dynamicStartAtIndex) { + for (let i = 0; i < index - dynamicStartAtIndex; i++) { + const cIndex = dynamicStartAtIndex + i + off += + dynamicGridCounts[cIndex] || + getGridCount(cIndex, startWeekOnMonday, startYear, endYear) + } + } else { + for (let i = 0; i < dynamicStartAtIndex - index; i++) { + const cIndex = dynamicStartAtIndex - i - 1 + off -= + dynamicGridCounts[cIndex] || + getGridCount(cIndex, startWeekOnMonday, startYear, endYear) + } + } + return off +} + +export function getIndexFromHorizontalOffset( + offset: number, + width: number, + startYear?: number, + endYear?: number +): number { + const dynamicStartAtIndex = getStartAtIndex(startYear, endYear) + return dynamicStartAtIndex + Math.floor(offset / width) +} + +export function getIndexFromVerticalOffset( + offset: number, + startWeekOnMonday: boolean, + startYear?: number, + endYear?: number +): number { + const dynamicStartAtIndex = getStartAtIndex(startYear, endYear) + const dynamicBeginOffset = estimatedMonthHeight * dynamicStartAtIndex + let estimatedIndex = + dynamicStartAtIndex + Math.ceil(offset / estimatedMonthHeight) + + const realOffset = getVerticalMonthsOffset( + estimatedIndex, + startWeekOnMonday, + startYear, + endYear + ) + const difference = + (realOffset - dynamicBeginOffset - offset) / estimatedMonthHeight + if (difference >= 1 || difference <= -1) { + estimatedIndex -= Math.floor(difference) + } + return estimatedIndex +} + +export function getHorizontalMonthOffset(index: number, width: number) { + if (index < 0) { + return 0 + } + return width * index +} + +export function getVerticalMonthsOffset( + index: number, + startWeekOnMonday: boolean, + startYear?: number, + endYear?: number +) { + const count = getIndexCount(index, startYear, endYear) + const ob = weeksOffset(index, startWeekOnMonday, startYear, endYear) + const monthsHeight = weekSize * ob + const c = monthsHeight + count * (dayNamesHeight + montHeaderHeight) + const dynamicBeginOffset = + estimatedMonthHeight * getStartAtIndex(startYear, endYear) + + return (c || 0) + dynamicBeginOffset +} + +export function getMonthHeight( + scrollMode: 'horizontal' | 'vertical', + index: number, + startWeekOnMonday: boolean, + startYear?: number, + endYear?: number +): number { + const calendarHeight = getCalendarHeaderHeight(scrollMode) + const gc = getGridCount(index, startWeekOnMonday, startYear, endYear) + + const currentMonthHeight = weekSize * gc + const extraHeight = + scrollMode === 'horizontal' ? monthHeaderSingleHeight : montHeaderHeight + const c = calendarHeight + currentMonthHeight + extraHeight + return c || 0 +} + +export default memo(Year) diff --git a/src/Date/dateUtils.tsx b/src/Date/dateUtils.tsx index 32886927..23543430 100644 --- a/src/Date/dateUtils.tsx +++ b/src/Date/dateUtils.tsx @@ -122,6 +122,17 @@ export function areDatesOnSameDay(a: Date, b?: Date | null | undefined) { ) } +export function areDatesInSameMonth(a: Date, b?: Date | null | undefined) { + if (!b) { + return false + } + + return ( + a.getFullYear() === b.getFullYear() && + a.getMonth() === b.getMonth() + ) +} + export function isDateBetween( date: Date, { @@ -260,9 +271,12 @@ export function getInitialIndex( return dynamicStartAtIndex + months } -export function useInputFormatter({ locale }: { locale: string | undefined }) { +export function useInputFormatter({ locale, monthOnly }: { locale: string | undefined, monthOnly: boolean | undefined }) { return useMemo(() => { - return new Intl.DateTimeFormat(locale, { + return new Intl.DateTimeFormat(locale, monthOnly ? { + month: '2-digit', + year: 'numeric', + } : { month: '2-digit', day: '2-digit', year: 'numeric', diff --git a/src/Date/inputUtils.ts b/src/Date/inputUtils.ts index dee9d49b..d5d8fdf3 100644 --- a/src/Date/inputUtils.ts +++ b/src/Date/inputUtils.ts @@ -8,6 +8,7 @@ export default function useDateInput({ value, validRange, inputMode, + monthOnly, onChange, onValidationError, }: { @@ -16,9 +17,10 @@ export default function useDateInput({ value: Date | undefined validRange: ValidRangeType | undefined inputMode: 'start' | 'end' + monthOnly: boolean | undefined onValidationError?: ((error: string | null) => void) | undefined }) { - const formatter = useInputFormatter({ locale }) + const formatter = useInputFormatter({ locale, monthOnly }) const inputFormat = useInputFormat({ formatter, locale }) const formattedValue = value ? formatter.format(value) : '' diff --git a/src/__tests__/Date/Calendar.test.tsx b/src/__tests__/Date/Calendar.test.tsx index fdf37fd8..ebc86813 100644 --- a/src/__tests__/Date/Calendar.test.tsx +++ b/src/__tests__/Date/Calendar.test.tsx @@ -1,12 +1,15 @@ import { render } from '@testing-library/react-native' import Calendar from '../../Date/Calendar' +// 7th of August, 2025 +const fixedDate = new Date(2025, 7, 7) + it('renders Calendar', () => { const { toJSON } = render( null} /> ) diff --git a/src/__tests__/Date/CalendarEdit.test.tsx b/src/__tests__/Date/CalendarEdit.test.tsx index c3f6daa5..bbef5111 100644 --- a/src/__tests__/Date/CalendarEdit.test.tsx +++ b/src/__tests__/Date/CalendarEdit.test.tsx @@ -1,15 +1,18 @@ import { render } from '@testing-library/react-native' import CalendarEdit from '../../Date/CalendarEdit' +// 7th of August, 2025 +const fixedDate = new Date(2025, 7, 7) + it('renders CalendarEdit', () => { const { toJSON } = render( null} diff --git a/src/__tests__/Date/CalendarHeader.test.tsx b/src/__tests__/Date/CalendarHeader.test.tsx index 83b2736c..a08c1a01 100644 --- a/src/__tests__/Date/CalendarHeader.test.tsx +++ b/src/__tests__/Date/CalendarHeader.test.tsx @@ -8,6 +8,7 @@ it('renders CalendarHeader', () => { onPrev={() => null} onNext={() => null} scrollMode="vertical" + hideDays={false} startWeekOnMonday={false} /> ) diff --git a/src/__tests__/Date/DatePickerInput.test.tsx b/src/__tests__/Date/DatePickerInput.test.tsx index b1701246..cfb0290f 100644 --- a/src/__tests__/Date/DatePickerInput.test.tsx +++ b/src/__tests__/Date/DatePickerInput.test.tsx @@ -1,11 +1,14 @@ import { render } from '@testing-library/react-native' import DatePickerInput from '../../Date/DatePickerInput' +// 7th of August, 2025 +const fixedDate = new Date(2025, 7, 7) + it('renders DatePickerInput', () => { const { toJSON } = render( null} inputMode="start" /> diff --git a/src/__tests__/Date/DatePickerInputWithoutModal.test.tsx b/src/__tests__/Date/DatePickerInputWithoutModal.test.tsx index d4d56ffa..3fbae1fe 100644 --- a/src/__tests__/Date/DatePickerInputWithoutModal.test.tsx +++ b/src/__tests__/Date/DatePickerInputWithoutModal.test.tsx @@ -1,11 +1,14 @@ import { render } from '@testing-library/react-native' import DatePickerInputWithoutModal from '../../Date/DatePickerInputWithoutModal' +// 7th of August, 2025 +const fixedDate = new Date(2025, 7, 7) + it('renders DatePickerInputWithoutModal', () => { const { toJSON } = render( null} inputMode="start" /> diff --git a/src/__tests__/Date/DatePickerModalContent.test.tsx b/src/__tests__/Date/DatePickerModalContent.test.tsx index d2a27666..fe69d645 100644 --- a/src/__tests__/Date/DatePickerModalContent.test.tsx +++ b/src/__tests__/Date/DatePickerModalContent.test.tsx @@ -2,13 +2,16 @@ import { render } from '@testing-library/react-native' import { SafeAreaProvider } from 'react-native-safe-area-context' import DatePickerModalContent from '../../Date/DatePickerModalContent' +// 7th of August, 2025 +const fixedDate = new Date(2025, 7, 7) + it('renders DatePickerModalContent', () => { const { toJSON } = render( null} onConfirm={() => null} /> diff --git a/src/index.tsx b/src/index.tsx index 502482f9..4e53b78e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,6 @@ export { default as Calendar } from './Date/Calendar' export { default as DatePickerModal } from './Date/DatePickerModal' -export type { - DatePickerModalSingleProps, - DatePickerModalMultiProps, - DatePickerModalRangeProps, -} from './Date/DatePickerModal' +export * from './Date/DatePickerModal' export { default as DatePickerModalContent } from './Date/DatePickerModalContent' export { default as TimePickerModal } from './Time/TimePickerModal' export { default as TimePicker } from './Time/TimePicker' diff --git a/src/translations/ar.ts b/src/translations/ar.ts index 209b56e8..6c94597f 100644 --- a/src/translations/ar.ts +++ b/src/translations/ar.ts @@ -5,6 +5,7 @@ const ar: TranslationsType = { selectSingle: 'حدد تاريخ', selectMultiple: 'حدد التواريخ', selectRange: 'حدد الفترة', + selectMonth: 'حدد شهرًا', notAccordingToDateFormat: (inputFormat: string) => `يجب أن يكون تنسيق التاريخ ${inputFormat}`, mustBeHigherThan: (date) => `يجب أن يكون بعد ${date}`, diff --git a/src/translations/ca.ts b/src/translations/ca.ts index 7e108476..b1e6b8fe 100644 --- a/src/translations/ca.ts +++ b/src/translations/ca.ts @@ -5,6 +5,7 @@ const ca: TranslationsType = { selectSingle: 'Seleccionar data', selectMultiple: 'Seleccionar dates', selectRange: 'Seleccionar període', + selectMonth: 'Seleccionar un mes', notAccordingToDateFormat: (inputFormat) => `El format de la data ha de ser ${inputFormat}`, mustBeHigherThan: (date) => `Ha de ser posterior a ${date}`, diff --git a/src/translations/cs.ts b/src/translations/cs.ts index 48e0e43d..4934c183 100644 --- a/src/translations/cs.ts +++ b/src/translations/cs.ts @@ -5,6 +5,7 @@ const cs: TranslationsType = { selectSingle: 'Vyberte datum', selectMultiple: 'Vyberte data', selectRange: 'Vyberte období', + selectMonth: 'Vyberte měsíc', notAccordingToDateFormat: (inputFormat) => `Formát data musí být ${inputFormat}`, mustBeHigherThan: (date) => `Musí to být později ${date}`, diff --git a/src/translations/de.ts b/src/translations/de.ts index 07b85452..74db2997 100644 --- a/src/translations/de.ts +++ b/src/translations/de.ts @@ -5,6 +5,7 @@ const de: TranslationsType = { selectSingle: 'Wähle Datum', selectMultiple: 'Wähle Daten', selectRange: 'Wähle Zeitspanne', + selectMonth: 'Wähle einen Monat aus', notAccordingToDateFormat: (inputFormat) => `Das Format sollte ${inputFormat} sein`, mustBeHigherThan: (date) => `Muss nach dem ${date} sein`, diff --git a/src/translations/el.ts b/src/translations/el.ts index 3d783b0c..e16c6899 100644 --- a/src/translations/el.ts +++ b/src/translations/el.ts @@ -5,6 +5,7 @@ const el: TranslationsType = { selectSingle: 'Επιλέξτε ημερομηνία', selectMultiple: 'Επιλέξτε ημερομηνίες', selectRange: 'Επιλέξτε περίοδο', + selectMonth: 'Επιλέξτε έναν μήνα', notAccordingToDateFormat: (inputFormat) => `Η μορφή ημερομηνίας πρέπει να είναι ${inputFormat}`, mustBeHigherThan: (date) => `Πρέπει να είναι αργότερα${date}`, diff --git a/src/translations/en.ts b/src/translations/en.ts index e5b2826e..3905e542 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -5,6 +5,7 @@ const en: TranslationsType = { selectSingle: 'Select date', selectMultiple: 'Select dates', selectRange: 'Select period', + selectMonth: 'Select month', notAccordingToDateFormat: (inputFormat) => `Date format must be ${inputFormat}`, mustBeHigherThan: (date) => `Must be later than ${date}`, diff --git a/src/translations/enGB.ts b/src/translations/enGB.ts index e6843111..957a3441 100644 --- a/src/translations/enGB.ts +++ b/src/translations/enGB.ts @@ -5,6 +5,7 @@ const enGB: TranslationsType = { selectSingle: 'Select date', selectMultiple: 'Select dates', selectRange: 'Select period', + selectMonth: 'Select month', notAccordingToDateFormat: (inputFormat) => `Date format must be ${inputFormat}`, mustBeHigherThan: (date) => `Must be later than ${date}`, diff --git a/src/translations/es.ts b/src/translations/es.ts index f0f30a01..31890399 100644 --- a/src/translations/es.ts +++ b/src/translations/es.ts @@ -5,6 +5,7 @@ const es: TranslationsType = { selectSingle: 'Seleccionar fecha', selectMultiple: 'Seleccionar fechas', selectRange: 'Seleccionar periodo', + selectMonth: 'Seleccionar un mes', notAccordingToDateFormat: (inputFormat) => `Formato de fecha debe ser ${inputFormat}`, mustBeHigherThan: (date) => `Debe ser posterior a ${date}`, diff --git a/src/translations/fr.ts b/src/translations/fr.ts index 7119b646..075ba2c2 100644 --- a/src/translations/fr.ts +++ b/src/translations/fr.ts @@ -5,6 +5,7 @@ const fr: TranslationsType = { selectSingle: 'Sélectionner une date', selectMultiple: 'Sélectionner plusieurs dates', selectRange: 'Sélectionner une période', + selectMonth: 'Sélectionner un mois', notAccordingToDateFormat: (inputFormat) => `La date doit être au format ${inputFormat}`, mustBeHigherThan: (date) => `La date doit être après le ${date}`, diff --git a/src/translations/he.ts b/src/translations/he.ts index c16316bd..d3f1d63e 100644 --- a/src/translations/he.ts +++ b/src/translations/he.ts @@ -5,6 +5,7 @@ const he: TranslationsType = { selectSingle: 'בחר תאריך', selectMultiple: 'בחר תאריכים', selectRange: 'בחר טווח', + selectMonth: 'בחר חודש', notAccordingToDateFormat: (inputFormat) => `פורמט של תאריך צריך להיות ${inputFormat}`, mustBeHigherThan: (date) => `חייב להיות אחרי ${date}`, diff --git a/src/translations/hi.ts b/src/translations/hi.ts index 78d72d94..2f54c615 100644 --- a/src/translations/hi.ts +++ b/src/translations/hi.ts @@ -5,6 +5,7 @@ const hi: TranslationsType = { selectSingle: 'तारीख़ चुनें', selectMultiple: 'तारीख़ें चुनें', selectRange: 'अवधि चुनें', + selectMonth: 'एक महीने का चयन करें', notAccordingToDateFormat: (inputFormat) => `तारीख़ का प्रारूप ${inputFormat} होना चाहिए`, mustBeHigherThan: (date) => `${date} के बाद होना चाहिए`, diff --git a/src/translations/id.ts b/src/translations/id.ts index 40fd1fdb..41619299 100644 --- a/src/translations/id.ts +++ b/src/translations/id.ts @@ -5,6 +5,7 @@ const id: TranslationsType = { selectSingle: 'Pilih tanggal', selectMultiple: 'Pilih tanggal', selectRange: 'Pilih periode', + selectMonth: 'Pilih bulan', notAccordingToDateFormat: (inputFormat) => `Format tanggal harus ${inputFormat}`, mustBeHigherThan: (date) => `Harus lebih besar dari ${date}`, diff --git a/src/translations/it.ts b/src/translations/it.ts index 13f08fc6..8ff07fc3 100644 --- a/src/translations/it.ts +++ b/src/translations/it.ts @@ -5,6 +5,7 @@ const it: TranslationsType = { selectSingle: 'Seleziona la data', selectMultiple: 'Seleziona le date', selectRange: 'Seleziona il periodo', + selectMonth: 'Seleziona un mese', notAccordingToDateFormat: (inputFormat) => `Il formato della data deve essere ${inputFormat}`, mustBeHigherThan: (date) => `Deve essere successivo a ${date}`, diff --git a/src/translations/ja.ts b/src/translations/ja.ts index e8514a2c..6cab8596 100644 --- a/src/translations/ja.ts +++ b/src/translations/ja.ts @@ -5,6 +5,7 @@ const ja: TranslationsType = { selectSingle: '日付を選択', selectMultiple: '日付を選択', selectRange: '期間を選択', + selectMonth: '月を選択', notAccordingToDateFormat: (inputFormat) => `形式が無効です。使用:${inputFormat}`, mustBeHigherThan: (date) => `${date}より後にする必要があります`, diff --git a/src/translations/ko.ts b/src/translations/ko.ts index 7d1eea40..6cee0d53 100644 --- a/src/translations/ko.ts +++ b/src/translations/ko.ts @@ -5,6 +5,7 @@ const ko: TranslationsType = { selectSingle: '날짜 선택', selectMultiple: '여러 날짜 선택', selectRange: '기간 선택', + selectMonth: '월을 선택하세요', notAccordingToDateFormat: (inputFormat) => `날짜 형식은 ${inputFormat}가 되어야 합니다`, mustBeHigherThan: (date) => `${date} 보다 커야 합니다`, diff --git a/src/translations/nl.ts b/src/translations/nl.ts index f97aec02..2afde5a9 100644 --- a/src/translations/nl.ts +++ b/src/translations/nl.ts @@ -5,6 +5,7 @@ const nl: TranslationsType = { selectSingle: 'Selecteer datum', selectMultiple: 'Selecteer datums', selectRange: 'Selecteer periode', + selectMonth: 'Selecteer een maand', notAccordingToDateFormat: (inputFormat) => `Datumformaat moet ${inputFormat} zijn`, mustBeHigherThan: (date) => `Moet later dan ${date}`, diff --git a/src/translations/noNO.ts b/src/translations/noNO.ts index e40f7f97..b7b9e1d1 100644 --- a/src/translations/noNO.ts +++ b/src/translations/noNO.ts @@ -5,6 +5,7 @@ const noNO: TranslationsType = { selectSingle: 'Velg dato', selectMultiple: 'Velg datoer', selectRange: 'Velg periode', + selectMonth: 'Velg en måned', notAccordingToDateFormat: (inputFormat) => `Datoformatet må være ${inputFormat}`, mustBeHigherThan: (date) => `Må være senere enn ${date}`, diff --git a/src/translations/pl.ts b/src/translations/pl.ts index fc8dfc36..f12bf264 100644 --- a/src/translations/pl.ts +++ b/src/translations/pl.ts @@ -5,6 +5,7 @@ const pl: TranslationsType = { selectSingle: 'Wybierz datę', selectMultiple: 'Wybierz daty', selectRange: 'Wybierz zakres', + selectMonth: 'Wybierz miesiąc', notAccordingToDateFormat: (inputFormat) => `Data musi mieć format ${inputFormat}`, mustBeHigherThan: (date) => `Nie wcześniej niżn ${date}`, diff --git a/src/translations/pt.ts b/src/translations/pt.ts index dc2d3f0a..e4171be5 100644 --- a/src/translations/pt.ts +++ b/src/translations/pt.ts @@ -5,6 +5,7 @@ const pt: TranslationsType = { selectSingle: 'Selecione a data', selectMultiple: 'Selecione as datas', selectRange: 'Selecione o período', + selectMonth: 'Selecione um mês', notAccordingToDateFormat: (inputFormat: string) => `O formato da data deve ser ${inputFormat}`, mustBeHigherThan: (date) => `Deve ser depois de ${date}`, diff --git a/src/translations/ro.ts b/src/translations/ro.ts index 11980212..9efe884f 100644 --- a/src/translations/ro.ts +++ b/src/translations/ro.ts @@ -5,6 +5,7 @@ const ro: TranslationsType = { selectSingle: 'Selectează data', selectMultiple: 'Selectează datele', selectRange: 'Selectează perioada', + selectMonth: 'Selectează lună', notAccordingToDateFormat: (inputFormat) => `Formatul datei trebuie să fie ${inputFormat}`, mustBeHigherThan: (date) => `Trebuie să fie mai târziu de ${date}`, diff --git a/src/translations/ru.ts b/src/translations/ru.ts index ce86e3df..01d9c49e 100644 --- a/src/translations/ru.ts +++ b/src/translations/ru.ts @@ -5,6 +5,7 @@ const ru: TranslationsType = { selectSingle: 'Выбор даты', selectMultiple: 'Выбор дат', selectRange: 'Выбор диапазона', + selectMonth: 'Выбор месяц', notAccordingToDateFormat: (inputFormat) => `Формат даты должен быть ${inputFormat}`, mustBeHigherThan: (date) => `Должен быть позже, чем ${date}`, diff --git a/src/translations/sv.ts b/src/translations/sv.ts index b9aa6408..78184e4d 100644 --- a/src/translations/sv.ts +++ b/src/translations/sv.ts @@ -5,6 +5,7 @@ const sv: TranslationsType = { selectSingle: 'Välj datum', selectMultiple: 'Välj datum', selectRange: 'Välj period', + selectMonth: 'Välj månad', notAccordingToDateFormat: (inputFormat) => `Datum måste vara i formatet ${inputFormat}`, mustBeHigherThan: (date) => `Måste vara efter ${date}`, diff --git a/src/translations/th.ts b/src/translations/th.ts index 32f5a0a3..290287ad 100644 --- a/src/translations/th.ts +++ b/src/translations/th.ts @@ -5,6 +5,7 @@ const th: TranslationsType = { selectSingle: 'เลือกวันที่', selectMultiple: 'เลือกวันที่', selectRange: 'เลือกช่วงวันที่', + selectMonth: 'เลือกเดือน', notAccordingToDateFormat: (inputFormat) => `รูปแบบวันที่จะต้องเป็น ${inputFormat}`, mustBeHigherThan: (date) => `ต้องอยู่ภายหลังวันที่ ${date}`, diff --git a/src/translations/tr.ts b/src/translations/tr.ts index c5a52da1..fea93aa1 100644 --- a/src/translations/tr.ts +++ b/src/translations/tr.ts @@ -5,6 +5,7 @@ const tr: TranslationsType = { selectSingle: 'Tarih seç', selectMultiple: 'Tarihleri seç', selectRange: 'Periyot seç', + selectMonth: 'Bir ay seç', notAccordingToDateFormat: (inputFormat) => `Tarih formatı ${inputFormat} olmalı`, mustBeHigherThan: (date) => `${date} tarihinden sonra olmalı`, diff --git a/src/translations/ukUA.ts b/src/translations/ukUA.ts index 0dc12c38..b342d595 100644 --- a/src/translations/ukUA.ts +++ b/src/translations/ukUA.ts @@ -5,6 +5,7 @@ const ukUA: TranslationsType = { selectSingle: 'Оберіть дату', selectMultiple: 'Оберіть дати', selectRange: 'Оберіть період', + selectMonth: 'Оберіть місяць', notAccordingToDateFormat: (inputFormat: string) => `Формат дати має бути ${inputFormat}`, mustBeHigherThan: (date: string) => `Має бути пізніше ніж ${date}`, diff --git a/src/translations/utils.ts b/src/translations/utils.ts index bf5e8d44..d28104e7 100644 --- a/src/translations/utils.ts +++ b/src/translations/utils.ts @@ -2,6 +2,7 @@ export type TranslationsType = { selectSingle: string selectMultiple: string selectRange: string + selectMonth: string save: string notAccordingToDateFormat: (inputFormat: string) => string mustBeHigherThan: (date: string) => string diff --git a/src/translations/zh.ts b/src/translations/zh.ts index 3290d7f1..3a08a834 100644 --- a/src/translations/zh.ts +++ b/src/translations/zh.ts @@ -5,6 +5,7 @@ const zh: TranslationsType = { selectSingle: '选择日期', selectMultiple: '选择多个日期', selectRange: '选择期间', + selectMonth: '选择月份', notAccordingToDateFormat: (inputFormat) => `日期格式必须是 ${inputFormat}`, mustBeHigherThan: (date) => `必须晚于 ${date}`, mustBeLowerThan: (date) => `必须早于 ${date}`, diff --git a/src/translations/zhTW.ts b/src/translations/zhTW.ts index d3fbb848..20e7d00e 100644 --- a/src/translations/zhTW.ts +++ b/src/translations/zhTW.ts @@ -5,6 +5,7 @@ const zhTW: TranslationsType = { selectSingle: '選擇日期', selectMultiple: '選擇多個日期', selectRange: '選擇期間', + selectMonth: '選擇月份', notAccordingToDateFormat: (inputFormat) => `日期格式必須是 ${inputFormat}`, mustBeHigherThan: (date) => `必須晚於 ${date}`, mustBeLowerThan: (date) => `必須早於 ${date}`,