@@ -6,17 +6,20 @@ import { pickSpacingProps } from '../../../../components/flex/utils'
6
6
import classnames from 'classnames'
7
7
import FieldBlock , { Props as FieldBlockProps } from '../../FieldBlock'
8
8
import SharedContext from '../../../../shared/Context'
9
- import { parseISO , isValid } from 'date-fns'
9
+ import { parseISO , isValid , isBefore , isAfter } from 'date-fns'
10
10
import useTranslation from '../../hooks/useTranslation'
11
- import { formatDate } from '../../Value/Date'
11
+ import { FormatDateOptions , formatDate } from '../../Value/Date'
12
12
import {
13
13
DatePickerEvent ,
14
14
DatePickerProps ,
15
15
} from '../../../../components/DatePicker'
16
+ import { convertStringToDate } from '../../../../components/date-picker/DatePickerCalc'
17
+ import { ProviderProps } from '../../../../shared/Provider'
18
+ import { FormError } from '../../utils'
16
19
17
20
// `range`, `showInput`, `showCancelButton` and `showResetButton` are not picked from the `DatePickerProps`
18
21
// Since they require `Field.Date` specific comments, due to them having different default values
19
- export type Props = FieldProps < string , undefined | string > & {
22
+ export type DateProps = FieldProps < string , undefined | string > & {
20
23
// Validation
21
24
pattern ?: string
22
25
/**
@@ -77,17 +80,17 @@ export type Props = FieldProps<string, undefined | string> & {
77
80
| 'onReset'
78
81
>
79
82
80
- function DateComponent ( props : Props ) {
81
- const translations = useTranslation ( )
83
+ function DateComponent ( props : DateProps ) {
84
+ const translations = useTranslation ( ) . Date
82
85
const { locale } = useContext ( SharedContext )
83
86
84
87
const errorMessages = useMemo ( ( ) => {
85
88
return {
86
- 'Field.errorRequired' : translations . Date . errorRequired ,
87
- 'Field.errorPattern' : translations . Date . errorRequired ,
89
+ 'Field.errorRequired' : translations . errorRequired ,
90
+ 'Field.errorPattern' : translations . errorRequired ,
88
91
...props . errorMessages ,
89
92
}
90
- } , [ props . errorMessages , translations . Date . errorRequired ] )
93
+ } , [ props . errorMessages , translations . errorRequired ] )
91
94
92
95
const schema = useMemo < AllJSONSchemaVersions > (
93
96
( ) =>
@@ -109,7 +112,31 @@ function DateComponent(props: Props) {
109
112
[ ]
110
113
)
111
114
112
- const preparedProps : Props = {
115
+ const dateLimitValidator = useCallback (
116
+ ( value : string ) => {
117
+ return validateDateLimit ( {
118
+ value,
119
+ locale,
120
+ minDate : props . minDate ,
121
+ maxDate : props . maxDate ,
122
+ isRange : props . range ,
123
+ } )
124
+ } ,
125
+ [ props . maxDate , props . minDate , props . range , locale ]
126
+ )
127
+
128
+ const hasDateLimitAndValue = useMemo ( ( ) => {
129
+ return ( props . minDate || props . maxDate ) && props . value
130
+ } , [ props . minDate , props . maxDate , props . value ] )
131
+
132
+ const validateInitially = useMemo ( ( ) => {
133
+ if ( hasDateLimitAndValue && ! props . validateInitially ) {
134
+ return true
135
+ }
136
+ return props . validateInitially
137
+ } , [ props . validateInitially , hasDateLimitAndValue ] )
138
+
139
+ const preparedProps : DateProps = {
113
140
...props ,
114
141
errorMessages,
115
142
schema,
@@ -121,6 +148,8 @@ function DateComponent(props: Props) {
121
148
return range ? `${ start_date } |${ end_date } ` : date
122
149
} ,
123
150
validateRequired,
151
+ validateInitially,
152
+ onBlurValidator : props . onBlurValidator ?? dateLimitValidator ,
124
153
}
125
154
126
155
const {
@@ -142,6 +171,8 @@ function DateComponent(props: Props) {
142
171
showResetButton = true ,
143
172
showInput = true ,
144
173
onReset,
174
+ minDate,
175
+ maxDate,
145
176
...rest
146
177
} = useFieldProps ( preparedProps )
147
178
@@ -157,27 +188,24 @@ function DateComponent(props: Props) {
157
188
}
158
189
}
159
190
160
- const [ startDate , endDate ] = valueProp
161
- . split ( '|' )
162
- // Assign to null if falsy value, to properly clear input values
163
- . map ( ( value ) => ( / ( u n d e f i n e d | n u l l ) / . test ( value ) ? null : value ) )
191
+ const [ startDate , endDate ] = parseRangeValue ( valueProp )
164
192
165
193
return {
166
- value : undefined ,
194
+ date : undefined ,
167
195
startDate,
168
196
endDate,
169
197
}
170
198
} , [ range , valueProp ] )
171
199
172
200
useMemo ( ( ) => {
173
201
if ( ( path || itemPath ) && valueProp ) {
174
- setDisplayValue ( formatDate ( valueProp , { locale } ) )
202
+ setDisplayValue ( formatDate ( valueProp , { locale } ) , undefined )
175
203
}
176
204
} , [ itemPath , locale , path , setDisplayValue , valueProp ] )
177
205
178
206
const fieldBlockProps : FieldBlockProps = {
179
207
forId : id ,
180
- label : label ?? translations . Date . label ,
208
+ label : label ?? translations . label ,
181
209
className : classnames ( 'dnb-forms-field-string' , className ) ,
182
210
...pickSpacingProps ( props ) ,
183
211
}
@@ -193,6 +221,8 @@ function DateComponent(props: Props) {
193
221
showResetButton = { showResetButton }
194
222
startDate = { startDate }
195
223
endDate = { endDate }
224
+ minDate = { minDate }
225
+ maxDate = { maxDate }
196
226
status = { hasError ? 'error' : undefined }
197
227
range = { range }
198
228
onChange = { handleChange }
@@ -209,6 +239,111 @@ function DateComponent(props: Props) {
209
239
)
210
240
}
211
241
242
+ function parseRangeValue ( value : DateProps [ 'value' ] ) {
243
+ return (
244
+ value
245
+ . split ( '|' )
246
+ // Assign to null if falsy value, to properly clear input values
247
+ . map ( ( value ) => ( / ( u n d e f i n e d | n u l l ) / . test ( value ) ? null : value ) )
248
+ )
249
+ }
250
+
251
+ function validateDateLimit ( {
252
+ value,
253
+ isRange,
254
+ locale,
255
+ ...dates
256
+ } : {
257
+ value : DateProps [ 'value' ]
258
+ minDate : DateProps [ 'minDate' ]
259
+ maxDate : DateProps [ 'maxDate' ]
260
+ isRange : DateProps [ 'range' ]
261
+ locale : ProviderProps [ 'locale' ]
262
+ } ) {
263
+ if ( ( ! dates . minDate && ! dates . maxDate ) || ! value ) {
264
+ return
265
+ }
266
+
267
+ const [ startDateParsed , endDateParsed ] = parseRangeValue ( value )
268
+
269
+ const minDate = convertStringToDate ( dates . minDate )
270
+ const maxDate = convertStringToDate ( dates . maxDate )
271
+
272
+ const startDate = convertStringToDate ( startDateParsed )
273
+ const endDate = convertStringToDate ( endDateParsed )
274
+
275
+ const isoDates = {
276
+ minDate :
277
+ dates . minDate instanceof Date
278
+ ? dates . minDate . toISOString ( )
279
+ : dates . minDate ,
280
+ maxDate :
281
+ dates . maxDate instanceof Date
282
+ ? dates . maxDate . toISOString ( )
283
+ : dates . maxDate ,
284
+ }
285
+
286
+ const options : FormatDateOptions = {
287
+ locale,
288
+ variant : 'long' ,
289
+ }
290
+
291
+ // Handle non range validation
292
+ if ( ! isRange ) {
293
+ if ( isBefore ( startDate , minDate ) ) {
294
+ return new FormError ( 'Date.errorMinDate' , {
295
+ messageValues : { date : formatDate ( isoDates . minDate , options ) } ,
296
+ } )
297
+ }
298
+
299
+ if ( isAfter ( startDate , maxDate ) ) {
300
+ return new FormError ( 'Date.errorMaxDate' , {
301
+ messageValues : { date : formatDate ( isoDates . maxDate , options ) } ,
302
+ } )
303
+ }
304
+
305
+ return
306
+ }
307
+
308
+ const messages : Array < FormError > = [ ]
309
+
310
+ // Start date validation
311
+ if ( isBefore ( startDate , minDate ) ) {
312
+ messages . push (
313
+ new FormError ( 'Date.errorStartDateMinDate' , {
314
+ messageValues : { date : formatDate ( isoDates . minDate , options ) } ,
315
+ } )
316
+ )
317
+ }
318
+
319
+ if ( isAfter ( startDate , maxDate ) ) {
320
+ messages . push (
321
+ new FormError ( 'Date.errorStartDateMaxDate' , {
322
+ messageValues : { date : formatDate ( isoDates . maxDate , options ) } ,
323
+ } )
324
+ )
325
+ }
326
+
327
+ // End date validation
328
+ if ( isBefore ( endDate , minDate ) ) {
329
+ messages . push (
330
+ new FormError ( 'Date.errorEndDateMinDate' , {
331
+ messageValues : { date : formatDate ( isoDates . minDate , options ) } ,
332
+ } )
333
+ )
334
+ }
335
+
336
+ if ( isAfter ( endDate , maxDate ) ) {
337
+ messages . push (
338
+ new FormError ( 'Date.errorEndDateMaxDate' , {
339
+ messageValues : { date : formatDate ( isoDates . maxDate , options ) } ,
340
+ } )
341
+ )
342
+ }
343
+
344
+ return messages
345
+ }
346
+
212
347
// Used to filter out DatePickerProps from the FieldProps.
213
348
// Includes DatePickerProps that are not destructured in useFieldProps
214
349
const datePickerPropKeys = [
@@ -251,7 +386,7 @@ const datePickerPropKeys = [
251
386
'onReset' ,
252
387
]
253
388
254
- function pickDatePickerProps ( props : Props ) {
389
+ function pickDatePickerProps ( props : DateProps ) {
255
390
const datePickerProps = Object . keys ( props ) . reduce (
256
391
( datePickerProps , key ) => {
257
392
if ( datePickerPropKeys . includes ( key ) ) {
0 commit comments