1 |
|
2 |
|
3 | import React from 'react';
|
4 | import PropTypes from 'prop-types';
|
5 | import momentPropTypes from 'react-moment-proptypes';
|
6 | import { forbidExtraProps, mutuallyExclusiveProps, nonNegativeInteger } from 'airbnb-prop-types';
|
7 | import { withStyles, withStylesPropTypes } from 'react-with-styles';
|
8 | import moment from 'moment';
|
9 |
|
10 | import { CalendarDayPhrases } from '../defaultPhrases';
|
11 | import getPhrasePropTypes from '../utils/getPhrasePropTypes';
|
12 |
|
13 | import CalendarWeek from './CalendarWeek';
|
14 | import CalendarDay from './CalendarDay';
|
15 |
|
16 | import calculateDimension from '../utils/calculateDimension';
|
17 | import getCalendarMonthWeeks from '../utils/getCalendarMonthWeeks';
|
18 | import isSameDay from '../utils/isSameDay';
|
19 | import toISODateString from '../utils/toISODateString';
|
20 |
|
21 | import ModifiersShape from '../shapes/ModifiersShape';
|
22 | import ScrollableOrientationShape from '../shapes/ScrollableOrientationShape';
|
23 | import DayOfWeekShape from '../shapes/DayOfWeekShape';
|
24 |
|
25 |
|
26 | import {
|
27 | HORIZONTAL_ORIENTATION,
|
28 | VERTICAL_SCROLLABLE,
|
29 | DAY_SIZE,
|
30 | } from '../constants';
|
31 |
|
32 | const 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,
|
55 | isFocused: PropTypes.bool,
|
56 |
|
57 |
|
58 | monthFormat: PropTypes.string,
|
59 | phrases: PropTypes.shape(getPhrasePropTypes(CalendarDayPhrases)),
|
60 | dayAriaLabelFormat: PropTypes.string,
|
61 | });
|
62 |
|
63 | const 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 |
|
93 | class 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 |
|
249 | CalendarMonth.propTypes = propTypes;
|
250 | CalendarMonth.defaultProps = defaultProps;
|
251 |
|
252 | export 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 |