react-day-picker 8.0.2 → 8.0.5

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 (213) hide show
  1. package/dist/components/Head/utils/getWeekdays.d.ts +1 -1
  2. package/dist/components/Table/utils/daysToMonthWeeks.d.ts +1 -1
  3. package/dist/components/Table/utils/getMonthWeeks.d.ts +1 -1
  4. package/dist/contexts/DayPicker/formatters/formatCaption.d.ts +1 -1
  5. package/dist/contexts/DayPicker/formatters/formatDay.d.ts +1 -1
  6. package/dist/contexts/DayPicker/formatters/formatMonthCaption.d.ts +1 -1
  7. package/dist/contexts/DayPicker/formatters/formatWeekdayName.d.ts +1 -1
  8. package/dist/hooks/useDayRender/useDayRender.d.ts +1 -1
  9. package/dist/index.esm.js +30 -7
  10. package/dist/index.esm.js.map +1 -1
  11. package/dist/index.js +154 -107
  12. package/dist/index.js.map +1 -1
  13. package/dist/react-day-picker.min.js +1 -1
  14. package/dist/style.css +11 -10
  15. package/dist/style.module.css +11 -10
  16. package/dist/types/DayPickerBase.d.ts +1 -1
  17. package/dist/types/Labels.d.ts +1 -1
  18. package/package.json +15 -14
  19. package/src/DayPicker.tsx +113 -0
  20. package/src/components/Button/Button.test.tsx +47 -0
  21. package/src/components/Button/Button.tsx +36 -0
  22. package/src/components/Button/index.ts +1 -0
  23. package/src/components/Caption/Caption.test.tsx +86 -0
  24. package/src/components/Caption/Caption.tsx +54 -0
  25. package/src/components/Caption/index.ts +1 -0
  26. package/src/components/CaptionDropdowns/CaptionDropdowns.test.tsx +123 -0
  27. package/src/components/CaptionDropdowns/CaptionDropdowns.tsx +43 -0
  28. package/src/components/CaptionDropdowns/index.ts +1 -0
  29. package/src/components/CaptionLabel/CaptionLabel.test.tsx +29 -0
  30. package/src/components/CaptionLabel/CaptionLabel.tsx +32 -0
  31. package/src/components/CaptionLabel/index.ts +1 -0
  32. package/src/components/CaptionNavigation/CaptionNavigation.test.tsx +172 -0
  33. package/src/components/CaptionNavigation/CaptionNavigation.tsx +63 -0
  34. package/src/components/CaptionNavigation/index.ts +1 -0
  35. package/src/components/Day/Day.test.tsx +84 -0
  36. package/src/components/Day/Day.tsx +30 -0
  37. package/src/components/Day/index.ts +1 -0
  38. package/src/components/DayContent/DayContent.test.tsx +51 -0
  39. package/src/components/DayContent/DayContent.tsx +36 -0
  40. package/src/components/DayContent/index.ts +1 -0
  41. package/src/components/Dropdown/Dropdown.test.tsx +73 -0
  42. package/src/components/Dropdown/Dropdown.tsx +56 -0
  43. package/src/components/Dropdown/index.ts +1 -0
  44. package/src/components/Footer/Footer.test.tsx +29 -0
  45. package/src/components/Footer/Footer.tsx +20 -0
  46. package/src/components/Footer/index.ts +1 -0
  47. package/src/components/Head/Head.test.tsx +117 -0
  48. package/src/components/Head/Head.tsx +51 -0
  49. package/src/components/Head/index.ts +1 -0
  50. package/src/components/Head/utils/getWeekdays.test.ts +36 -0
  51. package/src/components/Head/utils/getWeekdays.ts +22 -0
  52. package/src/components/Head/utils/index.ts +1 -0
  53. package/src/components/IconDropdown/IconDropdown.test.tsx +20 -0
  54. package/src/components/IconDropdown/IconDropdown.tsx +24 -0
  55. package/src/components/IconDropdown/index.ts +1 -0
  56. package/src/components/IconLeft/IconLeft.test.tsx +20 -0
  57. package/src/components/IconLeft/IconLeft.tsx +18 -0
  58. package/src/components/IconLeft/index.ts +1 -0
  59. package/src/components/IconRight/IconRight.test.tsx +20 -0
  60. package/src/components/IconRight/IconRight.tsx +17 -0
  61. package/src/components/IconRight/index.ts +1 -0
  62. package/src/components/Month/Month.test.tsx +216 -0
  63. package/src/components/Month/Month.tsx +53 -0
  64. package/src/components/Month/index.ts +1 -0
  65. package/src/components/MonthsDropdown/MonthsDropdown.test.tsx +99 -0
  66. package/src/components/MonthsDropdown/MonthsDropdown.tsx +75 -0
  67. package/src/components/MonthsDropdown/index.ts +1 -0
  68. package/src/components/Navigation/Navigation.test.tsx +129 -0
  69. package/src/components/Navigation/Navigation.tsx +102 -0
  70. package/src/components/Navigation/index.ts +1 -0
  71. package/src/components/Root/Root.test.tsx +123 -0
  72. package/src/components/Root/Root.tsx +58 -0
  73. package/src/components/Root/index.ts +1 -0
  74. package/src/components/Row/Row.test.tsx +69 -0
  75. package/src/components/Row/Row.tsx +51 -0
  76. package/src/components/Row/index.ts +1 -0
  77. package/src/components/Table/Table.test.tsx +42 -0
  78. package/src/components/Table/Table.tsx +60 -0
  79. package/src/components/Table/__snapshots__/Table.test.tsx.snap +1453 -0
  80. package/src/components/Table/index.ts +1 -0
  81. package/src/components/Table/utils/daysToMonthWeeks.ts +47 -0
  82. package/src/components/Table/utils/getMonthWeeks.test.ts +68 -0
  83. package/src/components/Table/utils/getMonthWeeks.ts +55 -0
  84. package/src/components/WeekNumber/WeekNumber.test.tsx +46 -0
  85. package/src/components/WeekNumber/WeekNumber.tsx +58 -0
  86. package/src/components/WeekNumber/__snapshots__/WeekNumber.test.tsx.snap +11 -0
  87. package/src/components/WeekNumber/index.ts +1 -0
  88. package/src/components/YearsDropdown/YearsDropdown.test.tsx +98 -0
  89. package/src/components/YearsDropdown/YearsDropdown.tsx +76 -0
  90. package/src/components/YearsDropdown/index.ts +1 -0
  91. package/src/contexts/DayPicker/DayPickerContext.tsx +156 -0
  92. package/src/contexts/DayPicker/defaultClassNames.ts +58 -0
  93. package/src/contexts/DayPicker/defaultContextValue.ts +37 -0
  94. package/src/contexts/DayPicker/formatters/formatCaption.test.ts +15 -0
  95. package/src/contexts/DayPicker/formatters/formatCaption.ts +12 -0
  96. package/src/contexts/DayPicker/formatters/formatDay.test.ts +7 -0
  97. package/src/contexts/DayPicker/formatters/formatDay.ts +9 -0
  98. package/src/contexts/DayPicker/formatters/formatMonthCaption.test.ts +15 -0
  99. package/src/contexts/DayPicker/formatters/formatMonthCaption.ts +12 -0
  100. package/src/contexts/DayPicker/formatters/formatWeekNumber.test.ts +5 -0
  101. package/src/contexts/DayPicker/formatters/formatWeekNumber.ts +6 -0
  102. package/src/contexts/DayPicker/formatters/formatWeekdayName.test.ts +15 -0
  103. package/src/contexts/DayPicker/formatters/formatWeekdayName.ts +12 -0
  104. package/src/contexts/DayPicker/formatters/formatYearCaption.test.ts +7 -0
  105. package/src/contexts/DayPicker/formatters/formatYearCaption.ts +11 -0
  106. package/src/contexts/DayPicker/formatters/index.ts +6 -0
  107. package/src/contexts/DayPicker/index.ts +2 -0
  108. package/src/contexts/DayPicker/labels/index.ts +7 -0
  109. package/src/contexts/DayPicker/labels/labelDay.test.ts +7 -0
  110. package/src/contexts/DayPicker/labels/labelDay.ts +10 -0
  111. package/src/contexts/DayPicker/labels/labelMonthDropdown.test.ts +5 -0
  112. package/src/contexts/DayPicker/labels/labelMonthDropdown.ts +6 -0
  113. package/src/contexts/DayPicker/labels/labelNext.test.ts +5 -0
  114. package/src/contexts/DayPicker/labels/labelNext.ts +8 -0
  115. package/src/contexts/DayPicker/labels/labelPrevious.test.ts +5 -0
  116. package/src/contexts/DayPicker/labels/labelPrevious.ts +8 -0
  117. package/src/contexts/DayPicker/labels/labelWeekNumber.test.ts +5 -0
  118. package/src/contexts/DayPicker/labels/labelWeekNumber.ts +8 -0
  119. package/src/contexts/DayPicker/labels/labelWeekday.test.ts +15 -0
  120. package/src/contexts/DayPicker/labels/labelWeekday.ts +10 -0
  121. package/src/contexts/DayPicker/labels/labelYearDropdown.test.ts +5 -0
  122. package/src/contexts/DayPicker/labels/labelYearDropdown.ts +6 -0
  123. package/src/contexts/DayPicker/useDayPicker.test.ts +297 -0
  124. package/src/contexts/DayPicker/useDayPicker.ts +17 -0
  125. package/src/contexts/DayPicker/utils/index.ts +1 -0
  126. package/src/contexts/DayPicker/utils/parseFromToProps.test.ts +47 -0
  127. package/src/contexts/DayPicker/utils/parseFromToProps.ts +32 -0
  128. package/src/contexts/Focus/FocusContext.tsx +174 -0
  129. package/src/contexts/Focus/index.ts +2 -0
  130. package/src/contexts/Focus/useFocusContext.test.ts +183 -0
  131. package/src/contexts/Focus/useFocusContext.ts +12 -0
  132. package/src/contexts/Focus/utils/getInitialFocusTarget.test.tsx +12 -0
  133. package/src/contexts/Focus/utils/getInitialFocusTarget.tsx +44 -0
  134. package/src/contexts/Modifiers/ModifiersContext.tsx +44 -0
  135. package/src/contexts/Modifiers/index.ts +2 -0
  136. package/src/contexts/Modifiers/useModifiers.test.ts +46 -0
  137. package/src/contexts/Modifiers/useModifiers.ts +17 -0
  138. package/src/contexts/Modifiers/utils/getActiveModifiers.test.ts +53 -0
  139. package/src/contexts/Modifiers/utils/getActiveModifiers.ts +33 -0
  140. package/src/contexts/Modifiers/utils/getCustomModifiers.test.ts +14 -0
  141. package/src/contexts/Modifiers/utils/getCustomModifiers.ts +14 -0
  142. package/src/contexts/Modifiers/utils/getInternalModifiers.test.ts +146 -0
  143. package/src/contexts/Modifiers/utils/getInternalModifiers.ts +58 -0
  144. package/src/contexts/Modifiers/utils/isDateInRange.test.ts +28 -0
  145. package/src/contexts/Modifiers/utils/isDateInRange.ts +27 -0
  146. package/src/contexts/Modifiers/utils/isMatch.test.ts +92 -0
  147. package/src/contexts/Modifiers/utils/isMatch.ts +76 -0
  148. package/src/contexts/Modifiers/utils/matcherToArray.test.ts +22 -0
  149. package/src/contexts/Modifiers/utils/matcherToArray.ts +14 -0
  150. package/src/contexts/Navigation/NavigationContext.tsx +84 -0
  151. package/src/contexts/Navigation/index.ts +2 -0
  152. package/src/contexts/Navigation/useNavigation.test.ts +126 -0
  153. package/src/contexts/Navigation/useNavigation.ts +12 -0
  154. package/src/contexts/Navigation/useNavigationState.test.ts +36 -0
  155. package/src/contexts/Navigation/useNavigationState.ts +25 -0
  156. package/src/contexts/Navigation/utils/getDisplayMonths.ts +31 -0
  157. package/src/contexts/Navigation/utils/getInitialMonth.test.ts +56 -0
  158. package/src/contexts/Navigation/utils/getInitialMonth.ts +24 -0
  159. package/src/contexts/Navigation/utils/getNextMonth.test.ts +75 -0
  160. package/src/contexts/Navigation/utils/getNextMonth.ts +45 -0
  161. package/src/contexts/Navigation/utils/getPreviousMonth.test.ts +55 -0
  162. package/src/contexts/Navigation/utils/getPreviousMonth.ts +44 -0
  163. package/src/contexts/RootProvider.tsx +37 -0
  164. package/src/contexts/SelectMultiple/SelectMultipleContext.tsx +135 -0
  165. package/src/contexts/SelectMultiple/index.ts +2 -0
  166. package/src/contexts/SelectMultiple/useSelectMultiple.test.ts +191 -0
  167. package/src/contexts/SelectMultiple/useSelectMultiple.ts +17 -0
  168. package/src/contexts/SelectRange/SelectRangeContext.tsx +158 -0
  169. package/src/contexts/SelectRange/index.ts +2 -0
  170. package/src/contexts/SelectRange/useSelectRange.test.ts +282 -0
  171. package/src/contexts/SelectRange/useSelectRange.ts +15 -0
  172. package/src/contexts/SelectRange/utils/addToRange.test.ts +119 -0
  173. package/src/contexts/SelectRange/utils/addToRange.ts +43 -0
  174. package/src/contexts/SelectSingle/SelectSingleContext.tsx +80 -0
  175. package/src/contexts/SelectSingle/index.ts +2 -0
  176. package/src/contexts/SelectSingle/useSelectSingle.test.ts +81 -0
  177. package/src/contexts/SelectSingle/useSelectSingle.ts +17 -0
  178. package/src/hooks/useActiveModifiers/index.ts +1 -0
  179. package/src/hooks/useActiveModifiers/useActiveModifiers.test.tsx +36 -0
  180. package/src/hooks/useActiveModifiers/useActiveModifiers.tsx +18 -0
  181. package/src/hooks/useControlledValue/index.ts +1 -0
  182. package/src/hooks/useControlledValue/useControlledValue.test.ts +68 -0
  183. package/src/hooks/useControlledValue/useControlledValue.ts +24 -0
  184. package/src/hooks/useDayEventHandlers/index.ts +1 -0
  185. package/src/hooks/useDayEventHandlers/useDayEventHandlers.test.tsx +213 -0
  186. package/src/hooks/useDayEventHandlers/useDayEventHandlers.tsx +195 -0
  187. package/src/hooks/useDayRender/index.ts +1 -0
  188. package/src/hooks/useDayRender/useDayRender.test.tsx +304 -0
  189. package/src/hooks/useDayRender/useDayRender.tsx +123 -0
  190. package/src/hooks/useDayRender/utils/getDayClassNames.test.ts +63 -0
  191. package/src/hooks/useDayRender/utils/getDayClassNames.ts +32 -0
  192. package/src/hooks/useDayRender/utils/getDayStyle.ts +19 -0
  193. package/src/hooks/useInput/index.ts +1 -0
  194. package/src/hooks/useInput/useInput.ts +175 -0
  195. package/src/hooks/useInput/utils/isValidDate.tsx +4 -0
  196. package/src/hooks/useSelectedDays/index.ts +1 -0
  197. package/src/hooks/useSelectedDays/useSelectedDays.test.ts +72 -0
  198. package/src/hooks/useSelectedDays/useSelectedDays.ts +32 -0
  199. package/src/index.ts +43 -0
  200. package/src/style.css +311 -0
  201. package/src/style.css.d.ts +38 -0
  202. package/src/types/DayPickerBase.ts +267 -0
  203. package/src/types/DayPickerDefault.ts +15 -0
  204. package/src/types/DayPickerMultiple.ts +26 -0
  205. package/src/types/DayPickerRange.ts +27 -0
  206. package/src/types/DayPickerSingle.ts +24 -0
  207. package/src/types/EventHandlers.ts +87 -0
  208. package/src/types/Formatters.ts +29 -0
  209. package/src/types/Labels.ts +36 -0
  210. package/src/types/Matchers.ts +106 -0
  211. package/src/types/Modifiers.ts +62 -0
  212. package/src/types/Styles.ts +108 -0
  213. package/tsconfig.json +24 -0
@@ -0,0 +1,216 @@
1
+ import React from 'react';
2
+
3
+ import { screen } from '@testing-library/react';
4
+ import { DayPickerProps } from 'DayPicker';
5
+
6
+ import { getMonthCaption, getMonthGrid } from 'test/po';
7
+ import { customRender } from 'test/render';
8
+
9
+ import { CustomComponents } from 'types/DayPickerBase';
10
+
11
+ import { Month, MonthProps } from './Month';
12
+
13
+ let root: HTMLDivElement;
14
+
15
+ const displayMonth = new Date(2022, 10, 4);
16
+
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const testStyles: Record<string, any> = {
19
+ caption_start: { color: 'red' },
20
+ caption_end: { background: 'blue' },
21
+ caption_between: { fontSize: 20 }
22
+ };
23
+
24
+ const testClassNames: Record<string, string> = {
25
+ caption_start: 'caption_start',
26
+ caption_end: 'caption_end',
27
+ caption_between: 'caption_between'
28
+ };
29
+
30
+ type Test = {
31
+ monthProps: MonthProps;
32
+ dayPickerProps: DayPickerProps;
33
+ expected: string[];
34
+ notExpected: string[];
35
+ };
36
+
37
+ function setup(props: MonthProps, dayPickerProps?: DayPickerProps) {
38
+ const view = customRender(<Month {...props} />, dayPickerProps);
39
+ root = view.container.firstChild as HTMLDivElement;
40
+ }
41
+ describe('when rendered', () => {
42
+ beforeEach(() => {
43
+ setup({ displayIndex: 0, displayMonth });
44
+ });
45
+ test('the caption id should be the aria-labelledby of the grid', () => {
46
+ const captionId = getMonthCaption().getAttribute('id');
47
+ const gridLabelledBy = getMonthGrid().getAttribute('aria-labelledby');
48
+ expect(captionId).toEqual(gridLabelledBy);
49
+ });
50
+ });
51
+
52
+ describe('when using a custom Caption component', () => {
53
+ const components: CustomComponents = {
54
+ Caption: () => <>custom caption foo</>
55
+ };
56
+ beforeEach(() => {
57
+ setup({ displayIndex: 0, displayMonth }, { components });
58
+ });
59
+ test('it should render the custom component instead', () => {
60
+ expect(screen.getByText('custom caption foo')).toBeInTheDocument();
61
+ });
62
+ });
63
+
64
+ describe('when dir is ltr', () => {
65
+ const testLtr: Test[] = [
66
+ {
67
+ monthProps: {
68
+ displayIndex: 0,
69
+ displayMonth
70
+ },
71
+ dayPickerProps: {
72
+ numberOfMonths: 1,
73
+ styles: testStyles,
74
+ classNames: testClassNames
75
+ },
76
+ expected: ['caption_start', 'caption_end'],
77
+ notExpected: ['caption_between']
78
+ },
79
+ {
80
+ monthProps: {
81
+ displayIndex: 0,
82
+ displayMonth
83
+ },
84
+ dayPickerProps: {
85
+ numberOfMonths: 2,
86
+ styles: testStyles,
87
+ classNames: testClassNames
88
+ },
89
+ expected: ['caption_start'],
90
+ notExpected: ['caption_between', 'caption_end']
91
+ },
92
+ {
93
+ monthProps: {
94
+ displayIndex: 1,
95
+ displayMonth
96
+ },
97
+ dayPickerProps: {
98
+ numberOfMonths: 2,
99
+ styles: testStyles,
100
+ classNames: testClassNames
101
+ },
102
+ expected: ['caption_end'],
103
+ notExpected: ['caption_start', 'caption_between']
104
+ },
105
+ {
106
+ monthProps: {
107
+ displayIndex: 1,
108
+ displayMonth
109
+ },
110
+ dayPickerProps: {
111
+ numberOfMonths: 3,
112
+ styles: testStyles,
113
+ classNames: testClassNames
114
+ },
115
+ expected: ['caption_between'],
116
+ notExpected: ['caption_start', 'caption_end']
117
+ }
118
+ ];
119
+
120
+ describe.each(testLtr)(
121
+ 'when displayIndex is $monthProps.displayIndex and numberOfMonths is $dayPickerProps.numberOfMonths',
122
+ ({ monthProps, dayPickerProps, expected, notExpected }) => {
123
+ beforeEach(() => {
124
+ setup(monthProps, dayPickerProps);
125
+ });
126
+ test.each(expected)(`the root should have the %s class`, (name) =>
127
+ expect(root).toHaveClass(testClassNames[name])
128
+ );
129
+ test.each(expected)(`the root should have the %s style`, (name) =>
130
+ expect(root).toHaveStyle(testStyles[name])
131
+ );
132
+ test.each(notExpected)(`the root should not have the %s class`, (name) =>
133
+ expect(root).not.toHaveClass(testClassNames[name])
134
+ );
135
+ }
136
+ );
137
+ });
138
+
139
+ describe('when dir is rtl', () => {
140
+ const testRtl: Test[] = [
141
+ {
142
+ monthProps: {
143
+ displayIndex: 0,
144
+ displayMonth
145
+ },
146
+ dayPickerProps: {
147
+ dir: 'rtl',
148
+ numberOfMonths: 1,
149
+ styles: testStyles,
150
+ classNames: testClassNames
151
+ },
152
+ expected: ['caption_start', 'caption_end'],
153
+ notExpected: ['caption_between']
154
+ },
155
+ {
156
+ monthProps: {
157
+ displayIndex: 0,
158
+ displayMonth
159
+ },
160
+ dayPickerProps: {
161
+ dir: 'rtl',
162
+ numberOfMonths: 2,
163
+ styles: testStyles,
164
+ classNames: testClassNames
165
+ },
166
+ expected: ['caption_end'],
167
+ notExpected: ['caption_between', 'caption_start']
168
+ },
169
+ {
170
+ monthProps: {
171
+ displayIndex: 1,
172
+ displayMonth
173
+ },
174
+ dayPickerProps: {
175
+ dir: 'rtl',
176
+ numberOfMonths: 2,
177
+ styles: testStyles,
178
+ classNames: testClassNames
179
+ },
180
+ expected: ['caption_start'],
181
+ notExpected: ['caption_end', 'caption_between']
182
+ },
183
+ {
184
+ monthProps: {
185
+ displayIndex: 1,
186
+ displayMonth
187
+ },
188
+ dayPickerProps: {
189
+ dir: 'rtl',
190
+ numberOfMonths: 3,
191
+ styles: testStyles,
192
+ classNames: testClassNames
193
+ },
194
+ expected: ['caption_between'],
195
+ notExpected: ['caption_start', 'caption_end']
196
+ }
197
+ ];
198
+
199
+ describe.each(testRtl)(
200
+ 'when displayIndex is $monthProps.displayIndex and numberOfMonths is $dayPickerProps.numberOfMonths',
201
+ ({ monthProps, dayPickerProps, expected, notExpected }) => {
202
+ beforeEach(() => {
203
+ setup(monthProps, dayPickerProps);
204
+ });
205
+ test.each(expected)(`the root should have the %s class`, (name) =>
206
+ expect(root).toHaveClass(testClassNames[name])
207
+ );
208
+ test.each(expected)(`the root should have the %s style`, (name) =>
209
+ expect(root).toHaveStyle(testStyles[name])
210
+ );
211
+ test.each(notExpected)(`the root should not have the %s class`, (name) =>
212
+ expect(root).not.toHaveClass(testClassNames[name])
213
+ );
214
+ }
215
+ );
216
+ });
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+
3
+ import { useId } from '@reach/auto-id';
4
+
5
+ import { Caption } from 'components/Caption';
6
+ import { Table } from 'components/Table';
7
+ import { useDayPicker } from 'contexts/DayPicker';
8
+ import { useNavigation } from 'contexts/Navigation';
9
+
10
+ /** The props for the [[Month]] component. */
11
+ export interface MonthProps {
12
+ displayIndex: number;
13
+ displayMonth: Date;
14
+ }
15
+
16
+ /** Render a month. */
17
+ export function Month(props: MonthProps) {
18
+ const dayPicker = useDayPicker();
19
+ const { dir, classNames, styles, components } = dayPicker;
20
+ const { displayMonths } = useNavigation();
21
+ const captionId = useId();
22
+ const className = [classNames.month];
23
+ let style = styles.month;
24
+
25
+ let isStart = props.displayIndex === 0;
26
+ let isEnd = props.displayIndex === displayMonths.length - 1;
27
+ const isCenter = !isStart && !isEnd;
28
+ if (dir === 'rtl') {
29
+ [isEnd, isStart] = [isStart, isEnd];
30
+ }
31
+
32
+ if (isStart) {
33
+ className.push(classNames.caption_start);
34
+ style = { ...style, ...styles.caption_start };
35
+ }
36
+ if (isEnd) {
37
+ className.push(classNames.caption_end);
38
+ style = { ...style, ...styles.caption_end };
39
+ }
40
+ if (isCenter) {
41
+ className.push(classNames.caption_between);
42
+ style = { ...style, ...styles.caption_between };
43
+ }
44
+
45
+ const CaptionComponent = components?.Caption ?? Caption;
46
+
47
+ return (
48
+ <div key={props.displayIndex} className={className.join(' ')} style={style}>
49
+ <CaptionComponent id={captionId} displayMonth={props.displayMonth} />
50
+ <Table aria-labelledby={captionId} displayMonth={props.displayMonth} />
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1 @@
1
+ export * from './Month';
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+
3
+ import { screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { differenceInMonths } from 'date-fns';
6
+ import { DayPickerProps } from 'DayPicker';
7
+
8
+ import { customRender } from 'test/render';
9
+ import { freezeBeforeAll } from 'test/utils';
10
+
11
+ import { MonthsDropdown, MonthsDropdownProps } from './MonthsDropdown';
12
+
13
+ const today = new Date(2020, 12, 22);
14
+
15
+ freezeBeforeAll(today);
16
+
17
+ let root: HTMLDivElement;
18
+ let options: HTMLCollectionOf<HTMLOptionElement> | undefined;
19
+ let select: HTMLSelectElement | null;
20
+
21
+ function setup(props: MonthsDropdownProps, dayPickerProps?: DayPickerProps) {
22
+ const view = customRender(<MonthsDropdown {...props} />, dayPickerProps);
23
+ root = view.container.firstChild as HTMLDivElement;
24
+ select = screen.queryByRole('combobox', { name: 'Month:' });
25
+ options = select?.getElementsByTagName('option');
26
+ }
27
+
28
+ const props: MonthsDropdownProps = {
29
+ displayMonth: today,
30
+ onChange: jest.fn()
31
+ };
32
+
33
+ describe('when "fromDate" is not set', () => {
34
+ beforeEach(() => {
35
+ setup(props, { fromDate: undefined });
36
+ });
37
+ test('should return nothing', () => {
38
+ expect(root).toBeNull();
39
+ });
40
+ });
41
+
42
+ describe('when "toDate" is not set', () => {
43
+ beforeEach(() => {
44
+ setup(props, { toDate: undefined });
45
+ });
46
+ test('should return nothing', () => {
47
+ expect(root).toBeNull();
48
+ });
49
+ });
50
+
51
+ describe('when "fromDate" and "toDate" are in the same year', () => {
52
+ const fromDate = new Date(2012, 0, 22);
53
+ const toDate = new Date(2012, 10, 22);
54
+ beforeEach(() => {
55
+ setup(props, { fromDate, toDate });
56
+ });
57
+ test('should display the months included between the two dates', () => {
58
+ expect(options).toHaveLength(differenceInMonths(toDate, fromDate) + 1);
59
+ });
60
+ test('the first month should be the fromDate month', () => {
61
+ expect(options?.[0]).toHaveValue(String(fromDate.getMonth()));
62
+ });
63
+ test('the last month should be the toMonth month', () => {
64
+ expect(options?.[options.length - 1]).toHaveValue(
65
+ String(toDate.getMonth())
66
+ );
67
+ });
68
+ });
69
+
70
+ describe('when "fromDate" and "toDate" are not in the same year', () => {
71
+ const fromDate = new Date(2012, 0, 22);
72
+ const toDate = new Date(2015, 10, 22);
73
+ const displayMonth = new Date(2015, 7, 0);
74
+ beforeEach(() => {
75
+ setup({ ...props, displayMonth }, { fromDate, toDate });
76
+ });
77
+ test('should display the 12 months', () => {
78
+ expect(options).toHaveLength(12);
79
+ });
80
+ test('the first month should be January', () => {
81
+ expect(options?.[0]).toHaveValue('0');
82
+ });
83
+ test('the last month should be December', () => {
84
+ expect(options?.[options.length - 1]).toHaveValue('11');
85
+ });
86
+ test('should select the displayed month', () => {
87
+ expect(select).toHaveValue(`${displayMonth.getMonth()}`);
88
+ });
89
+
90
+ describe('when the dropdown changes', () => {
91
+ beforeEach(() => {
92
+ if (select) userEvent.selectOptions(select, 'February');
93
+ });
94
+ test('should fire the "onChange" event handler', () => {
95
+ const expectedMonth = new Date(2015, 1, 1);
96
+ expect(props.onChange).toHaveBeenCalledWith(expectedMonth);
97
+ });
98
+ });
99
+ });
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+
3
+ import isSameYear from 'date-fns/isSameYear';
4
+ import setMonth from 'date-fns/setMonth';
5
+ import startOfMonth from 'date-fns/startOfMonth';
6
+
7
+ import { Dropdown } from 'components/Dropdown';
8
+ import { useDayPicker } from 'contexts/DayPicker';
9
+ import { MonthChangeEventHandler } from 'types/EventHandlers';
10
+
11
+ /** The props for the [[MonthsDropdown]] component. */
12
+ export interface MonthsDropdownProps {
13
+ /** The month where the dropdown is displayed. */
14
+ displayMonth: Date;
15
+ onChange: MonthChangeEventHandler;
16
+ }
17
+
18
+ /** Render the dropdown to navigate between months. */
19
+ export function MonthsDropdown(props: MonthsDropdownProps): JSX.Element {
20
+ const {
21
+ fromDate,
22
+ toDate,
23
+ styles,
24
+ locale,
25
+ formatters: { formatMonthCaption },
26
+ classNames,
27
+ components,
28
+ labels: { labelMonthDropdown }
29
+ } = useDayPicker();
30
+
31
+ // Dropdown should appear only when both from/toDate is set
32
+ if (!fromDate) return <></>;
33
+ if (!toDate) return <></>;
34
+
35
+ const dropdownMonths: Date[] = [];
36
+
37
+ if (isSameYear(fromDate, toDate)) {
38
+ // only display the months included in the range
39
+ const date = startOfMonth(fromDate);
40
+ for (let month = fromDate.getMonth(); month <= toDate.getMonth(); month++) {
41
+ dropdownMonths.push(setMonth(date, month));
42
+ }
43
+ } else {
44
+ // display all the 12 months
45
+ const date = startOfMonth(new Date()); // Any date should be OK, as we just need the year
46
+ for (let month = 0; month <= 11; month++) {
47
+ dropdownMonths.push(setMonth(date, month));
48
+ }
49
+ }
50
+
51
+ const handleChange: React.ChangeEventHandler<HTMLSelectElement> = (e) => {
52
+ const selectedMonth = Number(e.target.value);
53
+ const newMonth = setMonth(startOfMonth(props.displayMonth), selectedMonth);
54
+ props.onChange(newMonth);
55
+ };
56
+
57
+ const DropdownComponent = components?.Dropdown ?? Dropdown;
58
+
59
+ return (
60
+ <DropdownComponent
61
+ aria-label={labelMonthDropdown()}
62
+ className={classNames.dropdown_month}
63
+ style={styles.dropdown_month}
64
+ onChange={handleChange}
65
+ value={props.displayMonth.getMonth()}
66
+ caption={formatMonthCaption(props.displayMonth, { locale })}
67
+ >
68
+ {dropdownMonths.map((m) => (
69
+ <option key={m.getMonth()} value={m.getMonth()}>
70
+ {formatMonthCaption(m, { locale })}
71
+ </option>
72
+ ))}
73
+ </DropdownComponent>
74
+ );
75
+ }
@@ -0,0 +1 @@
1
+ export * from './MonthsDropdown';
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+
3
+ import userEvent from '@testing-library/user-event';
4
+ import { DayPickerProps } from 'DayPicker';
5
+
6
+ import { getNextButton, getPrevButton } from 'test/po';
7
+ import { customRender } from 'test/render';
8
+
9
+ import { Navigation, NavigationProps } from './Navigation';
10
+
11
+ let root: HTMLElement;
12
+
13
+ function setup(props: NavigationProps, dayPickerProps?: DayPickerProps) {
14
+ const view = customRender(<Navigation {...props} />, dayPickerProps);
15
+ root = view.container.firstChild as HTMLElement;
16
+ }
17
+
18
+ const props: NavigationProps = {
19
+ previousMonth: new Date(2021, 3),
20
+ nextMonth: new Date(2021, 5),
21
+ displayMonth: new Date(2021, 4),
22
+ hidePrevious: false,
23
+ hideNext: false,
24
+ onNextClick: jest.fn(),
25
+ onPreviousClick: jest.fn()
26
+ };
27
+
28
+ const dayPickerProps = {
29
+ classNames: {
30
+ nav: 'foo'
31
+ },
32
+ styles: {
33
+ nav: { color: 'red' }
34
+ },
35
+ components: {
36
+ IconRight: () => <svg>IconRight</svg>,
37
+ IconLeft: () => <svg>IconLeft</svg>
38
+ }
39
+ };
40
+
41
+ describe('when rendered', () => {
42
+ beforeEach(() => {
43
+ setup(props, dayPickerProps);
44
+ });
45
+ test('should add the class name', () => {
46
+ expect(root).toHaveClass(dayPickerProps.classNames.nav);
47
+ });
48
+ test('should apply the style', () => {
49
+ expect(root).toHaveStyle(dayPickerProps.styles.nav);
50
+ });
51
+ test('the previous button should display the left icon', () => {
52
+ const icons = root.getElementsByTagName('svg');
53
+ expect(icons[0]).toHaveTextContent('IconLeft');
54
+ });
55
+ test('the next button should display the right icon', () => {
56
+ const icons = root.getElementsByTagName('svg');
57
+ expect(icons[1]).toHaveTextContent('IconRight');
58
+ });
59
+ beforeEach(() => {
60
+ userEvent.click(getPrevButton());
61
+ });
62
+ test('should call "onPreviousClick"', () => {
63
+ expect(props.onPreviousClick).toHaveBeenCalled();
64
+ });
65
+
66
+ describe('when clicking the next button', () => {
67
+ beforeEach(() => {
68
+ userEvent.click(getNextButton());
69
+ });
70
+ test('should call "onNextClick"', () => {
71
+ expect(props.onNextClick).toHaveBeenCalled();
72
+ });
73
+ });
74
+ });
75
+
76
+ describe('when in right-to-left direction', () => {
77
+ beforeEach(() => {
78
+ setup(props, { ...dayPickerProps, dir: 'rtl' });
79
+ });
80
+ test('the previous button should display the right icon', () => {
81
+ const icons = root.getElementsByTagName('svg');
82
+ expect(icons[0]).toHaveTextContent('IconRight');
83
+ });
84
+ test('the next button should display the left icon', () => {
85
+ const icons = root.getElementsByTagName('svg');
86
+ expect(icons[1]).toHaveTextContent('IconLeft');
87
+ });
88
+
89
+ describe('when clicking the previous button', () => {
90
+ beforeEach(() => {
91
+ userEvent.click(getPrevButton());
92
+ });
93
+ test('should call "onPreviousClick"', () => {
94
+ expect(props.onPreviousClick).toHaveBeenCalled();
95
+ });
96
+ });
97
+ describe('when clicking the next button', () => {
98
+ beforeEach(() => {
99
+ userEvent.click(getNextButton());
100
+ });
101
+ test('should call "onNextClick"', () => {
102
+ expect(props.onNextClick).toHaveBeenCalled();
103
+ });
104
+ });
105
+ });
106
+
107
+ describe('when the previous month is undefined', () => {
108
+ beforeEach(() => {
109
+ setup({ ...props, previousMonth: undefined }, dayPickerProps);
110
+ });
111
+ test('the previous button should be aria-disabled', () => {
112
+ expect(getPrevButton()).toHaveAttribute('aria-disabled', 'true');
113
+ });
114
+ test('the next button should not be aria-disabled', () => {
115
+ expect(getNextButton()).not.toHaveAttribute('aria-disabled', 'true');
116
+ });
117
+ });
118
+
119
+ describe('when the next month is undefined', () => {
120
+ beforeEach(() => {
121
+ setup({ ...props, nextMonth: undefined }, dayPickerProps);
122
+ });
123
+ test('the previous button should be enabled', () => {
124
+ expect(getPrevButton()).not.toHaveAttribute('aria-disabled', 'true');
125
+ });
126
+ test('the next button should be disabled', () => {
127
+ expect(getNextButton()).toHaveAttribute('aria-disabled', 'true');
128
+ });
129
+ });
@@ -0,0 +1,102 @@
1
+ import React from 'react';
2
+
3
+ import { IconLeft } from 'components/IconLeft';
4
+ import { IconRight } from 'components/IconRight';
5
+ import { useDayPicker } from 'contexts/DayPicker';
6
+
7
+ import { Button } from '../Button';
8
+
9
+ /** The props for the [[Navigation]] component. */
10
+ export interface NavigationProps {
11
+ /** The month where the caption is displayed. */
12
+ displayMonth: Date;
13
+ /** The previous month. */
14
+ previousMonth?: Date;
15
+ /** The next month. */
16
+ nextMonth?: Date;
17
+ /** Hide the previous button. */
18
+ hidePrevious: boolean;
19
+ /** Hide the next button. */
20
+ hideNext: boolean;
21
+ /** Event handler when the next button is clicked. */
22
+ onNextClick: React.MouseEventHandler<HTMLButtonElement>;
23
+ /** Event handler when the previous button is clicked. */
24
+ onPreviousClick: React.MouseEventHandler<HTMLButtonElement>;
25
+ }
26
+
27
+ /** A component rendering the navigation buttons or the drop-downs. */
28
+ export function Navigation(props: NavigationProps): JSX.Element {
29
+ const {
30
+ dir,
31
+ locale,
32
+ classNames,
33
+ styles,
34
+ labels: { labelPrevious, labelNext },
35
+ components
36
+ } = useDayPicker();
37
+
38
+ if (!props.nextMonth && !props.previousMonth) {
39
+ return <></>;
40
+ }
41
+
42
+ const previousLabel = labelPrevious(props.previousMonth, { locale });
43
+ const previousClassName = [
44
+ classNames.nav_button,
45
+ classNames.nav_button_previous
46
+ ].join(' ');
47
+
48
+ const nextLabel = labelNext(props.nextMonth, { locale });
49
+ const nextClassName = [
50
+ classNames.nav_button,
51
+ classNames.nav_button_next
52
+ ].join(' ');
53
+
54
+ const IconRightComponent = components?.IconRight ?? IconRight;
55
+ const IconLeftComponent = components?.IconLeft ?? IconLeft;
56
+ return (
57
+ <div className={classNames.nav} style={styles.nav}>
58
+ {!props.hidePrevious && (
59
+ <Button
60
+ aria-label={previousLabel}
61
+ className={previousClassName}
62
+ style={styles.nav_button_previous}
63
+ aria-disabled={!props.previousMonth}
64
+ onClick={props.onPreviousClick}
65
+ >
66
+ {dir === 'rtl' ? (
67
+ <IconRightComponent
68
+ className={classNames.nav_icon}
69
+ style={styles.nav_icon}
70
+ />
71
+ ) : (
72
+ <IconLeftComponent
73
+ className={classNames.nav_icon}
74
+ style={styles.nav_icon}
75
+ />
76
+ )}
77
+ </Button>
78
+ )}
79
+ {!props.hideNext && (
80
+ <Button
81
+ aria-label={nextLabel}
82
+ className={nextClassName}
83
+ style={styles.nav_button_next}
84
+ aria-disabled={!props.nextMonth}
85
+ onClick={props.onNextClick}
86
+ >
87
+ {dir === 'rtl' ? (
88
+ <IconLeftComponent
89
+ className={classNames.nav_icon}
90
+ style={styles.nav_icon}
91
+ />
92
+ ) : (
93
+ <IconRightComponent
94
+ className={classNames.nav_icon}
95
+ style={styles.nav_icon}
96
+ />
97
+ )}
98
+ </Button>
99
+ )}
100
+ </div>
101
+ );
102
+ }
@@ -0,0 +1 @@
1
+ export * from './Navigation';