Skip to content

Commit b135125

Browse files
Merge pull request #49 from nandorojo/valid-range
Add date range support
2 parents 377f886 + c9f809e commit b135125

File tree

8 files changed

+113
-14471
lines changed

8 files changed

+113
-14471
lines changed

example/src/App.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ function App({
246246
{dates
247247
?.map((date) => date && dateFormatter.format(date))
248248
.filter(Boolean)
249-
.join(', ')}
249+
.join(', ') ?? '-'}
250250
</Text>
251251
</Row>
252252
</View>
@@ -377,6 +377,9 @@ function App({
377377
visible={multiOpen}
378378
onDismiss={onDismissMulti}
379379
dates={dates}
380+
validRange={{
381+
startDate: new Date(),
382+
}}
380383
onConfirm={onChangeMulti}
381384
// moreLabel="more" // optional, if multiple are selected this will show if we can't show all dates
382385
// onChange={onChangeMulti}

example/yarn.lock

Lines changed: 0 additions & 14463 deletions
Large diffs are not rendered by default.

src/Date/Calendar.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
dateToUnix,
1010
DisableWeekDaysType,
1111
getInitialIndex,
12+
isDateWithinOptionalRange,
1213
} from './dateUtils'
1314

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

2324
export type ScrollModeType = 'horizontal' | 'vertical'
2425

26+
export type ValidRangeType = {
27+
startDate?: Date
28+
endDate?: Date
29+
}
30+
2531
export type BaseCalendarProps = {
2632
locale?: undefined | string
2733
disableWeekDays?: DisableWeekDaysType
34+
validRange?: ValidRangeType
2835
}
2936

3037
export type CalendarDate = Date | undefined
@@ -95,6 +102,7 @@ function Calendar(
95102
disableWeekDays,
96103
// @ts-ignore
97104
dates,
105+
validRange,
98106
} = props
99107

100108
const theme = useTheme()
@@ -130,9 +138,20 @@ function Calendar(
130138
RangeChange | SingleChange | ExcludeInRangeChange | MultiChange
131139
>(onChange)
132140
const datesRef = useLatest<Date[]>(dates)
141+
const validRangeStart = useLatest(validRange?.startDate)
142+
const validRangeEnd = useLatest(validRange?.endDate)
133143

134144
const onPressDate = useCallback(
135145
(d: Date) => {
146+
const isWithinValidRange = isDateWithinOptionalRange(d, {
147+
startDate: validRangeStart.current,
148+
endDate: validRangeEnd.current,
149+
})
150+
151+
if (!isWithinValidRange) {
152+
return
153+
}
154+
136155
if (mode === 'single') {
137156
;(onChangeRef.current as SingleChange)({
138157
date: d,
@@ -180,7 +199,16 @@ function Calendar(
180199
})
181200
}
182201
},
183-
[mode, onChangeRef, startDateRef, endDateRef, excludedDatesRef, datesRef]
202+
[
203+
validRangeStart,
204+
validRangeEnd,
205+
mode,
206+
onChangeRef,
207+
startDateRef,
208+
endDateRef,
209+
excludedDatesRef,
210+
datesRef,
211+
]
184212
)
185213

186214
return (
@@ -194,6 +222,7 @@ function Calendar(
194222
locale={locale}
195223
mode={mode}
196224
key={index}
225+
validRange={validRange}
197226
index={index}
198227
startDate={startDate}
199228
endDate={endDate}

src/Date/DatePickerModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@ interface DatePickerModalProps {
2727
disableStatusBarPadding?: boolean
2828
}
2929

30-
interface DatePickerModalSingleProps
30+
export interface DatePickerModalSingleProps
3131
extends DatePickerModalContentSingleProps,
3232
DatePickerModalProps {}
3333

34-
interface DatePickerModalMultiProps
34+
export interface DatePickerModalMultiProps
3535
extends DatePickerModalContentMultiProps,
3636
DatePickerModalProps {}
3737

38-
interface DatePickerModalRangeProps
38+
export interface DatePickerModalRangeProps
3939
extends DatePickerModalContentRangeProps,
4040
DatePickerModalProps {}
4141

42-
interface DatePickerModalExcludeInRangeProps
42+
export interface DatePickerModalExcludeInRangeProps
4343
extends DatePickerModalContentExcludeInRangeProps,
4444
DatePickerModalProps {}
4545

src/Date/DatePickerModalContent.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function DatePickerModalContent(
9191
disableSafeTop,
9292
disableWeekDays,
9393
locale,
94+
validRange,
9495
} = props
9596

9697
const anyProps = props as any
@@ -193,6 +194,7 @@ export function DatePickerModalContent(
193194
onChange={onInnerChange}
194195
disableWeekDays={disableWeekDays}
195196
dates={state.dates}
197+
validRange={validRange}
196198
/>
197199
}
198200
calendarEdit={

src/Date/Month.tsx

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ import {
1818
startAtIndex,
1919
beginOffset,
2020
estimatedMonthHeight,
21+
isDateWithinOptionalRange,
2122
} from './dateUtils'
2223
import { getCalendarHeaderHeight } from './CalendarHeader'
23-
import { ModeType } from './Calendar'
24+
import type { ModeType, ValidRangeType } from './Calendar'
2425
import { dayNamesHeight } from './DayNames'
2526
import { useTextColorOnPrimary } from '../utils'
2627

@@ -40,6 +41,7 @@ interface BaseMonthProps {
4041
primaryColor: string
4142
selectColor: string
4243
roundness: number
44+
validRange?: ValidRangeType
4345
}
4446

4547
interface MonthRangeProps extends BaseMonthProps {
@@ -82,6 +84,7 @@ function Month({
8284
locale,
8385
// @ts-ignore
8486
dates,
87+
validRange,
8588
}:
8689
| MonthSingleProps
8790
| MonthRangeProps
@@ -92,6 +95,16 @@ function Month({
9295
const realIndex = getRealIndex(index)
9396
const isHorizontal = scrollMode === 'horizontal'
9497

98+
const validRangeStart =
99+
validRange?.startDate instanceof Date
100+
? validRange?.startDate?.toISOString()
101+
: null
102+
103+
const validRangeEnd =
104+
validRange?.endDate instanceof Date
105+
? validRange?.endDate?.toISOString()
106+
: null
107+
95108
const { monthName, month, year } = React.useMemo(() => {
96109
const md = addMonths(new Date(), realIndex)
97110
const y = md.getFullYear()
@@ -125,6 +138,11 @@ function Month({
125138
const selectedEndDay = areDatesOnSameDay(day, endDate)
126139
const selectedDay = areDatesOnSameDay(day, date)
127140

141+
const isWithinOptionalValidRange = isDateWithinOptionalRange(day, {
142+
startDate: validRangeStart ? new Date(validRangeStart) : undefined,
143+
endDate: validRangeEnd ? new Date(validRangeEnd) : undefined,
144+
})
145+
128146
const multiDates = dates as Date[] | undefined
129147

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

172+
if (!isWithinOptionalValidRange) {
173+
disabled = true
174+
}
175+
154176
let leftCrop: boolean = selectedStartDay || dayOfMonth === 1
155177
let rightCrop: boolean = selectedEndDay || dayOfMonth === daysInMonth
156178

@@ -208,7 +230,19 @@ function Month({
208230
}),
209231
}
210232
})
211-
}, [year, month, index, startDate, endDate, date, dates, mode, excludedDates])
233+
}, [
234+
year,
235+
month,
236+
index,
237+
startDate,
238+
endDate,
239+
date,
240+
validRangeStart,
241+
validRangeEnd,
242+
dates,
243+
mode,
244+
excludedDates,
245+
])
212246

213247
return (
214248
<View style={[styles.month, { height: getMonthHeight(scrollMode, index) }]}>

src/Date/dateUtils.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,42 @@ export function isDateBetween(
9999
return date <= endDate && date >= startDate
100100
}
101101

102+
/**
103+
* Check if a date is within an optional range.
104+
*
105+
* If the range doesn't exist, it defaults to `true`.
106+
*/
107+
export function isDateWithinOptionalRange(
108+
date: Date,
109+
{
110+
startDate,
111+
endDate,
112+
}: { startDate?: Date | null | undefined; endDate?: Date | null | undefined }
113+
) {
114+
if (startDate) {
115+
// if we're on the same day, we're within the valid range (inclusive)
116+
const isSameDay = areDatesOnSameDay(startDate, date)
117+
const isBeforeMinDate = date < startDate
118+
119+
if (!isSameDay && isBeforeMinDate) {
120+
// disable the selection
121+
return false
122+
}
123+
}
124+
125+
if (endDate) {
126+
// if we're on the same day, we're within the valid range (inclusive)
127+
const isSameDay = areDatesOnSameDay(endDate, date)
128+
const isAfterMaxDate = date > endDate
129+
130+
if (!isSameDay && isAfterMaxDate) {
131+
return false
132+
}
133+
}
134+
135+
return true
136+
}
137+
102138
export function isLeapYear({ year }: { year: number }) {
103139
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
104140
}

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { default as Calendar } from './Date/Calendar'
22
export { default as DatePickerModal } from './Date/DatePickerModal'
3+
export * from './Date/DatePickerModal'
34
export { default as DatePickerModalContent } from './Date/DatePickerModalContent'
45
export { default as TimePickerModal } from './Time/TimePickerModal'
56
export { default as TimePicker } from './Time/TimePicker'

0 commit comments

Comments
 (0)