@navikt/ds-react 1.3.7 → 1.3.9

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 (185) hide show
  1. package/_docs.json +4956 -2568
  2. package/cjs/date/DateInput.js +79 -0
  3. package/cjs/date/datepicker/DatePicker.js +113 -0
  4. package/cjs/date/datepicker/DatePickerStandalone.js +81 -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/cjs/util/AnimateHeight.js +2 -2
  39. package/esm/date/DateInput.d.ts +30 -0
  40. package/esm/date/DateInput.js +51 -0
  41. package/esm/date/DateInput.js.map +1 -0
  42. package/esm/date/datepicker/DatePicker.d.ts +95 -0
  43. package/esm/date/datepicker/DatePicker.js +85 -0
  44. package/esm/date/datepicker/DatePicker.js.map +1 -0
  45. package/esm/date/datepicker/DatePickerStandalone.d.ts +12 -0
  46. package/esm/date/datepicker/DatePickerStandalone.js +53 -0
  47. package/esm/date/datepicker/DatePickerStandalone.js.map +1 -0
  48. package/esm/date/datepicker/DayButton.d.ts +3 -0
  49. package/esm/date/datepicker/DayButton.js +17 -0
  50. package/esm/date/datepicker/DayButton.js.map +1 -0
  51. package/esm/date/datepicker/caption/Caption.d.ts +4 -0
  52. package/esm/date/datepicker/caption/Caption.js +17 -0
  53. package/esm/date/datepicker/caption/Caption.js.map +1 -0
  54. package/esm/date/datepicker/caption/DropdownCaption.d.ts +4 -0
  55. package/esm/date/datepicker/caption/DropdownCaption.js +32 -0
  56. package/esm/date/datepicker/caption/DropdownCaption.js.map +1 -0
  57. package/esm/date/datepicker/caption/index.d.ts +2 -0
  58. package/esm/date/datepicker/caption/index.js +3 -0
  59. package/esm/date/datepicker/caption/index.js.map +1 -0
  60. package/esm/date/hooks/index.d.ts +5 -0
  61. package/esm/date/hooks/index.js +6 -0
  62. package/esm/date/hooks/index.js.map +1 -0
  63. package/esm/date/hooks/useDateInputContext.d.ts +18 -0
  64. package/esm/date/hooks/useDateInputContext.js +14 -0
  65. package/esm/date/hooks/useDateInputContext.js.map +1 -0
  66. package/esm/date/hooks/useDatepicker.d.ts +37 -0
  67. package/esm/date/hooks/useDatepicker.js +132 -0
  68. package/esm/date/hooks/useDatepicker.js.map +1 -0
  69. package/esm/date/hooks/useMonthPicker.d.ts +33 -0
  70. package/esm/date/hooks/useMonthPicker.js +136 -0
  71. package/esm/date/hooks/useMonthPicker.js.map +1 -0
  72. package/esm/date/hooks/useRangeDatepicker.d.ts +39 -0
  73. package/esm/date/hooks/useRangeDatepicker.js +212 -0
  74. package/esm/date/hooks/useRangeDatepicker.js.map +1 -0
  75. package/esm/date/hooks/useSharedMonthContext.d.ts +21 -0
  76. package/esm/date/hooks/useSharedMonthContext.js +36 -0
  77. package/esm/date/hooks/useSharedMonthContext.js.map +1 -0
  78. package/esm/date/index.d.ts +6 -0
  79. package/esm/date/index.js +4 -0
  80. package/esm/date/index.js.map +1 -0
  81. package/esm/date/monthpicker/MonthButton.d.ts +11 -0
  82. package/esm/date/monthpicker/MonthButton.js +51 -0
  83. package/esm/date/monthpicker/MonthButton.js.map +1 -0
  84. package/esm/date/monthpicker/MonthCaption.d.ts +3 -0
  85. package/esm/date/monthpicker/MonthCaption.js +41 -0
  86. package/esm/date/monthpicker/MonthCaption.js.map +1 -0
  87. package/esm/date/monthpicker/MonthPicker.d.ts +90 -0
  88. package/esm/date/monthpicker/MonthPicker.js +46 -0
  89. package/esm/date/monthpicker/MonthPicker.js.map +1 -0
  90. package/esm/date/monthpicker/MonthPickerStandalone.d.ts +11 -0
  91. package/esm/date/monthpicker/MonthPickerStandalone.js +26 -0
  92. package/esm/date/monthpicker/MonthPickerStandalone.js.map +1 -0
  93. package/esm/date/monthpicker/MonthSelector.d.ts +3 -0
  94. package/esm/date/monthpicker/MonthSelector.js +50 -0
  95. package/esm/date/monthpicker/MonthSelector.js.map +1 -0
  96. package/esm/date/utils/check-dates.d.ts +4 -0
  97. package/esm/date/utils/check-dates.js +12 -0
  98. package/esm/date/utils/check-dates.js.map +1 -0
  99. package/esm/date/utils/dates-disabled.d.ts +1 -0
  100. package/esm/date/utils/dates-disabled.js +26 -0
  101. package/esm/date/utils/dates-disabled.js.map +1 -0
  102. package/esm/date/utils/format-date.d.ts +1 -0
  103. package/esm/date/utils/format-date.js +9 -0
  104. package/esm/date/utils/format-date.js.map +1 -0
  105. package/esm/date/utils/get-dates.d.ts +2 -0
  106. package/esm/date/utils/get-dates.js +39 -0
  107. package/esm/date/utils/get-dates.js.map +1 -0
  108. package/esm/date/utils/get-initial-year.d.ts +5 -0
  109. package/esm/date/utils/get-initial-year.js +18 -0
  110. package/esm/date/utils/get-initial-year.js.map +1 -0
  111. package/esm/date/utils/index.d.ts +10 -0
  112. package/esm/date/utils/index.js +11 -0
  113. package/esm/date/utils/index.js.map +1 -0
  114. package/esm/date/utils/is-match.d.ts +4 -0
  115. package/esm/date/utils/is-match.js +57 -0
  116. package/esm/date/utils/is-match.js.map +1 -0
  117. package/esm/date/utils/labels.d.ts +6 -0
  118. package/esm/date/utils/labels.js +79 -0
  119. package/esm/date/utils/labels.js.map +1 -0
  120. package/esm/date/utils/locale.d.ts +2 -0
  121. package/esm/date/utils/locale.js +15 -0
  122. package/esm/date/utils/locale.js.map +1 -0
  123. package/esm/date/utils/navigation.d.ts +2 -0
  124. package/esm/date/utils/navigation.js +152 -0
  125. package/esm/date/utils/navigation.js.map +1 -0
  126. package/esm/date/utils/parse-date.d.ts +3 -0
  127. package/esm/date/utils/parse-date.js +36 -0
  128. package/esm/date/utils/parse-date.js.map +1 -0
  129. package/esm/form/ConfirmationPanel.js +5 -4
  130. package/esm/form/ConfirmationPanel.js.map +1 -1
  131. package/esm/index.d.ts +1 -0
  132. package/esm/index.js +1 -0
  133. package/esm/index.js.map +1 -1
  134. package/esm/typography/Detail.d.ts +1 -1
  135. package/esm/util/AnimateHeight.js +2 -2
  136. package/esm/util/AnimateHeight.js.map +1 -1
  137. package/package.json +6 -4
  138. package/src/chat/chat.stories.tsx +15 -15
  139. package/src/date/DateInput.tsx +167 -0
  140. package/src/date/datepicker/DatePicker.tsx +252 -0
  141. package/src/date/datepicker/DatePickerStandalone.tsx +120 -0
  142. package/src/date/datepicker/DayButton.tsx +26 -0
  143. package/src/date/datepicker/caption/Caption.tsx +51 -0
  144. package/src/date/datepicker/caption/DropdownCaption.tsx +98 -0
  145. package/src/date/datepicker/caption/index.ts +2 -0
  146. package/src/date/datepicker/datepicker.stories.tsx +235 -0
  147. package/src/date/hooks/index.ts +8 -0
  148. package/src/date/hooks/useDateInputContext.tsx +32 -0
  149. package/src/date/hooks/useDatepicker.tsx +225 -0
  150. package/src/date/hooks/useMonthPicker.tsx +223 -0
  151. package/src/date/hooks/useRangeDatepicker.tsx +348 -0
  152. package/src/date/hooks/useSharedMonthContext.tsx +68 -0
  153. package/src/date/index.ts +16 -0
  154. package/src/date/monthpicker/MonthButton.tsx +109 -0
  155. package/src/date/monthpicker/MonthCaption.tsx +94 -0
  156. package/src/date/monthpicker/MonthPicker.tsx +204 -0
  157. package/src/date/monthpicker/MonthPickerStandalone.tsx +87 -0
  158. package/src/date/monthpicker/MonthSelector.tsx +85 -0
  159. package/src/date/monthpicker/monthpicker.stories.tsx +128 -0
  160. package/src/date/utils/__tests__/check-dates.test.ts +47 -0
  161. package/src/date/utils/__tests__/dates-disabled.test.ts +48 -0
  162. package/src/date/utils/__tests__/format-dates.test.ts +22 -0
  163. package/src/date/utils/__tests__/get-dates.test.ts +79 -0
  164. package/src/date/utils/__tests__/get-initial-year.test.ts +94 -0
  165. package/src/date/utils/__tests__/is-match.test.ts +42 -0
  166. package/src/date/utils/__tests__/parse-dates.test.ts +81 -0
  167. package/src/date/utils/check-dates.ts +17 -0
  168. package/src/date/utils/dates-disabled.ts +26 -0
  169. package/src/date/utils/format-date.ts +17 -0
  170. package/src/date/utils/get-dates.ts +44 -0
  171. package/src/date/utils/get-initial-year.ts +25 -0
  172. package/src/date/utils/index.ts +21 -0
  173. package/src/date/utils/is-match.ts +88 -0
  174. package/src/date/utils/labels.ts +84 -0
  175. package/src/date/utils/locale.ts +15 -0
  176. package/src/date/utils/navigation.ts +292 -0
  177. package/src/date/utils/parse-date.ts +46 -0
  178. package/src/form/ConfirmationPanel.tsx +9 -2
  179. package/src/form/checkbox/Checkbox.test.tsx +2 -2
  180. package/src/form/radio/radio.stories.tsx +4 -4
  181. package/src/form/stories/textarea.stories.tsx +1 -3
  182. package/src/help-text/help-text.stories.tsx +3 -0
  183. package/src/index.ts +1 -0
  184. package/src/typography/Detail.tsx +1 -1
  185. package/src/util/AnimateHeight.tsx +6 -7
@@ -0,0 +1,98 @@
1
+ import { Left, Right } from "@navikt/ds-icons";
2
+ import { setMonth, setYear, startOfMonth } from "date-fns";
3
+ import React from "react";
4
+ import { CaptionProps, useDayPicker, useNavigation } from "react-day-picker";
5
+ import { Button, Select } from "../../..";
6
+ import { getMonths, getYears } from "../../utils/get-dates";
7
+ import { labelMonthDropdown, labelYearDropdown } from "../../utils/labels";
8
+
9
+ export const DropdownCaption = ({ displayMonth, id }: CaptionProps) => {
10
+ const { goToMonth, nextMonth, previousMonth } = useNavigation();
11
+ const {
12
+ fromDate,
13
+ toDate,
14
+ formatters: { formatYearCaption, formatMonthCaption, formatCaption },
15
+ labels: { labelPrevious, labelNext },
16
+ locale,
17
+ } = useDayPicker();
18
+
19
+ if (!fromDate || !toDate) {
20
+ console.warn("Using dropdownCaption required fromDate and toDate");
21
+ return null;
22
+ }
23
+
24
+ const handleYearChange: React.ChangeEventHandler<HTMLSelectElement> = (e) =>
25
+ goToMonth(setYear(startOfMonth(displayMonth), Number(e.target.value)));
26
+
27
+ const handleMonthChange: React.ChangeEventHandler<HTMLSelectElement> = (e) =>
28
+ goToMonth(setMonth(startOfMonth(displayMonth), Number(e.target.value)));
29
+
30
+ const years = getYears(fromDate, toDate);
31
+ const months = getMonths(fromDate, toDate, displayMonth);
32
+
33
+ const previousLabel = labelPrevious(previousMonth, { locale });
34
+ const nextLabel = labelNext(nextMonth, { locale });
35
+ const yearDropdownLabel = labelYearDropdown(locale);
36
+ const MonthDropdownLabel = labelMonthDropdown(locale);
37
+
38
+ return (
39
+ <div className="navds-date__caption-dropdown">
40
+ <span
41
+ aria-live="polite"
42
+ aria-atomic="true"
43
+ id={id}
44
+ className="navds-sr-only"
45
+ >
46
+ {formatCaption(displayMonth, { locale })}
47
+ </span>
48
+ <Button
49
+ aria-label={previousLabel}
50
+ variant="tertiary"
51
+ disabled={!previousMonth}
52
+ onClick={() => previousMonth && goToMonth(previousMonth)}
53
+ icon={<Left title="velg forrige måned" />}
54
+ className="navds-date__caption-button"
55
+ />
56
+
57
+ <div className="navds-date__caption__month-wrapper">
58
+ <Select
59
+ label={MonthDropdownLabel}
60
+ hideLabel
61
+ className="navds-date__caption__month"
62
+ value={displayMonth.getMonth()}
63
+ onChange={handleMonthChange}
64
+ >
65
+ {months.map((m) => (
66
+ <option key={m.getMonth()} value={m.getMonth()}>
67
+ {formatMonthCaption(m, { locale })}
68
+ </option>
69
+ ))}
70
+ </Select>
71
+ <Select
72
+ label={yearDropdownLabel}
73
+ hideLabel
74
+ value={displayMonth.getFullYear()}
75
+ onChange={handleYearChange}
76
+ className="navds-date__caption__year"
77
+ >
78
+ {years.map((year) => (
79
+ <option key={year.getFullYear()} value={year.getFullYear()}>
80
+ {formatYearCaption(year, { locale })}
81
+ </option>
82
+ ))}
83
+ </Select>
84
+ </div>
85
+
86
+ <Button
87
+ aria-label={nextLabel}
88
+ icon={<Right title="velg neste måned" />}
89
+ onClick={() => nextMonth && goToMonth(nextMonth)}
90
+ disabled={!nextMonth}
91
+ variant="tertiary"
92
+ className="navds-date__caption-button"
93
+ />
94
+ </div>
95
+ );
96
+ };
97
+
98
+ export default DropdownCaption;
@@ -0,0 +1,2 @@
1
+ export { default as Caption } from "./Caption";
2
+ export { default as DropdownCaption } from "./DropdownCaption";
@@ -0,0 +1,235 @@
1
+ import { isSaturday } from "date-fns";
2
+ import React, { useId, useState } from "react";
3
+ import { UNSAFE_useDatepicker, UNSAFE_useRangeDatepicker } from "..";
4
+ import { Button } from "../..";
5
+ import DatePicker from "./DatePicker";
6
+
7
+ const disabledDays = [
8
+ new Date("Oct 10 2022"),
9
+ { from: new Date("Aug 31 2022"), to: new Date("Sep 8 2022") },
10
+ ];
11
+
12
+ export default {
13
+ title: "ds-react/Datepicker",
14
+ component: DatePicker,
15
+ argTypes: {
16
+ size: {
17
+ control: {
18
+ type: "radio",
19
+ options: ["medium", "small"],
20
+ },
21
+ },
22
+ locale: {
23
+ control: {
24
+ type: "radio",
25
+ options: ["nb", "nn", "en"],
26
+ },
27
+ },
28
+ mode: {
29
+ defaultValue: "single",
30
+ control: {
31
+ type: "radio",
32
+ options: ["single", "multiple", "range"],
33
+ },
34
+ },
35
+ },
36
+ };
37
+
38
+ export const Default = (props) => {
39
+ const [open, setOpen] = useState(false);
40
+
41
+ const rangeCtx = UNSAFE_useRangeDatepicker({
42
+ fromDate: new Date("Aug 23 2020"),
43
+ toDate: new Date("Aug 23 2023"),
44
+ });
45
+
46
+ const singleCtx = UNSAFE_useDatepicker({
47
+ fromDate: new Date("Aug 23 2020"),
48
+ toDate: new Date("Aug 23 2023"),
49
+ });
50
+
51
+ const newProps = {
52
+ ...(!props.inputfield || props.mode === "multiple"
53
+ ? {
54
+ open,
55
+ onClose: () => setOpen(false),
56
+ fromDate: new Date("Aug 23 2020"),
57
+ toDate: new Date("Aug 23 2023"),
58
+ }
59
+ : {}),
60
+ };
61
+
62
+ const Comp = !props.standalone ? DatePicker : DatePicker.Standalone;
63
+
64
+ return (
65
+ <div>
66
+ <Comp
67
+ locale={props?.locale}
68
+ dropdownCaption={props?.dropdownCaption}
69
+ disableWeekends={props?.disableWeekends}
70
+ showWeekNumber={props.showWeekNumber}
71
+ mode={props.mode}
72
+ {...(props.mode === "single"
73
+ ? singleCtx.datepickerProps
74
+ : props.mode === "range"
75
+ ? rangeCtx.datepickerProps
76
+ : {})}
77
+ {...newProps}
78
+ >
79
+ {!props.standalone && (
80
+ <>
81
+ {props.inputfield && props.mode !== "multiple" ? (
82
+ <>
83
+ {props.mode === "range" ? (
84
+ <div style={{ display: "flex", gap: "1rem" }}>
85
+ <DatePicker.Input
86
+ label="Fra"
87
+ size={props?.size}
88
+ {...rangeCtx.fromInputProps}
89
+ />
90
+ <DatePicker.Input
91
+ label="Til"
92
+ size={props?.size}
93
+ {...rangeCtx.toInputProps}
94
+ />
95
+ </div>
96
+ ) : (
97
+ <>
98
+ <DatePicker.Input
99
+ label="Velg dato"
100
+ size={props?.size}
101
+ {...singleCtx.inputProps}
102
+ />
103
+ </>
104
+ )}
105
+ </>
106
+ ) : (
107
+ <Button onClick={() => setOpen((x) => !x)}>
108
+ Åpne datovelger
109
+ </Button>
110
+ )}
111
+ </>
112
+ )}
113
+ </Comp>
114
+ </div>
115
+ );
116
+ };
117
+
118
+ Default.args = {
119
+ dropdownCaption: false,
120
+ disableWeekends: false,
121
+ showWeekNumber: false,
122
+ inputfield: true,
123
+ standalone: false,
124
+ openOnFocus: true,
125
+ };
126
+
127
+ export const DropdownCaption = () => (
128
+ <DatePicker.Standalone
129
+ dropdownCaption
130
+ fromDate={new Date("Aug 23 2018")}
131
+ toDate={new Date("Aug 23 2022")}
132
+ />
133
+ );
134
+
135
+ export const DisabledDays = () => (
136
+ <DatePicker.Standalone disabled={disabledDays} disableWeekends />
137
+ );
138
+
139
+ export const ShowWeekNumber = () => <DatePicker.Standalone showWeekNumber />;
140
+
141
+ export const UseDatepicker = () => {
142
+ const { datepickerProps, inputProps } = UNSAFE_useDatepicker({
143
+ fromDate: new Date("Aug 23 2019"),
144
+ locale: "en",
145
+ });
146
+
147
+ return (
148
+ <div style={{ display: "flex", gap: "1rem" }}>
149
+ <DatePicker {...datepickerProps}>
150
+ <DatePicker.Input {...inputProps} label="Velg dato" />
151
+ </DatePicker>
152
+ </div>
153
+ );
154
+ };
155
+
156
+ export const UseRangedDatepicker = () => {
157
+ const { datepickerProps, fromInputProps, toInputProps } =
158
+ UNSAFE_useRangeDatepicker({
159
+ fromDate: new Date("Aug 23 2019"),
160
+ });
161
+
162
+ return (
163
+ <div style={{ display: "flex", gap: "1rem" }}>
164
+ <DatePicker {...datepickerProps}>
165
+ <div style={{ display: "flex", gap: "1rem" }}>
166
+ <DatePicker.Input {...fromInputProps} label="Fra" />
167
+ <DatePicker.Input {...toInputProps} label="Til" />
168
+ </div>
169
+ </DatePicker>
170
+ </div>
171
+ );
172
+ };
173
+
174
+ export const NB = () => <DatePicker.Standalone locale="nb" />;
175
+ export const NN = () => <DatePicker.Standalone locale="nn" />;
176
+ export const EN = () => <DatePicker.Standalone locale="en" />;
177
+
178
+ export const Standalone = () => <DatePicker.Standalone />;
179
+
180
+ export const StandaloneRange = () => <DatePicker.Standalone mode="range" />;
181
+ export const StandaloneMultiple = () => (
182
+ <DatePicker.Standalone mode="multiple" />
183
+ );
184
+
185
+ export const UserControlled = () => {
186
+ const [open, setOpen] = useState(false);
187
+ const id = useId();
188
+
189
+ return (
190
+ <div>
191
+ <DatePicker
192
+ mode="multiple"
193
+ open={open}
194
+ onClose={() => setOpen(false)}
195
+ id={id}
196
+ >
197
+ <Button aria-controls={id} onClick={() => setOpen((x) => !x)}>
198
+ Legg til dager
199
+ </Button>
200
+ </DatePicker>
201
+ </div>
202
+ );
203
+ };
204
+
205
+ export const Validering = () => {
206
+ const { datepickerProps, selectedDay, inputProps } = UNSAFE_useDatepicker({
207
+ fromDate: new Date("Aug 23 2019"),
208
+ });
209
+
210
+ return (
211
+ <div style={{ display: "flex", gap: "1rem" }}>
212
+ <DatePicker {...datepickerProps}>
213
+ <DatePicker.Input
214
+ error={
215
+ selectedDay && isSaturday(selectedDay)
216
+ ? "NAV-kontoret er ikke åpent på lørdager. Velg en annen dag."
217
+ : undefined
218
+ }
219
+ {...inputProps}
220
+ label="Velg dato"
221
+ />
222
+ </DatePicker>
223
+ </div>
224
+ );
225
+ };
226
+
227
+ export const DisabledInput = () => {
228
+ return (
229
+ <div style={{ display: "flex", gap: "1rem" }}>
230
+ <DatePicker>
231
+ <DatePicker.Input disabled label="Velg dato" />
232
+ </DatePicker>
233
+ </div>
234
+ );
235
+ };
@@ -0,0 +1,8 @@
1
+ export { useDatepicker as UNSAFE_useDatepicker } from "./useDatepicker";
2
+ export { useRangeDatepicker as UNSAFE_useRangeDatepicker } from "./useRangeDatepicker";
3
+ export { useMonthPicker as UNSAFE_useMonthPicker } from "./useMonthPicker";
4
+ export { useDateInputContext, DateContext } from "./useDateInputContext";
5
+ export {
6
+ useSharedMonthContext,
7
+ SharedMonthProvider,
8
+ } from "./useSharedMonthContext";
@@ -0,0 +1,32 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ interface DateContextContextProps {
4
+ /**
5
+ * Open state for popover
6
+ */
7
+ open: boolean;
8
+ /**
9
+ * Callback for opOpen toggle
10
+ */
11
+ onOpen: () => void;
12
+ /**
13
+ * Aria-connected ID
14
+ */
15
+ ariaId?: string;
16
+ }
17
+
18
+ export const DateContext = createContext<DateContextContextProps>({
19
+ open: false,
20
+ onOpen: () => null,
21
+ ariaId: undefined,
22
+ });
23
+
24
+ export const useDateInputContext = () => {
25
+ const context = useContext(DateContext);
26
+
27
+ if (!context) {
28
+ console.warn("useDateInputContext must be used with DateContext");
29
+ }
30
+
31
+ return context;
32
+ };
@@ -0,0 +1,225 @@
1
+ import { differenceInCalendarDays, isWeekend } from "date-fns";
2
+ import React, { useCallback, useEffect, useRef, useState } from "react";
3
+ import { DayClickEventHandler, 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
+
13
+ export interface UseDatepickerOptions
14
+ extends Pick<
15
+ DatePickerProps,
16
+ | "locale"
17
+ | "fromDate"
18
+ | "toDate"
19
+ | "today"
20
+ | "toDate"
21
+ | "fromDate"
22
+ | "toDate"
23
+ | "disabled"
24
+ | "disableWeekends"
25
+ > {
26
+ /**
27
+ * The initially selected Date
28
+ */
29
+ defaultSelected?: Date;
30
+ /**
31
+ * Make selection of Date required
32
+ */
33
+ required?: boolean;
34
+ }
35
+
36
+ interface UseDatepickerValue {
37
+ /**
38
+ * Use: <DatePicker {...datepickerProps}/>
39
+ */
40
+ datepickerProps: DatePickerProps;
41
+ /**
42
+ * Use: <DatePicker.Input {...inputProps}/>
43
+ */
44
+ inputProps: Pick<DateInputProps, "onChange" | "onFocus" | "onBlur" | "value">;
45
+ /**
46
+ * Resets all states (callback)
47
+ */
48
+ reset: () => void;
49
+ /**
50
+ * Currently selected date
51
+ * Up to user to validate date
52
+ */
53
+ selectedDay?: Date;
54
+ /**
55
+ * Manually override currently selected day
56
+ */
57
+ setSelected: (date?: Date) => void;
58
+ }
59
+
60
+ export const useDatepicker = (
61
+ opt: UseDatepickerOptions = {}
62
+ ): UseDatepickerValue => {
63
+ const {
64
+ locale: _locale = "nb",
65
+ required,
66
+ defaultSelected,
67
+ today = new Date(),
68
+ fromDate,
69
+ toDate,
70
+ disabled,
71
+ disableWeekends,
72
+ } = opt;
73
+
74
+ const locale = getLocaleFromString(_locale);
75
+
76
+ const inputRef = useRef<HTMLDivElement>(null);
77
+ const daypickerRef = useRef<HTMLDivElement>(null);
78
+
79
+ // Initialize states
80
+ const [month, setMonth] = useState(defaultSelected ?? today);
81
+ const [selectedDay, setSelectedDay] = useState(defaultSelected);
82
+ const [open, setOpen] = useState(false);
83
+
84
+ const defaultInputValue = defaultSelected
85
+ ? formatDateForInput(defaultSelected, locale, "date")
86
+ : "";
87
+ const [inputValue, setInputValue] = useState(defaultInputValue);
88
+
89
+ const handleFocusIn = useCallback(
90
+ (e) => {
91
+ ![
92
+ daypickerRef.current,
93
+ inputRef.current,
94
+ inputRef.current?.nextSibling,
95
+ ].some((element) => element?.contains(e.target)) &&
96
+ open &&
97
+ setOpen(false);
98
+ },
99
+ [open]
100
+ );
101
+
102
+ useEffect(() => {
103
+ window.addEventListener("focusin", handleFocusIn);
104
+ window.addEventListener("click", handleFocusIn);
105
+ return () => {
106
+ window?.removeEventListener?.("focusin", handleFocusIn);
107
+ window?.removeEventListener?.("click", handleFocusIn);
108
+ };
109
+ }, [handleFocusIn]);
110
+
111
+ const reset = () => {
112
+ setSelectedDay(defaultSelected);
113
+ setMonth(defaultSelected ?? today);
114
+ setInputValue(defaultInputValue ?? "");
115
+ };
116
+
117
+ const setSelected = (date: Date | undefined) => {
118
+ setSelectedDay(date);
119
+ setMonth(date ?? today);
120
+ setInputValue(date ? formatDateForInput(date, locale, "date") : "");
121
+ };
122
+
123
+ const handleFocus: React.FocusEventHandler<HTMLInputElement> = (e) => {
124
+ !open && setOpen(true);
125
+ if (!e.target.value) {
126
+ reset();
127
+ return;
128
+ }
129
+ let day = parseDate(e.target.value, today, locale, "date");
130
+ if (isValidDate(day)) {
131
+ setMonth(day);
132
+ setInputValue(formatDateForInput(day, locale, "date"));
133
+ }
134
+ };
135
+
136
+ const handleBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
137
+ let day = parseDate(e.target.value, today, locale, "date");
138
+ isValidDate(day) && setInputValue(formatDateForInput(day, locale, "date"));
139
+ };
140
+
141
+ /* Only allow de-selecting if not required */
142
+ const handleDayClick: DayClickEventHandler = (day, { selected }) => {
143
+ if (day && !selected) {
144
+ setOpen(false);
145
+ inputRef.current && inputRef.current.focus();
146
+ }
147
+
148
+ if (!required && selected) {
149
+ setSelectedDay(undefined);
150
+ setInputValue("");
151
+ return;
152
+ }
153
+ setSelectedDay(day);
154
+ setMonth(day);
155
+ setInputValue(day ? formatDateForInput(day, locale, "date") : "");
156
+ };
157
+
158
+ // When changing the input field, save its value in state and check if the
159
+ // string is a valid date. If it is a valid day, set it as selected and update
160
+ // the calendar’s month.
161
+ const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
162
+ setInputValue(e.target.value);
163
+ const day = parseDate(e.target.value, today, locale, "date");
164
+
165
+ if (
166
+ !isValidDate(day) ||
167
+ (disabled &&
168
+ ((disableWeekends && isWeekend(day)) || isMatch(day, disabled)))
169
+ ) {
170
+ setSelectedDay(undefined);
171
+ return;
172
+ }
173
+
174
+ const isBefore = fromDate && differenceInCalendarDays(fromDate, day) > 0;
175
+ const isAfter = toDate && differenceInCalendarDays(day, toDate) > 0;
176
+ if (isBefore || isAfter) {
177
+ setSelectedDay(undefined);
178
+ return;
179
+ }
180
+ setSelectedDay(day);
181
+ setMonth(day);
182
+ };
183
+
184
+ const handleClose = useCallback(() => {
185
+ setOpen(false);
186
+ inputRef.current && inputRef.current.focus();
187
+ }, []);
188
+
189
+ const escape = useCallback(
190
+ (e) => open && e.key === "Escape" && handleClose(),
191
+ [handleClose, open]
192
+ );
193
+
194
+ useEffect(() => {
195
+ window.addEventListener("keydown", escape, false);
196
+
197
+ return () => {
198
+ window.removeEventListener("keydown", escape, false);
199
+ };
200
+ }, [escape]);
201
+
202
+ const datepickerProps = {
203
+ month,
204
+ onMonthChange: (month) => setMonth(month),
205
+ onDayClick: handleDayClick,
206
+ selected: selectedDay,
207
+ locale: _locale,
208
+ fromDate,
209
+ toDate,
210
+ today,
211
+ open,
212
+ onOpenToggle: () => setOpen((x) => !x),
213
+ ref: daypickerRef,
214
+ };
215
+
216
+ const inputProps = {
217
+ onChange: handleChange,
218
+ onFocus: handleFocus,
219
+ onBlur: handleBlur,
220
+ value: inputValue,
221
+ ref: inputRef,
222
+ };
223
+
224
+ return { datepickerProps, inputProps, reset, selectedDay, setSelected };
225
+ };