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}`,