Skip to content

Commit 65f1072

Browse files
Add excludeInRange mode
1 parent 6a76be5 commit 65f1072

File tree

11 files changed

+410
-117
lines changed

11 files changed

+410
-117
lines changed

example/src/App.tsx

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ import {
2525
DatePickerModalContent,
2626
TimePickerModal,
2727
} from '../../src';
28+
import { addMonths } from '../../src/Date/dateUtils';
29+
30+
const baseDate = new Date();
31+
const rangeExcludeDateStart = new Date(
32+
baseDate.getFullYear(),
33+
baseDate.getMonth(),
34+
baseDate.getDate()
35+
);
36+
const rangeExcludeDateEnd = addMonths(new Date(), 3);
2837

2938
function App({
3039
onToggleDarkMode,
@@ -49,12 +58,15 @@ function App({
4958
startDate: Date | undefined;
5059
endDate: Date | undefined;
5160
}>({ startDate: undefined, endDate: undefined });
61+
const [excludedDates, setExcludedDates] = React.useState<Date[]>([]);
5262
const [time, setTime] = React.useState<{
5363
hours: number | undefined;
5464
minutes: number | undefined;
5565
}>({ hours: undefined, minutes: undefined });
5666
const [timeOpen, setTimeOpen] = React.useState(false);
5767
const [rangeOpen, setRangeOpen] = React.useState(false);
68+
const [rangeExcludeOpen, setRangeExcludeOpen] = React.useState(false);
69+
5870
const [singleOpen, setSingleOpen] = React.useState(false);
5971

6072
const onDismissTime = React.useCallback(() => {
@@ -65,6 +77,10 @@ function App({
6577
setRangeOpen(false);
6678
}, [setRangeOpen]);
6779

80+
const onDismissExcludeRange = React.useCallback(() => {
81+
setRangeExcludeOpen(false);
82+
}, [setRangeExcludeOpen]);
83+
6884
const onDismissSingle = React.useCallback(() => {
6985
setSingleOpen(false);
7086
}, [setSingleOpen]);
@@ -77,6 +93,14 @@ function App({
7793
[setRangeOpen, setRange]
7894
);
7995

96+
const onChangeExcludeRange = React.useCallback(
97+
({ excludedDates }: { excludedDates: Date[] }) => {
98+
setRangeExcludeOpen(false);
99+
setExcludedDates(excludedDates);
100+
},
101+
[setRangeExcludeOpen, setExcludedDates]
102+
);
103+
80104
const onChangeSingle = React.useCallback(
81105
(params) => {
82106
setSingleOpen(false);
@@ -102,7 +126,7 @@ function App({
102126
theme.dark && theme.mode === 'adaptive'
103127
? overlay(3, theme.colors.surface)
104128
: (theme.colors.surface as any);
105-
129+
console.log({ excludedDates });
106130
return (
107131
<>
108132
<ScrollView
@@ -197,6 +221,7 @@ function App({
197221
onPress={() => setSingleOpen(true)}
198222
uppercase={false}
199223
mode="outlined"
224+
style={styles.pickButton}
200225
>
201226
Pick single date
202227
</Button>
@@ -205,23 +230,30 @@ function App({
205230
onPress={() => setRangeOpen(true)}
206231
uppercase={false}
207232
mode="outlined"
233+
style={styles.pickButton}
208234
>
209235
Pick range
210236
</Button>
211237
<View style={styles.buttonSeparator} />
238+
<Button
239+
onPress={() => setRangeExcludeOpen(true)}
240+
uppercase={false}
241+
mode="outlined"
242+
style={styles.pickButton}
243+
>
244+
Exclude dates in range
245+
</Button>
246+
<View style={styles.buttonSeparator} />
212247
<Button
213248
onPress={() => setTimeOpen(true)}
214249
uppercase={false}
215250
mode="outlined"
251+
style={styles.pickButton}
216252
>
217253
Pick time
218254
</Button>
219255
</View>
220256
<Enter />
221-
222-
{/*<DatePickerInput />*/}
223-
{/*<Enter />*/}
224-
{/*<DateRangeInput />*/}
225257
</Animated.View>
226258
<View style={styles.content}>
227259
<Title>Inside page</Title>
@@ -237,8 +269,8 @@ function App({
237269
<DatePickerModalContent
238270
mode="range"
239271
onDismiss={onDismissRange}
240-
startDate={undefined}
241-
endDate={undefined}
272+
startDate={range.startDate}
273+
endDate={range.endDate}
242274
onConfirm={onChangeRange}
243275
/>
244276
</Animated.View>
@@ -250,13 +282,25 @@ function App({
250282
mode="range"
251283
visible={rangeOpen}
252284
onDismiss={onDismissRange}
253-
startDate={undefined}
254-
endDate={undefined}
285+
startDate={range.startDate}
286+
endDate={range.endDate}
255287
onConfirm={onChangeRange}
256-
saveLabel="Opslaan" // optional
257-
label="Selecteer periode" // optional
258-
startLabel="Van" // optional
259-
endLabel="Tot" // optional
288+
// saveLabel="Save" // optional
289+
// label="Select period" // optional
290+
// startLabel="From" // optional
291+
// endLabel="To" // optional
292+
// animationType="slide" // optional, default is slide on ios/android and none on web
293+
/>
294+
<DatePickerModal
295+
mode="excludeInRange"
296+
visible={rangeExcludeOpen}
297+
onDismiss={onDismissExcludeRange}
298+
startDate={rangeExcludeDateStart}
299+
endDate={rangeExcludeDateEnd}
300+
excludedDates={excludedDates}
301+
onConfirm={onChangeExcludeRange}
302+
// sunday, saturday
303+
disableWeekDays={disabledWeekDays}
260304
// animationType="slide" // optional, default is slide on ios/android and none on web
261305
/>
262306
<DatePickerModal
@@ -265,8 +309,8 @@ function App({
265309
onDismiss={onDismissSingle}
266310
date={undefined}
267311
onConfirm={onChangeSingle}
268-
saveLabel="Save" // optional
269-
label="Select date" // optional
312+
// saveLabel="Save" // optional
313+
// label="Select date" // optional
270314
// animationType="slide" // optional, default is 'slide' on ios/android and 'none' on web
271315
/>
272316

@@ -276,15 +320,17 @@ function App({
276320
onConfirm={onConfirmTime}
277321
hours={time.hours} // optional, default: current hours
278322
minutes={time.minutes} // optional, default: current minutes
279-
label="Select time" // optional, default 'Select time'
280-
cancelLabel="Cancel" // optional, default: 'Cancel'
281-
confirmLabel="Ok" // optional, default: 'Ok'
323+
// label="Select time" // optional, default 'Select time'
324+
// cancelLabel="Cancel" // optional, default: 'Cancel'
325+
// confirmLabel="Ok" // optional, default: 'Ok'
282326
// animationType="fade" // optional, default is 'none'
283327
/>
284328
</>
285329
);
286330
}
287331

332+
const disabledWeekDays = [0, 6];
333+
288334
function Enter() {
289335
return <View style={styles.enter} />;
290336
}
@@ -398,7 +444,8 @@ const styles = StyleSheet.create({
398444
},
399445
switchSpace: { flex: 1 },
400446
switchLabel: { fontSize: 16 },
401-
buttons: { flexDirection: 'row', marginTop: 24 },
447+
buttons: { flexDirection: 'row', flexWrap: 'wrap', marginTop: 24 },
448+
pickButton: { marginTop: 6 },
402449
buttonSeparator: { width: 6 },
403450
enter: { height: 12 },
404451
label: { width: 100, fontSize: 16 },

src/Date/Calendar.tsx

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { StyleSheet, View } from 'react-native'
44
import Swiper from './Swiper'
55

66
import Month from './Month'
7-
import { dateToUnix } from './dateUtils'
7+
import { areDatesOnSameDay, dateToUnix, DisableWeekDaysType } from './dateUtils'
88

99
import CalendarHeader from './CalendarHeader'
1010
import { useCallback, useMemo } from 'react'
@@ -13,33 +13,49 @@ import Color from 'color'
1313
import { useTheme } from 'react-native-paper'
1414
import { useLatest } from '../utils'
1515

16-
export type ModeType = 'single' | 'range'
16+
export type ModeType = 'single' | 'range' | 'excludeInRange'
1717

1818
export type ScrollModeType = 'horizontal' | 'vertical'
1919

20+
export type BaseCalendarProps = {
21+
disableWeekDays?: DisableWeekDaysType
22+
}
23+
2024
export type CalendarDate = Date | undefined
2125

26+
export type ExcludeInRangeChange = (params: { excludedDates: Date[] }) => any
27+
2228
export type RangeChange = (params: {
2329
startDate: CalendarDate
2430
endDate: CalendarDate
2531
}) => any
2632

2733
export type SingleChange = (params: { date: CalendarDate }) => any
2834

29-
export interface CalendarRangeProps {
35+
export interface CalendarSingleProps extends BaseCalendarProps {
36+
mode: 'single'
37+
date?: CalendarDate
38+
onChange: SingleChange
39+
}
40+
41+
export interface CalendarRangeProps extends BaseCalendarProps {
3042
mode: 'range'
3143
startDate: CalendarDate
3244
endDate: CalendarDate
3345
onChange: RangeChange
3446
}
3547

36-
export interface CalendarSingleProps {
37-
mode: 'single'
38-
date?: CalendarDate
39-
onChange: SingleChange
48+
export interface CalendarExcludeInRangeProps extends BaseCalendarProps {
49+
mode: 'excludeInRange'
50+
excludedDates: Date[]
51+
startDate: CalendarDate
52+
endDate: CalendarDate
53+
onChange: ExcludeInRangeChange
4054
}
4155

42-
function Calendar(props: CalendarSingleProps | CalendarRangeProps) {
56+
function Calendar(
57+
props: CalendarSingleProps | CalendarRangeProps | CalendarExcludeInRangeProps
58+
) {
4359
const {
4460
mode,
4561
onChange,
@@ -49,6 +65,9 @@ function Calendar(props: CalendarSingleProps | CalendarRangeProps) {
4965
endDate,
5066
// @ts-ignore
5167
date,
68+
// @ts-ignore
69+
excludedDates,
70+
disableWeekDays,
5271
} = props
5372

5473
const theme = useTheme()
@@ -60,7 +79,8 @@ function Calendar(props: CalendarSingleProps | CalendarRangeProps) {
6079
return Color(theme.colors.primary).lighten(0.9).hex()
6180
}, [theme])
6281

63-
const scrollMode = mode === 'range' ? 'vertical' : 'horizontal'
82+
const scrollMode =
83+
mode === 'range' || mode === 'excludeInRange' ? 'vertical' : 'horizontal'
6484

6585
const [selectedYear, setSelectedYear] = React.useState<number | undefined>(
6686
undefined
@@ -77,15 +97,16 @@ function Calendar(props: CalendarSingleProps | CalendarRangeProps) {
7797
// prevent re-rendering all months when something changed we only need the
7898
// latest version of the props and we don't want the useCallback to change
7999
const startDateRef = useLatest<CalendarDate>(startDate)
100+
const excludedDatesRef = useLatest<Date[]>(excludedDates)
80101
const endDateRef = useLatest<CalendarDate>(endDate)
81-
const onChangeRef = useLatest<RangeChange | SingleChange>(onChange)
102+
const onChangeRef = useLatest<
103+
RangeChange | SingleChange | ExcludeInRangeChange
104+
>(onChange)
82105

83106
const onPressDate = useCallback(
84107
(d: Date) => {
85108
if (mode === 'single') {
86-
onChangeRef.current({
87-
startDate: undefined,
88-
endDate: undefined,
109+
;(onChangeRef.current as SingleChange)({
89110
date: d,
90111
})
91112
} else if (mode === 'range') {
@@ -95,16 +116,28 @@ function Calendar(props: CalendarSingleProps | CalendarRangeProps) {
95116
if (sd && !ed && dateToUnix(d) > dateToUnix(sd!)) {
96117
isStart = false
97118
}
98-
onChangeRef.current({
119+
;(onChangeRef.current as RangeChange)({
99120
startDate: isStart ? d : sd,
100121
endDate: !isStart
101122
? new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59)
102123
: undefined,
103-
date: undefined,
124+
})
125+
} else if (mode === 'excludeInRange') {
126+
const exists = excludedDatesRef.current.some((ed) =>
127+
areDatesOnSameDay(ed, d)
128+
)
129+
130+
;(onChangeRef.current as ExcludeInRangeChange)({
131+
excludedDates: exists
132+
? excludedDatesRef.current.filter((ed) => !areDatesOnSameDay(ed, d))
133+
: [
134+
...excludedDatesRef.current,
135+
new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0),
136+
],
104137
})
105138
}
106139
},
107-
[mode, onChangeRef, startDateRef, endDateRef]
140+
[mode, onChangeRef, startDateRef, endDateRef, excludedDatesRef]
108141
)
109142

110143
return (
@@ -128,13 +161,16 @@ function Calendar(props: CalendarSingleProps | CalendarRangeProps) {
128161
primaryColor={theme.colors.primary}
129162
selectColor={selectColor}
130163
roundness={theme.roundness}
164+
disableWeekDays={disableWeekDays}
165+
excludedDates={excludedDates}
131166
/>
132167
)}
133168
renderHeader={({ onPrev, onNext }) => (
134169
<CalendarHeader
135170
onPrev={onPrev}
136171
onNext={onNext}
137172
scrollMode={scrollMode}
173+
disableWeekDays={disableWeekDays}
138174
/>
139175
)}
140176
/>

src/Date/CalendarHeader.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react'
22
import { StyleSheet, View } from 'react-native'
33
import { IconButton, useTheme } from 'react-native-paper'
44
import DayNames, { dayNamesHeight } from './DayNames'
5+
import { DisableWeekDaysType } from './dateUtils'
56

67
const buttonContainerHeight = 56
78
const buttonContainerMarginTop = 4
@@ -23,10 +24,12 @@ function CalendarHeader({
2324
scrollMode,
2425
onPrev,
2526
onNext,
27+
disableWeekDays,
2628
}: {
2729
scrollMode: 'horizontal' | 'vertical'
2830
onPrev: () => any
2931
onNext: () => any
32+
disableWeekDays?: DisableWeekDaysType
3033
}) {
3134
const theme = useTheme()
3235
const isHorizontal = scrollMode === 'horizontal'
@@ -54,7 +57,7 @@ function CalendarHeader({
5457
</View>
5558
</View>
5659
) : null}
57-
<DayNames />
60+
<DayNames disableWeekDays={disableWeekDays} />
5861
</View>
5962
)
6063
}

0 commit comments

Comments
 (0)