1
1
import { useDatePicker , useDateRangePicker } from "@react-aria/datepicker" ;
2
2
import { useDatePickerState , useDateRangePickerState } from "@react-stately/datepicker" ;
3
3
import { useRef } from "react" ;
4
+ import { match } from "ts-pattern" ;
4
5
import { FieldProps } from "../Field/FieldProps" ;
5
6
import { CalendarDate , DateValue , getLocalTimeZone } from "@internationalized/date" ;
6
- import { Input } from "./Input" ;
7
- import { Calendar } from "./Calendar" ;
8
- import { Box } from "../Box/Box" ;
7
+ import { BaseDateInput } from "./BaseDateInput" ;
9
8
import { Field } from "../Field/Field" ;
10
- import { LocalizedString } from "../util/ConfigurableTypes" ;
11
9
import { RangeValue } from "@react-types/shared" ;
12
- import { Inline } from "../Layout/Inline " ;
13
- import { Button } from ".. " ;
10
+ import { DateProps , SingleDateProps , RangeDateProps } from "./types " ;
11
+ import { dateToCalendarDate } from "./utils " ;
14
12
15
- export type ShortcutProps < Value > = {
16
- label : LocalizedString ;
17
- value : Value ;
18
- } ;
19
- type SingleDateFieldProps = {
20
- type ?: "single" ;
21
- shortcuts ?: ShortcutProps < Date | null > [ ] ;
22
- } & FieldProps < Date | null > ;
23
- type RangeDateFieldProps = {
24
- type : "range" ;
25
- shortcuts ?: ShortcutProps < [ Date , Date ] | null > [ ] ;
26
- } & FieldProps < [ Date , Date ] | null > ;
27
- type Props = ( SingleDateFieldProps | RangeDateFieldProps ) & {
28
- minDate ?: Date ;
29
- maxDate ?: Date ;
30
- shouldDisableDate ?: ( date : Date ) => boolean ;
31
- readOnly ?: boolean ;
32
- } ;
13
+ type SingleDateFieldProps = SingleDateProps & FieldProps < Date | null > & DateProps ;
14
+ type RangeDateFieldProps = RangeDateProps & FieldProps < [ Date , Date ] | null > & DateProps ;
33
15
34
- function dateToCalendarDate ( date : Date ) : CalendarDate {
35
- return new CalendarDate ( date . getFullYear ( ) , date . getMonth ( ) + 1 , date . getDate ( ) ) ;
36
- }
37
-
38
- function SingleDateField ( { disabled, readOnly, ...props } : Extract < Props , { type ?: "single" } > ) {
16
+ function SingleDateField ( props : SingleDateFieldProps ) {
39
17
const localTimeZone = getLocalTimeZone ( ) ;
18
+ const ref = useRef ( null ) ;
40
19
41
20
const internalProps = {
42
21
...props ,
43
22
value : props . value ? dateToCalendarDate ( props . value ) : props . value ,
44
23
onChange : ( date : CalendarDate | null ) => {
45
24
props . onChange ( date ?. toDate ( localTimeZone ) ?? null ) ;
46
25
} ,
47
- isDisabled : disabled ,
48
- isReadOnly : readOnly ,
26
+ isDisabled : props . disabled ,
27
+ isReadOnly : props . isReadOnly ?? props . readOnly ,
49
28
validationState : props . issues ? "invalid" : "valid" ,
50
29
minValue : props . minDate ? dateToCalendarDate ( props . minDate ) : undefined ,
51
30
maxValue : props . maxDate ? dateToCalendarDate ( props . maxDate ) : undefined ,
52
31
isDateUnavailable : props . shouldDisableDate
53
32
? ( date : DateValue ) => props . shouldDisableDate ! ( date . toDate ( localTimeZone ) )
54
33
: undefined ,
55
34
shouldForceLeadingZeros : true ,
35
+ onBlur : props . onBlur ,
36
+ autoFocus : props . autoFocus ,
56
37
} as const ;
57
- const state = useDatePickerState ( internalProps ) ;
58
- const ref = useRef ( null ) ;
59
- const {
60
- groupProps,
61
- labelProps,
62
- fieldProps,
63
- buttonProps,
64
- descriptionProps,
65
- errorMessageProps,
66
- calendarProps,
67
- } = useDatePicker ( internalProps , state , ref ) ;
68
38
69
- const shortcuts = props . shortcuts && (
70
- < Inline space = { 4 } >
71
- { props . shortcuts . map ( ( shortcut ) => (
72
- < Button
73
- key = { shortcut . label }
74
- kind = "transparent"
75
- hierarchy = "secondary"
76
- size = "small"
77
- label = { shortcut . label }
78
- onPress = { ( ) => {
79
- props . onChange ( shortcut . value ) ;
80
- state . close ( ) ;
81
- } }
82
- />
83
- ) ) }
84
- </ Inline >
85
- ) ;
39
+ const datePickerState = useDatePickerState ( internalProps ) ;
40
+ const datePickerAria = useDatePicker ( internalProps , datePickerState , ref ) ;
86
41
87
42
return (
88
43
< Field
89
44
{ ...props }
90
- disabled = { disabled }
91
- labelProps = { labelProps }
92
- assistiveTextProps = { descriptionProps }
93
- errorMessageProps = { errorMessageProps }
45
+ disabled = { props . disabled }
46
+ labelProps = { datePickerAria . labelProps }
47
+ assistiveTextProps = { datePickerAria . descriptionProps }
48
+ errorMessageProps = { datePickerAria . errorMessageProps }
94
49
>
95
- < Box { ...groupProps } ref = { ref } >
96
- < Input
97
- type = "single"
98
- fieldProps = { fieldProps }
99
- buttonProps = { buttonProps }
100
- isCalendarOpen = { state . isOpen }
101
- />
102
- </ Box >
103
- { state . isOpen && (
104
- < Calendar
105
- type = "single"
106
- { ...calendarProps }
107
- inputRef = { ref }
108
- onClose = { state . close }
109
- shortcuts = { shortcuts }
110
- />
111
- ) }
50
+ < BaseDateInput
51
+ type = "single"
52
+ value = { props . value }
53
+ onChange = { props . onChange }
54
+ shortcuts = { props . shortcuts }
55
+ datePickerAria = { datePickerAria }
56
+ datePickerState = { datePickerState }
57
+ inputRef = { ref }
58
+ />
112
59
</ Field >
113
60
) ;
114
61
}
115
62
116
- function RangeDateField ( { disabled , readOnly , ... props } : Extract < Props , { type : "range" } > ) {
63
+ function RangeDateField ( props : RangeDateFieldProps ) {
117
64
const localTimeZone = getLocalTimeZone ( ) ;
65
+ const ref = useRef ( null ) ;
66
+
118
67
const internalProps = {
119
68
...props ,
120
- isDisabled : disabled ,
121
- isReadOnly : readOnly ,
122
- validationState : props . issues ? "invalid" : "valid" ,
123
- minValue : props . minDate ? dateToCalendarDate ( props . minDate ) : undefined ,
124
- maxValue : props . maxDate ? dateToCalendarDate ( props . maxDate ) : undefined ,
125
- isDateUnavailable : props . shouldDisableDate
126
- ? ( date : DateValue ) => props . shouldDisableDate ! ( date . toDate ( localTimeZone ) )
127
- : undefined ,
128
69
value : props . value
129
70
? {
130
71
start : dateToCalendarDate ( props . value [ 0 ] ) ,
@@ -138,70 +79,52 @@ function RangeDateField({ disabled, readOnly, ...props }: Extract<Props, { type:
138
79
props . onChange ( [ range . start . toDate ( localTimeZone ) , range . end . toDate ( localTimeZone ) ] ) ;
139
80
}
140
81
} ,
82
+ isDisabled : props . disabled ,
83
+ isReadOnly : props . isReadOnly ?? props . readOnly ,
84
+ validationState : props . issues ? "invalid" : "valid" ,
85
+ minValue : props . minDate ? dateToCalendarDate ( props . minDate ) : undefined ,
86
+ maxValue : props . maxDate ? dateToCalendarDate ( props . maxDate ) : undefined ,
87
+ isDateUnavailable : props . shouldDisableDate
88
+ ? ( date : DateValue ) => props . shouldDisableDate ! ( date . toDate ( localTimeZone ) )
89
+ : undefined ,
141
90
shouldForceLeadingZeros : true ,
91
+ onBlur : props . onBlur ,
92
+ autoFocus : props . autoFocus ,
142
93
} as const ;
143
- const state = useDateRangePickerState ( internalProps ) ;
144
- const ref = useRef ( null ) ;
145
- const {
146
- groupProps,
147
- labelProps,
148
- buttonProps,
149
- descriptionProps,
150
- errorMessageProps,
151
- calendarProps,
152
- startFieldProps,
153
- endFieldProps,
154
- } = useDateRangePicker ( internalProps , state , ref ) ;
155
94
156
- const shortcuts = props . shortcuts && (
157
- < Inline space = { 4 } >
158
- { props . shortcuts . map ( ( shortcut ) => (
159
- < Button
160
- key = { shortcut . label }
161
- kind = "transparent"
162
- hierarchy = "secondary"
163
- size = "small"
164
- label = { shortcut . label }
165
- onPress = { ( ) => {
166
- props . onChange ( shortcut . value ) ;
167
- state . close ( ) ;
168
- } }
169
- />
170
- ) ) }
171
- </ Inline >
172
- ) ;
95
+ const rangeDatePickerState = useDateRangePickerState ( internalProps ) ;
96
+ const rangeDatePickerAria = useDateRangePicker ( internalProps , rangeDatePickerState , ref ) ;
173
97
174
98
return (
175
99
< Field
176
100
{ ...props }
177
- disabled = { disabled }
178
- labelProps = { labelProps }
179
- assistiveTextProps = { descriptionProps }
180
- errorMessageProps = { errorMessageProps }
101
+ disabled = { props . disabled }
102
+ labelProps = { rangeDatePickerAria . labelProps }
103
+ assistiveTextProps = { rangeDatePickerAria . descriptionProps }
104
+ errorMessageProps = { rangeDatePickerAria . errorMessageProps }
181
105
>
182
- < Box { ...groupProps } ref = { ref } >
183
- < Input
184
- type = "range"
185
- fieldProps = { { start : startFieldProps , end : endFieldProps } }
186
- buttonProps = { buttonProps }
187
- isCalendarOpen = { state . isOpen }
188
- />
189
- </ Box >
190
- { state . isOpen && (
191
- < Calendar
192
- type = "range"
193
- { ...calendarProps }
194
- inputRef = { ref }
195
- onClose = { state . close }
196
- shortcuts = { shortcuts }
197
- />
198
- ) }
106
+ < BaseDateInput
107
+ type = "range"
108
+ value = { props . value }
109
+ onChange = { props . onChange }
110
+ shortcuts = { props . shortcuts }
111
+ dateRangePickerAria = { rangeDatePickerAria }
112
+ dateRangePickerState = { rangeDatePickerState }
113
+ inputRef = { ref }
114
+ />
199
115
</ Field >
200
116
) ;
201
117
}
202
118
203
- export function DateField ( props : Props ) {
204
- return props . type === "range" ? < RangeDateField { ...props } /> : < SingleDateField { ...props } /> ;
205
- }
119
+ export type DateFieldProps = SingleDateFieldProps | RangeDateFieldProps ;
206
120
207
- export type { Props as DateFieldProps } ;
121
+ export function DateField ( props : DateFieldProps ) {
122
+ // Note: checking this case in the pattern matching below doesn't work for some reason
123
+ if ( props . type == null ) {
124
+ return < SingleDateField { ...props } /> ;
125
+ }
126
+ return match ( props )
127
+ . with ( { type : "single" } , ( props ) => < SingleDateField { ...props } /> )
128
+ . with ( { type : "range" } , ( props ) => < RangeDateField { ...props } /> )
129
+ . exhaustive ( ) ;
130
+ }
0 commit comments