@navikt/ds-react 1.3.8 → 1.3.10

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.
Files changed (181) hide show
  1. package/_docs.json +4956 -2568
  2. package/cjs/date/DateInput.js +79 -0
  3. package/cjs/date/datepicker/DatePicker.js +115 -0
  4. package/cjs/date/datepicker/DatePickerStandalone.js +83 -0
  5. package/cjs/date/datepicker/DayButton.js +43 -0
  6. package/cjs/date/datepicker/caption/Caption.js +23 -0
  7. package/cjs/date/datepicker/caption/DropdownCaption.js +38 -0
  8. package/cjs/date/datepicker/caption/index.js +10 -0
  9. package/cjs/date/datepicker/caption/package.json +6 -0
  10. package/cjs/date/hooks/index.js +15 -0
  11. package/cjs/date/hooks/package.json +6 -0
  12. package/cjs/date/hooks/useDateInputContext.js +17 -0
  13. package/cjs/date/hooks/useDatepicker.js +135 -0
  14. package/cjs/date/hooks/useMonthPicker.js +139 -0
  15. package/cjs/date/hooks/useRangeDatepicker.js +215 -0
  16. package/cjs/date/hooks/useSharedMonthContext.js +63 -0
  17. package/cjs/date/index.js +14 -0
  18. package/cjs/date/monthpicker/MonthButton.js +80 -0
  19. package/cjs/date/monthpicker/MonthCaption.js +47 -0
  20. package/cjs/date/monthpicker/MonthPicker.js +74 -0
  21. package/cjs/date/monthpicker/MonthPickerStandalone.js +54 -0
  22. package/cjs/date/monthpicker/MonthSelector.js +79 -0
  23. package/cjs/date/package.json +6 -0
  24. package/cjs/date/utils/check-dates.js +17 -0
  25. package/cjs/date/utils/dates-disabled.js +29 -0
  26. package/cjs/date/utils/format-date.js +12 -0
  27. package/cjs/date/utils/get-dates.js +43 -0
  28. package/cjs/date/utils/get-initial-year.js +21 -0
  29. package/cjs/date/utils/index.js +33 -0
  30. package/cjs/date/utils/is-match.js +61 -0
  31. package/cjs/date/utils/labels.js +85 -0
  32. package/cjs/date/utils/locale.js +21 -0
  33. package/cjs/date/utils/navigation.js +155 -0
  34. package/cjs/date/utils/package.json +6 -0
  35. package/cjs/date/utils/parse-date.js +39 -0
  36. package/cjs/form/ConfirmationPanel.js +4 -3
  37. package/cjs/index.js +1 -0
  38. package/esm/date/DateInput.d.ts +30 -0
  39. package/esm/date/DateInput.js +51 -0
  40. package/esm/date/DateInput.js.map +1 -0
  41. package/esm/date/datepicker/DatePicker.d.ts +95 -0
  42. package/esm/date/datepicker/DatePicker.js +87 -0
  43. package/esm/date/datepicker/DatePicker.js.map +1 -0
  44. package/esm/date/datepicker/DatePickerStandalone.d.ts +12 -0
  45. package/esm/date/datepicker/DatePickerStandalone.js +55 -0
  46. package/esm/date/datepicker/DatePickerStandalone.js.map +1 -0
  47. package/esm/date/datepicker/DayButton.d.ts +3 -0
  48. package/esm/date/datepicker/DayButton.js +17 -0
  49. package/esm/date/datepicker/DayButton.js.map +1 -0
  50. package/esm/date/datepicker/caption/Caption.d.ts +4 -0
  51. package/esm/date/datepicker/caption/Caption.js +17 -0
  52. package/esm/date/datepicker/caption/Caption.js.map +1 -0
  53. package/esm/date/datepicker/caption/DropdownCaption.d.ts +4 -0
  54. package/esm/date/datepicker/caption/DropdownCaption.js +32 -0
  55. package/esm/date/datepicker/caption/DropdownCaption.js.map +1 -0
  56. package/esm/date/datepicker/caption/index.d.ts +2 -0
  57. package/esm/date/datepicker/caption/index.js +3 -0
  58. package/esm/date/datepicker/caption/index.js.map +1 -0
  59. package/esm/date/hooks/index.d.ts +5 -0
  60. package/esm/date/hooks/index.js +6 -0
  61. package/esm/date/hooks/index.js.map +1 -0
  62. package/esm/date/hooks/useDateInputContext.d.ts +18 -0
  63. package/esm/date/hooks/useDateInputContext.js +14 -0
  64. package/esm/date/hooks/useDateInputContext.js.map +1 -0
  65. package/esm/date/hooks/useDatepicker.d.ts +37 -0
  66. package/esm/date/hooks/useDatepicker.js +132 -0
  67. package/esm/date/hooks/useDatepicker.js.map +1 -0
  68. package/esm/date/hooks/useMonthPicker.d.ts +33 -0
  69. package/esm/date/hooks/useMonthPicker.js +136 -0
  70. package/esm/date/hooks/useMonthPicker.js.map +1 -0
  71. package/esm/date/hooks/useRangeDatepicker.d.ts +39 -0
  72. package/esm/date/hooks/useRangeDatepicker.js +212 -0
  73. package/esm/date/hooks/useRangeDatepicker.js.map +1 -0
  74. package/esm/date/hooks/useSharedMonthContext.d.ts +21 -0
  75. package/esm/date/hooks/useSharedMonthContext.js +36 -0
  76. package/esm/date/hooks/useSharedMonthContext.js.map +1 -0
  77. package/esm/date/index.d.ts +6 -0
  78. package/esm/date/index.js +4 -0
  79. package/esm/date/index.js.map +1 -0
  80. package/esm/date/monthpicker/MonthButton.d.ts +11 -0
  81. package/esm/date/monthpicker/MonthButton.js +51 -0
  82. package/esm/date/monthpicker/MonthButton.js.map +1 -0
  83. package/esm/date/monthpicker/MonthCaption.d.ts +3 -0
  84. package/esm/date/monthpicker/MonthCaption.js +41 -0
  85. package/esm/date/monthpicker/MonthCaption.js.map +1 -0
  86. package/esm/date/monthpicker/MonthPicker.d.ts +90 -0
  87. package/esm/date/monthpicker/MonthPicker.js +46 -0
  88. package/esm/date/monthpicker/MonthPicker.js.map +1 -0
  89. package/esm/date/monthpicker/MonthPickerStandalone.d.ts +11 -0
  90. package/esm/date/monthpicker/MonthPickerStandalone.js +26 -0
  91. package/esm/date/monthpicker/MonthPickerStandalone.js.map +1 -0
  92. package/esm/date/monthpicker/MonthSelector.d.ts +3 -0
  93. package/esm/date/monthpicker/MonthSelector.js +50 -0
  94. package/esm/date/monthpicker/MonthSelector.js.map +1 -0
  95. package/esm/date/utils/check-dates.d.ts +4 -0
  96. package/esm/date/utils/check-dates.js +12 -0
  97. package/esm/date/utils/check-dates.js.map +1 -0
  98. package/esm/date/utils/dates-disabled.d.ts +1 -0
  99. package/esm/date/utils/dates-disabled.js +26 -0
  100. package/esm/date/utils/dates-disabled.js.map +1 -0
  101. package/esm/date/utils/format-date.d.ts +1 -0
  102. package/esm/date/utils/format-date.js +9 -0
  103. package/esm/date/utils/format-date.js.map +1 -0
  104. package/esm/date/utils/get-dates.d.ts +2 -0
  105. package/esm/date/utils/get-dates.js +39 -0
  106. package/esm/date/utils/get-dates.js.map +1 -0
  107. package/esm/date/utils/get-initial-year.d.ts +5 -0
  108. package/esm/date/utils/get-initial-year.js +18 -0
  109. package/esm/date/utils/get-initial-year.js.map +1 -0
  110. package/esm/date/utils/index.d.ts +10 -0
  111. package/esm/date/utils/index.js +11 -0
  112. package/esm/date/utils/index.js.map +1 -0
  113. package/esm/date/utils/is-match.d.ts +4 -0
  114. package/esm/date/utils/is-match.js +57 -0
  115. package/esm/date/utils/is-match.js.map +1 -0
  116. package/esm/date/utils/labels.d.ts +6 -0
  117. package/esm/date/utils/labels.js +79 -0
  118. package/esm/date/utils/labels.js.map +1 -0
  119. package/esm/date/utils/locale.d.ts +2 -0
  120. package/esm/date/utils/locale.js +15 -0
  121. package/esm/date/utils/locale.js.map +1 -0
  122. package/esm/date/utils/navigation.d.ts +2 -0
  123. package/esm/date/utils/navigation.js +152 -0
  124. package/esm/date/utils/navigation.js.map +1 -0
  125. package/esm/date/utils/parse-date.d.ts +3 -0
  126. package/esm/date/utils/parse-date.js +36 -0
  127. package/esm/date/utils/parse-date.js.map +1 -0
  128. package/esm/form/ConfirmationPanel.js +5 -4
  129. package/esm/form/ConfirmationPanel.js.map +1 -1
  130. package/esm/index.d.ts +1 -0
  131. package/esm/index.js +1 -0
  132. package/esm/index.js.map +1 -1
  133. package/esm/typography/Detail.d.ts +1 -1
  134. package/package.json +6 -4
  135. package/src/chat/chat.stories.tsx +15 -15
  136. package/src/date/DateInput.tsx +167 -0
  137. package/src/date/datepicker/DatePicker.tsx +253 -0
  138. package/src/date/datepicker/DatePickerStandalone.tsx +121 -0
  139. package/src/date/datepicker/DayButton.tsx +26 -0
  140. package/src/date/datepicker/caption/Caption.tsx +51 -0
  141. package/src/date/datepicker/caption/DropdownCaption.tsx +98 -0
  142. package/src/date/datepicker/caption/index.ts +2 -0
  143. package/src/date/datepicker/datepicker.stories.tsx +235 -0
  144. package/src/date/hooks/index.ts +8 -0
  145. package/src/date/hooks/useDateInputContext.tsx +32 -0
  146. package/src/date/hooks/useDatepicker.tsx +225 -0
  147. package/src/date/hooks/useMonthPicker.tsx +223 -0
  148. package/src/date/hooks/useRangeDatepicker.tsx +348 -0
  149. package/src/date/hooks/useSharedMonthContext.tsx +68 -0
  150. package/src/date/index.ts +16 -0
  151. package/src/date/monthpicker/MonthButton.tsx +109 -0
  152. package/src/date/monthpicker/MonthCaption.tsx +94 -0
  153. package/src/date/monthpicker/MonthPicker.tsx +204 -0
  154. package/src/date/monthpicker/MonthPickerStandalone.tsx +87 -0
  155. package/src/date/monthpicker/MonthSelector.tsx +85 -0
  156. package/src/date/monthpicker/monthpicker.stories.tsx +128 -0
  157. package/src/date/utils/__tests__/check-dates.test.ts +47 -0
  158. package/src/date/utils/__tests__/dates-disabled.test.ts +48 -0
  159. package/src/date/utils/__tests__/format-dates.test.ts +22 -0
  160. package/src/date/utils/__tests__/get-dates.test.ts +79 -0
  161. package/src/date/utils/__tests__/get-initial-year.test.ts +94 -0
  162. package/src/date/utils/__tests__/is-match.test.ts +42 -0
  163. package/src/date/utils/__tests__/parse-dates.test.ts +81 -0
  164. package/src/date/utils/check-dates.ts +17 -0
  165. package/src/date/utils/dates-disabled.ts +26 -0
  166. package/src/date/utils/format-date.ts +17 -0
  167. package/src/date/utils/get-dates.ts +44 -0
  168. package/src/date/utils/get-initial-year.ts +25 -0
  169. package/src/date/utils/index.ts +21 -0
  170. package/src/date/utils/is-match.ts +88 -0
  171. package/src/date/utils/labels.ts +84 -0
  172. package/src/date/utils/locale.ts +15 -0
  173. package/src/date/utils/navigation.ts +292 -0
  174. package/src/date/utils/parse-date.ts +46 -0
  175. package/src/form/ConfirmationPanel.tsx +9 -2
  176. package/src/form/checkbox/Checkbox.test.tsx +2 -2
  177. package/src/form/radio/radio.stories.tsx +4 -4
  178. package/src/form/stories/textarea.stories.tsx +1 -3
  179. package/src/help-text/help-text.stories.tsx +3 -0
  180. package/src/index.ts +1 -0
  181. package/src/typography/Detail.tsx +1 -1
@@ -0,0 +1,223 @@
1
+ import React, { useCallback, useEffect, useRef, useState } from "react";
2
+ import { DateInputProps } from "../DateInput";
3
+ import { MonthPickerProps } from "../monthpicker/MonthPicker";
4
+ import {
5
+ formatDateForInput,
6
+ getLocaleFromString,
7
+ isMatch,
8
+ isValidDate,
9
+ parseDate,
10
+ } from "../utils";
11
+
12
+ export interface UseMonthPickerOptions
13
+ extends Pick<
14
+ MonthPickerProps,
15
+ "locale" | "fromDate" | "toDate" | "disabled" | "defaultSelected"
16
+ > {
17
+ /**
18
+ * Make Date-selection required
19
+ */
20
+ required?: boolean;
21
+ }
22
+
23
+ interface UseMonthPickerValue {
24
+ /**
25
+ * Use: <MonthPicker {...monthpickerProps} />
26
+ */
27
+ monthpickerProps: MonthPickerProps;
28
+ /**
29
+ * Use: <MonthPicker.Input {...inputProps} />
30
+ */
31
+ inputProps: Pick<
32
+ DateInputProps,
33
+ "onChange" | "onFocus" | "value" | "wrapperRef"
34
+ >;
35
+ /**
36
+ * Currently selected Date
37
+ * Up to user to validate value and extract month
38
+ */
39
+ selectedMonth?: Date;
40
+ /**
41
+ * Manually set selected month if needed
42
+ */
43
+ setSelected: (date?: Date) => void;
44
+ /**
45
+ * Resets all states
46
+ */
47
+ reset: () => void;
48
+ }
49
+
50
+ export const useMonthPicker = (
51
+ opt: UseMonthPickerOptions = {}
52
+ ): UseMonthPickerValue => {
53
+ const {
54
+ locale: _locale = "nb",
55
+ defaultSelected,
56
+ fromDate,
57
+ toDate,
58
+ disabled,
59
+ required,
60
+ } = opt;
61
+
62
+ const today = new Date();
63
+ const locale = getLocaleFromString(_locale);
64
+
65
+ const inputRef = useRef<HTMLDivElement>(null);
66
+ const monthpickerRef = useRef<HTMLDivElement>(null);
67
+
68
+ // Initialize states
69
+ const [year, setYear] = useState(defaultSelected ?? today);
70
+ const [selectedMonth, setSelectedMonth] = useState(defaultSelected);
71
+ const [open, setOpen] = useState(false);
72
+
73
+ const defaultInputValue = defaultSelected
74
+ ? formatDateForInput(defaultSelected, locale, "month")
75
+ : "";
76
+
77
+ const [inputValue, setInputValue] = useState(defaultInputValue);
78
+
79
+ const handleFocusIn = useCallback(
80
+ (e) =>
81
+ ![
82
+ monthpickerRef.current,
83
+ inputRef.current,
84
+ inputRef.current?.nextSibling,
85
+ ].some((element) => element?.contains(e.target)) &&
86
+ open &&
87
+ setOpen(false),
88
+ [open]
89
+ );
90
+
91
+ useEffect(() => {
92
+ window.addEventListener("focusin", handleFocusIn);
93
+ window.addEventListener("click", handleFocusIn);
94
+ return () => {
95
+ window?.removeEventListener?.("focusin", handleFocusIn);
96
+ window?.removeEventListener?.("click", handleFocusIn);
97
+ };
98
+ }, [handleFocusIn]);
99
+
100
+ const reset = () => {
101
+ setSelectedMonth(defaultSelected);
102
+ setYear(defaultSelected ?? today);
103
+ setInputValue(defaultInputValue ?? "");
104
+ };
105
+
106
+ const setSelected = (date: Date | undefined) => {
107
+ setSelectedMonth(date);
108
+ setYear(date ?? today);
109
+ setInputValue(date ? formatDateForInput(date, locale, "month") : "");
110
+ };
111
+
112
+ const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
113
+ !open && setOpen(true);
114
+ if (!e.target.value) {
115
+ reset();
116
+ return;
117
+ }
118
+ let day = parseDate(e.target.value, today, locale, "month");
119
+ if (isValidDate(day)) {
120
+ setYear(day);
121
+ setInputValue(formatDateForInput(day, locale, "month"));
122
+ }
123
+ };
124
+
125
+ const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
126
+ let day = parseDate(e.target.value, today, locale, "month");
127
+ isValidDate(day) && setInputValue(formatDateForInput(day, locale, "month"));
128
+ };
129
+
130
+ /* Only allow de-selecting if not required */
131
+ const handleMonthClick = (month?: Date) => {
132
+ if (month) {
133
+ setOpen(false);
134
+ inputRef.current && inputRef.current.focus();
135
+ }
136
+
137
+ if (!required && !month) {
138
+ setSelectedMonth(undefined);
139
+ setInputValue("");
140
+ return;
141
+ }
142
+ setSelectedMonth(month);
143
+ setInputValue(month ? formatDateForInput(month, locale, "month") : "");
144
+ };
145
+
146
+ // When changing the input field, save its value in state and check if the
147
+ // string is a valid date. If it is a valid day, set it as selected and update
148
+ // the calendar’s month.
149
+ const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
150
+ setInputValue(e.target.value);
151
+ const month = parseDate(e.target.value, today, locale, "month");
152
+
153
+ if (!isValidDate(month) || (disabled && isMatch(month, disabled))) {
154
+ setSelectedMonth(undefined);
155
+ return;
156
+ }
157
+
158
+ const isBefore =
159
+ fromDate &&
160
+ (fromDate.getFullYear() > month.getFullYear() ||
161
+ (fromDate.getFullYear() === month.getFullYear() &&
162
+ fromDate.getMonth() > month.getMonth()));
163
+
164
+ const isAfter =
165
+ toDate &&
166
+ (toDate.getFullYear() < month.getFullYear() ||
167
+ (toDate.getFullYear() === month.getFullYear() &&
168
+ toDate.getMonth() < month.getMonth()));
169
+
170
+ if (
171
+ isAfter ||
172
+ isBefore ||
173
+ (fromDate && toDate && !isMatch(month, [{ from: fromDate, to: toDate }]))
174
+ ) {
175
+ setSelectedMonth(undefined);
176
+ return;
177
+ }
178
+ setSelectedMonth(month);
179
+ setYear(month);
180
+ };
181
+
182
+ const handleClose = useCallback(() => {
183
+ setOpen(false);
184
+ inputRef.current && inputRef.current.focus();
185
+ }, []);
186
+
187
+ const escape = useCallback(
188
+ (e) => open && e.key === "Escape" && handleClose(),
189
+ [handleClose, open]
190
+ );
191
+
192
+ useEffect(() => {
193
+ window.addEventListener("keydown", escape, false);
194
+
195
+ return () => {
196
+ window.removeEventListener("keydown", escape, false);
197
+ };
198
+ }, [escape]);
199
+
200
+ const monthpickerProps = {
201
+ year,
202
+ onYearChange: (y?: Date) => setYear(y ?? today),
203
+ onMonthSelect: handleMonthClick,
204
+ selected: selectedMonth,
205
+ locale: _locale,
206
+ fromDate,
207
+ toDate,
208
+ open,
209
+ onOpenToggle: () => setOpen((x) => !x),
210
+ disabled,
211
+ ref: monthpickerRef,
212
+ };
213
+
214
+ const inputProps = {
215
+ onChange: handleChange,
216
+ onFocus: handleFocus,
217
+ onBlur: handleBlur,
218
+ value: inputValue,
219
+ ref: inputRef,
220
+ };
221
+
222
+ return { monthpickerProps, inputProps, reset, selectedMonth, setSelected };
223
+ };
@@ -0,0 +1,348 @@
1
+ import { differenceInCalendarDays, isWeekend } from "date-fns";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ import { DateRange, isMatch } from "react-day-picker";
4
+ import { DateInputProps } from "../DateInput";
5
+ import { DatePickerProps } from "../datepicker/DatePicker";
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
+ /**
17
+ * The initially selected DateRange
18
+ */
19
+ defaultSelected?: DateRange;
20
+ }
21
+
22
+ interface UseRangeDatepickerValue {
23
+ /**
24
+ * Use: <DatePicker {...datepickerProps}/>
25
+ */
26
+ datepickerProps: DatePickerProps;
27
+ /**
28
+ * Use: <DatePicker.Input label="from" {...fromInputProps}/>
29
+ */
30
+ fromInputProps: Pick<
31
+ DateInputProps,
32
+ "onChange" | "onFocus" | "onBlur" | "value"
33
+ >;
34
+ /**
35
+ * Use: <DatePicker.Input label="to" {...toInputProps}/>
36
+ */
37
+ toInputProps: Pick<
38
+ DateInputProps,
39
+ "onChange" | "onFocus" | "onBlur" | "value"
40
+ >;
41
+ /**
42
+ * Resets all states (callback)
43
+ */
44
+ reset: () => void;
45
+ /**
46
+ * Currently selected DateRange
47
+ * Up to user to validate values
48
+ */
49
+ selectedRange?: DateRange;
50
+ /**
51
+ * Manually override currently selected day
52
+ */
53
+ setSelected: (date?: DateRange) => void;
54
+ }
55
+
56
+ const RANGE = {
57
+ FROM: "FROM",
58
+ TO: "TO",
59
+ } as const;
60
+
61
+ type RangeT = typeof RANGE[keyof typeof RANGE];
62
+
63
+ export const useRangeDatepicker = (
64
+ opt: UseRangeDatepickerOptions = {}
65
+ ): UseRangeDatepickerValue => {
66
+ const {
67
+ locale: _locale = "nb",
68
+ defaultSelected,
69
+ today = new Date(),
70
+ fromDate,
71
+ toDate,
72
+ disabled,
73
+ disableWeekends,
74
+ } = opt;
75
+
76
+ const locale = getLocaleFromString(_locale);
77
+
78
+ const inputRefTo = useRef<HTMLDivElement>(null);
79
+ const inputRefFrom = useRef<HTMLDivElement>(null);
80
+ const datePickerRef = useRef<HTMLDivElement | null>(null);
81
+
82
+ // Initialize states
83
+ const [month, setMonth] = useState(
84
+ defaultSelected ? defaultSelected.from : today
85
+ );
86
+ const [selectedRange, setSelectedRange] = useState(defaultSelected);
87
+
88
+ const [fromInputValue, setFromInputValue] = useState(
89
+ defaultSelected?.from
90
+ ? formatDateForInput(defaultSelected.from, locale, "date")
91
+ : ""
92
+ );
93
+
94
+ const [toInputValue, setToInputValue] = useState(
95
+ defaultSelected?.to
96
+ ? formatDateForInput(defaultSelected.to, locale, "date")
97
+ : ""
98
+ );
99
+ const [open, setOpen] = useState(false);
100
+
101
+ const handleFocusIn = useCallback(
102
+ (e) =>
103
+ ![
104
+ datePickerRef.current,
105
+ inputRefTo.current,
106
+ inputRefFrom.current,
107
+ inputRefTo.current?.nextSibling,
108
+ inputRefFrom.current?.nextSibling,
109
+ ].some((element) => element?.contains(e.target)) &&
110
+ open &&
111
+ setOpen(false),
112
+ [open]
113
+ );
114
+
115
+ useEffect(() => {
116
+ window.addEventListener("focusin", handleFocusIn);
117
+ window.addEventListener("click", handleFocusIn);
118
+ return () => {
119
+ window?.removeEventListener?.("focusin", handleFocusIn);
120
+ window?.removeEventListener?.("click", handleFocusIn);
121
+ };
122
+ }, [handleFocusIn]);
123
+
124
+ const reset = () => {
125
+ setSelectedRange(defaultSelected);
126
+ setMonth(defaultSelected ? defaultSelected.from : today);
127
+ setFromInputValue(
128
+ defaultSelected?.from
129
+ ? formatDateForInput(defaultSelected.from, locale, "date")
130
+ : ""
131
+ );
132
+ setToInputValue(
133
+ defaultSelected?.to
134
+ ? formatDateForInput(defaultSelected.to, locale, "date")
135
+ : ""
136
+ );
137
+ };
138
+
139
+ const setSelected = (range?: DateRange) => {
140
+ setSelectedRange(range);
141
+ setFromInputValue(
142
+ range?.from ? formatDateForInput(range.from, locale, "date") : ""
143
+ );
144
+ setToInputValue(
145
+ range?.to ? formatDateForInput(range?.to, locale, "date") : ""
146
+ );
147
+ };
148
+
149
+ const handleFocus = (e, src: RangeT) => {
150
+ !open && setOpen(true);
151
+ let day = parseDate(e.target.value, today, locale, "date");
152
+ if (isValidDate(day)) {
153
+ setMonth(day);
154
+ src === RANGE.FROM
155
+ ? setFromInputValue(formatDateForInput(day, locale, "date"))
156
+ : setToInputValue(formatDateForInput(day, locale, "date"));
157
+ }
158
+ };
159
+
160
+ const handleInputs = (day: Date, src: RangeT) => {
161
+ if (src === RANGE.FROM) {
162
+ const isAfter =
163
+ toInputValue &&
164
+ differenceInCalendarDays(
165
+ day,
166
+ parseDate(toInputValue, today, locale, "date")
167
+ ) > 0;
168
+
169
+ if (isAfter) {
170
+ setFromInputValue(
171
+ formatDateForInput(
172
+ parseDate(toInputValue, today, locale, "date"),
173
+ locale,
174
+ "date"
175
+ )
176
+ );
177
+ setToInputValue(formatDateForInput(day, locale, "date"));
178
+ } else {
179
+ setFromInputValue(formatDateForInput(day, locale, "date"));
180
+ }
181
+ } else if (src === RANGE.TO) {
182
+ const isBefore =
183
+ fromInputValue &&
184
+ differenceInCalendarDays(
185
+ parseDate(fromInputValue, today, locale, "date"),
186
+ day
187
+ ) > 0;
188
+
189
+ if (isBefore) {
190
+ setToInputValue(
191
+ formatDateForInput(
192
+ parseDate(fromInputValue, today, locale, "date"),
193
+ locale,
194
+ "date"
195
+ )
196
+ );
197
+ setFromInputValue(formatDateForInput(day, locale, "date"));
198
+ } else {
199
+ setToInputValue(formatDateForInput(day, locale, "date"));
200
+ }
201
+ }
202
+ };
203
+
204
+ const handleBlur = (e, src: RangeT) => {
205
+ let day = parseDate(e.target.value, today, locale, "date");
206
+ if (!isValidDate(day)) {
207
+ return;
208
+ }
209
+
210
+ handleInputs(day, src);
211
+ };
212
+
213
+ const handleSelect = (range) => {
214
+ if (range.from && range.to) {
215
+ setOpen(false);
216
+ // TODO: fokus på en av input-feltene?
217
+ }
218
+ const prevToRange =
219
+ !selectedRange?.from && selectedRange?.to ? selectedRange?.to : range?.to;
220
+
221
+ range?.from
222
+ ? setFromInputValue(formatDateForInput(range?.from, locale, "date"))
223
+ : setFromInputValue("");
224
+ prevToRange
225
+ ? setToInputValue(formatDateForInput(prevToRange, locale, "date"))
226
+ : setToInputValue("");
227
+ setSelectedRange({ from: range?.from, to: prevToRange });
228
+ };
229
+
230
+ /* live-update datepicker based on changes in inputfields */
231
+ const handleChange = (e, src: RangeT) => {
232
+ src === RANGE.FROM
233
+ ? setFromInputValue(e.target.value)
234
+ : setToInputValue(e.target.value);
235
+ const day = parseDate(e.target.value, today, locale, "date");
236
+
237
+ if (
238
+ !isValidDate(day) ||
239
+ (disabled &&
240
+ ((disableWeekends && isWeekend(day)) || isMatch(day, disabled)))
241
+ ) {
242
+ setSelectedRange((x) =>
243
+ src === RANGE.FROM
244
+ ? { ...x, from: undefined }
245
+ : { from: x?.from, to: undefined }
246
+ );
247
+ return;
248
+ }
249
+
250
+ const isBefore = fromDate && differenceInCalendarDays(fromDate, day) > 0;
251
+ const isAfter = toDate && differenceInCalendarDays(day, toDate) > 0;
252
+ if (isBefore || isAfter) {
253
+ src === RANGE.FROM
254
+ ? setSelectedRange((x) => ({ ...x, from: undefined }))
255
+ : setSelectedRange((x) => ({ from: x?.from, to: undefined }));
256
+ return;
257
+ }
258
+
259
+ /* If to-value < from-value, switch places in state */
260
+ if (
261
+ src === RANGE.TO &&
262
+ selectedRange?.from &&
263
+ differenceInCalendarDays(selectedRange?.from, day) >= 0
264
+ ) {
265
+ setSelectedRange({ from: day, to: selectedRange?.from });
266
+ setMonth(day);
267
+ return;
268
+ }
269
+
270
+ /* If from-value > to-value , switch places in state */
271
+ if (
272
+ src === RANGE.FROM &&
273
+ selectedRange?.to &&
274
+ differenceInCalendarDays(day, selectedRange?.to) >= 0
275
+ ) {
276
+ setSelectedRange({ to: day, from: selectedRange?.to });
277
+ setMonth(day);
278
+ return;
279
+ }
280
+
281
+ src === RANGE.FROM && setSelectedRange((x) => ({ ...x, from: day }));
282
+ src === RANGE.TO && setSelectedRange((x) => ({ from: x?.from, to: day }));
283
+ setMonth(day);
284
+ };
285
+
286
+ const handleClose = useCallback(() => {
287
+ setOpen(false);
288
+ if (selectedRange?.from && !selectedRange?.to) {
289
+ inputRefTo?.current?.focus();
290
+ } else {
291
+ console.log(inputRefFrom);
292
+ inputRefFrom?.current?.focus();
293
+ }
294
+ }, [selectedRange]);
295
+
296
+ const escape = useCallback(
297
+ (e) => open && e.key === "Escape" && handleClose(),
298
+ [handleClose, open]
299
+ );
300
+
301
+ useEffect(() => {
302
+ window.addEventListener("keydown", escape, false);
303
+
304
+ return () => {
305
+ window.removeEventListener("keydown", escape, false);
306
+ };
307
+ }, [escape]);
308
+
309
+ const datepickerProps = {
310
+ month: month,
311
+ onMonthChange: (month) => setMonth(month),
312
+ onSelect: handleSelect,
313
+ selected: selectedRange,
314
+ locale: _locale,
315
+ fromDate,
316
+ toDate,
317
+ today,
318
+ mode: "range" as const,
319
+ open,
320
+ onOpenToggle: () => setOpen((x) => !x),
321
+ ref: datePickerRef,
322
+ };
323
+
324
+ const fromInputProps = {
325
+ onChange: (e) => handleChange(e, RANGE.FROM),
326
+ onFocus: (e) => handleFocus(e, RANGE.FROM),
327
+ onBlur: (e) => handleBlur(e, RANGE.FROM),
328
+ value: fromInputValue,
329
+ ref: inputRefFrom,
330
+ };
331
+
332
+ const toInputProps = {
333
+ onChange: (e) => handleChange(e, RANGE.TO),
334
+ onFocus: (e) => handleFocus(e, RANGE.TO),
335
+ onBlur: (e) => handleBlur(e, RANGE.TO),
336
+ value: toInputValue,
337
+ ref: inputRefTo,
338
+ };
339
+
340
+ return {
341
+ datepickerProps,
342
+ fromInputProps,
343
+ toInputProps,
344
+ reset,
345
+ selectedRange,
346
+ setSelected,
347
+ };
348
+ };
@@ -0,0 +1,68 @@
1
+ import { setYear, startOfMonth } from "date-fns";
2
+ import React, { createContext, useContext, useState } from "react";
3
+ import { useDayPicker } from "react-day-picker";
4
+ import { getInitialYear, Matcher } from "../utils";
5
+
6
+ export type SharedMonthContextType = {
7
+ hasDropdown: boolean;
8
+ year: Date;
9
+ toYear: (date: Date) => void;
10
+ disabled: Matcher[];
11
+ selected?: Date;
12
+ onSelect: (v?: Date) => void;
13
+ };
14
+
15
+ export const SharedMonthContext = createContext<SharedMonthContextType>({
16
+ hasDropdown: false,
17
+ year: new Date(),
18
+ toYear: () => null,
19
+ disabled: [],
20
+ onSelect: () => null,
21
+ });
22
+
23
+ export const useSharedMonthContext = () => useContext(SharedMonthContext);
24
+
25
+ export const SharedMonthProvider = ({
26
+ children,
27
+ dropdownCaption,
28
+ disabled,
29
+ selected,
30
+ onSelect,
31
+ year: _year,
32
+ onYearChange,
33
+ }) => {
34
+ const context = useDayPicker();
35
+
36
+ const [year, toYear] = useState<Date>(getInitialYear(context));
37
+
38
+ const hasDropdown = !!(dropdownCaption && context.fromDate && context.toDate);
39
+
40
+ if (
41
+ context.fromDate &&
42
+ context.toDate &&
43
+ context?.fromDate >= context?.toDate
44
+ ) {
45
+ console.warn("fromDate needs to be before toDate - MonthPicker");
46
+ }
47
+
48
+ return (
49
+ <SharedMonthContext.Provider
50
+ value={{
51
+ year: _year ?? year,
52
+ toYear: (y) => {
53
+ toYear(y);
54
+ onYearChange?.(y);
55
+ },
56
+ hasDropdown,
57
+ disabled,
58
+ selected,
59
+ onSelect: (v?: Date) =>
60
+ v
61
+ ? onSelect(setYear(startOfMonth(v), (_year ?? year).getFullYear()))
62
+ : onSelect(undefined),
63
+ }}
64
+ >
65
+ {children}
66
+ </SharedMonthContext.Provider>
67
+ );
68
+ };
@@ -0,0 +1,16 @@
1
+ export { DateInputProps } from "./DateInput";
2
+ export {
3
+ DatePickerProps,
4
+ default as UNSAFE_DatePicker,
5
+ } from "./datepicker/DatePicker";
6
+ export { DatePickerStandaloneProps } from "./datepicker/DatePickerStandalone";
7
+ export { MonthPickerStandaloneProps } from "./monthpicker/MonthPickerStandalone";
8
+ export {
9
+ UNSAFE_useDatepicker,
10
+ UNSAFE_useMonthPicker,
11
+ UNSAFE_useRangeDatepicker,
12
+ } from "./hooks";
13
+ export {
14
+ default as UNSAFE_MonthPicker,
15
+ MonthPickerProps,
16
+ } from "./monthpicker/MonthPicker";