@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,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
+ };