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,54 @@
1
+ import React from 'react';
2
+
3
+ import { CaptionDropdowns } from 'components/CaptionDropdowns';
4
+ import { CaptionLabel } from 'components/CaptionLabel';
5
+ import { CaptionNavigation } from 'components/CaptionNavigation';
6
+ import { useDayPicker } from 'contexts/DayPicker';
7
+
8
+ /** Represent the props of the [[Caption]] component. */
9
+ export interface CaptionProps {
10
+ /** The ID for the heading element. Must be the same as the labelled-by in Table. */
11
+ id?: string;
12
+ /** The month where the caption is displayed. */
13
+ displayMonth: Date;
14
+ }
15
+
16
+ /**
17
+ * The layout of the caption:
18
+ *
19
+ * - `dropdown`: display dropdowns for choosing the month and the year.
20
+ * - `buttons`: display previous month / next month buttons.
21
+ */
22
+ export type CaptionLayout = 'dropdown' | 'buttons';
23
+
24
+ /**
25
+ * Render the caption of a month. The caption has a different layout when
26
+ * setting the [[DayPickerProps.captionLayout]] prop.
27
+ */
28
+ export function Caption(props: CaptionProps): JSX.Element {
29
+ const { classNames, disableNavigation, styles, captionLayout, components } =
30
+ useDayPicker();
31
+
32
+ const CaptionLabelComponent = components?.CaptionLabel ?? CaptionLabel;
33
+
34
+ let caption: JSX.Element;
35
+ if (disableNavigation) {
36
+ caption = (
37
+ <CaptionLabelComponent id={props.id} displayMonth={props.displayMonth} />
38
+ );
39
+ } else if (captionLayout === 'dropdown') {
40
+ caption = (
41
+ <CaptionDropdowns displayMonth={props.displayMonth} id={props.id} />
42
+ );
43
+ } else {
44
+ caption = (
45
+ <CaptionNavigation displayMonth={props.displayMonth} id={props.id} />
46
+ );
47
+ }
48
+
49
+ return (
50
+ <div className={classNames.caption} style={styles.caption}>
51
+ {caption}
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1 @@
1
+ export * from './Caption';
@@ -0,0 +1,123 @@
1
+ import React from 'react';
2
+
3
+ import { screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { setMonth, setYear } from 'date-fns';
6
+ import { DayPickerProps } from 'DayPicker';
7
+
8
+ import {
9
+ getMonthDropdown,
10
+ getYearDropdown,
11
+ queryMonthDropdown,
12
+ queryYearDropdown
13
+ } from 'test/po';
14
+ import { customRender } from 'test/render';
15
+ import { freezeBeforeAll } from 'test/utils';
16
+
17
+ import { CaptionProps } from 'components/Caption';
18
+ import { CustomComponents } from 'types/DayPickerBase';
19
+
20
+ import { CaptionDropdowns } from './CaptionDropdowns';
21
+
22
+ const today = new Date(2021, 8);
23
+ const fromYear = 2020;
24
+ const toYear = 2025;
25
+
26
+ freezeBeforeAll(today);
27
+
28
+ function setup(props: CaptionProps, dayPickerProps?: DayPickerProps) {
29
+ customRender(<CaptionDropdowns {...props} />, dayPickerProps);
30
+ }
31
+
32
+ describe('when using a custom CaptionLabel component', () => {
33
+ const components: CustomComponents = {
34
+ CaptionLabel: () => <>custom label foo</>
35
+ };
36
+ const props = { displayMonth: today };
37
+ beforeEach(() => {
38
+ setup(props, { components });
39
+ });
40
+ test('it should render the custom component instead', () => {
41
+ expect(screen.getByText('custom label foo')).toBeInTheDocument();
42
+ });
43
+ });
44
+
45
+ describe('when rendered with custom styles or classnames', () => {
46
+ let container: HTMLElement;
47
+
48
+ beforeEach(() => {
49
+ const dayPickerProps: DayPickerProps = {
50
+ captionLayout: 'dropdown',
51
+ fromYear,
52
+ toYear,
53
+ classNames: { caption_dropdowns: 'foo_dropdowns' },
54
+ styles: { caption_dropdowns: { color: 'red' } }
55
+ };
56
+ const view = customRender(
57
+ <CaptionDropdowns displayMonth={today} />,
58
+ dayPickerProps
59
+ );
60
+ container = view.container;
61
+ });
62
+ test('should use the `caption_dropdowns` class name', () => {
63
+ expect(container.firstChild).toHaveClass('foo_dropdowns');
64
+ });
65
+ test('should use the `caption_dropdowns` style', () => {
66
+ expect(container.firstChild).toHaveStyle({ color: 'red' });
67
+ });
68
+ test('should render the month drop-down', () => {
69
+ expect(getMonthDropdown()).toBeInTheDocument();
70
+ });
71
+ test('should render the year drop-down', () => {
72
+ expect(getYearDropdown()).toBeInTheDocument();
73
+ });
74
+ });
75
+
76
+ describe('when a month is selected', () => {
77
+ const dayPickerProps: DayPickerProps = {
78
+ captionLayout: 'dropdown',
79
+ fromYear,
80
+ toYear,
81
+ onMonthChange: jest.fn()
82
+ };
83
+ beforeEach(() => {
84
+ customRender(<CaptionDropdowns displayMonth={today} />, dayPickerProps);
85
+ });
86
+ describe('from the months drop-down', () => {
87
+ const newMonth = setMonth(today, 0);
88
+ beforeEach(() => {
89
+ userEvent.selectOptions(
90
+ getMonthDropdown(),
91
+ newMonth.getMonth().toString()
92
+ );
93
+ });
94
+ test('should call the `onMonthChange` callback', () => {
95
+ expect(dayPickerProps.onMonthChange).toHaveBeenCalledWith(newMonth);
96
+ });
97
+ });
98
+ describe('from the years drop-down', () => {
99
+ const newYear = setYear(today, 2022);
100
+ beforeEach(() => {
101
+ userEvent.selectOptions(
102
+ getYearDropdown(),
103
+ newYear.getFullYear().toString()
104
+ );
105
+ });
106
+ test('should call the `onMonthChange` callback', () => {
107
+ expect(dayPickerProps.onMonthChange).toHaveBeenCalledWith(newYear);
108
+ });
109
+ });
110
+ });
111
+
112
+ describe('when no date limits are set', () => {
113
+ const dayPickerProps: DayPickerProps = {
114
+ captionLayout: 'dropdown'
115
+ };
116
+ beforeEach(() => {
117
+ customRender(<CaptionDropdowns displayMonth={today} />, dayPickerProps);
118
+ });
119
+ test('should not render the drop-downs', () => {
120
+ expect(queryMonthDropdown()).toBeNull();
121
+ expect(queryYearDropdown()).toBeNull();
122
+ });
123
+ });
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+
3
+ import { CaptionProps } from 'components/Caption/Caption';
4
+ import { CaptionLabel } from 'components/CaptionLabel';
5
+ import { MonthsDropdown } from 'components/MonthsDropdown';
6
+ import { YearsDropdown } from 'components/YearsDropdown';
7
+ import { useDayPicker } from 'contexts/DayPicker';
8
+ import { useNavigation } from 'contexts/Navigation';
9
+ import { MonthChangeEventHandler } from 'types/EventHandlers';
10
+
11
+ /**
12
+ * Render a caption with the dropdowns to navigate between months and years.
13
+ */
14
+ export function CaptionDropdowns(props: CaptionProps): JSX.Element {
15
+ const { classNames, styles, onMonthChange, components } = useDayPicker();
16
+ const { goToMonth } = useNavigation();
17
+
18
+ const handleMonthChange: MonthChangeEventHandler = (newMonth) => {
19
+ goToMonth(newMonth);
20
+ onMonthChange?.(newMonth);
21
+ };
22
+ const CaptionLabelComponent = components?.CaptionLabel ?? CaptionLabel;
23
+ const captionLabel = (
24
+ <CaptionLabelComponent id={props.id} displayMonth={props.displayMonth} />
25
+ );
26
+ return (
27
+ <div
28
+ className={classNames.caption_dropdowns}
29
+ style={styles.caption_dropdowns}
30
+ >
31
+ {/* Caption label is visually hidden but for a11y. */}
32
+ <div className={classNames.vhidden}>{captionLabel}</div>
33
+ <MonthsDropdown
34
+ onChange={handleMonthChange}
35
+ displayMonth={props.displayMonth}
36
+ />
37
+ <YearsDropdown
38
+ onChange={handleMonthChange}
39
+ displayMonth={props.displayMonth}
40
+ />
41
+ </div>
42
+ );
43
+ }
@@ -0,0 +1 @@
1
+ export * from './CaptionDropdowns';
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+
3
+ import { getMonthCaption } from 'test/po';
4
+ import { customRender } from 'test/render';
5
+ import { freezeBeforeAll } from 'test/utils';
6
+
7
+ import { CaptionLabel } from './CaptionLabel';
8
+
9
+ const today = new Date(1979, 8);
10
+ freezeBeforeAll(today);
11
+
12
+ test('should render the formatted display month', () => {
13
+ customRender(<CaptionLabel displayMonth={today} />);
14
+ expect(getMonthCaption()).toHaveTextContent('September 1979');
15
+ });
16
+
17
+ test('should apply the `caption_label` class name', () => {
18
+ customRender(<CaptionLabel displayMonth={today} />, {
19
+ classNames: { caption_label: 'foo' }
20
+ });
21
+ expect(getMonthCaption()).toHaveClass('foo');
22
+ });
23
+
24
+ test('should apply the `caption_label` style', () => {
25
+ customRender(<CaptionLabel displayMonth={today} />, {
26
+ styles: { caption_label: { color: 'red' } }
27
+ });
28
+ expect(getMonthCaption()).toHaveStyle({ color: 'red' });
29
+ });
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+
3
+ import { useDayPicker } from 'contexts/DayPicker';
4
+
5
+ /** The props for the [[CaptionLabel]] component. */
6
+ export interface CaptionLabelProps {
7
+ /** The ID for the heading element. Must be the same as the labelled-by in Table. */
8
+ id?: string;
9
+ /** The month where the caption is displayed. */
10
+ displayMonth: Date;
11
+ }
12
+
13
+ /** Render the caption for the displayed month. This component is used when `captionLayout="buttons"`. */
14
+ export function CaptionLabel(props: CaptionLabelProps): JSX.Element {
15
+ const {
16
+ locale,
17
+ classNames,
18
+ styles,
19
+ formatters: { formatCaption }
20
+ } = useDayPicker();
21
+ return (
22
+ <h2
23
+ className={classNames.caption_label}
24
+ style={styles.caption_label}
25
+ aria-live="polite"
26
+ aria-atomic="true"
27
+ id={props.id}
28
+ >
29
+ {formatCaption(props.displayMonth, { locale })}
30
+ </h2>
31
+ );
32
+ }
@@ -0,0 +1 @@
1
+ export * from './CaptionLabel';
@@ -0,0 +1,172 @@
1
+ import React from 'react';
2
+
3
+ import { screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { addMonths } from 'date-fns';
6
+ import { DayPickerProps } from 'DayPicker';
7
+
8
+ import {
9
+ getMonthCaption,
10
+ getNextButton,
11
+ getPrevButton,
12
+ queryNextButton,
13
+ queryPrevButton
14
+ } from 'test/po';
15
+ import { customRender } from 'test/render';
16
+ import { freezeBeforeAll } from 'test/utils';
17
+
18
+ import { CaptionProps } from 'components/Caption';
19
+ import { CustomComponents } from 'types/DayPickerBase';
20
+
21
+ import { CaptionNavigation } from './CaptionNavigation';
22
+
23
+ const today = new Date(2021, 8);
24
+
25
+ freezeBeforeAll(today);
26
+
27
+ function setup(props: CaptionProps, dayPickerProps?: DayPickerProps) {
28
+ customRender(<CaptionNavigation {...props} />, dayPickerProps);
29
+ }
30
+
31
+ describe('when using a custom CaptionLabel component', () => {
32
+ const components: CustomComponents = {
33
+ CaptionLabel: () => <>custom label foo</>
34
+ };
35
+ const props = { displayMonth: today };
36
+ beforeEach(() => {
37
+ setup(props, { components });
38
+ });
39
+ test('it should render the custom component instead', () => {
40
+ expect(screen.getByText('custom label foo')).toBeInTheDocument();
41
+ });
42
+ });
43
+
44
+ describe('when rendered', () => {
45
+ const dayPickerProps: DayPickerProps = {
46
+ captionLayout: 'buttons'
47
+ };
48
+ test('should render the caption label', () => {
49
+ customRender(<CaptionNavigation displayMonth={today} />, dayPickerProps);
50
+ expect(getMonthCaption()).toHaveTextContent('September 2021');
51
+ });
52
+ test('should render the next month button', () => {
53
+ customRender(<CaptionNavigation displayMonth={today} />, dayPickerProps);
54
+ expect(getNextButton()).toBeInTheDocument();
55
+ });
56
+ test('should render the previous month button', () => {
57
+ customRender(<CaptionNavigation displayMonth={today} />, dayPickerProps);
58
+ expect(getPrevButton()).toBeInTheDocument();
59
+ });
60
+
61
+ describe('when displaying the first of multiple months', () => {
62
+ const numberOfMonths = 3;
63
+ beforeEach(() => {
64
+ customRender(<CaptionNavigation displayMonth={today} />, {
65
+ ...dayPickerProps,
66
+ numberOfMonths
67
+ });
68
+ });
69
+ test('should not display the next month button', () => {
70
+ expect(queryNextButton()).toBeNull();
71
+ });
72
+ test('should show the previous month button', () => {
73
+ expect(getPrevButton()).toBeInTheDocument();
74
+ });
75
+ });
76
+
77
+ describe('when displaying the last of multiple months', () => {
78
+ const numberOfMonths = 3;
79
+ beforeEach(() => {
80
+ const lastMonth = addMonths(today, numberOfMonths - 1);
81
+ customRender(<CaptionNavigation displayMonth={lastMonth} />, {
82
+ ...dayPickerProps,
83
+ numberOfMonths
84
+ });
85
+ });
86
+ test('should hide the previous month button', () => {
87
+ expect(queryPrevButton()).toBeNull();
88
+ });
89
+ test('should show the next month button', () => {
90
+ expect(getNextButton()).toBeInTheDocument();
91
+ });
92
+ });
93
+
94
+ describe('when displaying a month in the middle of multiple months', () => {
95
+ const numberOfMonths = 3;
96
+ beforeEach(() => {
97
+ const lastMonth = addMonths(today, numberOfMonths - 2);
98
+ customRender(<CaptionNavigation displayMonth={lastMonth} />, {
99
+ ...dayPickerProps,
100
+ numberOfMonths
101
+ });
102
+ });
103
+ test('should not render the previous month button', () => {
104
+ expect(queryPrevButton()).toBeNull();
105
+ });
106
+ test('should not render the next month button', () => {
107
+ expect(queryNextButton()).toBeNull();
108
+ });
109
+ });
110
+
111
+ describe('when clicking the previous button', () => {
112
+ describe('and a previous month is defined', () => {
113
+ const testContext = {
114
+ ...dayPickerProps,
115
+ onMonthChange: jest.fn()
116
+ };
117
+ const previousMonth = addMonths(today, -1);
118
+ beforeEach(() => {
119
+ customRender(<CaptionNavigation displayMonth={today} />, testContext);
120
+ userEvent.click(getPrevButton());
121
+ });
122
+ test('should call the `onMonthChange` callback', () => {
123
+ expect(testContext.onMonthChange).toHaveBeenCalledWith(previousMonth);
124
+ });
125
+ });
126
+ describe('and the previous month is not defined', () => {
127
+ const testContext = {
128
+ ...dayPickerProps,
129
+ fromDate: today,
130
+ onMonthChange: jest.fn()
131
+ };
132
+ beforeEach(() => {
133
+ customRender(<CaptionNavigation displayMonth={today} />, testContext);
134
+ userEvent.click(getPrevButton());
135
+ });
136
+ test('should call the `onMonthChange` callback', () => {
137
+ expect(testContext.onMonthChange).not.toHaveBeenCalled();
138
+ });
139
+ });
140
+ });
141
+
142
+ describe('when clicking the next month button', () => {
143
+ describe('and the next month is defined', () => {
144
+ const testContext = {
145
+ ...dayPickerProps,
146
+ onMonthChange: jest.fn()
147
+ };
148
+ const nextMonth = addMonths(today, 1);
149
+ beforeEach(() => {
150
+ customRender(<CaptionNavigation displayMonth={today} />, testContext);
151
+ userEvent.click(getNextButton());
152
+ });
153
+ test('should call the `onMonthChange` callback', () => {
154
+ expect(testContext.onMonthChange).toHaveBeenCalledWith(nextMonth);
155
+ });
156
+ });
157
+ describe('and the next month is not defined', () => {
158
+ const testContext = {
159
+ ...dayPickerProps,
160
+ toDate: today,
161
+ onMonthChange: jest.fn()
162
+ };
163
+ beforeEach(() => {
164
+ customRender(<CaptionNavigation displayMonth={today} />, testContext);
165
+ userEvent.click(getNextButton());
166
+ });
167
+ test('should call the `onMonthChange` callback', () => {
168
+ expect(testContext.onMonthChange).not.toHaveBeenCalled();
169
+ });
170
+ });
171
+ });
172
+ });
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+
3
+ import isSameMonth from 'date-fns/isSameMonth';
4
+
5
+ import { CaptionProps } from 'components/Caption/Caption';
6
+ import { CaptionLabel } from 'components/CaptionLabel';
7
+ import { Navigation } from 'components/Navigation';
8
+ import { useDayPicker } from 'contexts/DayPicker';
9
+ import { useNavigation } from 'contexts/Navigation';
10
+
11
+ /**
12
+ * Render a caption with a button-based navigation.
13
+ */
14
+ export function CaptionNavigation(props: CaptionProps): JSX.Element {
15
+ const { numberOfMonths, onMonthChange, dir, components } = useDayPicker();
16
+ const { previousMonth, nextMonth, goToMonth, displayMonths } =
17
+ useNavigation();
18
+
19
+ const displayIndex = displayMonths.findIndex((month) =>
20
+ isSameMonth(props.displayMonth, month)
21
+ );
22
+
23
+ let isFirst = displayIndex === 0;
24
+ let isLast = displayIndex === displayMonths.length - 1;
25
+ if (dir === 'rtl') {
26
+ [isLast, isFirst] = [isFirst, isLast];
27
+ }
28
+
29
+ const hideNext = numberOfMonths > 1 && (isFirst || !isLast);
30
+ const hidePrevious = numberOfMonths > 1 && (isLast || !isFirst);
31
+
32
+ const handlePreviousClick: React.MouseEventHandler = () => {
33
+ if (!previousMonth) return;
34
+ goToMonth(previousMonth);
35
+ onMonthChange?.(previousMonth);
36
+ };
37
+
38
+ const handleNextClick: React.MouseEventHandler = () => {
39
+ if (!nextMonth) return;
40
+ goToMonth(nextMonth);
41
+ onMonthChange?.(nextMonth);
42
+ };
43
+
44
+ const CaptionLabelComponent = components?.CaptionLabel ?? CaptionLabel;
45
+ const captionLabel = (
46
+ <CaptionLabelComponent id={props.id} displayMonth={props.displayMonth} />
47
+ );
48
+
49
+ return (
50
+ <>
51
+ {captionLabel}
52
+ <Navigation
53
+ displayMonth={props.displayMonth}
54
+ hideNext={hideNext}
55
+ hidePrevious={hidePrevious}
56
+ nextMonth={nextMonth}
57
+ previousMonth={previousMonth}
58
+ onPreviousClick={handlePreviousClick}
59
+ onNextClick={handleNextClick}
60
+ />
61
+ </>
62
+ );
63
+ }
@@ -0,0 +1 @@
1
+ export * from './CaptionNavigation';
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+
3
+ import { screen } from '@testing-library/react';
4
+ import { DayPickerProps } from 'DayPicker';
5
+
6
+ import { customRender } from 'test/render';
7
+ import { freezeBeforeAll } from 'test/utils';
8
+
9
+ import { CustomComponents } from 'types/DayPickerBase';
10
+
11
+ import { Day, DayProps } from './Day';
12
+
13
+ const today = new Date(2021, 8);
14
+
15
+ freezeBeforeAll(today);
16
+ let container: HTMLElement;
17
+ function setup(props: DayProps, dayPickerProps?: DayPickerProps) {
18
+ const view = customRender(<Day {...props} />, dayPickerProps);
19
+ container = view.container;
20
+ }
21
+
22
+ const date = today;
23
+ const displayMonth = today;
24
+ const props: DayProps = {
25
+ date: date,
26
+ displayMonth
27
+ };
28
+
29
+ describe('when the day to render has an hidden modifier', () => {
30
+ const dayPickerProps: DayPickerProps = {
31
+ modifiers: { hidden: date }
32
+ };
33
+ beforeEach(() => {
34
+ setup(props, dayPickerProps);
35
+ });
36
+ test('should render an empty element', () => {
37
+ expect(container).toBeEmptyDOMElement();
38
+ });
39
+ });
40
+ describe('when a no selection mode and no "onDayClick"', () => {
41
+ const dayPickerProps: DayPickerProps = { mode: 'default' };
42
+ beforeEach(() => {
43
+ setup(props, dayPickerProps);
44
+ });
45
+ test('should render a div', () => {
46
+ expect(container.firstChild?.nodeName).toBe('DIV');
47
+ });
48
+ });
49
+
50
+ describe('when a selection mode is set', () => {
51
+ const dayPickerProps: DayPickerProps = {
52
+ mode: 'single'
53
+ };
54
+ beforeEach(() => {
55
+ setup(props, dayPickerProps);
56
+ });
57
+ test('should render a button', () => {
58
+ expect(container.firstChild?.nodeName).toBe('BUTTON');
59
+ });
60
+ });
61
+
62
+ describe('when "onDayClick" is present', () => {
63
+ const dayPickerProps: DayPickerProps = {
64
+ onDayClick: jest.fn()
65
+ };
66
+ beforeEach(() => {
67
+ setup(props, dayPickerProps);
68
+ });
69
+ test('should render a button', () => {
70
+ expect(container.firstChild?.nodeName).toBe('BUTTON');
71
+ });
72
+ });
73
+
74
+ describe('when using a custom DayContent component', () => {
75
+ const components: CustomComponents = {
76
+ DayContent: () => <>Custom DayContent</>
77
+ };
78
+ beforeEach(() => {
79
+ setup(props, { components });
80
+ });
81
+ test('it should render the custom component instead', () => {
82
+ expect(screen.getByText('Custom DayContent')).toBeInTheDocument();
83
+ });
84
+ });
@@ -0,0 +1,30 @@
1
+ import React, { useRef } from 'react';
2
+
3
+ import { useDayRender } from 'hooks/useDayRender';
4
+
5
+ import { Button } from '../Button';
6
+
7
+ /** Represent the props used by the [[Day]] component. */
8
+ export interface DayProps {
9
+ /** The month where the date is displayed. */
10
+ displayMonth: Date;
11
+ /** The date to render. */
12
+ date: Date;
13
+ }
14
+
15
+ /**
16
+ * The content of a day cell – as a button or span element according to its
17
+ * modifiers.
18
+ */
19
+ export function Day(props: DayProps): JSX.Element {
20
+ const buttonRef = useRef<HTMLButtonElement>(null);
21
+ const dayRender = useDayRender(props.date, props.displayMonth, buttonRef);
22
+
23
+ if (dayRender.isHidden) {
24
+ return <></>;
25
+ }
26
+ if (!dayRender.isButton) {
27
+ return <div {...dayRender.divProps} />;
28
+ }
29
+ return <Button ref={buttonRef} {...dayRender.buttonProps} />;
30
+ }
@@ -0,0 +1 @@
1
+ export * from './Day';