@navikt/ds-react 1.2.2 → 1.3.0-alpha.0
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 +1119 -319
- package/cjs/datepicker/DatePicker.js +122 -0
- package/cjs/datepicker/DatePickerInput.js +68 -0
- package/cjs/datepicker/DatePickerStandalone.js +80 -0
- package/cjs/datepicker/caption/Caption.js +23 -0
- package/cjs/datepicker/caption/DropdownCaption.js +36 -0
- package/cjs/datepicker/caption/index.js +10 -0
- package/cjs/datepicker/caption/package.json +6 -0
- package/cjs/datepicker/hooks/index.js +7 -0
- package/cjs/datepicker/hooks/package.json +6 -0
- package/cjs/datepicker/hooks/useDatepicker.js +101 -0
- package/cjs/datepicker/hooks/useRangeDatepicker.js +174 -0
- package/cjs/datepicker/index.js +11 -0
- package/cjs/datepicker/package.json +6 -0
- package/cjs/datepicker/utils/dates-disabled.js +29 -0
- package/cjs/datepicker/utils/format-date.js +7 -0
- package/cjs/datepicker/utils/get-dates.js +43 -0
- package/cjs/datepicker/utils/index.js +19 -0
- package/cjs/datepicker/utils/labels.js +59 -0
- package/cjs/datepicker/utils/locale.js +21 -0
- package/cjs/datepicker/utils/package.json +6 -0
- package/cjs/datepicker/utils/parse-date.js +29 -0
- package/cjs/datepicker/utils/valid-date.js +8 -0
- package/cjs/index.js +1 -0
- package/cjs/monthpicker/MonthPicker.js +112 -0
- package/cjs/monthpicker/index.js +8 -0
- package/cjs/monthpicker/package.json +6 -0
- package/cjs/monthpicker/utils/check-dates.js +12 -0
- package/cjs/monthpicker/utils/handle-selected.js +17 -0
- package/esm/chat/Chat.d.ts +1 -1
- package/esm/datepicker/DatePicker.d.ts +107 -0
- package/esm/datepicker/DatePicker.js +94 -0
- package/esm/datepicker/DatePicker.js.map +1 -0
- package/esm/datepicker/DatePickerInput.d.ts +25 -0
- package/esm/datepicker/DatePickerInput.js +40 -0
- package/esm/datepicker/DatePickerInput.js.map +1 -0
- package/esm/datepicker/DatePickerStandalone.d.ts +12 -0
- package/esm/datepicker/DatePickerStandalone.js +52 -0
- package/esm/datepicker/DatePickerStandalone.js.map +1 -0
- package/esm/datepicker/caption/Caption.d.ts +4 -0
- package/esm/datepicker/caption/Caption.js +17 -0
- package/esm/datepicker/caption/Caption.js.map +1 -0
- package/esm/datepicker/caption/DropdownCaption.d.ts +4 -0
- package/esm/datepicker/caption/DropdownCaption.js +30 -0
- package/esm/datepicker/caption/DropdownCaption.js.map +1 -0
- package/esm/datepicker/caption/index.d.ts +2 -0
- package/esm/datepicker/caption/index.js +3 -0
- package/esm/datepicker/caption/index.js.map +1 -0
- package/esm/datepicker/hooks/index.d.ts +2 -0
- package/esm/datepicker/hooks/index.js +3 -0
- package/esm/datepicker/hooks/index.js.map +1 -0
- package/esm/datepicker/hooks/useDatepicker.d.ts +37 -0
- package/esm/datepicker/hooks/useDatepicker.js +98 -0
- package/esm/datepicker/hooks/useDatepicker.js.map +1 -0
- package/esm/datepicker/hooks/useRangeDatepicker.d.ts +36 -0
- package/esm/datepicker/hooks/useRangeDatepicker.js +171 -0
- package/esm/datepicker/hooks/useRangeDatepicker.js.map +1 -0
- package/esm/datepicker/index.d.ts +5 -0
- package/esm/datepicker/index.js +3 -0
- package/esm/datepicker/index.js.map +1 -0
- package/esm/datepicker/utils/dates-disabled.d.ts +1 -0
- package/esm/datepicker/utils/dates-disabled.js +26 -0
- package/esm/datepicker/utils/dates-disabled.js.map +1 -0
- package/esm/datepicker/utils/format-date.d.ts +1 -0
- package/esm/datepicker/utils/format-date.js +4 -0
- package/esm/datepicker/utils/format-date.js.map +1 -0
- package/esm/datepicker/utils/get-dates.d.ts +2 -0
- package/esm/datepicker/utils/get-dates.js +39 -0
- package/esm/datepicker/utils/get-dates.js.map +1 -0
- package/esm/datepicker/utils/index.d.ts +6 -0
- package/esm/datepicker/utils/index.js +7 -0
- package/esm/datepicker/utils/index.js.map +1 -0
- package/esm/datepicker/utils/labels.d.ts +4 -0
- package/esm/datepicker/utils/labels.js +55 -0
- package/esm/datepicker/utils/labels.js.map +1 -0
- package/esm/datepicker/utils/locale.d.ts +2 -0
- package/esm/datepicker/utils/locale.js +15 -0
- package/esm/datepicker/utils/locale.js.map +1 -0
- package/esm/datepicker/utils/parse-date.d.ts +2 -0
- package/esm/datepicker/utils/parse-date.js +26 -0
- package/esm/datepicker/utils/parse-date.js.map +1 -0
- package/esm/datepicker/utils/valid-date.d.ts +2 -0
- package/esm/datepicker/utils/valid-date.js +5 -0
- package/esm/datepicker/utils/valid-date.js.map +1 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/monthpicker/MonthPicker.d.ts +27 -0
- package/esm/monthpicker/MonthPicker.js +84 -0
- package/esm/monthpicker/MonthPicker.js.map +1 -0
- package/esm/monthpicker/index.d.ts +2 -0
- package/esm/monthpicker/index.js +2 -0
- package/esm/monthpicker/index.js.map +1 -0
- package/esm/monthpicker/utils/check-dates.d.ts +2 -0
- package/esm/monthpicker/utils/check-dates.js +8 -0
- package/esm/monthpicker/utils/check-dates.js.map +1 -0
- package/esm/monthpicker/utils/handle-selected.d.ts +3 -0
- package/esm/monthpicker/utils/handle-selected.js +12 -0
- package/esm/monthpicker/utils/handle-selected.js.map +1 -0
- package/package.json +7 -3
- package/src/chat/Chat.tsx +1 -1
- package/src/chat/chat.stories.tsx +1 -5
- package/src/datepicker/DatePicker.tsx +281 -0
- package/src/datepicker/DatePickerInput.tsx +131 -0
- package/src/datepicker/DatePickerStandalone.tsx +121 -0
- package/src/datepicker/caption/Caption.tsx +51 -0
- package/src/datepicker/caption/DropdownCaption.tsx +94 -0
- package/src/datepicker/caption/index.ts +2 -0
- package/src/datepicker/datepicker.stories.mdx +467 -0
- package/src/datepicker/datepicker.stories.tsx +257 -0
- package/src/datepicker/hooks/index.ts +2 -0
- package/src/datepicker/hooks/useDatepicker.tsx +181 -0
- package/src/datepicker/hooks/useRangeDatepicker.tsx +285 -0
- package/src/datepicker/index.ts +5 -0
- package/src/datepicker/utils/__tests__/dates-disabled.test.ts +48 -0
- package/src/datepicker/utils/__tests__/format-dates.test.ts +14 -0
- package/src/datepicker/utils/__tests__/get-dates.test.ts +79 -0
- package/src/datepicker/utils/__tests__/parse-dates.test.ts +81 -0
- package/src/datepicker/utils/dates-disabled.ts +26 -0
- package/src/datepicker/utils/format-date.ts +5 -0
- package/src/datepicker/utils/get-dates.ts +44 -0
- package/src/datepicker/utils/index.ts +6 -0
- package/src/datepicker/utils/labels.ts +58 -0
- package/src/datepicker/utils/locale.ts +15 -0
- package/src/datepicker/utils/parse-date.ts +28 -0
- package/src/datepicker/utils/valid-date.ts +4 -0
- package/src/index.ts +1 -0
- package/src/monthpicker/MonthPicker.tsx +238 -0
- package/src/monthpicker/index.ts +2 -0
- package/src/monthpicker/monthpicker.stories.tsx +35 -0
- package/src/monthpicker/utils/__tests__/check-dates.test.ts +34 -0
- package/src/monthpicker/utils/__tests__/handle-selected.test.ts +87 -0
- package/src/monthpicker/utils/check-dates.ts +15 -0
- package/src/monthpicker/utils/handle-selected.ts +26 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { differenceInCalendarDays } from "date-fns";
|
|
2
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { DayClickEventHandler } from "react-day-picker";
|
|
4
|
+
import { DatePickerProps } from "../DatePicker";
|
|
5
|
+
import { DatePickerInputProps } from "../DatePickerInput";
|
|
6
|
+
import { formatDateForInput, getLocaleFromString, isValidDate } from "../utils";
|
|
7
|
+
import { parseDate } from "../utils/parse-date";
|
|
8
|
+
|
|
9
|
+
export interface UseDatepickerOptions
|
|
10
|
+
extends Pick<
|
|
11
|
+
DatePickerProps,
|
|
12
|
+
| "locale"
|
|
13
|
+
| "fromDate"
|
|
14
|
+
| "toDate"
|
|
15
|
+
| "today"
|
|
16
|
+
| "toDate"
|
|
17
|
+
| "fromDate"
|
|
18
|
+
| "toDate"
|
|
19
|
+
> {
|
|
20
|
+
/** The initially selected date */
|
|
21
|
+
defaultSelected?: Date;
|
|
22
|
+
/** Make the selection required. */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Opens datepicker on input-focus
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
openOnFocus?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface UseDatepickerValue {
|
|
32
|
+
/**
|
|
33
|
+
* Use: <DatePicker {...dayPickerProps}/>
|
|
34
|
+
*/
|
|
35
|
+
dayPickerProps: DatePickerProps;
|
|
36
|
+
/**
|
|
37
|
+
* Use: <DatePicker.Input {...inputProps}/>
|
|
38
|
+
*/
|
|
39
|
+
inputProps: Pick<
|
|
40
|
+
DatePickerInputProps,
|
|
41
|
+
"onChange" | "onFocus" | "onBlur" | "value" | "wrapperRef"
|
|
42
|
+
>;
|
|
43
|
+
/**
|
|
44
|
+
* Resets all states
|
|
45
|
+
*/
|
|
46
|
+
reset: () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Selected Date callback
|
|
49
|
+
*/
|
|
50
|
+
selectedDay?: Date;
|
|
51
|
+
/**
|
|
52
|
+
* Manually set selected day if needed
|
|
53
|
+
*/
|
|
54
|
+
setSelected: (date?: Date) => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const useDatepicker = (
|
|
58
|
+
opt: UseDatepickerOptions = {}
|
|
59
|
+
): UseDatepickerValue => {
|
|
60
|
+
const {
|
|
61
|
+
locale: _locale = "nb",
|
|
62
|
+
required,
|
|
63
|
+
defaultSelected,
|
|
64
|
+
today = new Date(),
|
|
65
|
+
fromDate,
|
|
66
|
+
toDate,
|
|
67
|
+
openOnFocus = true,
|
|
68
|
+
} = opt;
|
|
69
|
+
|
|
70
|
+
const locale = getLocaleFromString(_locale);
|
|
71
|
+
|
|
72
|
+
const inputRef = useRef<HTMLDivElement>(null);
|
|
73
|
+
const daypickerRef = useRef<HTMLDivElement>(null);
|
|
74
|
+
|
|
75
|
+
// Initialize states
|
|
76
|
+
const [month, setMonth] = useState(defaultSelected ?? today);
|
|
77
|
+
const [selectedDay, setSelectedDay] = useState(defaultSelected);
|
|
78
|
+
const [open, setOpen] = useState(false);
|
|
79
|
+
|
|
80
|
+
const defaultInputValue = defaultSelected
|
|
81
|
+
? formatDateForInput(defaultSelected, locale)
|
|
82
|
+
: "";
|
|
83
|
+
const [inputValue, setInputValue] = useState(defaultInputValue);
|
|
84
|
+
|
|
85
|
+
const handleFocusOut = useCallback(
|
|
86
|
+
(e) =>
|
|
87
|
+
![daypickerRef.current, inputRef.current].some((element) =>
|
|
88
|
+
element?.contains(e.relatedTarget)
|
|
89
|
+
) && setOpen(false),
|
|
90
|
+
[]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const el = inputRef.current;
|
|
95
|
+
el?.addEventListener("focusout", handleFocusOut);
|
|
96
|
+
return () => el?.removeEventListener?.("focusout", handleFocusOut);
|
|
97
|
+
}, [handleFocusOut]);
|
|
98
|
+
|
|
99
|
+
const reset = () => {
|
|
100
|
+
setSelectedDay(defaultSelected);
|
|
101
|
+
setMonth(defaultSelected ?? today);
|
|
102
|
+
setInputValue(defaultInputValue ?? "");
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const setSelected = (date: Date | undefined) => {
|
|
106
|
+
setSelectedDay(date);
|
|
107
|
+
setMonth(date ?? today);
|
|
108
|
+
setInputValue(date ? formatDateForInput(date, locale) : "");
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
|
112
|
+
!open && openOnFocus && setOpen(true);
|
|
113
|
+
if (!e.target.value) {
|
|
114
|
+
reset();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
let day = parseDate(e.target.value, today, locale);
|
|
118
|
+
if (isValidDate(day)) {
|
|
119
|
+
setMonth(day);
|
|
120
|
+
setInputValue(formatDateForInput(day, locale));
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
|
125
|
+
let day = parseDate(e.target.value, today, locale);
|
|
126
|
+
isValidDate(day) && setInputValue(formatDateForInput(day, locale));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/* Only allow de-selecting if not required */
|
|
130
|
+
const handleDayClick: DayClickEventHandler = (day, { selected }) => {
|
|
131
|
+
if (!required && selected) {
|
|
132
|
+
setSelectedDay(undefined);
|
|
133
|
+
setInputValue("");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setSelectedDay(day);
|
|
137
|
+
setInputValue(day ? formatDateForInput(day, locale) : "");
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// When changing the input field, save its value in state and check if the
|
|
141
|
+
// string is a valid date. If it is a valid day, set it as selected and update
|
|
142
|
+
// the calendar’s month.
|
|
143
|
+
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
|
144
|
+
setInputValue(e.target.value);
|
|
145
|
+
const day = parseDate(e.target.value, today, locale);
|
|
146
|
+
|
|
147
|
+
const isBefore = fromDate && differenceInCalendarDays(fromDate, day) > 0;
|
|
148
|
+
const isAfter = toDate && differenceInCalendarDays(day, toDate) > 0;
|
|
149
|
+
if (!isValidDate(day) || isBefore || isAfter) {
|
|
150
|
+
setSelectedDay(undefined);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
setSelectedDay(day);
|
|
154
|
+
setMonth(day);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const dayPickerProps = {
|
|
158
|
+
month,
|
|
159
|
+
onMonthChange: (month) => setMonth(month),
|
|
160
|
+
onDayClick: handleDayClick,
|
|
161
|
+
selected: selectedDay,
|
|
162
|
+
locale: _locale,
|
|
163
|
+
fromDate,
|
|
164
|
+
toDate,
|
|
165
|
+
today,
|
|
166
|
+
open,
|
|
167
|
+
onClose: () => setOpen(false),
|
|
168
|
+
onOpenToggle: () => setOpen((x) => !x),
|
|
169
|
+
ref: daypickerRef,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const inputProps = {
|
|
173
|
+
onChange: handleChange,
|
|
174
|
+
onFocus: handleFocus,
|
|
175
|
+
onBlur: handleBlur,
|
|
176
|
+
value: inputValue,
|
|
177
|
+
wrapperRef: inputRef,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return { dayPickerProps, inputProps, reset, selectedDay, setSelected };
|
|
181
|
+
};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { differenceInCalendarDays } from "date-fns";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { DateRange } from "react-day-picker";
|
|
4
|
+
import { DatePickerProps } from "../DatePicker";
|
|
5
|
+
import { DatePickerInputProps } from "../DatePickerInput";
|
|
6
|
+
import {
|
|
7
|
+
formatDateForInput,
|
|
8
|
+
getLocaleFromString,
|
|
9
|
+
isValidDate,
|
|
10
|
+
parseDate,
|
|
11
|
+
} from "../utils";
|
|
12
|
+
import { UseDatepickerOptions } from "./useDatepicker";
|
|
13
|
+
|
|
14
|
+
interface UseRangeDatepickerOptions
|
|
15
|
+
extends Omit<UseDatepickerOptions, "defaultSelected"> {
|
|
16
|
+
/** The initially selected date-range */
|
|
17
|
+
defaultSelected?: DateRange;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface UseRangeDatepickerValue {
|
|
21
|
+
/**
|
|
22
|
+
* Use: <DatePicker {...dayPickerProps}/>
|
|
23
|
+
*/
|
|
24
|
+
dayPickerProps: DatePickerProps;
|
|
25
|
+
/**
|
|
26
|
+
* Use: <DatePicker.Input label="from" {...fromInputProps}/>
|
|
27
|
+
*/
|
|
28
|
+
fromInputProps: Pick<
|
|
29
|
+
DatePickerInputProps,
|
|
30
|
+
"onChange" | "onFocus" | "onBlur" | "value"
|
|
31
|
+
>;
|
|
32
|
+
/**
|
|
33
|
+
* Use: <DatePicker.Input label="to" {...toInputProps}/>
|
|
34
|
+
*/
|
|
35
|
+
toInputProps: Pick<
|
|
36
|
+
DatePickerInputProps,
|
|
37
|
+
"onChange" | "onFocus" | "onBlur" | "value"
|
|
38
|
+
>;
|
|
39
|
+
/**
|
|
40
|
+
* Resets all states
|
|
41
|
+
*/
|
|
42
|
+
reset: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Selected range-callback
|
|
45
|
+
*/
|
|
46
|
+
selectedRange?: DateRange;
|
|
47
|
+
/**
|
|
48
|
+
* Manually set selected range if needed
|
|
49
|
+
*/
|
|
50
|
+
setSelected: (date?: DateRange) => void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const RANGE = {
|
|
54
|
+
FROM: "FROM",
|
|
55
|
+
TO: "TO",
|
|
56
|
+
} as const;
|
|
57
|
+
|
|
58
|
+
type RangeT = typeof RANGE[keyof typeof RANGE];
|
|
59
|
+
|
|
60
|
+
export const useRangeDatepicker = (
|
|
61
|
+
opt: UseRangeDatepickerOptions = {}
|
|
62
|
+
): UseRangeDatepickerValue => {
|
|
63
|
+
const {
|
|
64
|
+
locale: _locale = "nb",
|
|
65
|
+
defaultSelected,
|
|
66
|
+
today = new Date(),
|
|
67
|
+
fromDate,
|
|
68
|
+
toDate,
|
|
69
|
+
openOnFocus = true,
|
|
70
|
+
} = opt;
|
|
71
|
+
|
|
72
|
+
const locale = getLocaleFromString(_locale);
|
|
73
|
+
|
|
74
|
+
const inputRefTo = useRef<HTMLDivElement>(null);
|
|
75
|
+
const inputRefFrom = useRef<HTMLDivElement>(null);
|
|
76
|
+
const datePickerRef = useRef<HTMLDivElement | null>(null);
|
|
77
|
+
|
|
78
|
+
// Initialize states
|
|
79
|
+
const [month, setMonth] = useState(
|
|
80
|
+
defaultSelected ? defaultSelected.from : today
|
|
81
|
+
);
|
|
82
|
+
const [selectedRange, setSelectedRange] = useState(defaultSelected);
|
|
83
|
+
|
|
84
|
+
const [fromInputValue, setFromInputValue] = useState(
|
|
85
|
+
defaultSelected?.from
|
|
86
|
+
? formatDateForInput(defaultSelected.from, locale)
|
|
87
|
+
: ""
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const [toInputValue, setToInputValue] = useState(
|
|
91
|
+
defaultSelected?.to ? formatDateForInput(defaultSelected.to, locale) : ""
|
|
92
|
+
);
|
|
93
|
+
const [open, setOpen] = useState(false);
|
|
94
|
+
|
|
95
|
+
const handleFocusOut = useCallback(
|
|
96
|
+
(e) =>
|
|
97
|
+
![datePickerRef.current, inputRefTo.current, inputRefFrom.current].some(
|
|
98
|
+
(element) => element?.contains(e.relatedTarget)
|
|
99
|
+
) && setOpen(false),
|
|
100
|
+
[]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const from = inputRefFrom.current;
|
|
105
|
+
const to = inputRefTo.current;
|
|
106
|
+
from?.addEventListener("focusout", handleFocusOut);
|
|
107
|
+
to?.addEventListener("focusout", handleFocusOut);
|
|
108
|
+
return () => {
|
|
109
|
+
from?.removeEventListener?.("focusout", handleFocusOut);
|
|
110
|
+
to?.removeEventListener?.("focusout", handleFocusOut);
|
|
111
|
+
};
|
|
112
|
+
}, [handleFocusOut]);
|
|
113
|
+
|
|
114
|
+
const reset = () => {
|
|
115
|
+
setSelectedRange(defaultSelected);
|
|
116
|
+
setMonth(defaultSelected ? defaultSelected.from : today);
|
|
117
|
+
setFromInputValue(
|
|
118
|
+
defaultSelected?.from
|
|
119
|
+
? formatDateForInput(defaultSelected.from, locale)
|
|
120
|
+
: ""
|
|
121
|
+
);
|
|
122
|
+
setToInputValue(
|
|
123
|
+
defaultSelected?.to ? formatDateForInput(defaultSelected.to, locale) : ""
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const setSelected = (range?: DateRange) => {
|
|
128
|
+
setSelectedRange(range);
|
|
129
|
+
setFromInputValue(
|
|
130
|
+
range?.from ? formatDateForInput(range.from, locale) : ""
|
|
131
|
+
);
|
|
132
|
+
setToInputValue(range?.to ? formatDateForInput(range?.to, locale) : "");
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const handleFocus = (e, src: RangeT) => {
|
|
136
|
+
!open && openOnFocus && setOpen(true);
|
|
137
|
+
let day = parseDate(e.target.value, today, locale);
|
|
138
|
+
if (isValidDate(day)) {
|
|
139
|
+
setMonth(day);
|
|
140
|
+
src === RANGE.FROM
|
|
141
|
+
? setFromInputValue(formatDateForInput(day, locale))
|
|
142
|
+
: setToInputValue(formatDateForInput(day, locale));
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const handleInputs = (day: Date, src: RangeT) => {
|
|
147
|
+
if (src === RANGE.FROM) {
|
|
148
|
+
const isAfter =
|
|
149
|
+
toInputValue &&
|
|
150
|
+
differenceInCalendarDays(day, parseDate(toInputValue, today, locale)) >
|
|
151
|
+
0;
|
|
152
|
+
|
|
153
|
+
if (isAfter) {
|
|
154
|
+
setFromInputValue(
|
|
155
|
+
formatDateForInput(parseDate(toInputValue, today, locale), locale)
|
|
156
|
+
);
|
|
157
|
+
setToInputValue(formatDateForInput(day, locale));
|
|
158
|
+
} else {
|
|
159
|
+
setFromInputValue(formatDateForInput(day, locale));
|
|
160
|
+
}
|
|
161
|
+
} else if (src === RANGE.TO) {
|
|
162
|
+
const isBefore =
|
|
163
|
+
fromInputValue &&
|
|
164
|
+
differenceInCalendarDays(
|
|
165
|
+
parseDate(fromInputValue, today, locale),
|
|
166
|
+
day
|
|
167
|
+
) > 0;
|
|
168
|
+
|
|
169
|
+
if (isBefore) {
|
|
170
|
+
setToInputValue(
|
|
171
|
+
formatDateForInput(parseDate(fromInputValue, today, locale), locale)
|
|
172
|
+
);
|
|
173
|
+
setFromInputValue(formatDateForInput(day, locale));
|
|
174
|
+
} else {
|
|
175
|
+
setToInputValue(formatDateForInput(day, locale));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const handleBlur = (e, src: RangeT) => {
|
|
181
|
+
let day = parseDate(e.target.value, today, locale);
|
|
182
|
+
if (!isValidDate(day)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
handleInputs(day, src);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const handleSelect = (range) => {
|
|
190
|
+
const prevToRange =
|
|
191
|
+
!selectedRange?.from && selectedRange?.to ? selectedRange?.to : range?.to;
|
|
192
|
+
|
|
193
|
+
range?.from
|
|
194
|
+
? setFromInputValue(formatDateForInput(range?.from, locale))
|
|
195
|
+
: setFromInputValue("");
|
|
196
|
+
prevToRange
|
|
197
|
+
? setToInputValue(formatDateForInput(prevToRange, locale))
|
|
198
|
+
: setToInputValue("");
|
|
199
|
+
setSelectedRange({ from: range?.from, to: prevToRange });
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/* live-update datepicker based on changes in inputfields */
|
|
203
|
+
const handleChange = (e, src: RangeT) => {
|
|
204
|
+
src === RANGE.FROM
|
|
205
|
+
? setFromInputValue(e.target.value)
|
|
206
|
+
: setToInputValue(e.target.value);
|
|
207
|
+
const day = parseDate(e.target.value, today, locale);
|
|
208
|
+
|
|
209
|
+
const isBefore = fromDate && differenceInCalendarDays(fromDate, day) > 0;
|
|
210
|
+
const isAfter = toDate && differenceInCalendarDays(day, toDate) > 0;
|
|
211
|
+
if (!isValidDate(day) || isBefore || isAfter) {
|
|
212
|
+
src === RANGE.FROM
|
|
213
|
+
? setSelectedRange((x) => ({ ...x, from: undefined }))
|
|
214
|
+
: setSelectedRange((x) => ({ from: x?.from, to: undefined }));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* If to-value < from-value, switch places in state */
|
|
219
|
+
if (
|
|
220
|
+
src === RANGE.TO &&
|
|
221
|
+
selectedRange?.from &&
|
|
222
|
+
differenceInCalendarDays(selectedRange?.from, day) >= 0
|
|
223
|
+
) {
|
|
224
|
+
setSelectedRange({ from: day, to: selectedRange?.from });
|
|
225
|
+
setMonth(day);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* If from-value > to-value , switch places in state */
|
|
230
|
+
if (
|
|
231
|
+
src === RANGE.FROM &&
|
|
232
|
+
selectedRange?.to &&
|
|
233
|
+
differenceInCalendarDays(day, selectedRange?.to) >= 0
|
|
234
|
+
) {
|
|
235
|
+
setSelectedRange({ to: day, from: selectedRange?.to });
|
|
236
|
+
setMonth(day);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
src === RANGE.FROM && setSelectedRange((x) => ({ ...x, from: day }));
|
|
241
|
+
src === RANGE.TO && setSelectedRange((x) => ({ from: x?.from, to: day }));
|
|
242
|
+
setMonth(day);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const dayPickerProps = {
|
|
246
|
+
month: month,
|
|
247
|
+
onMonthChange: (month) => setMonth(month),
|
|
248
|
+
onSelect: handleSelect,
|
|
249
|
+
selected: selectedRange,
|
|
250
|
+
locale: _locale,
|
|
251
|
+
fromDate,
|
|
252
|
+
toDate,
|
|
253
|
+
today,
|
|
254
|
+
mode: "range" as const,
|
|
255
|
+
open,
|
|
256
|
+
onClose: () => setOpen(false),
|
|
257
|
+
onOpenToggle: () => setOpen((x) => !x),
|
|
258
|
+
ref: datePickerRef,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const fromInputProps = {
|
|
262
|
+
onChange: (e) => handleChange(e, RANGE.FROM),
|
|
263
|
+
onFocus: (e) => handleFocus(e, RANGE.FROM),
|
|
264
|
+
onBlur: (e) => handleBlur(e, RANGE.FROM),
|
|
265
|
+
value: fromInputValue,
|
|
266
|
+
wrapperRef: inputRefFrom,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const toInputProps = {
|
|
270
|
+
onChange: (e) => handleChange(e, RANGE.TO),
|
|
271
|
+
onFocus: (e) => handleFocus(e, RANGE.TO),
|
|
272
|
+
onBlur: (e) => handleBlur(e, RANGE.TO),
|
|
273
|
+
value: toInputValue,
|
|
274
|
+
wrapperRef: inputRefTo,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
dayPickerProps,
|
|
279
|
+
fromInputProps,
|
|
280
|
+
toInputProps,
|
|
281
|
+
reset,
|
|
282
|
+
selectedRange,
|
|
283
|
+
setSelected,
|
|
284
|
+
};
|
|
285
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as DatePicker } from "./DatePicker";
|
|
2
|
+
export { DatePickerProps } from "./DatePicker";
|
|
3
|
+
export { DatePickerStandaloneProps } from "./DatePickerStandalone";
|
|
4
|
+
export { DatePickerInputProps } from "./DatePickerInput";
|
|
5
|
+
export { useDatepicker, useRangeDatepicker } from "./hooks";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { disableDate } from "../dates-disabled";
|
|
2
|
+
|
|
3
|
+
describe("Returns if date should be disabled", () => {
|
|
4
|
+
test("Should be disabled using Date (true)", () => {
|
|
5
|
+
const dateToDisable = new Date("Aug 3 2022");
|
|
6
|
+
expect(disableDate(dateToDisable, new Date("Aug 3 2022"))).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("Should not be disabled using Date (false)", () => {
|
|
10
|
+
const dateToDisable = new Date("Aug 3 2022");
|
|
11
|
+
expect(disableDate(dateToDisable, new Date("Okt 11 2019"))).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("Should be disabled using Array (true)", () => {
|
|
15
|
+
const dateToDisable = [
|
|
16
|
+
new Date("Nov 2 2019"),
|
|
17
|
+
new Date("Aug 13 2021"),
|
|
18
|
+
new Date("Jul 17 2018"),
|
|
19
|
+
];
|
|
20
|
+
expect(disableDate(dateToDisable, new Date("Jul 17 2018"))).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("Should not be disabled using Array (false)", () => {
|
|
24
|
+
const dateToDisable = [
|
|
25
|
+
new Date("Nov 2 2019"),
|
|
26
|
+
new Date("Aug 13 2021"),
|
|
27
|
+
new Date("Jul 17 2018"),
|
|
28
|
+
];
|
|
29
|
+
expect(disableDate(dateToDisable, new Date("Jul 18 2018"))).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("Should be disabled using Array with range (true)", () => {
|
|
33
|
+
const dateToDisable = [
|
|
34
|
+
{ from: new Date("Jul 05 2018"), to: new Date("Sept 09 2020") },
|
|
35
|
+
{ from: new Date("Sep 18 2020"), to: new Date("Des 09 2020") },
|
|
36
|
+
new Date("Nov 2 2019"),
|
|
37
|
+
];
|
|
38
|
+
expect(disableDate(dateToDisable, new Date("Jul 18 2018"))).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("Should not be disabled using Array with range (false)", () => {
|
|
42
|
+
const dateToDisable = [
|
|
43
|
+
{ from: new Date("Jul 05 2018"), to: new Date("Sept 09 2020") },
|
|
44
|
+
new Date("Nov 2 2019"),
|
|
45
|
+
];
|
|
46
|
+
expect(disableDate(dateToDisable, new Date("Jul 4 2018"))).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { parseDate } from "../parse-date";
|
|
2
|
+
import nb from "date-fns/locale/nb";
|
|
3
|
+
import { formatDateForInput } from "../format-date";
|
|
4
|
+
|
|
5
|
+
const parse = (inp: string) => parseDate(inp, new Date(), nb);
|
|
6
|
+
|
|
7
|
+
describe("Format date to correct output", () => {
|
|
8
|
+
test("formatDateForInput", () => {
|
|
9
|
+
expect(formatDateForInput(parse("15/05/22"), nb)).toEqual("15.05.2022");
|
|
10
|
+
expect(formatDateForInput(parse("1/5/22"), nb)).toEqual("01.05.2022");
|
|
11
|
+
expect(formatDateForInput(parse("1/05/22"), nb)).toEqual("01.05.2022");
|
|
12
|
+
expect(formatDateForInput(parse("15/5/22"), nb)).toEqual("15.05.2022");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getMonths, getYears } from "../get-dates";
|
|
2
|
+
|
|
3
|
+
describe("Extracts correct months", () => {
|
|
4
|
+
test("March - October (8)", () => {
|
|
5
|
+
const t = {
|
|
6
|
+
start: new Date(2019, 2, 22),
|
|
7
|
+
end: new Date(2019, 9, 22),
|
|
8
|
+
current: new Date(2019, 6, 22),
|
|
9
|
+
res: 8,
|
|
10
|
+
};
|
|
11
|
+
expect(getMonths(t.start, t.end, t.current).length).toEqual(t.res);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("Only first 7 months", () => {
|
|
15
|
+
const t = {
|
|
16
|
+
start: new Date(2019, 0, 22),
|
|
17
|
+
end: new Date(2022, 7, 22),
|
|
18
|
+
current: new Date(2022, 6, 22),
|
|
19
|
+
res: 8,
|
|
20
|
+
};
|
|
21
|
+
expect(getMonths(t.start, t.end, t.current).length).toEqual(t.res);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("All of 2019 when only 2019 avaliable (12)", () => {
|
|
25
|
+
const t = {
|
|
26
|
+
start: new Date(2019, 0, 22),
|
|
27
|
+
end: new Date(2019, 11, 22),
|
|
28
|
+
current: new Date(2019, 6, 22),
|
|
29
|
+
res: 12,
|
|
30
|
+
};
|
|
31
|
+
expect(getMonths(t.start, t.end, t.current).length).toEqual(t.res);
|
|
32
|
+
});
|
|
33
|
+
test("All of 2019 when start + current is 2019 and end is later(12)", () => {
|
|
34
|
+
const t = {
|
|
35
|
+
start: new Date(2019, 0, 22),
|
|
36
|
+
end: new Date(2022, 7, 22),
|
|
37
|
+
current: new Date(2019, 6, 22),
|
|
38
|
+
res: 12,
|
|
39
|
+
};
|
|
40
|
+
expect(getMonths(t.start, t.end, t.current).length).toEqual(t.res);
|
|
41
|
+
});
|
|
42
|
+
test("All of 2020 when between 2019 and 2022 (12)", () => {
|
|
43
|
+
const t = {
|
|
44
|
+
start: new Date(2019, 0, 22),
|
|
45
|
+
end: new Date(2022, 7, 22),
|
|
46
|
+
current: new Date(2020, 6, 22),
|
|
47
|
+
res: 12,
|
|
48
|
+
};
|
|
49
|
+
expect(getMonths(t.start, t.end, t.current).length).toEqual(t.res);
|
|
50
|
+
});
|
|
51
|
+
test("Tail part of 2019, including starting month (12)", () => {
|
|
52
|
+
const t = {
|
|
53
|
+
start: new Date(2019, 5, 22),
|
|
54
|
+
end: new Date(2022, 7, 22),
|
|
55
|
+
current: new Date(2019, 6, 22),
|
|
56
|
+
res: 7,
|
|
57
|
+
};
|
|
58
|
+
expect(getMonths(t.start, t.end, t.current).length).toEqual(t.res);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("Extracts correct years", () => {
|
|
63
|
+
test("Same year (1)", () => {
|
|
64
|
+
const t = {
|
|
65
|
+
start: new Date(2019, 2, 22),
|
|
66
|
+
end: new Date(2019, 9, 22),
|
|
67
|
+
res: 1,
|
|
68
|
+
};
|
|
69
|
+
expect(getYears(t.start, t.end).length).toEqual(t.res);
|
|
70
|
+
});
|
|
71
|
+
test("Multiple years (11)", () => {
|
|
72
|
+
const t = {
|
|
73
|
+
start: new Date(2019, 2, 22),
|
|
74
|
+
end: new Date(2029, 9, 22),
|
|
75
|
+
res: 11,
|
|
76
|
+
};
|
|
77
|
+
expect(getYears(t.start, t.end).length).toEqual(t.res);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { parseDate } from "../parse-date";
|
|
2
|
+
import { isValidDate } from "../valid-date";
|
|
3
|
+
import nb from "date-fns/locale/nb";
|
|
4
|
+
import { getMonth, getYear } from "date-fns";
|
|
5
|
+
|
|
6
|
+
const check = (inp: string) =>
|
|
7
|
+
expect(isValidDate(parseDate(inp, new Date(), nb)));
|
|
8
|
+
|
|
9
|
+
const parse = (inp: string) => parseDate(inp, new Date(), nb);
|
|
10
|
+
|
|
11
|
+
describe("Parse date-inputs", () => {
|
|
12
|
+
test("No spaces", () => {
|
|
13
|
+
check("150522").toBeTruthy();
|
|
14
|
+
check("11052022").toBeTruthy();
|
|
15
|
+
check("15052022").toBeTruthy();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test(". divider", () => {
|
|
19
|
+
check("1.5.22").toBeTruthy();
|
|
20
|
+
check("1.5.2022").toBeTruthy();
|
|
21
|
+
check("11.05.22").toBeTruthy();
|
|
22
|
+
check("11.05.2022").toBeTruthy();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("/ divider", () => {
|
|
26
|
+
check("1/5/22").toBeTruthy();
|
|
27
|
+
check("10/5/22").toBeTruthy();
|
|
28
|
+
check("1/5/2022").toBeTruthy();
|
|
29
|
+
check("10/5/2022").toBeTruthy();
|
|
30
|
+
check("1/05/22").toBeTruthy();
|
|
31
|
+
check("10/05/22").toBeTruthy();
|
|
32
|
+
check("1/05/2022").toBeTruthy();
|
|
33
|
+
check("10/05/2022").toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("- divider", () => {
|
|
37
|
+
check("1-5-22").toBeTruthy();
|
|
38
|
+
check("10-5-22").toBeTruthy();
|
|
39
|
+
check("1-5-2022").toBeTruthy();
|
|
40
|
+
check("10-5-2022").toBeTruthy();
|
|
41
|
+
check("1-05-22").toBeTruthy();
|
|
42
|
+
check("10-05-22").toBeTruthy();
|
|
43
|
+
check("1-05-2022").toBeTruthy();
|
|
44
|
+
check("10-05-2022").toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("Parse date-inputs to correct year", () => {
|
|
49
|
+
test("No spaces", () => {
|
|
50
|
+
expect(getYear(parse("150522"))).toEqual(2022);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test(". divider", () => {
|
|
54
|
+
expect(getYear(parse("1.05.22"))).toEqual(2022);
|
|
55
|
+
expect(getYear(parse("11.05.22"))).toEqual(2022);
|
|
56
|
+
expect(getYear(parse("1.5.22"))).toEqual(2022);
|
|
57
|
+
expect(getYear(parse("11.5.22"))).toEqual(2022);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("/ divider", () => {
|
|
61
|
+
expect(getYear(parse("1/5/22"))).toEqual(2022);
|
|
62
|
+
expect(getYear(parse("10/5/22"))).toEqual(2022);
|
|
63
|
+
expect(getYear(parse("1/05/22"))).toEqual(2022);
|
|
64
|
+
expect(getYear(parse("10/05/22"))).toEqual(2022);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("- divider", () => {
|
|
68
|
+
expect(getYear(parse("1-5-22"))).toEqual(2022);
|
|
69
|
+
expect(getYear(parse("10-5-22"))).toEqual(2022);
|
|
70
|
+
expect(getYear(parse("1-05-22"))).toEqual(2022);
|
|
71
|
+
expect(getYear(parse("10-05-22"))).toEqual(2022);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe("Parse date-inputs to correct month", () => {
|
|
76
|
+
test("No spaces", () => {
|
|
77
|
+
expect(getMonth(parse("150522"))).toEqual(4);
|
|
78
|
+
expect(getMonth(parse("11052022"))).toEqual(4);
|
|
79
|
+
expect(getMonth(parse("15052022"))).toEqual(4);
|
|
80
|
+
});
|
|
81
|
+
});
|