UNPKG

11.8 kBJSXView Raw
1import React from 'react';
2import PropTypes from 'prop-types';
3import momentPropTypes from 'react-moment-proptypes';
4import { forbidExtraProps, nonNegativeInteger, or } from 'airbnb-prop-types';
5import { withStyles, withStylesPropTypes } from 'react-with-styles';
6import moment from 'moment';
7import raf from 'raf';
8
9import { CalendarDayPhrases } from '../defaultPhrases';
10import getPhrasePropTypes from '../utils/getPhrasePropTypes';
11import getCalendarDaySettings from '../utils/getCalendarDaySettings';
12
13import { DAY_SIZE } from '../constants';
14import DefaultTheme from '../theme/DefaultTheme';
15
16const { reactDates: { color } } = DefaultTheme;
17
18function 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
29const 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
41const 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 // style overrides
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 // internationalization
76 phrases: PropTypes.shape(getPhrasePropTypes(CalendarDayPhrases)),
77});
78
79export 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
91export const outsideStyles = {
92 background: color.outside.backgroundColor,
93 border: 0,
94 color: color.outside.color,
95};
96
97export 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
107export 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
118export 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
130export 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
142export 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
154export 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
166export const lastInRangeStyles = {};
167
168export 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
180const 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 // style defaults
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 // internationalization
214 phrases: CalendarDayPhrases,
215};
216
217class 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
361CustomizableCalendarDay.propTypes = propTypes;
362CustomizableCalendarDay.defaultProps = defaultProps;
363
364export { CustomizableCalendarDay as PureCustomizableCalendarDay };
365export 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