@navikt/ds-react 1.4.1 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_docs.json +120 -0
- package/cjs/date/hooks/useDatepicker.js +36 -11
- package/cjs/date/hooks/useMonthPicker.js +34 -10
- package/cjs/date/hooks/useRangeDatepicker.js +181 -65
- package/cjs/date/utils/format-date.js +3 -3
- package/cjs/date/utils/parse-date.js +14 -8
- package/esm/date/hooks/index.d.ts +3 -0
- package/esm/date/hooks/index.js.map +1 -1
- package/esm/date/hooks/useDatepicker.d.ts +18 -0
- package/esm/date/hooks/useDatepicker.js +36 -11
- package/esm/date/hooks/useDatepicker.js.map +1 -1
- package/esm/date/hooks/useMonthPicker.d.ts +17 -0
- package/esm/date/hooks/useMonthPicker.js +34 -10
- package/esm/date/hooks/useMonthPicker.js.map +1 -1
- package/esm/date/hooks/useRangeDatepicker.d.ts +12 -2
- package/esm/date/hooks/useRangeDatepicker.js +181 -65
- package/esm/date/hooks/useRangeDatepicker.js.map +1 -1
- package/esm/date/utils/format-date.d.ts +1 -1
- package/esm/date/utils/format-date.js +3 -3
- package/esm/date/utils/format-date.js.map +1 -1
- package/esm/date/utils/parse-date.d.ts +1 -1
- package/esm/date/utils/parse-date.js +14 -8
- package/esm/date/utils/parse-date.js.map +1 -1
- package/package.json +2 -2
- package/src/date/datepicker/datepicker.stories.tsx +30 -5
- package/src/date/hooks/index.ts +3 -0
- package/src/date/hooks/useDatepicker.tsx +73 -10
- package/src/date/hooks/useMonthPicker.tsx +68 -10
- package/src/date/hooks/useRangeDatepicker.test.tsx +59 -0
- package/src/date/hooks/useRangeDatepicker.tsx +285 -92
- package/src/date/monthpicker/monthpicker.stories.tsx +20 -0
- package/src/date/utils/__tests__/format-dates.test.ts +4 -4
- package/src/date/utils/__tests__/parse-dates.test.ts +0 -40
- package/src/date/utils/format-date.ts +5 -3
- package/src/date/utils/parse-date.ts +18 -9
|
@@ -3,16 +3,10 @@ import { isValidDate } from ".";
|
|
|
3
3
|
export const INPUT_DATE_STRING_FORMAT_DATE = "dd.MM.yyyy";
|
|
4
4
|
export const INPUT_DATE_STRING_FORMAT_MONTH = "MMMM yyyy";
|
|
5
5
|
const ALLOWED_INPUT_FORMATS_DATE = [
|
|
6
|
-
"ddMMyy",
|
|
7
|
-
"d.M.yy",
|
|
8
|
-
"dd.MM.yy",
|
|
9
|
-
"dd/MM/yy",
|
|
10
|
-
"dd-MM-yy",
|
|
11
6
|
INPUT_DATE_STRING_FORMAT_DATE,
|
|
12
7
|
"ddMMyyyy",
|
|
13
8
|
"dd/MM/yyyy",
|
|
14
9
|
"dd-MM-yyyy",
|
|
15
|
-
"d.M.yyyy",
|
|
16
10
|
];
|
|
17
11
|
const ALLOWED_INPUT_FORMATS_MONTH = [
|
|
18
12
|
"M/yyyy",
|
|
@@ -23,15 +17,27 @@ const ALLOWED_INPUT_FORMATS_MONTH = [
|
|
|
23
17
|
INPUT_DATE_STRING_FORMAT_MONTH,
|
|
24
18
|
...ALLOWED_INPUT_FORMATS_DATE,
|
|
25
19
|
];
|
|
20
|
+
const isTwoDigitYear = (dateString, today, locale, formats) => {
|
|
21
|
+
let parsed;
|
|
22
|
+
const newFormat = formats.map((x) => x.replace("yyyy", "yy"));
|
|
23
|
+
for (const format of newFormat) {
|
|
24
|
+
parsed = parse(dateString, format, today, { locale });
|
|
25
|
+
if (isValidDate(parsed)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
26
31
|
export const parseDate = (date, today, locale, type) => {
|
|
27
32
|
let parsed;
|
|
28
33
|
const ALLOWED_FORMATS = type === "date" ? ALLOWED_INPUT_FORMATS_DATE : ALLOWED_INPUT_FORMATS_MONTH;
|
|
29
34
|
for (const format of ALLOWED_FORMATS) {
|
|
30
35
|
parsed = parse(date, format, today, { locale });
|
|
31
|
-
if (isValidDate(parsed)
|
|
36
|
+
if (isValidDate(parsed) &&
|
|
37
|
+
!isTwoDigitYear(date, today, locale, ALLOWED_FORMATS)) {
|
|
32
38
|
return parsed;
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
|
-
return
|
|
41
|
+
return new Date("Invalid date");
|
|
36
42
|
};
|
|
37
43
|
//# sourceMappingURL=parse-date.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse-date.js","sourceRoot":"","sources":["../../../src/date/utils/parse-date.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,gBAAgB,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,CAAC;AAEhC,MAAM,CAAC,MAAM,6BAA6B,GAAG,YAAY,CAAC;AAE1D,MAAM,CAAC,MAAM,8BAA8B,GAAG,WAAW,CAAC;AAE1D,MAAM,0BAA0B,GAAG;IACjC,
|
|
1
|
+
{"version":3,"file":"parse-date.js","sourceRoot":"","sources":["../../../src/date/utils/parse-date.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,gBAAgB,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,CAAC;AAEhC,MAAM,CAAC,MAAM,6BAA6B,GAAG,YAAY,CAAC;AAE1D,MAAM,CAAC,MAAM,8BAA8B,GAAG,WAAW,CAAC;AAE1D,MAAM,0BAA0B,GAAG;IACjC,6BAA6B;IAC7B,UAAU;IACV,YAAY;IACZ,YAAY;CACb,CAAC;AAEF,MAAM,2BAA2B,GAAG;IAClC,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,SAAS;IACT,SAAS;IACT,8BAA8B;IAC9B,GAAG,0BAA0B;CAC9B,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAC5D,IAAI,MAAM,CAAC;IACX,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9D,KAAK,MAAM,MAAM,IAAI,SAAS,EAAE;QAC9B,MAAM,GAAG,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACtD,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE;YACvB,OAAO,IAAI,CAAC;SACb;KACF;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,IAAY,EACZ,KAAW,EACX,MAAc,EACd,IAAsB,EAChB,EAAE;IACR,IAAI,MAAM,CAAC;IACX,MAAM,eAAe,GACnB,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,2BAA2B,CAAC;IAC7E,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE;QACpC,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAChD,IACE,WAAW,CAAC,MAAM,CAAC;YACnB,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EACrD;YACA,OAAO,MAAM,CAAC;SACf;KACF;IACD,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC;AAClC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navikt/ds-react",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "NAV designsystem react components",
|
|
5
5
|
"author": "NAV Designsystem team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@floating-ui/react-dom-interactions": "0.9.2",
|
|
40
|
-
"@navikt/ds-icons": "^1.4.
|
|
40
|
+
"@navikt/ds-icons": "^1.4.3",
|
|
41
41
|
"@radix-ui/react-tabs": "1.0.0",
|
|
42
42
|
"@radix-ui/react-toggle-group": "1.0.0",
|
|
43
43
|
"clsx": "^1.2.1",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import isSaturday from "date-fns/isSaturday";
|
|
2
1
|
import React, { useId, useState } from "react";
|
|
3
2
|
import { UNSAFE_useDatepicker, UNSAFE_useRangeDatepicker } from "..";
|
|
4
3
|
import { Button } from "../..";
|
|
@@ -221,8 +220,13 @@ export const UserControlled = () => {
|
|
|
221
220
|
};
|
|
222
221
|
|
|
223
222
|
export const Validering = () => {
|
|
224
|
-
const
|
|
225
|
-
|
|
223
|
+
const [error, setError] = useState(false);
|
|
224
|
+
const { datepickerProps, inputProps } = UNSAFE_useDatepicker({
|
|
225
|
+
fromDate: new Date("Aug 2 2019"),
|
|
226
|
+
onValidate: (val) => setError(val.isWeekend),
|
|
227
|
+
defaultSelected: new Date("Nov 26 2022"),
|
|
228
|
+
disableWeekends: true,
|
|
229
|
+
onDateChange: console.log,
|
|
226
230
|
});
|
|
227
231
|
|
|
228
232
|
return (
|
|
@@ -230,8 +234,8 @@ export const Validering = () => {
|
|
|
230
234
|
<DatePicker {...datepickerProps}>
|
|
231
235
|
<DatePicker.Input
|
|
232
236
|
error={
|
|
233
|
-
|
|
234
|
-
? "NAV-kontoret er ikke åpent
|
|
237
|
+
error
|
|
238
|
+
? "NAV-kontoret er ikke åpent i helger. Velg en annen dag."
|
|
235
239
|
: undefined
|
|
236
240
|
}
|
|
237
241
|
{...inputProps}
|
|
@@ -261,3 +265,24 @@ export const ErrorInput = () => {
|
|
|
261
265
|
</div>
|
|
262
266
|
);
|
|
263
267
|
};
|
|
268
|
+
|
|
269
|
+
export const UseRangedDatepickerValidation = () => {
|
|
270
|
+
const { datepickerProps, fromInputProps, toInputProps } =
|
|
271
|
+
UNSAFE_useRangeDatepicker({
|
|
272
|
+
fromDate: new Date("Aug 23 2019"),
|
|
273
|
+
disableWeekends: true,
|
|
274
|
+
disabled: [new Date("Oct 10 2022")],
|
|
275
|
+
onValidate: console.table,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<div style={{ display: "flex", gap: "1rem" }}>
|
|
280
|
+
<DatePicker {...datepickerProps}>
|
|
281
|
+
<div style={{ display: "flex", gap: "1rem" }}>
|
|
282
|
+
<DatePicker.Input {...fromInputProps} label="Fra" />
|
|
283
|
+
<DatePicker.Input {...toInputProps} label="Til" />
|
|
284
|
+
</div>
|
|
285
|
+
</DatePicker>
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
};
|
package/src/date/hooks/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { useDatepicker as UNSAFE_useDatepicker } from "./useDatepicker";
|
|
2
|
+
export type { DateValidationT } from "./useDatepicker";
|
|
2
3
|
export { useRangeDatepicker as UNSAFE_useRangeDatepicker } from "./useRangeDatepicker";
|
|
4
|
+
export type { RangeValidationT } from "./useRangeDatepicker";
|
|
3
5
|
export { useMonthpicker as UNSAFE_useMonthpicker } from "./useMonthPicker";
|
|
6
|
+
export type { MonthValidationT } from "./useMonthPicker";
|
|
4
7
|
export { useDateInputContext, DateContext } from "./useDateInputContext";
|
|
5
8
|
export {
|
|
6
9
|
useSharedMonthContext,
|
|
@@ -36,6 +36,15 @@ export interface UseDatepickerOptions
|
|
|
36
36
|
* Callback for changed state
|
|
37
37
|
*/
|
|
38
38
|
onDateChange?: (val?: Date) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Input-format
|
|
41
|
+
* @default "dd.MM.yyyy"
|
|
42
|
+
*/
|
|
43
|
+
inputFormat?: string;
|
|
44
|
+
/**
|
|
45
|
+
* validation-callback
|
|
46
|
+
*/
|
|
47
|
+
onValidate?: (val: DateValidationT) => void;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
interface UseDatepickerValue {
|
|
@@ -62,6 +71,27 @@ interface UseDatepickerValue {
|
|
|
62
71
|
setSelected: (date?: Date) => void;
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
export type DateValidationT = {
|
|
75
|
+
isDisabled: boolean;
|
|
76
|
+
isWeekend: boolean;
|
|
77
|
+
isEmpty: boolean;
|
|
78
|
+
isInvalid: boolean;
|
|
79
|
+
isValidDate: boolean;
|
|
80
|
+
isBefore: boolean;
|
|
81
|
+
isAfter: boolean;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const getValidationMessage = (val = {}): DateValidationT => ({
|
|
85
|
+
isDisabled: false,
|
|
86
|
+
isWeekend: false,
|
|
87
|
+
isEmpty: false,
|
|
88
|
+
isInvalid: false,
|
|
89
|
+
isBefore: false,
|
|
90
|
+
isAfter: false,
|
|
91
|
+
isValidDate: true,
|
|
92
|
+
...val,
|
|
93
|
+
});
|
|
94
|
+
|
|
65
95
|
export const useDatepicker = (
|
|
66
96
|
opt: UseDatepickerOptions = {}
|
|
67
97
|
): UseDatepickerValue => {
|
|
@@ -75,6 +105,8 @@ export const useDatepicker = (
|
|
|
75
105
|
disabled,
|
|
76
106
|
disableWeekends,
|
|
77
107
|
onDateChange,
|
|
108
|
+
inputFormat,
|
|
109
|
+
onValidate,
|
|
78
110
|
} = opt;
|
|
79
111
|
|
|
80
112
|
const locale = getLocaleFromString(_locale);
|
|
@@ -90,7 +122,7 @@ export const useDatepicker = (
|
|
|
90
122
|
const [open, setOpen] = useState(false);
|
|
91
123
|
|
|
92
124
|
const defaultInputValue = defaultSelected
|
|
93
|
-
? formatDateForInput(defaultSelected, locale, "date")
|
|
125
|
+
? formatDateForInput(defaultSelected, locale, "date", inputFormat)
|
|
94
126
|
: "";
|
|
95
127
|
const [inputValue, setInputValue] = useState(defaultInputValue);
|
|
96
128
|
|
|
@@ -99,6 +131,11 @@ export const useDatepicker = (
|
|
|
99
131
|
setSelectedDay(date);
|
|
100
132
|
};
|
|
101
133
|
|
|
134
|
+
const updateValidation = (val: Partial<DateValidationT> = {}) => {
|
|
135
|
+
const msg = getValidationMessage(val);
|
|
136
|
+
onValidate?.(msg);
|
|
137
|
+
};
|
|
138
|
+
|
|
102
139
|
const handleFocusIn = useCallback(
|
|
103
140
|
(e) => {
|
|
104
141
|
if (!e?.target || !e?.target?.nodeType) {
|
|
@@ -134,7 +171,9 @@ export const useDatepicker = (
|
|
|
134
171
|
const setSelected = (date: Date | undefined) => {
|
|
135
172
|
updateDate(date);
|
|
136
173
|
setMonth(date ?? today);
|
|
137
|
-
setInputValue(
|
|
174
|
+
setInputValue(
|
|
175
|
+
date ? formatDateForInput(date, locale, "date", inputFormat) : ""
|
|
176
|
+
);
|
|
138
177
|
};
|
|
139
178
|
|
|
140
179
|
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
|
@@ -143,13 +182,14 @@ export const useDatepicker = (
|
|
|
143
182
|
let day = parseDate(e.target.value, today, locale, "date");
|
|
144
183
|
if (isValidDate(day)) {
|
|
145
184
|
setMonth(day);
|
|
146
|
-
setInputValue(formatDateForInput(day, locale, "date"));
|
|
185
|
+
setInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
|
147
186
|
}
|
|
148
187
|
};
|
|
149
188
|
|
|
150
189
|
const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
|
151
190
|
let day = parseDate(e.target.value, today, locale, "date");
|
|
152
|
-
isValidDate(day) &&
|
|
191
|
+
isValidDate(day) &&
|
|
192
|
+
setInputValue(formatDateForInput(day, locale, "date", inputFormat));
|
|
153
193
|
};
|
|
154
194
|
|
|
155
195
|
/* Only allow de-selecting if not required */
|
|
@@ -162,11 +202,15 @@ export const useDatepicker = (
|
|
|
162
202
|
if (!required && selected) {
|
|
163
203
|
updateDate(undefined);
|
|
164
204
|
setInputValue("");
|
|
205
|
+
updateValidation({ isValidDate: false, isEmpty: true });
|
|
165
206
|
return;
|
|
166
207
|
}
|
|
167
208
|
updateDate(day);
|
|
209
|
+
updateValidation();
|
|
168
210
|
setMonth(day);
|
|
169
|
-
setInputValue(
|
|
211
|
+
setInputValue(
|
|
212
|
+
day ? formatDateForInput(day, locale, "date", inputFormat) : ""
|
|
213
|
+
);
|
|
170
214
|
};
|
|
171
215
|
|
|
172
216
|
// When changing the input field, save its value in state and check if the
|
|
@@ -176,22 +220,39 @@ export const useDatepicker = (
|
|
|
176
220
|
setInputValue(e.target.value);
|
|
177
221
|
const day = parseDate(e.target.value, today, locale, "date");
|
|
178
222
|
|
|
223
|
+
const isBefore =
|
|
224
|
+
fromDate && day && differenceInCalendarDays(fromDate, day) > 0;
|
|
225
|
+
const isAfter = toDate && day && differenceInCalendarDays(day, toDate) > 0;
|
|
226
|
+
|
|
179
227
|
if (
|
|
180
228
|
!isValidDate(day) ||
|
|
181
|
-
(
|
|
182
|
-
|
|
229
|
+
(disableWeekends && isWeekend(day)) ||
|
|
230
|
+
(disabled && isMatch(day, disabled))
|
|
183
231
|
) {
|
|
184
232
|
updateDate(undefined);
|
|
233
|
+
updateValidation({
|
|
234
|
+
isInvalid: isValidDate(day),
|
|
235
|
+
isWeekend: disableWeekends && isWeekend(day),
|
|
236
|
+
isDisabled: disabled && isMatch(day, disabled),
|
|
237
|
+
isValidDate: false,
|
|
238
|
+
isEmpty: !e.target.value,
|
|
239
|
+
isBefore: isBefore ?? false,
|
|
240
|
+
isAfter: isAfter ?? false,
|
|
241
|
+
});
|
|
185
242
|
return;
|
|
186
243
|
}
|
|
187
244
|
|
|
188
|
-
const isBefore = fromDate && differenceInCalendarDays(fromDate, day) > 0;
|
|
189
|
-
const isAfter = toDate && differenceInCalendarDays(day, toDate) > 0;
|
|
190
245
|
if (isBefore || isAfter) {
|
|
191
246
|
updateDate(undefined);
|
|
247
|
+
updateValidation({
|
|
248
|
+
isValidDate: false,
|
|
249
|
+
isBefore: isBefore ?? false,
|
|
250
|
+
isAfter: isAfter ?? false,
|
|
251
|
+
});
|
|
192
252
|
return;
|
|
193
253
|
}
|
|
194
254
|
updateDate(day);
|
|
255
|
+
updateValidation();
|
|
195
256
|
setMonth(day);
|
|
196
257
|
};
|
|
197
258
|
|
|
@@ -217,13 +278,15 @@ export const useDatepicker = (
|
|
|
217
278
|
month,
|
|
218
279
|
onMonthChange: (month) => setMonth(month),
|
|
219
280
|
onDayClick: handleDayClick,
|
|
220
|
-
selected: selectedDay,
|
|
281
|
+
selected: selectedDay ?? new Date("Invalid date"),
|
|
221
282
|
locale: _locale,
|
|
222
283
|
fromDate,
|
|
223
284
|
toDate,
|
|
224
285
|
today,
|
|
225
286
|
open,
|
|
226
287
|
onOpenToggle: () => setOpen((x) => !x),
|
|
288
|
+
disabled,
|
|
289
|
+
disableWeekends,
|
|
227
290
|
ref: daypickerRef,
|
|
228
291
|
};
|
|
229
292
|
|
|
@@ -22,6 +22,15 @@ export interface UseMonthPickerOptions
|
|
|
22
22
|
* Callback for month-change
|
|
23
23
|
*/
|
|
24
24
|
onMonthChange?: (date?: Date) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Input-format
|
|
27
|
+
* @default "MMMM yyyy"
|
|
28
|
+
*/
|
|
29
|
+
inputFormat?: string;
|
|
30
|
+
/**
|
|
31
|
+
* validation-callback
|
|
32
|
+
*/
|
|
33
|
+
onValidate?: (val: MonthValidationT) => void;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
interface UseMonthPickerValue {
|
|
@@ -48,6 +57,25 @@ interface UseMonthPickerValue {
|
|
|
48
57
|
reset: () => void;
|
|
49
58
|
}
|
|
50
59
|
|
|
60
|
+
export type MonthValidationT = {
|
|
61
|
+
isDisabled: boolean;
|
|
62
|
+
isEmpty: boolean;
|
|
63
|
+
isInvalid: boolean;
|
|
64
|
+
isValidMonth: boolean;
|
|
65
|
+
isBefore: boolean;
|
|
66
|
+
isAfter: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const getValidationMessage = (val = {}): MonthValidationT => ({
|
|
70
|
+
isDisabled: false,
|
|
71
|
+
isEmpty: false,
|
|
72
|
+
isInvalid: false,
|
|
73
|
+
isBefore: false,
|
|
74
|
+
isAfter: false,
|
|
75
|
+
isValidMonth: true,
|
|
76
|
+
...val,
|
|
77
|
+
});
|
|
78
|
+
|
|
51
79
|
export const useMonthpicker = (
|
|
52
80
|
opt: UseMonthPickerOptions = {}
|
|
53
81
|
): UseMonthPickerValue => {
|
|
@@ -59,6 +87,8 @@ export const useMonthpicker = (
|
|
|
59
87
|
disabled,
|
|
60
88
|
required,
|
|
61
89
|
onMonthChange,
|
|
90
|
+
inputFormat,
|
|
91
|
+
onValidate,
|
|
62
92
|
} = opt;
|
|
63
93
|
|
|
64
94
|
const [defaultSelected, setDefaultSelected] = useState(_defaultSelected);
|
|
@@ -75,7 +105,7 @@ export const useMonthpicker = (
|
|
|
75
105
|
const [open, setOpen] = useState(false);
|
|
76
106
|
|
|
77
107
|
const defaultInputValue = defaultSelected
|
|
78
|
-
? formatDateForInput(defaultSelected, locale, "month")
|
|
108
|
+
? formatDateForInput(defaultSelected, locale, "month", inputFormat)
|
|
79
109
|
: "";
|
|
80
110
|
|
|
81
111
|
const [inputValue, setInputValue] = useState(defaultInputValue);
|
|
@@ -85,6 +115,11 @@ export const useMonthpicker = (
|
|
|
85
115
|
setSelectedMonth(date);
|
|
86
116
|
};
|
|
87
117
|
|
|
118
|
+
const updateValidation = (val: Partial<MonthValidationT> = {}) => {
|
|
119
|
+
const msg = getValidationMessage(val);
|
|
120
|
+
onValidate?.(msg);
|
|
121
|
+
};
|
|
122
|
+
|
|
88
123
|
const handleFocusIn = useCallback(
|
|
89
124
|
(e) => {
|
|
90
125
|
if (!e?.target || !e?.target?.nodeType) {
|
|
@@ -120,7 +155,9 @@ export const useMonthpicker = (
|
|
|
120
155
|
const setSelected = (date: Date | undefined) => {
|
|
121
156
|
updateMonth(date);
|
|
122
157
|
setYear(date ?? today);
|
|
123
|
-
setInputValue(
|
|
158
|
+
setInputValue(
|
|
159
|
+
date ? formatDateForInput(date, locale, "month", inputFormat) : ""
|
|
160
|
+
);
|
|
124
161
|
};
|
|
125
162
|
|
|
126
163
|
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
|
@@ -128,13 +165,14 @@ export const useMonthpicker = (
|
|
|
128
165
|
let day = parseDate(e.target.value, today, locale, "month");
|
|
129
166
|
if (isValidDate(day)) {
|
|
130
167
|
setYear(day);
|
|
131
|
-
setInputValue(formatDateForInput(day, locale, "month"));
|
|
168
|
+
setInputValue(formatDateForInput(day, locale, "month", inputFormat));
|
|
132
169
|
}
|
|
133
170
|
};
|
|
134
171
|
|
|
135
172
|
const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
|
136
173
|
let day = parseDate(e.target.value, today, locale, "month");
|
|
137
|
-
isValidDate(day) &&
|
|
174
|
+
isValidDate(day) &&
|
|
175
|
+
setInputValue(formatDateForInput(day, locale, "month", inputFormat));
|
|
138
176
|
};
|
|
139
177
|
|
|
140
178
|
/* Only allow de-selecting if not required */
|
|
@@ -146,11 +184,15 @@ export const useMonthpicker = (
|
|
|
146
184
|
|
|
147
185
|
if (!required && !month) {
|
|
148
186
|
updateMonth(undefined);
|
|
187
|
+
updateValidation({ isValidMonth: false, isEmpty: true });
|
|
149
188
|
setInputValue("");
|
|
150
189
|
return;
|
|
151
190
|
}
|
|
152
191
|
updateMonth(month);
|
|
153
|
-
|
|
192
|
+
updateValidation();
|
|
193
|
+
setInputValue(
|
|
194
|
+
month ? formatDateForInput(month, locale, "month", inputFormat) : ""
|
|
195
|
+
);
|
|
154
196
|
};
|
|
155
197
|
|
|
156
198
|
// When changing the input field, save its value in state and check if the
|
|
@@ -160,32 +202,48 @@ export const useMonthpicker = (
|
|
|
160
202
|
setInputValue(e.target.value);
|
|
161
203
|
const month = parseDate(e.target.value, today, locale, "month");
|
|
162
204
|
|
|
163
|
-
if (!isValidDate(month) || (disabled && isMatch(month, disabled))) {
|
|
164
|
-
updateMonth(undefined);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
205
|
const isBefore =
|
|
169
206
|
fromDate &&
|
|
207
|
+
month &&
|
|
170
208
|
(fromDate.getFullYear() > month.getFullYear() ||
|
|
171
209
|
(fromDate.getFullYear() === month.getFullYear() &&
|
|
172
210
|
fromDate.getMonth() > month.getMonth()));
|
|
173
211
|
|
|
174
212
|
const isAfter =
|
|
175
213
|
toDate &&
|
|
214
|
+
month &&
|
|
176
215
|
(toDate.getFullYear() < month.getFullYear() ||
|
|
177
216
|
(toDate.getFullYear() === month.getFullYear() &&
|
|
178
217
|
toDate.getMonth() < month.getMonth()));
|
|
179
218
|
|
|
219
|
+
if (!isValidDate(month) || (disabled && isMatch(month, disabled))) {
|
|
220
|
+
updateMonth(undefined);
|
|
221
|
+
updateValidation({
|
|
222
|
+
isInvalid: isValidDate(month),
|
|
223
|
+
isDisabled: disabled && isMatch(month, disabled),
|
|
224
|
+
isValidMonth: false,
|
|
225
|
+
isEmpty: !e.target.value,
|
|
226
|
+
isBefore: isBefore ?? false,
|
|
227
|
+
isAfter: isAfter ?? false,
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
180
232
|
if (
|
|
181
233
|
isAfter ||
|
|
182
234
|
isBefore ||
|
|
183
235
|
(fromDate && toDate && !isMatch(month, [{ from: fromDate, to: toDate }]))
|
|
184
236
|
) {
|
|
185
237
|
updateMonth(undefined);
|
|
238
|
+
updateValidation({
|
|
239
|
+
isValidMonth: false,
|
|
240
|
+
isBefore: isBefore ?? false,
|
|
241
|
+
isAfter: isAfter ?? false,
|
|
242
|
+
});
|
|
186
243
|
return;
|
|
187
244
|
}
|
|
188
245
|
updateMonth(month);
|
|
246
|
+
updateValidation();
|
|
189
247
|
setYear(month);
|
|
190
248
|
};
|
|
191
249
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* eslint-disable react/jsx-pascal-case */
|
|
2
|
+
import { act, render } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { UNSAFE_DatePicker, UNSAFE_useRangeDatepicker } from "..";
|
|
6
|
+
|
|
7
|
+
const RangeDemo = () => {
|
|
8
|
+
const { datepickerProps, fromInputProps, selectedRange, toInputProps } =
|
|
9
|
+
UNSAFE_useRangeDatepicker({
|
|
10
|
+
fromDate: new Date("Aug 23 2019"),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ display: "flex", gap: "1rem" }}>
|
|
15
|
+
<UNSAFE_DatePicker {...datepickerProps}>
|
|
16
|
+
<UNSAFE_DatePicker.Input {...fromInputProps} label="Fra" />
|
|
17
|
+
<UNSAFE_DatePicker.Input {...toInputProps} label="Til" />
|
|
18
|
+
</UNSAFE_DatePicker>
|
|
19
|
+
<div title="res">{JSON.stringify(selectedRange)}</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe("Writing in input sets correct values", () => {
|
|
25
|
+
it("useRangeDatepicker same date", async () => {
|
|
26
|
+
const utils = render(<RangeDemo />);
|
|
27
|
+
|
|
28
|
+
const fraInput = utils.getByRole("textbox", { name: "Fra" });
|
|
29
|
+
const tilInput = utils.getByRole("textbox", { name: "Til" });
|
|
30
|
+
await act(async () => {
|
|
31
|
+
await userEvent.type(fraInput, "03.08.2022");
|
|
32
|
+
await userEvent.type(tilInput, "03.08.2022");
|
|
33
|
+
});
|
|
34
|
+
const res = utils.getByTitle("res");
|
|
35
|
+
expect(res.innerHTML).toEqual(
|
|
36
|
+
JSON.stringify({
|
|
37
|
+
from: "2022-08-02T22:00:00.000Z",
|
|
38
|
+
to: "2022-08-02T22:00:00.000Z",
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("useRangeDatepicker before after to ", async () => {
|
|
44
|
+
const utils = render(<RangeDemo />);
|
|
45
|
+
|
|
46
|
+
const fraInput = utils.getByRole("textbox", { name: "Fra" });
|
|
47
|
+
const tilInput = utils.getByRole("textbox", { name: "Til" });
|
|
48
|
+
await act(async () => {
|
|
49
|
+
await userEvent.type(fraInput, "03.08.2022");
|
|
50
|
+
await userEvent.type(tilInput, "02.08.2022");
|
|
51
|
+
});
|
|
52
|
+
const res = utils.getByTitle("res");
|
|
53
|
+
expect(res.innerHTML).toEqual(
|
|
54
|
+
JSON.stringify({
|
|
55
|
+
from: "2022-08-02T22:00:00.000Z",
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|