UNPKG

8.02 kBJSXView Raw
1/* eslint react/no-array-index-key: 0 */
2
3import React from 'react';
4import PropTypes from 'prop-types';
5import momentPropTypes from 'react-moment-proptypes';
6import { forbidExtraProps, mutuallyExclusiveProps, nonNegativeInteger } from 'airbnb-prop-types';
7import { withStyles, withStylesPropTypes } from 'react-with-styles';
8import moment from 'moment';
9
10import { CalendarDayPhrases } from '../defaultPhrases';
11import getPhrasePropTypes from '../utils/getPhrasePropTypes';
12
13import CalendarWeek from './CalendarWeek';
14import CalendarDay from './CalendarDay';
15
16import calculateDimension from '../utils/calculateDimension';
17import getCalendarMonthWeeks from '../utils/getCalendarMonthWeeks';
18import isSameDay from '../utils/isSameDay';
19import toISODateString from '../utils/toISODateString';
20
21import ModifiersShape from '../shapes/ModifiersShape';
22import ScrollableOrientationShape from '../shapes/ScrollableOrientationShape';
23import DayOfWeekShape from '../shapes/DayOfWeekShape';
24
25
26import {
27 HORIZONTAL_ORIENTATION,
28 VERTICAL_SCROLLABLE,
29 DAY_SIZE,
30} from '../constants';
31
32const propTypes = forbidExtraProps({
33 ...withStylesPropTypes,
34 month: momentPropTypes.momentObj,
35 horizontalMonthPadding: nonNegativeInteger,
36 isVisible: PropTypes.bool,
37 enableOutsideDays: PropTypes.bool,
38 modifiers: PropTypes.objectOf(ModifiersShape),
39 orientation: ScrollableOrientationShape,
40 daySize: nonNegativeInteger,
41 onDayClick: PropTypes.func,
42 onDayMouseEnter: PropTypes.func,
43 onDayMouseLeave: PropTypes.func,
44 onMonthSelect: PropTypes.func,
45 onYearSelect: PropTypes.func,
46 renderMonthText: mutuallyExclusiveProps(PropTypes.func, 'renderMonthText', 'renderMonthElement'),
47 renderCalendarDay: PropTypes.func,
48 renderDayContents: PropTypes.func,
49 renderMonthElement: mutuallyExclusiveProps(PropTypes.func, 'renderMonthText', 'renderMonthElement'),
50 firstDayOfWeek: DayOfWeekShape,
51 setMonthTitleHeight: PropTypes.func,
52 verticalBorderSpacing: nonNegativeInteger,
53
54 focusedDate: momentPropTypes.momentObj, // indicates focusable day
55 isFocused: PropTypes.bool, // indicates whether or not to move focus to focusable day
56
57 // i18n
58 monthFormat: PropTypes.string,
59 phrases: PropTypes.shape(getPhrasePropTypes(CalendarDayPhrases)),
60 dayAriaLabelFormat: PropTypes.string,
61});
62
63const defaultProps = {
64 month: moment(),
65 horizontalMonthPadding: 13,
66 isVisible: true,
67 enableOutsideDays: false,
68 modifiers: {},
69 orientation: HORIZONTAL_ORIENTATION,
70 daySize: DAY_SIZE,
71 onDayClick() {},
72 onDayMouseEnter() {},
73 onDayMouseLeave() {},
74 onMonthSelect() {},
75 onYearSelect() {},
76 renderMonthText: null,
77 renderCalendarDay: (props) => (<CalendarDay {...props} />),
78 renderDayContents: null,
79 renderMonthElement: null,
80 firstDayOfWeek: null,
81 setMonthTitleHeight: null,
82
83 focusedDate: null,
84 isFocused: false,
85
86 // i18n
87 monthFormat: 'MMMM YYYY', // english locale
88 phrases: CalendarDayPhrases,
89 dayAriaLabelFormat: undefined,
90 verticalBorderSpacing: undefined,
91};
92
93class CalendarMonth extends React.PureComponent {
94 constructor(props) {
95 super(props);
96
97 this.state = {
98 weeks: getCalendarMonthWeeks(
99 props.month,
100 props.enableOutsideDays,
101 props.firstDayOfWeek == null ? moment.localeData().firstDayOfWeek() : props.firstDayOfWeek,
102 ),
103 };
104
105 this.setCaptionRef = this.setCaptionRef.bind(this);
106 this.setMonthTitleHeight = this.setMonthTitleHeight.bind(this);
107 }
108
109 componentDidMount() {
110 this.setMonthTitleHeightTimeout = setTimeout(this.setMonthTitleHeight, 0);
111 }
112
113 componentWillReceiveProps(nextProps) {
114 const { month, enableOutsideDays, firstDayOfWeek } = nextProps;
115 const {
116 month: prevMonth,
117 enableOutsideDays: prevEnableOutsideDays,
118 firstDayOfWeek: prevFirstDayOfWeek,
119 } = this.props;
120 if (
121 !month.isSame(prevMonth)
122 || enableOutsideDays !== prevEnableOutsideDays
123 || firstDayOfWeek !== prevFirstDayOfWeek
124 ) {
125 this.setState({
126 weeks: getCalendarMonthWeeks(
127 month,
128 enableOutsideDays,
129 firstDayOfWeek == null ? moment.localeData().firstDayOfWeek() : firstDayOfWeek,
130 ),
131 });
132 }
133 }
134
135 componentWillUnmount() {
136 if (this.setMonthTitleHeightTimeout) {
137 clearTimeout(this.setMonthTitleHeightTimeout);
138 }
139 }
140
141 setMonthTitleHeight() {
142 const { setMonthTitleHeight } = this.props;
143 if (setMonthTitleHeight) {
144 const captionHeight = calculateDimension(this.captionRef, 'height', true, true);
145 setMonthTitleHeight(captionHeight);
146 }
147 }
148
149 setCaptionRef(ref) {
150 this.captionRef = ref;
151 }
152
153 render() {
154 const {
155 css,
156 dayAriaLabelFormat,
157 daySize,
158 focusedDate,
159 horizontalMonthPadding,
160 isFocused,
161 isVisible,
162 modifiers,
163 month,
164 monthFormat,
165 onDayClick,
166 onDayMouseEnter,
167 onDayMouseLeave,
168 onMonthSelect,
169 onYearSelect,
170 orientation,
171 phrases,
172 renderCalendarDay,
173 renderDayContents,
174 renderMonthElement,
175 renderMonthText,
176 styles,
177 verticalBorderSpacing,
178 } = this.props;
179
180 const { weeks } = this.state;
181 const monthTitle = renderMonthText ? renderMonthText(month) : month.format(monthFormat);
182
183 const verticalScrollable = orientation === VERTICAL_SCROLLABLE;
184
185 return (
186 <div
187 {...css(
188 styles.CalendarMonth,
189 { padding: `0 ${horizontalMonthPadding}px` },
190 )}
191 data-visible={isVisible}
192 >
193 <div
194 ref={this.setCaptionRef}
195 {...css(
196 styles.CalendarMonth_caption,
197 verticalScrollable && styles.CalendarMonth_caption__verticalScrollable,
198 )}
199 >
200 {renderMonthElement ? (
201 renderMonthElement({
202 month,
203 onMonthSelect,
204 onYearSelect,
205 isVisible,
206 })
207 ) : (
208 <strong>
209 {monthTitle}
210 </strong>
211 )}
212 </div>
213
214 <table
215 {...css(
216 !verticalBorderSpacing && styles.CalendarMonth_table,
217 verticalBorderSpacing && styles.CalendarMonth_verticalSpacing,
218 verticalBorderSpacing && { borderSpacing: `0px ${verticalBorderSpacing}px` },
219 )}
220 role="presentation"
221 >
222 <tbody>
223 {weeks.map((week, i) => (
224 <CalendarWeek key={i}>
225 {week.map((day, dayOfWeek) => renderCalendarDay({
226 key: dayOfWeek,
227 day,
228 daySize,
229 isOutsideDay: !day || day.month() !== month.month(),
230 tabIndex: isVisible && isSameDay(day, focusedDate) ? 0 : -1,
231 isFocused,
232 onDayMouseEnter,
233 onDayMouseLeave,
234 onDayClick,
235 renderDayContents,
236 phrases,
237 modifiers: modifiers[toISODateString(day)],
238 ariaLabelFormat: dayAriaLabelFormat,
239 }))}
240 </CalendarWeek>
241 ))}
242 </tbody>
243 </table>
244 </div>
245 );
246 }
247}
248
249CalendarMonth.propTypes = propTypes;
250CalendarMonth.defaultProps = defaultProps;
251
252export default withStyles(({ reactDates: { color, font, spacing } }) => ({
253 CalendarMonth: {
254 background: color.background,
255 textAlign: 'center',
256 verticalAlign: 'top',
257 userSelect: 'none',
258 },
259
260 CalendarMonth_table: {
261 borderCollapse: 'collapse',
262 borderSpacing: 0,
263 },
264
265 CalendarMonth_verticalSpacing: {
266 borderCollapse: 'separate',
267 },
268
269 CalendarMonth_caption: {
270 color: color.text,
271 fontSize: font.captionSize,
272 textAlign: 'center',
273 paddingTop: spacing.captionPaddingTop,
274 paddingBottom: spacing.captionPaddingBottom,
275 captionSide: 'initial',
276 },
277
278 CalendarMonth_caption__verticalScrollable: {
279 paddingTop: 12,
280 paddingBottom: 7,
281 },
282}), { pureComponent: typeof React.PureComponent !== 'undefined' })(CalendarMonth);
283
\No newline at end of file