@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.
- package/_docs.json +4956 -2568
- package/cjs/date/DateInput.js +79 -0
- package/cjs/date/datepicker/DatePicker.js +113 -0
- package/cjs/date/datepicker/DatePickerStandalone.js +81 -0
- package/cjs/date/datepicker/DayButton.js +43 -0
- package/cjs/date/datepicker/caption/Caption.js +23 -0
- package/cjs/date/datepicker/caption/DropdownCaption.js +38 -0
- package/cjs/date/datepicker/caption/index.js +10 -0
- package/cjs/date/datepicker/caption/package.json +6 -0
- package/cjs/date/hooks/index.js +15 -0
- package/cjs/date/hooks/package.json +6 -0
- package/cjs/date/hooks/useDateInputContext.js +17 -0
- package/cjs/date/hooks/useDatepicker.js +135 -0
- package/cjs/date/hooks/useMonthPicker.js +139 -0
- package/cjs/date/hooks/useRangeDatepicker.js +215 -0
- package/cjs/date/hooks/useSharedMonthContext.js +63 -0
- package/cjs/date/index.js +14 -0
- package/cjs/date/monthpicker/MonthButton.js +80 -0
- package/cjs/date/monthpicker/MonthCaption.js +47 -0
- package/cjs/date/monthpicker/MonthPicker.js +74 -0
- package/cjs/date/monthpicker/MonthPickerStandalone.js +54 -0
- package/cjs/date/monthpicker/MonthSelector.js +79 -0
- package/cjs/date/package.json +6 -0
- package/cjs/date/utils/check-dates.js +17 -0
- package/cjs/date/utils/dates-disabled.js +29 -0
- package/cjs/date/utils/format-date.js +12 -0
- package/cjs/date/utils/get-dates.js +43 -0
- package/cjs/date/utils/get-initial-year.js +21 -0
- package/cjs/date/utils/index.js +33 -0
- package/cjs/date/utils/is-match.js +61 -0
- package/cjs/date/utils/labels.js +85 -0
- package/cjs/date/utils/locale.js +21 -0
- package/cjs/date/utils/navigation.js +155 -0
- package/cjs/date/utils/package.json +6 -0
- package/cjs/date/utils/parse-date.js +39 -0
- package/cjs/form/ConfirmationPanel.js +4 -3
- package/cjs/index.js +1 -0
- package/cjs/util/AnimateHeight.js +2 -2
- package/esm/date/DateInput.d.ts +30 -0
- package/esm/date/DateInput.js +51 -0
- package/esm/date/DateInput.js.map +1 -0
- package/esm/date/datepicker/DatePicker.d.ts +95 -0
- package/esm/date/datepicker/DatePicker.js +85 -0
- package/esm/date/datepicker/DatePicker.js.map +1 -0
- package/esm/date/datepicker/DatePickerStandalone.d.ts +12 -0
- package/esm/date/datepicker/DatePickerStandalone.js +53 -0
- package/esm/date/datepicker/DatePickerStandalone.js.map +1 -0
- package/esm/date/datepicker/DayButton.d.ts +3 -0
- package/esm/date/datepicker/DayButton.js +17 -0
- package/esm/date/datepicker/DayButton.js.map +1 -0
- package/esm/date/datepicker/caption/Caption.d.ts +4 -0
- package/esm/date/datepicker/caption/Caption.js +17 -0
- package/esm/date/datepicker/caption/Caption.js.map +1 -0
- package/esm/date/datepicker/caption/DropdownCaption.d.ts +4 -0
- package/esm/date/datepicker/caption/DropdownCaption.js +32 -0
- package/esm/date/datepicker/caption/DropdownCaption.js.map +1 -0
- package/esm/date/datepicker/caption/index.d.ts +2 -0
- package/esm/date/datepicker/caption/index.js +3 -0
- package/esm/date/datepicker/caption/index.js.map +1 -0
- package/esm/date/hooks/index.d.ts +5 -0
- package/esm/date/hooks/index.js +6 -0
- package/esm/date/hooks/index.js.map +1 -0
- package/esm/date/hooks/useDateInputContext.d.ts +18 -0
- package/esm/date/hooks/useDateInputContext.js +14 -0
- package/esm/date/hooks/useDateInputContext.js.map +1 -0
- package/esm/date/hooks/useDatepicker.d.ts +37 -0
- package/esm/date/hooks/useDatepicker.js +132 -0
- package/esm/date/hooks/useDatepicker.js.map +1 -0
- package/esm/date/hooks/useMonthPicker.d.ts +33 -0
- package/esm/date/hooks/useMonthPicker.js +136 -0
- package/esm/date/hooks/useMonthPicker.js.map +1 -0
- package/esm/date/hooks/useRangeDatepicker.d.ts +39 -0
- package/esm/date/hooks/useRangeDatepicker.js +212 -0
- package/esm/date/hooks/useRangeDatepicker.js.map +1 -0
- package/esm/date/hooks/useSharedMonthContext.d.ts +21 -0
- package/esm/date/hooks/useSharedMonthContext.js +36 -0
- package/esm/date/hooks/useSharedMonthContext.js.map +1 -0
- package/esm/date/index.d.ts +6 -0
- package/esm/date/index.js +4 -0
- package/esm/date/index.js.map +1 -0
- package/esm/date/monthpicker/MonthButton.d.ts +11 -0
- package/esm/date/monthpicker/MonthButton.js +51 -0
- package/esm/date/monthpicker/MonthButton.js.map +1 -0
- package/esm/date/monthpicker/MonthCaption.d.ts +3 -0
- package/esm/date/monthpicker/MonthCaption.js +41 -0
- package/esm/date/monthpicker/MonthCaption.js.map +1 -0
- package/esm/date/monthpicker/MonthPicker.d.ts +90 -0
- package/esm/date/monthpicker/MonthPicker.js +46 -0
- package/esm/date/monthpicker/MonthPicker.js.map +1 -0
- package/esm/date/monthpicker/MonthPickerStandalone.d.ts +11 -0
- package/esm/date/monthpicker/MonthPickerStandalone.js +26 -0
- package/esm/date/monthpicker/MonthPickerStandalone.js.map +1 -0
- package/esm/date/monthpicker/MonthSelector.d.ts +3 -0
- package/esm/date/monthpicker/MonthSelector.js +50 -0
- package/esm/date/monthpicker/MonthSelector.js.map +1 -0
- package/esm/date/utils/check-dates.d.ts +4 -0
- package/esm/date/utils/check-dates.js +12 -0
- package/esm/date/utils/check-dates.js.map +1 -0
- package/esm/date/utils/dates-disabled.d.ts +1 -0
- package/esm/date/utils/dates-disabled.js +26 -0
- package/esm/date/utils/dates-disabled.js.map +1 -0
- package/esm/date/utils/format-date.d.ts +1 -0
- package/esm/date/utils/format-date.js +9 -0
- package/esm/date/utils/format-date.js.map +1 -0
- package/esm/date/utils/get-dates.d.ts +2 -0
- package/esm/date/utils/get-dates.js +39 -0
- package/esm/date/utils/get-dates.js.map +1 -0
- package/esm/date/utils/get-initial-year.d.ts +5 -0
- package/esm/date/utils/get-initial-year.js +18 -0
- package/esm/date/utils/get-initial-year.js.map +1 -0
- package/esm/date/utils/index.d.ts +10 -0
- package/esm/date/utils/index.js +11 -0
- package/esm/date/utils/index.js.map +1 -0
- package/esm/date/utils/is-match.d.ts +4 -0
- package/esm/date/utils/is-match.js +57 -0
- package/esm/date/utils/is-match.js.map +1 -0
- package/esm/date/utils/labels.d.ts +6 -0
- package/esm/date/utils/labels.js +79 -0
- package/esm/date/utils/labels.js.map +1 -0
- package/esm/date/utils/locale.d.ts +2 -0
- package/esm/date/utils/locale.js +15 -0
- package/esm/date/utils/locale.js.map +1 -0
- package/esm/date/utils/navigation.d.ts +2 -0
- package/esm/date/utils/navigation.js +152 -0
- package/esm/date/utils/navigation.js.map +1 -0
- package/esm/date/utils/parse-date.d.ts +3 -0
- package/esm/date/utils/parse-date.js +36 -0
- package/esm/date/utils/parse-date.js.map +1 -0
- package/esm/form/ConfirmationPanel.js +5 -4
- package/esm/form/ConfirmationPanel.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/typography/Detail.d.ts +1 -1
- package/esm/util/AnimateHeight.js +2 -2
- package/esm/util/AnimateHeight.js.map +1 -1
- package/package.json +6 -4
- package/src/chat/chat.stories.tsx +15 -15
- package/src/date/DateInput.tsx +167 -0
- package/src/date/datepicker/DatePicker.tsx +252 -0
- package/src/date/datepicker/DatePickerStandalone.tsx +120 -0
- package/src/date/datepicker/DayButton.tsx +26 -0
- package/src/date/datepicker/caption/Caption.tsx +51 -0
- package/src/date/datepicker/caption/DropdownCaption.tsx +98 -0
- package/src/date/datepicker/caption/index.ts +2 -0
- package/src/date/datepicker/datepicker.stories.tsx +235 -0
- package/src/date/hooks/index.ts +8 -0
- package/src/date/hooks/useDateInputContext.tsx +32 -0
- package/src/date/hooks/useDatepicker.tsx +225 -0
- package/src/date/hooks/useMonthPicker.tsx +223 -0
- package/src/date/hooks/useRangeDatepicker.tsx +348 -0
- package/src/date/hooks/useSharedMonthContext.tsx +68 -0
- package/src/date/index.ts +16 -0
- package/src/date/monthpicker/MonthButton.tsx +109 -0
- package/src/date/monthpicker/MonthCaption.tsx +94 -0
- package/src/date/monthpicker/MonthPicker.tsx +204 -0
- package/src/date/monthpicker/MonthPickerStandalone.tsx +87 -0
- package/src/date/monthpicker/MonthSelector.tsx +85 -0
- package/src/date/monthpicker/monthpicker.stories.tsx +128 -0
- package/src/date/utils/__tests__/check-dates.test.ts +47 -0
- package/src/date/utils/__tests__/dates-disabled.test.ts +48 -0
- package/src/date/utils/__tests__/format-dates.test.ts +22 -0
- package/src/date/utils/__tests__/get-dates.test.ts +79 -0
- package/src/date/utils/__tests__/get-initial-year.test.ts +94 -0
- package/src/date/utils/__tests__/is-match.test.ts +42 -0
- package/src/date/utils/__tests__/parse-dates.test.ts +81 -0
- package/src/date/utils/check-dates.ts +17 -0
- package/src/date/utils/dates-disabled.ts +26 -0
- package/src/date/utils/format-date.ts +17 -0
- package/src/date/utils/get-dates.ts +44 -0
- package/src/date/utils/get-initial-year.ts +25 -0
- package/src/date/utils/index.ts +21 -0
- package/src/date/utils/is-match.ts +88 -0
- package/src/date/utils/labels.ts +84 -0
- package/src/date/utils/locale.ts +15 -0
- package/src/date/utils/navigation.ts +292 -0
- package/src/date/utils/parse-date.ts +46 -0
- package/src/form/ConfirmationPanel.tsx +9 -2
- package/src/form/checkbox/Checkbox.test.tsx +2 -2
- package/src/form/radio/radio.stories.tsx +4 -4
- package/src/form/stories/textarea.stories.tsx +1 -3
- package/src/help-text/help-text.stories.tsx +3 -0
- package/src/index.ts +1 -0
- package/src/typography/Detail.tsx +1 -1
- 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,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
|
+
};
|