1 | import React from 'react';
|
2 | import PropTypes from 'prop-types';
|
3 | import momentPropTypes from 'react-moment-proptypes';
|
4 | import { forbidExtraProps, nonNegativeInteger, or } from 'airbnb-prop-types';
|
5 | import { withStyles, withStylesPropTypes } from 'react-with-styles';
|
6 | import moment from 'moment';
|
7 | import raf from 'raf';
|
8 |
|
9 | import { CalendarDayPhrases } from '../defaultPhrases';
|
10 | import getPhrasePropTypes from '../utils/getPhrasePropTypes';
|
11 | import getCalendarDaySettings from '../utils/getCalendarDaySettings';
|
12 |
|
13 | import { DAY_SIZE } from '../constants';
|
14 | import DefaultTheme from '../theme/DefaultTheme';
|
15 |
|
16 | const { reactDates: { color } } = DefaultTheme;
|
17 |
|
18 | function getStyles(stylesObj, isHovered) {
|
19 | if (!stylesObj) return null;
|
20 |
|
21 | const { hover } = stylesObj;
|
22 | if (isHovered && hover) {
|
23 | return hover;
|
24 | }
|
25 |
|
26 | return stylesObj;
|
27 | }
|
28 |
|
29 | const DayStyleShape = PropTypes.shape({
|
30 | background: PropTypes.string,
|
31 | border: or([PropTypes.string, PropTypes.number]),
|
32 | color: PropTypes.string,
|
33 |
|
34 | hover: PropTypes.shape({
|
35 | background: PropTypes.string,
|
36 | border: or([PropTypes.string, PropTypes.number]),
|
37 | color: PropTypes.string,
|
38 | }),
|
39 | });
|
40 |
|
41 | const propTypes = forbidExtraProps({
|
42 | ...withStylesPropTypes,
|
43 | day: momentPropTypes.momentObj,
|
44 | daySize: nonNegativeInteger,
|
45 | isOutsideDay: PropTypes.bool,
|
46 | modifiers: PropTypes.instanceOf(Set),
|
47 | isFocused: PropTypes.bool,
|
48 | tabIndex: PropTypes.oneOf([0, -1]),
|
49 | onDayClick: PropTypes.func,
|
50 | onDayMouseEnter: PropTypes.func,
|
51 | onDayMouseLeave: PropTypes.func,
|
52 | renderDayContents: PropTypes.func,
|
53 | ariaLabelFormat: PropTypes.string,
|
54 |
|
55 |
|
56 | defaultStyles: DayStyleShape,
|
57 | outsideStyles: DayStyleShape,
|
58 | todayStyles: DayStyleShape,
|
59 | firstDayOfWeekStyles: DayStyleShape,
|
60 | lastDayOfWeekStyles: DayStyleShape,
|
61 | highlightedCalendarStyles: DayStyleShape,
|
62 | blockedMinNightsStyles: DayStyleShape,
|
63 | blockedCalendarStyles: DayStyleShape,
|
64 | blockedOutOfRangeStyles: DayStyleShape,
|
65 | hoveredSpanStyles: DayStyleShape,
|
66 | selectedSpanStyles: DayStyleShape,
|
67 | lastInRangeStyles: DayStyleShape,
|
68 | selectedStyles: DayStyleShape,
|
69 | selectedStartStyles: DayStyleShape,
|
70 | selectedEndStyles: DayStyleShape,
|
71 | afterHoveredStartStyles: DayStyleShape,
|
72 | hoveredStartFirstPossibleEndStyles: DayStyleShape,
|
73 | hoveredStartBlockedMinNightsStyles: DayStyleShape,
|
74 |
|
75 |
|
76 | phrases: PropTypes.shape(getPhrasePropTypes(CalendarDayPhrases)),
|
77 | });
|
78 |
|
79 | export const defaultStyles = {
|
80 | border: `1px solid ${color.core.borderLight}`,
|
81 | color: color.text,
|
82 | background: color.background,
|
83 |
|
84 | hover: {
|
85 | background: color.core.borderLight,
|
86 | border: `1px solid ${color.core.borderLight}`,
|
87 | color: 'inherit',
|
88 | },
|
89 | };
|
90 |
|
91 | export const outsideStyles = {
|
92 | background: color.outside.backgroundColor,
|
93 | border: 0,
|
94 | color: color.outside.color,
|
95 | };
|
96 |
|
97 | export const highlightedCalendarStyles = {
|
98 | background: color.highlighted.backgroundColor,
|
99 | color: color.highlighted.color,
|
100 |
|
101 | hover: {
|
102 | background: color.highlighted.backgroundColor_hover,
|
103 | color: color.highlighted.color_active,
|
104 | },
|
105 | };
|
106 |
|
107 | export const blockedMinNightsStyles = {
|
108 | background: color.minimumNights.backgroundColor,
|
109 | border: `1px solid ${color.minimumNights.borderColor}`,
|
110 | color: color.minimumNights.color,
|
111 |
|
112 | hover: {
|
113 | background: color.minimumNights.backgroundColor_hover,
|
114 | color: color.minimumNights.color_active,
|
115 | },
|
116 | };
|
117 |
|
118 | export const blockedCalendarStyles = {
|
119 | background: color.blocked_calendar.backgroundColor,
|
120 | border: `1px solid ${color.blocked_calendar.borderColor}`,
|
121 | color: color.blocked_calendar.color,
|
122 |
|
123 | hover: {
|
124 | background: color.blocked_calendar.backgroundColor_hover,
|
125 | border: `1px solid ${color.blocked_calendar.borderColor}`,
|
126 | color: color.blocked_calendar.color_active,
|
127 | },
|
128 | };
|
129 |
|
130 | export const blockedOutOfRangeStyles = {
|
131 | background: color.blocked_out_of_range.backgroundColor,
|
132 | border: `1px solid ${color.blocked_out_of_range.borderColor}`,
|
133 | color: color.blocked_out_of_range.color,
|
134 |
|
135 | hover: {
|
136 | background: color.blocked_out_of_range.backgroundColor_hover,
|
137 | border: `1px solid ${color.blocked_out_of_range.borderColor}`,
|
138 | color: color.blocked_out_of_range.color_active,
|
139 | },
|
140 | };
|
141 |
|
142 | export const hoveredSpanStyles = {
|
143 | background: color.hoveredSpan.backgroundColor,
|
144 | border: `1px double ${color.hoveredSpan.borderColor}`,
|
145 | color: color.hoveredSpan.color,
|
146 |
|
147 | hover: {
|
148 | background: color.hoveredSpan.backgroundColor_hover,
|
149 | border: `1px double ${color.hoveredSpan.borderColor}`,
|
150 | color: color.hoveredSpan.color_active,
|
151 | },
|
152 | };
|
153 |
|
154 | export const selectedSpanStyles = {
|
155 | background: color.selectedSpan.backgroundColor,
|
156 | border: `1px double ${color.selectedSpan.borderColor}`,
|
157 | color: color.selectedSpan.color,
|
158 |
|
159 | hover: {
|
160 | background: color.selectedSpan.backgroundColor_hover,
|
161 | border: `1px double ${color.selectedSpan.borderColor}`,
|
162 | color: color.selectedSpan.color_active,
|
163 | },
|
164 | };
|
165 |
|
166 | export const lastInRangeStyles = {};
|
167 |
|
168 | export const selectedStyles = {
|
169 | background: color.selected.backgroundColor,
|
170 | border: `1px double ${color.selected.borderColor}`,
|
171 | color: color.selected.color,
|
172 |
|
173 | hover: {
|
174 | background: color.selected.backgroundColor_hover,
|
175 | border: `1px double ${color.selected.borderColor}`,
|
176 | color: color.selected.color_active,
|
177 | },
|
178 | };
|
179 |
|
180 | const defaultProps = {
|
181 | day: moment(),
|
182 | daySize: DAY_SIZE,
|
183 | isOutsideDay: false,
|
184 | modifiers: new Set(),
|
185 | isFocused: false,
|
186 | tabIndex: -1,
|
187 | onDayClick() {},
|
188 | onDayMouseEnter() {},
|
189 | onDayMouseLeave() {},
|
190 | renderDayContents: null,
|
191 | ariaLabelFormat: 'dddd, LL',
|
192 |
|
193 |
|
194 | defaultStyles,
|
195 | outsideStyles,
|
196 | todayStyles: {},
|
197 | highlightedCalendarStyles,
|
198 | blockedMinNightsStyles,
|
199 | blockedCalendarStyles,
|
200 | blockedOutOfRangeStyles,
|
201 | hoveredSpanStyles,
|
202 | selectedSpanStyles,
|
203 | lastInRangeStyles,
|
204 | selectedStyles,
|
205 | selectedStartStyles: {},
|
206 | selectedEndStyles: {},
|
207 | afterHoveredStartStyles: {},
|
208 | firstDayOfWeekStyles: {},
|
209 | lastDayOfWeekStyles: {},
|
210 | hoveredStartFirstPossibleEndStyles: {},
|
211 | hoveredStartBlockedMinNightsStyles: {},
|
212 |
|
213 |
|
214 | phrases: CalendarDayPhrases,
|
215 | };
|
216 |
|
217 | class CustomizableCalendarDay extends React.PureComponent {
|
218 | constructor(...args) {
|
219 | super(...args);
|
220 |
|
221 | this.state = {
|
222 | isHovered: false,
|
223 | };
|
224 |
|
225 | this.setButtonRef = this.setButtonRef.bind(this);
|
226 | }
|
227 |
|
228 | componentDidUpdate(prevProps) {
|
229 | const { isFocused, tabIndex } = this.props;
|
230 | if (tabIndex === 0) {
|
231 | if (isFocused || tabIndex !== prevProps.tabIndex) {
|
232 | raf(() => {
|
233 | if (this.buttonRef) {
|
234 | this.buttonRef.focus();
|
235 | }
|
236 | });
|
237 | }
|
238 | }
|
239 | }
|
240 |
|
241 | onDayClick(day, e) {
|
242 | const { onDayClick } = this.props;
|
243 | onDayClick(day, e);
|
244 | }
|
245 |
|
246 | onDayMouseEnter(day, e) {
|
247 | const { onDayMouseEnter } = this.props;
|
248 | this.setState({ isHovered: true });
|
249 | onDayMouseEnter(day, e);
|
250 | }
|
251 |
|
252 | onDayMouseLeave(day, e) {
|
253 | const { onDayMouseLeave } = this.props;
|
254 | this.setState({ isHovered: false });
|
255 | onDayMouseLeave(day, e);
|
256 | }
|
257 |
|
258 | onKeyDown(day, e) {
|
259 | const {
|
260 | onDayClick,
|
261 | } = this.props;
|
262 |
|
263 | const { key } = e;
|
264 | if (key === 'Enter' || key === ' ') {
|
265 | onDayClick(day, e);
|
266 | }
|
267 | }
|
268 |
|
269 | setButtonRef(ref) {
|
270 | this.buttonRef = ref;
|
271 | }
|
272 |
|
273 | render() {
|
274 | const {
|
275 | css,
|
276 | day,
|
277 | ariaLabelFormat,
|
278 | daySize,
|
279 | isOutsideDay,
|
280 | modifiers,
|
281 | tabIndex,
|
282 | renderDayContents,
|
283 | styles,
|
284 | phrases,
|
285 |
|
286 | defaultStyles: defaultStylesWithHover,
|
287 | outsideStyles: outsideStylesWithHover,
|
288 | todayStyles: todayStylesWithHover,
|
289 | firstDayOfWeekStyles: firstDayOfWeekStylesWithHover,
|
290 | lastDayOfWeekStyles: lastDayOfWeekStylesWithHover,
|
291 | highlightedCalendarStyles: highlightedCalendarStylesWithHover,
|
292 | blockedMinNightsStyles: blockedMinNightsStylesWithHover,
|
293 | blockedCalendarStyles: blockedCalendarStylesWithHover,
|
294 | blockedOutOfRangeStyles: blockedOutOfRangeStylesWithHover,
|
295 | hoveredSpanStyles: hoveredSpanStylesWithHover,
|
296 | selectedSpanStyles: selectedSpanStylesWithHover,
|
297 | lastInRangeStyles: lastInRangeStylesWithHover,
|
298 | selectedStyles: selectedStylesWithHover,
|
299 | selectedStartStyles: selectedStartStylesWithHover,
|
300 | selectedEndStyles: selectedEndStylesWithHover,
|
301 | afterHoveredStartStyles: afterHoveredStartStylesWithHover,
|
302 | hoveredStartFirstPossibleEndStyles: hoveredStartFirstPossibleEndStylesWithHover,
|
303 | hoveredStartBlockedMinNightsStyles: hoveredStartBlockedMinNightsStylesWithHover,
|
304 | } = this.props;
|
305 |
|
306 | const { isHovered } = this.state;
|
307 |
|
308 | if (!day) return <td />;
|
309 |
|
310 | const {
|
311 | daySizeStyles,
|
312 | useDefaultCursor,
|
313 | selected,
|
314 | hoveredSpan,
|
315 | isOutsideRange,
|
316 | ariaLabel,
|
317 | } = getCalendarDaySettings(day, ariaLabelFormat, daySize, modifiers, phrases);
|
318 |
|
319 | return (
|
320 | <td
|
321 | {...css(
|
322 | styles.CalendarDay,
|
323 | useDefaultCursor && styles.CalendarDay__defaultCursor,
|
324 | daySizeStyles,
|
325 | getStyles(defaultStylesWithHover, isHovered),
|
326 | isOutsideDay && getStyles(outsideStylesWithHover, isHovered),
|
327 | modifiers.has('today') && getStyles(todayStylesWithHover, isHovered),
|
328 | modifiers.has('first-day-of-week') && getStyles(firstDayOfWeekStylesWithHover, isHovered),
|
329 | modifiers.has('last-day-of-week') && getStyles(lastDayOfWeekStylesWithHover, isHovered),
|
330 | modifiers.has('hovered-start-first-possible-end') && getStyles(hoveredStartFirstPossibleEndStylesWithHover, isHovered),
|
331 | modifiers.has('hovered-start-blocked-minimum-nights') && getStyles(hoveredStartBlockedMinNightsStylesWithHover, isHovered),
|
332 | modifiers.has('highlighted-calendar') && getStyles(highlightedCalendarStylesWithHover, isHovered),
|
333 | modifiers.has('blocked-minimum-nights') && getStyles(blockedMinNightsStylesWithHover, isHovered),
|
334 | modifiers.has('blocked-calendar') && getStyles(blockedCalendarStylesWithHover, isHovered),
|
335 | hoveredSpan && getStyles(hoveredSpanStylesWithHover, isHovered),
|
336 | modifiers.has('after-hovered-start') && getStyles(afterHoveredStartStylesWithHover, isHovered),
|
337 | modifiers.has('selected-span') && getStyles(selectedSpanStylesWithHover, isHovered),
|
338 | modifiers.has('last-in-range') && getStyles(lastInRangeStylesWithHover, isHovered),
|
339 | selected && getStyles(selectedStylesWithHover, isHovered),
|
340 | modifiers.has('selected-start') && getStyles(selectedStartStylesWithHover, isHovered),
|
341 | modifiers.has('selected-end') && getStyles(selectedEndStylesWithHover, isHovered),
|
342 | isOutsideRange && getStyles(blockedOutOfRangeStylesWithHover, isHovered),
|
343 | )}
|
344 | role="button" // eslint-disable-line jsx-a11y/no-noninteractive-element-to-interactive-role
|
345 | ref={this.setButtonRef}
|
346 | aria-disabled={modifiers.has('blocked')}
|
347 | aria-label={ariaLabel}
|
348 | onMouseEnter={(e) => { this.onDayMouseEnter(day, e); }}
|
349 | onMouseLeave={(e) => { this.onDayMouseLeave(day, e); }}
|
350 | onMouseUp={(e) => { e.currentTarget.blur(); }}
|
351 | onClick={(e) => { this.onDayClick(day, e); }}
|
352 | onKeyDown={(e) => { this.onKeyDown(day, e); }}
|
353 | tabIndex={tabIndex}
|
354 | >
|
355 | {renderDayContents ? renderDayContents(day, modifiers) : day.format('D')}
|
356 | </td>
|
357 | );
|
358 | }
|
359 | }
|
360 |
|
361 | CustomizableCalendarDay.propTypes = propTypes;
|
362 | CustomizableCalendarDay.defaultProps = defaultProps;
|
363 |
|
364 | export { CustomizableCalendarDay as PureCustomizableCalendarDay };
|
365 | export default withStyles(({ reactDates: { font } }) => ({
|
366 | CalendarDay: {
|
367 | boxSizing: 'border-box',
|
368 | cursor: 'pointer',
|
369 | fontSize: font.size,
|
370 | textAlign: 'center',
|
371 |
|
372 | ':active': {
|
373 | outline: 0,
|
374 | },
|
375 | },
|
376 |
|
377 | CalendarDay__defaultCursor: {
|
378 | cursor: 'default',
|
379 | },
|
380 | }), { pureComponent: typeof React.PureComponent !== 'undefined' })(CustomizableCalendarDay);
|
381 |
|
\ | No newline at end of file |