Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ function App({
{dates
?.map((date) => date && dateFormatter.format(date))
.filter(Boolean)
.join(', ')}
.join(', ') ?? '-'}
</Text>
</Row>
</View>
Expand Down Expand Up @@ -377,6 +377,9 @@ function App({
visible={multiOpen}
onDismiss={onDismissMulti}
dates={dates}
validRange={{
startDate: new Date(),
}}
onConfirm={onChangeMulti}
// moreLabel="more" // optional, if multiple are selected this will show if we can't show all dates
// onChange={onChangeMulti}
Expand Down
14,463 changes: 0 additions & 14,463 deletions example/yarn.lock

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion src/Date/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
dateToUnix,
DisableWeekDaysType,
getInitialIndex,
isDateWithinOptionalRange,
} from './dateUtils'

import CalendarHeader from './CalendarHeader'
Expand All @@ -22,9 +23,15 @@ export type ModeType = 'single' | 'range' | 'excludeInRange' | 'multiple'

export type ScrollModeType = 'horizontal' | 'vertical'

export type ValidRangeType = {
startDate?: Date
endDate?: Date
}

export type BaseCalendarProps = {
locale?: undefined | string
disableWeekDays?: DisableWeekDaysType
validRange?: ValidRangeType
}

export type CalendarDate = Date | undefined
Expand Down Expand Up @@ -95,6 +102,7 @@ function Calendar(
disableWeekDays,
// @ts-ignore
dates,
validRange,
} = props

const theme = useTheme()
Expand Down Expand Up @@ -130,9 +138,20 @@ function Calendar(
RangeChange | SingleChange | ExcludeInRangeChange | MultiChange
>(onChange)
const datesRef = useLatest<Date[]>(dates)
const validRangeStart = useLatest(validRange?.startDate)
const validRangeEnd = useLatest(validRange?.endDate)

const onPressDate = useCallback(
(d: Date) => {
const isWithinValidRange = isDateWithinOptionalRange(d, {
startDate: validRangeStart.current,
endDate: validRangeEnd.current,
})

if (!isWithinValidRange) {
return
}

if (mode === 'single') {
;(onChangeRef.current as SingleChange)({
date: d,
Expand Down Expand Up @@ -180,7 +199,16 @@ function Calendar(
})
}
},
[mode, onChangeRef, startDateRef, endDateRef, excludedDatesRef, datesRef]
[
validRangeStart,
validRangeEnd,
mode,
onChangeRef,
startDateRef,
endDateRef,
excludedDatesRef,
datesRef,
]
)

return (
Expand All @@ -194,6 +222,7 @@ function Calendar(
locale={locale}
mode={mode}
key={index}
validRange={validRange}
index={index}
startDate={startDate}
endDate={endDate}
Expand Down
8 changes: 4 additions & 4 deletions src/Date/DatePickerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ interface DatePickerModalProps {
disableStatusBarPadding?: boolean
}

interface DatePickerModalSingleProps
export interface DatePickerModalSingleProps
extends DatePickerModalContentSingleProps,
DatePickerModalProps {}

interface DatePickerModalMultiProps
export interface DatePickerModalMultiProps
extends DatePickerModalContentMultiProps,
DatePickerModalProps {}

interface DatePickerModalRangeProps
export interface DatePickerModalRangeProps
extends DatePickerModalContentRangeProps,
DatePickerModalProps {}

interface DatePickerModalExcludeInRangeProps
export interface DatePickerModalExcludeInRangeProps
extends DatePickerModalContentExcludeInRangeProps,
DatePickerModalProps {}

Expand Down
2 changes: 2 additions & 0 deletions src/Date/DatePickerModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export function DatePickerModalContent(
disableSafeTop,
disableWeekDays,
locale,
validRange,
} = props

const anyProps = props as any
Expand Down Expand Up @@ -193,6 +194,7 @@ export function DatePickerModalContent(
onChange={onInnerChange}
disableWeekDays={disableWeekDays}
dates={state.dates}
validRange={validRange}
/>
}
calendarEdit={
Expand Down
38 changes: 36 additions & 2 deletions src/Date/Month.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ import {
startAtIndex,
beginOffset,
estimatedMonthHeight,
isDateWithinOptionalRange,
} from './dateUtils'
import { getCalendarHeaderHeight } from './CalendarHeader'
import { ModeType } from './Calendar'
import type { ModeType, ValidRangeType } from './Calendar'
import { dayNamesHeight } from './DayNames'
import { useTextColorOnPrimary } from '../utils'

Expand All @@ -40,6 +41,7 @@ interface BaseMonthProps {
primaryColor: string
selectColor: string
roundness: number
validRange?: ValidRangeType
}

interface MonthRangeProps extends BaseMonthProps {
Expand Down Expand Up @@ -82,6 +84,7 @@ function Month({
locale,
// @ts-ignore
dates,
validRange,
}:
| MonthSingleProps
| MonthRangeProps
Expand All @@ -92,6 +95,16 @@ function Month({
const realIndex = getRealIndex(index)
const isHorizontal = scrollMode === 'horizontal'

const validRangeStart =
validRange?.startDate instanceof Date
? validRange?.startDate?.toISOString()
: null

const validRangeEnd =
validRange?.endDate instanceof Date
? validRange?.endDate?.toISOString()
: null

const { monthName, month, year } = React.useMemo(() => {
const md = addMonths(new Date(), realIndex)
const y = md.getFullYear()
Expand Down Expand Up @@ -125,6 +138,11 @@ function Month({
const selectedEndDay = areDatesOnSameDay(day, endDate)
const selectedDay = areDatesOnSameDay(day, date)

const isWithinOptionalValidRange = isDateWithinOptionalRange(day, {
startDate: validRangeStart ? new Date(validRangeStart) : undefined,
endDate: validRangeEnd ? new Date(validRangeEnd) : undefined,
})

const multiDates = dates as Date[] | undefined

const selectedMultiDay = !!multiDates?.some((d) =>
Expand All @@ -151,6 +169,10 @@ function Month({
disabled = false
}

if (!isWithinOptionalValidRange) {
disabled = true
}

let leftCrop: boolean = selectedStartDay || dayOfMonth === 1
let rightCrop: boolean = selectedEndDay || dayOfMonth === daysInMonth

Expand Down Expand Up @@ -208,7 +230,19 @@ function Month({
}),
}
})
}, [year, month, index, startDate, endDate, date, dates, mode, excludedDates])
}, [
year,
month,
index,
startDate,
endDate,
date,
validRangeStart,
validRangeEnd,
dates,
mode,
excludedDates,
])

return (
<View style={[styles.month, { height: getMonthHeight(scrollMode, index) }]}>
Expand Down
36 changes: 36 additions & 0 deletions src/Date/dateUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,42 @@ export function isDateBetween(
return date <= endDate && date >= startDate
}

/**
* Check if a date is within an optional range.
*
* If the range doesn't exist, it defaults to `true`.
*/
export function isDateWithinOptionalRange(
date: Date,
{
startDate,
endDate,
}: { startDate?: Date | null | undefined; endDate?: Date | null | undefined }
) {
if (startDate) {
// if we're on the same day, we're within the valid range (inclusive)
const isSameDay = areDatesOnSameDay(startDate, date)
const isBeforeMinDate = date < startDate

if (!isSameDay && isBeforeMinDate) {
// disable the selection
return false
}
}

if (endDate) {
// if we're on the same day, we're within the valid range (inclusive)
const isSameDay = areDatesOnSameDay(endDate, date)
const isAfterMaxDate = date > endDate

if (!isSameDay && isAfterMaxDate) {
return false
}
}

return true
}

export function isLeapYear({ year }: { year: number }) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
}
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as Calendar } from './Date/Calendar'
export { default as DatePickerModal } 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'