@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,109 @@
|
|
|
1
|
+
import cl from "clsx";
|
|
2
|
+
import {
|
|
3
|
+
compareAsc,
|
|
4
|
+
compareDesc,
|
|
5
|
+
format,
|
|
6
|
+
isSameMonth,
|
|
7
|
+
setYear,
|
|
8
|
+
} from "date-fns";
|
|
9
|
+
import React, { useEffect, useRef } from "react";
|
|
10
|
+
import { useDayPicker } from "react-day-picker";
|
|
11
|
+
import { useSharedMonthContext } from "../hooks";
|
|
12
|
+
import { dateIsInCurrentMonth, isMatch, nextEnabled } from "../utils";
|
|
13
|
+
|
|
14
|
+
interface MonthType {
|
|
15
|
+
month: Date;
|
|
16
|
+
months: Date[];
|
|
17
|
+
focus: Date | undefined;
|
|
18
|
+
setFocus: Function;
|
|
19
|
+
tabRoot?: Date;
|
|
20
|
+
setTabRoot: Function;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const disableMonth = (month: Date, fromDate?: Date, toDate?: Date) => {
|
|
24
|
+
if (fromDate && toDate) {
|
|
25
|
+
return (
|
|
26
|
+
compareAsc(month, fromDate) === -1 || compareDesc(month, toDate) === -1
|
|
27
|
+
);
|
|
28
|
+
} else if (fromDate) {
|
|
29
|
+
return compareAsc(month, fromDate) === -1;
|
|
30
|
+
} else if (toDate) {
|
|
31
|
+
return compareDesc(month, toDate) === -1;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const MonthButton = ({
|
|
37
|
+
month,
|
|
38
|
+
months,
|
|
39
|
+
focus,
|
|
40
|
+
setFocus,
|
|
41
|
+
tabRoot,
|
|
42
|
+
setTabRoot,
|
|
43
|
+
}: MonthType) => {
|
|
44
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
45
|
+
const { hasDropdown, selected, onSelect, year, toYear, disabled } =
|
|
46
|
+
useSharedMonthContext();
|
|
47
|
+
|
|
48
|
+
const { fromDate, toDate, locale } = useDayPicker();
|
|
49
|
+
const isSelected = selected && isSameMonth(month, selected);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (focus) {
|
|
53
|
+
isSameMonth(month, focus) && ref.current && ref.current.focus();
|
|
54
|
+
setFocus();
|
|
55
|
+
}
|
|
56
|
+
}, [focus, month, setFocus]);
|
|
57
|
+
|
|
58
|
+
const isDisabled =
|
|
59
|
+
isMatch(setYear(month, year.getFullYear()), disabled) ||
|
|
60
|
+
disableMonth(month, fromDate, toDate);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<button
|
|
64
|
+
ref={ref}
|
|
65
|
+
type="button"
|
|
66
|
+
onClick={() => onSelect(isSelected ? undefined : month)}
|
|
67
|
+
disabled={isDisabled}
|
|
68
|
+
className={cl("navds-date__month-button", {
|
|
69
|
+
"rdp-day_today": dateIsInCurrentMonth(month, year),
|
|
70
|
+
"rdp-day_selected": isSelected,
|
|
71
|
+
"rdp-day_disabled": isDisabled,
|
|
72
|
+
})}
|
|
73
|
+
tabIndex={
|
|
74
|
+
tabRoot && isSameMonth(month, setYear(tabRoot, year.getFullYear()))
|
|
75
|
+
? 0
|
|
76
|
+
: -1
|
|
77
|
+
}
|
|
78
|
+
onKeyDown={(e) => {
|
|
79
|
+
const next = nextEnabled(
|
|
80
|
+
months,
|
|
81
|
+
e.key,
|
|
82
|
+
disabled,
|
|
83
|
+
month,
|
|
84
|
+
toYear,
|
|
85
|
+
year,
|
|
86
|
+
hasDropdown,
|
|
87
|
+
fromDate,
|
|
88
|
+
toDate
|
|
89
|
+
);
|
|
90
|
+
setFocus(next);
|
|
91
|
+
setTabRoot(next);
|
|
92
|
+
}}
|
|
93
|
+
onFocus={() => {
|
|
94
|
+
setTabRoot(focus);
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<span aria-hidden="true">
|
|
98
|
+
{format(new Date(month), "LLL", { locale })
|
|
99
|
+
.replace(".", "")
|
|
100
|
+
.substring(0, 3)}
|
|
101
|
+
</span>
|
|
102
|
+
<span className="navds-sr-only">
|
|
103
|
+
{format(new Date(month), "LLLL", { locale })}
|
|
104
|
+
</span>
|
|
105
|
+
</button>
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default MonthButton;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Left, Right } from "@navikt/ds-icons";
|
|
2
|
+
import { isSameYear, setYear, startOfMonth, startOfYear } from "date-fns";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useDayPicker } from "react-day-picker";
|
|
5
|
+
import { Button, Select } from "../..";
|
|
6
|
+
import { useSharedMonthContext } from "../hooks";
|
|
7
|
+
import { hasNextYear, labelNextYear, labelPrevYear } from "../utils";
|
|
8
|
+
|
|
9
|
+
export const MonthCaption = () => {
|
|
10
|
+
const {
|
|
11
|
+
fromDate,
|
|
12
|
+
toDate,
|
|
13
|
+
formatters: { formatYearCaption },
|
|
14
|
+
locale,
|
|
15
|
+
} = useDayPicker();
|
|
16
|
+
|
|
17
|
+
const { hasDropdown, year, toYear } = useSharedMonthContext();
|
|
18
|
+
|
|
19
|
+
const years: Date[] = [];
|
|
20
|
+
|
|
21
|
+
if (hasDropdown && fromDate && toDate) {
|
|
22
|
+
const fromYear = fromDate.getFullYear();
|
|
23
|
+
const toYear = toDate.getFullYear();
|
|
24
|
+
for (let year = fromYear; year <= toYear; year++) {
|
|
25
|
+
years.push(setYear(startOfYear(new Date()), year));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const handleYearChange = (e) =>
|
|
30
|
+
toYear(setYear(startOfMonth(new Date()), Number(e.target.value)));
|
|
31
|
+
|
|
32
|
+
const handleButtonClick = (val: number) => {
|
|
33
|
+
let newMonth: Date;
|
|
34
|
+
if (hasDropdown && hasNextYear(year, years, val)) {
|
|
35
|
+
newMonth = setYear(new Date(), year.getFullYear() + val);
|
|
36
|
+
toYear(newMonth);
|
|
37
|
+
} else if (!hasDropdown) {
|
|
38
|
+
const newYear = Number(year.getFullYear() + val);
|
|
39
|
+
newMonth = setYear(year, newYear);
|
|
40
|
+
toYear(newMonth);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const hasFollowingYear = (value: number) => {
|
|
45
|
+
return years.some((y) =>
|
|
46
|
+
isSameYear(y, setYear(year, Number(year.getFullYear() + value)))
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="navds-date__caption">
|
|
52
|
+
<Button
|
|
53
|
+
className="navds-date__caption-button"
|
|
54
|
+
disabled={!hasDropdown ? false : !hasFollowingYear(-1)}
|
|
55
|
+
onClick={() => handleButtonClick(-1)}
|
|
56
|
+
aria-label={labelPrevYear(locale?.code)}
|
|
57
|
+
icon={<Left aria-hidden />}
|
|
58
|
+
variant="tertiary"
|
|
59
|
+
type="button"
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
{hasDropdown ? (
|
|
63
|
+
<Select
|
|
64
|
+
label="velg år"
|
|
65
|
+
hideLabel
|
|
66
|
+
value={year?.getFullYear()}
|
|
67
|
+
onChange={handleYearChange}
|
|
68
|
+
className="navds-date__caption__year"
|
|
69
|
+
>
|
|
70
|
+
{years.map((year) => (
|
|
71
|
+
<option key={year.getFullYear()} value={year.getFullYear()}>
|
|
72
|
+
{formatYearCaption(year, { locale })}
|
|
73
|
+
</option>
|
|
74
|
+
))}
|
|
75
|
+
</Select>
|
|
76
|
+
) : (
|
|
77
|
+
<span className="navds-date__year-label" aria-live="polite">
|
|
78
|
+
{year.getFullYear()}
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
<Button
|
|
82
|
+
className="navds-date__caption-button"
|
|
83
|
+
disabled={!hasDropdown ? false : !hasFollowingYear(1)}
|
|
84
|
+
onClick={() => handleButtonClick(1)}
|
|
85
|
+
aria-label={labelNextYear(locale?.code)}
|
|
86
|
+
icon={<Right aria-hidden />}
|
|
87
|
+
variant="tertiary"
|
|
88
|
+
type="button"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default MonthCaption;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import cl from "clsx";
|
|
2
|
+
import React, { forwardRef, useRef, useState } from "react";
|
|
3
|
+
import { RootProvider } from "react-day-picker";
|
|
4
|
+
import { Popover, useId } from "../..";
|
|
5
|
+
import { DateInputType, MonthPickerInput } from "../DateInput";
|
|
6
|
+
import { DateContext, SharedMonthProvider } from "../hooks";
|
|
7
|
+
import { getLocaleFromString, Matcher } from "../utils";
|
|
8
|
+
import MonthCaption from "./MonthCaption";
|
|
9
|
+
import MonthPickerStandalone, {
|
|
10
|
+
MonthPickerStandaloneType,
|
|
11
|
+
} from "./MonthPickerStandalone";
|
|
12
|
+
import MonthSelector from "./MonthSelector";
|
|
13
|
+
|
|
14
|
+
export interface MonthPickerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
15
|
+
/**
|
|
16
|
+
* Element monthpicker anchors to. Use <MonthPicker.Input /> for built-in toggle,
|
|
17
|
+
* or make your own with the open/onClose props
|
|
18
|
+
*/
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Classname for datepicker in popover
|
|
22
|
+
*/
|
|
23
|
+
className?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Classname for wrapper
|
|
26
|
+
*/
|
|
27
|
+
wrapperClassName?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The earliest month to start navigation.
|
|
30
|
+
*/
|
|
31
|
+
fromDate?: Date;
|
|
32
|
+
/**
|
|
33
|
+
* The latest day to end navigation.
|
|
34
|
+
*/
|
|
35
|
+
toDate?: Date;
|
|
36
|
+
/**
|
|
37
|
+
* Changes monthpicker locale
|
|
38
|
+
* @default "nb" (norsk bokmål)
|
|
39
|
+
*/
|
|
40
|
+
locale?: "nb" | "nn" | "en";
|
|
41
|
+
/**
|
|
42
|
+
* Display dropdown for choosing year.
|
|
43
|
+
* Needs `fromDate` + `toDate` to work.
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
dropdownCaption?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Apply the disabled modifier to the matching months. Uses a subset of React Day Picker Matcher type.
|
|
49
|
+
* {@link https://react-day-picker.js.org/api/types/Matcher | Matcher type-definition}
|
|
50
|
+
*/
|
|
51
|
+
disabled?: Matcher[];
|
|
52
|
+
/**
|
|
53
|
+
* Controlled selected-month
|
|
54
|
+
*/
|
|
55
|
+
selected?: Date;
|
|
56
|
+
/**
|
|
57
|
+
* Default selected month.
|
|
58
|
+
*/
|
|
59
|
+
defaultSelected?: Date;
|
|
60
|
+
/**
|
|
61
|
+
* Open state for user-controlled state
|
|
62
|
+
* Component controlled by default
|
|
63
|
+
*/
|
|
64
|
+
open?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* onClose callback for user-controlled state
|
|
67
|
+
*/
|
|
68
|
+
onClose?: () => void;
|
|
69
|
+
/**
|
|
70
|
+
* onOpenToggle callback for user-controlled-state
|
|
71
|
+
* only called if `<MonthPicker.Input />` is used
|
|
72
|
+
*/
|
|
73
|
+
onOpenToggle?: () => void;
|
|
74
|
+
/**
|
|
75
|
+
* Callback for user-controlled state
|
|
76
|
+
*/
|
|
77
|
+
onMonthSelect?: Function;
|
|
78
|
+
/**
|
|
79
|
+
* Used to set visible year programmatically
|
|
80
|
+
* Component controlled by default
|
|
81
|
+
*/
|
|
82
|
+
year?: Date;
|
|
83
|
+
/**
|
|
84
|
+
* Event fired when the user navigates between years.
|
|
85
|
+
*/
|
|
86
|
+
onYearChange?: (y?: Date) => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface MonthPickerComponent
|
|
90
|
+
extends React.ForwardRefExoticComponent<MonthPickerProps> {
|
|
91
|
+
/**
|
|
92
|
+
* Variant without popover
|
|
93
|
+
*/
|
|
94
|
+
Standalone: MonthPickerStandaloneType;
|
|
95
|
+
/**
|
|
96
|
+
* Built-in Inputfield
|
|
97
|
+
*/
|
|
98
|
+
Input: DateInputType;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const MonthPicker = forwardRef<HTMLDivElement, MonthPickerProps>(
|
|
102
|
+
(
|
|
103
|
+
{
|
|
104
|
+
children,
|
|
105
|
+
dropdownCaption = false,
|
|
106
|
+
fromDate,
|
|
107
|
+
toDate,
|
|
108
|
+
disabled = [],
|
|
109
|
+
selected,
|
|
110
|
+
open: _open,
|
|
111
|
+
id,
|
|
112
|
+
onClose,
|
|
113
|
+
onOpenToggle,
|
|
114
|
+
locale = "nb",
|
|
115
|
+
onMonthSelect,
|
|
116
|
+
className,
|
|
117
|
+
wrapperClassName,
|
|
118
|
+
defaultSelected,
|
|
119
|
+
year,
|
|
120
|
+
onYearChange,
|
|
121
|
+
},
|
|
122
|
+
ref
|
|
123
|
+
) => {
|
|
124
|
+
const ariaId = useId(id);
|
|
125
|
+
const [open, setOpen] = useState(_open ?? false);
|
|
126
|
+
|
|
127
|
+
const wrapperRef = useRef<HTMLDivElement | null>(null);
|
|
128
|
+
|
|
129
|
+
const [selectedMonth, setSelectedMonth] = useState<Date | undefined>(
|
|
130
|
+
defaultSelected
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const handleSelect = (month?: Date) => {
|
|
134
|
+
setSelectedMonth(month);
|
|
135
|
+
onMonthSelect?.(month);
|
|
136
|
+
month && (onClose?.() ?? setOpen(false));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if (dropdownCaption && (!fromDate || !toDate)) {
|
|
140
|
+
console.warn("Using dropdownCaption required fromDate and toDate");
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<DateContext.Provider
|
|
146
|
+
value={{
|
|
147
|
+
open: _open ?? open,
|
|
148
|
+
onOpen: () => {
|
|
149
|
+
setOpen((x) => !x);
|
|
150
|
+
onOpenToggle?.();
|
|
151
|
+
},
|
|
152
|
+
ariaId,
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<div
|
|
156
|
+
ref={wrapperRef}
|
|
157
|
+
className={cl("navds-date__wrapper", wrapperClassName)}
|
|
158
|
+
>
|
|
159
|
+
{children}
|
|
160
|
+
{(_open ?? open) && (
|
|
161
|
+
<Popover
|
|
162
|
+
arrow={false}
|
|
163
|
+
anchorEl={wrapperRef.current}
|
|
164
|
+
open={_open ?? open}
|
|
165
|
+
onClose={() => onClose?.() ?? setOpen(false)}
|
|
166
|
+
placement="bottom-start"
|
|
167
|
+
role="dialog"
|
|
168
|
+
ref={ref}
|
|
169
|
+
id={ariaId}
|
|
170
|
+
className="navds-date"
|
|
171
|
+
>
|
|
172
|
+
<RootProvider
|
|
173
|
+
locale={getLocaleFromString(locale)}
|
|
174
|
+
selected={selected}
|
|
175
|
+
toDate={toDate}
|
|
176
|
+
fromDate={fromDate}
|
|
177
|
+
month={selected ?? selectedMonth}
|
|
178
|
+
>
|
|
179
|
+
<div className={cl("rdp-month", className)}>
|
|
180
|
+
<SharedMonthProvider
|
|
181
|
+
dropdownCaption={dropdownCaption}
|
|
182
|
+
disabled={disabled}
|
|
183
|
+
selected={selected ?? selectedMonth}
|
|
184
|
+
onSelect={handleSelect}
|
|
185
|
+
year={year}
|
|
186
|
+
onYearChange={onYearChange}
|
|
187
|
+
>
|
|
188
|
+
<MonthCaption />
|
|
189
|
+
<MonthSelector />
|
|
190
|
+
</SharedMonthProvider>
|
|
191
|
+
</div>
|
|
192
|
+
</RootProvider>
|
|
193
|
+
</Popover>
|
|
194
|
+
)}
|
|
195
|
+
</div>
|
|
196
|
+
</DateContext.Provider>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
) as MonthPickerComponent;
|
|
200
|
+
|
|
201
|
+
MonthPicker.Standalone = MonthPickerStandalone;
|
|
202
|
+
MonthPicker.Input = MonthPickerInput;
|
|
203
|
+
|
|
204
|
+
export default MonthPicker;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import cl from "clsx";
|
|
2
|
+
import React, { forwardRef, useState } from "react";
|
|
3
|
+
import { RootProvider } from "react-day-picker";
|
|
4
|
+
import { SharedMonthProvider } from "../hooks";
|
|
5
|
+
import { getLocaleFromString } from "../utils";
|
|
6
|
+
import MonthCaption from "./MonthCaption";
|
|
7
|
+
import { MonthPickerProps } from "./MonthPicker";
|
|
8
|
+
import MonthSelector from "./MonthSelector";
|
|
9
|
+
|
|
10
|
+
export interface MonthPickerStandaloneProps
|
|
11
|
+
extends Omit<
|
|
12
|
+
MonthPickerProps,
|
|
13
|
+
"open" | "onClose" | "onOpenToggle" | "wrapperClassName"
|
|
14
|
+
> {
|
|
15
|
+
/**
|
|
16
|
+
* Monthpicker classname
|
|
17
|
+
*/
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type MonthPickerStandaloneType = React.ForwardRefExoticComponent<
|
|
22
|
+
MonthPickerStandaloneProps & React.RefAttributes<HTMLDivElement>
|
|
23
|
+
>;
|
|
24
|
+
|
|
25
|
+
export const MonthPickerStandalone = forwardRef<
|
|
26
|
+
HTMLDivElement,
|
|
27
|
+
MonthPickerStandaloneProps
|
|
28
|
+
>(
|
|
29
|
+
(
|
|
30
|
+
{
|
|
31
|
+
dropdownCaption = false,
|
|
32
|
+
fromDate,
|
|
33
|
+
toDate,
|
|
34
|
+
disabled = [],
|
|
35
|
+
selected,
|
|
36
|
+
className,
|
|
37
|
+
locale = "nb",
|
|
38
|
+
onMonthSelect,
|
|
39
|
+
defaultSelected,
|
|
40
|
+
year,
|
|
41
|
+
onYearChange,
|
|
42
|
+
},
|
|
43
|
+
ref
|
|
44
|
+
) => {
|
|
45
|
+
const [selectedMonth, setSelectedMonth] = useState<Date | undefined>(
|
|
46
|
+
defaultSelected
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const handleSelect = (month?: Date) => {
|
|
50
|
+
setSelectedMonth(month);
|
|
51
|
+
onMonthSelect?.(month);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (dropdownCaption && (!fromDate || !toDate)) {
|
|
55
|
+
console.warn("Using dropdownCaption required fromDate and toDate");
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div ref={ref} className={cl("navds-date__wrapper", className)}>
|
|
61
|
+
<RootProvider
|
|
62
|
+
locale={getLocaleFromString(locale)}
|
|
63
|
+
selected={selected ?? selectedMonth}
|
|
64
|
+
toDate={toDate}
|
|
65
|
+
fromDate={fromDate}
|
|
66
|
+
month={selected ?? selectedMonth}
|
|
67
|
+
>
|
|
68
|
+
<div className="navds-date rdp-month">
|
|
69
|
+
<SharedMonthProvider
|
|
70
|
+
dropdownCaption={dropdownCaption}
|
|
71
|
+
disabled={disabled}
|
|
72
|
+
selected={selected ?? selectedMonth}
|
|
73
|
+
onSelect={handleSelect}
|
|
74
|
+
year={year}
|
|
75
|
+
onYearChange={onYearChange}
|
|
76
|
+
>
|
|
77
|
+
<MonthCaption />
|
|
78
|
+
<MonthSelector />
|
|
79
|
+
</SharedMonthProvider>
|
|
80
|
+
</div>
|
|
81
|
+
</RootProvider>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export default MonthPickerStandalone;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { isSameMonth, setMonth, setYear, startOfMonth } from "date-fns";
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import { BodyShort } from "../..";
|
|
4
|
+
import { useSharedMonthContext } from "../hooks";
|
|
5
|
+
import { isMatch } from "../utils";
|
|
6
|
+
import MonthButton from "./MonthButton";
|
|
7
|
+
|
|
8
|
+
const getAllMonths = () => {
|
|
9
|
+
const months: Date[] = [];
|
|
10
|
+
const date = startOfMonth(new Date());
|
|
11
|
+
for (let month = 0; month <= 11; month++) {
|
|
12
|
+
months.push(setMonth(date, month));
|
|
13
|
+
}
|
|
14
|
+
return months;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const MonthSelector = () => {
|
|
18
|
+
const [focus, setFocus] = useState<Date>();
|
|
19
|
+
|
|
20
|
+
const { selected, year, disabled } = useSharedMonthContext();
|
|
21
|
+
|
|
22
|
+
const months: Date[] = getAllMonths();
|
|
23
|
+
|
|
24
|
+
const hasSelected =
|
|
25
|
+
selected &&
|
|
26
|
+
months.some((m) => isSameMonth(setYear(m, year.getFullYear()), selected));
|
|
27
|
+
|
|
28
|
+
const getRootFallback = () => {
|
|
29
|
+
const today = startOfMonth(new Date());
|
|
30
|
+
if (
|
|
31
|
+
year?.getFullYear() === today.getFullYear() &&
|
|
32
|
+
!isMatch(today, disabled)
|
|
33
|
+
) {
|
|
34
|
+
return today;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < months.length; i++) {
|
|
38
|
+
const m = months[i];
|
|
39
|
+
if (!isMatch(setYear(m, year.getFullYear()), disabled)) {
|
|
40
|
+
return setYear(m, year.getFullYear());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const [tabRoot, setTabRoot] = useState(
|
|
46
|
+
hasSelected ? selected : getRootFallback()
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (tabRoot?.getFullYear() !== year.getFullYear()) {
|
|
50
|
+
setTabRoot(hasSelected ? selected : getRootFallback());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const tableMonths = [
|
|
54
|
+
months.slice(0, 4),
|
|
55
|
+
months.slice(4, 8),
|
|
56
|
+
months.slice(8, 12),
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<BodyShort as="table" className="rdp-table">
|
|
61
|
+
<tbody className="rdp-tbody">
|
|
62
|
+
{tableMonths.map((x, y) => (
|
|
63
|
+
<tr className="rdp-row" key={y}>
|
|
64
|
+
{x.map((month: Date, y) => {
|
|
65
|
+
return (
|
|
66
|
+
<td key={month.toDateString()} className="rdp-cell">
|
|
67
|
+
<MonthButton
|
|
68
|
+
month={setYear(month, year.getFullYear())}
|
|
69
|
+
months={months}
|
|
70
|
+
focus={focus}
|
|
71
|
+
setFocus={setFocus}
|
|
72
|
+
tabRoot={tabRoot}
|
|
73
|
+
setTabRoot={setTabRoot}
|
|
74
|
+
/>
|
|
75
|
+
</td>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</tr>
|
|
79
|
+
))}
|
|
80
|
+
</tbody>
|
|
81
|
+
</BodyShort>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default MonthSelector;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React, { useId, useState } from "react";
|
|
2
|
+
import { Button } from "../..";
|
|
3
|
+
import { UNSAFE_useMonthPicker } from "../hooks";
|
|
4
|
+
import MonthPicker from "./MonthPicker";
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: "ds-react/Monthpicker",
|
|
8
|
+
component: MonthPicker,
|
|
9
|
+
argTypes: {
|
|
10
|
+
size: {
|
|
11
|
+
control: {
|
|
12
|
+
type: "radio",
|
|
13
|
+
options: ["medium", "small"],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
locale: {
|
|
17
|
+
control: {
|
|
18
|
+
type: "radio",
|
|
19
|
+
options: ["nb", "nn", "en"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Default = () => {
|
|
26
|
+
const { inputProps, monthpickerProps } = UNSAFE_useMonthPicker({
|
|
27
|
+
disabled: [new Date("Apr 1 2022")],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div style={{ height: "20rem" }}>
|
|
32
|
+
<MonthPicker {...monthpickerProps}>
|
|
33
|
+
<MonthPicker.Input
|
|
34
|
+
label="Velg måned"
|
|
35
|
+
variant="monthpicker"
|
|
36
|
+
{...inputProps}
|
|
37
|
+
/>
|
|
38
|
+
</MonthPicker>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const dropdownCaption = () => {
|
|
44
|
+
return (
|
|
45
|
+
<MonthPicker.Standalone
|
|
46
|
+
dropdownCaption
|
|
47
|
+
fromDate={new Date("Jan 1 2019")}
|
|
48
|
+
toDate={new Date("Sep 27 2032")}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const NB = () => <MonthPicker.Standalone locale="nb" />;
|
|
54
|
+
export const NN = () => <MonthPicker.Standalone locale="nn" />;
|
|
55
|
+
export const EN = () => <MonthPicker.Standalone locale="en" />;
|
|
56
|
+
|
|
57
|
+
export const DisabledMonths = (props) => {
|
|
58
|
+
return (
|
|
59
|
+
<MonthPicker.Standalone
|
|
60
|
+
disabled={[
|
|
61
|
+
{ from: new Date("Jan 1 2022"), to: new Date("Jul 6 2022") },
|
|
62
|
+
{ from: new Date("Apr 2 2023"), to: new Date("Dec 4 2023") },
|
|
63
|
+
new Date("Sep 5 2022"),
|
|
64
|
+
new Date("Jan 5 2023"),
|
|
65
|
+
]}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Standalone = () => {
|
|
71
|
+
return <MonthPicker.Standalone />;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const UseMonthPicker = () => {
|
|
75
|
+
const { inputProps, monthpickerProps } = UNSAFE_useMonthPicker({
|
|
76
|
+
locale: "nb",
|
|
77
|
+
defaultSelected: new Date(),
|
|
78
|
+
disabled: [new Date("Apr 1 2022")],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div style={{ height: "20rem" }}>
|
|
83
|
+
<MonthPicker {...monthpickerProps}>
|
|
84
|
+
<MonthPicker.Input
|
|
85
|
+
{...inputProps}
|
|
86
|
+
label="Velg måned"
|
|
87
|
+
variant="monthpicker"
|
|
88
|
+
/>
|
|
89
|
+
</MonthPicker>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const Required = () => {
|
|
95
|
+
const { inputProps, monthpickerProps } = UNSAFE_useMonthPicker({
|
|
96
|
+
locale: "nb",
|
|
97
|
+
defaultSelected: new Date(),
|
|
98
|
+
disabled: [new Date("Apr 1 2022")],
|
|
99
|
+
required: true,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div style={{ height: "20rem" }}>
|
|
104
|
+
<MonthPicker {...monthpickerProps}>
|
|
105
|
+
<MonthPicker.Input
|
|
106
|
+
{...inputProps}
|
|
107
|
+
label="Velg måned"
|
|
108
|
+
variant="monthpicker"
|
|
109
|
+
/>
|
|
110
|
+
</MonthPicker>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const UserControlled = () => {
|
|
116
|
+
const [open, setOpen] = useState(false);
|
|
117
|
+
const id = useId();
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div>
|
|
121
|
+
<MonthPicker open={open} onClose={() => setOpen(false)} id={id}>
|
|
122
|
+
<Button aria-controls={id} onClick={() => setOpen((x) => !x)}>
|
|
123
|
+
Velg måned
|
|
124
|
+
</Button>
|
|
125
|
+
</MonthPicker>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
};
|