1 | import React from 'react';
|
2 | import PropTypes from 'prop-types';
|
3 | import { forbidExtraProps, nonNegativeInteger } from 'airbnb-prop-types';
|
4 | import { withStyles, withStylesPropTypes } from 'react-with-styles';
|
5 |
|
6 | import { DateRangePickerInputPhrases } from '../defaultPhrases';
|
7 | import getPhrasePropTypes from '../utils/getPhrasePropTypes';
|
8 | import noflip from '../utils/noflip';
|
9 | import openDirectionShape from '../shapes/OpenDirectionShape';
|
10 |
|
11 | import DateInput from './DateInput';
|
12 | import IconPositionShape from '../shapes/IconPositionShape';
|
13 | import DisabledShape from '../shapes/DisabledShape';
|
14 |
|
15 | import RightArrow from './RightArrow';
|
16 | import LeftArrow from './LeftArrow';
|
17 | import CloseButton from './CloseButton';
|
18 | import CalendarIcon from './CalendarIcon';
|
19 |
|
20 | import {
|
21 | START_DATE,
|
22 | END_DATE,
|
23 | ICON_BEFORE_POSITION,
|
24 | ICON_AFTER_POSITION,
|
25 | OPEN_DOWN,
|
26 | } from '../constants';
|
27 |
|
28 | const propTypes = forbidExtraProps({
|
29 | ...withStylesPropTypes,
|
30 |
|
31 | children: PropTypes.node,
|
32 |
|
33 | startDateId: PropTypes.string,
|
34 | startDatePlaceholderText: PropTypes.string,
|
35 | startDateAriaLabel: PropTypes.string,
|
36 | screenReaderMessage: PropTypes.string,
|
37 |
|
38 | endDateId: PropTypes.string,
|
39 | endDatePlaceholderText: PropTypes.string,
|
40 | endDateAriaLabel: PropTypes.string,
|
41 |
|
42 | onStartDateFocus: PropTypes.func,
|
43 | onEndDateFocus: PropTypes.func,
|
44 | onStartDateChange: PropTypes.func,
|
45 | onEndDateChange: PropTypes.func,
|
46 | onStartDateShiftTab: PropTypes.func,
|
47 | onEndDateTab: PropTypes.func,
|
48 | onClearDates: PropTypes.func,
|
49 | onKeyDownArrowDown: PropTypes.func,
|
50 | onKeyDownQuestionMark: PropTypes.func,
|
51 |
|
52 | startDate: PropTypes.string,
|
53 | endDate: PropTypes.string,
|
54 |
|
55 | isStartDateFocused: PropTypes.bool,
|
56 | isEndDateFocused: PropTypes.bool,
|
57 | showClearDates: PropTypes.bool,
|
58 | disabled: DisabledShape,
|
59 | required: PropTypes.bool,
|
60 | readOnly: PropTypes.bool,
|
61 | openDirection: openDirectionShape,
|
62 | showCaret: PropTypes.bool,
|
63 | showDefaultInputIcon: PropTypes.bool,
|
64 | inputIconPosition: IconPositionShape,
|
65 | customInputIcon: PropTypes.node,
|
66 | customArrowIcon: PropTypes.node,
|
67 | customCloseIcon: PropTypes.node,
|
68 | noBorder: PropTypes.bool,
|
69 | block: PropTypes.bool,
|
70 | small: PropTypes.bool,
|
71 | regular: PropTypes.bool,
|
72 | verticalSpacing: nonNegativeInteger,
|
73 |
|
74 |
|
75 | isFocused: PropTypes.bool,
|
76 |
|
77 |
|
78 | phrases: PropTypes.shape(getPhrasePropTypes(DateRangePickerInputPhrases)),
|
79 |
|
80 | isRTL: PropTypes.bool,
|
81 | });
|
82 |
|
83 | const defaultProps = {
|
84 | children: null,
|
85 | startDateId: START_DATE,
|
86 | endDateId: END_DATE,
|
87 | startDatePlaceholderText: 'Start Date',
|
88 | endDatePlaceholderText: 'End Date',
|
89 | startDateAriaLabel: undefined,
|
90 | endDateAriaLabel: undefined,
|
91 | screenReaderMessage: '',
|
92 | onStartDateFocus() {},
|
93 | onEndDateFocus() {},
|
94 | onStartDateChange() {},
|
95 | onEndDateChange() {},
|
96 | onStartDateShiftTab() {},
|
97 | onEndDateTab() {},
|
98 | onClearDates() {},
|
99 | onKeyDownArrowDown() {},
|
100 | onKeyDownQuestionMark() {},
|
101 |
|
102 | startDate: '',
|
103 | endDate: '',
|
104 |
|
105 | isStartDateFocused: false,
|
106 | isEndDateFocused: false,
|
107 | showClearDates: false,
|
108 | disabled: false,
|
109 | required: false,
|
110 | readOnly: false,
|
111 | openDirection: OPEN_DOWN,
|
112 | showCaret: false,
|
113 | showDefaultInputIcon: false,
|
114 | inputIconPosition: ICON_BEFORE_POSITION,
|
115 | customInputIcon: null,
|
116 | customArrowIcon: null,
|
117 | customCloseIcon: null,
|
118 | noBorder: false,
|
119 | block: false,
|
120 | small: false,
|
121 | regular: false,
|
122 | verticalSpacing: undefined,
|
123 |
|
124 |
|
125 | isFocused: false,
|
126 |
|
127 |
|
128 | phrases: DateRangePickerInputPhrases,
|
129 |
|
130 | isRTL: false,
|
131 | };
|
132 |
|
133 | function DateRangePickerInput({
|
134 | children,
|
135 | css,
|
136 | startDate,
|
137 | startDateId,
|
138 | startDatePlaceholderText,
|
139 | screenReaderMessage,
|
140 | isStartDateFocused,
|
141 | onStartDateChange,
|
142 | onStartDateFocus,
|
143 | onStartDateShiftTab,
|
144 | startDateAriaLabel,
|
145 | endDate,
|
146 | endDateId,
|
147 | endDatePlaceholderText,
|
148 | isEndDateFocused,
|
149 | onEndDateChange,
|
150 | onEndDateFocus,
|
151 | onEndDateTab,
|
152 | endDateAriaLabel,
|
153 | onKeyDownArrowDown,
|
154 | onKeyDownQuestionMark,
|
155 | onClearDates,
|
156 | showClearDates,
|
157 | disabled,
|
158 | required,
|
159 | readOnly,
|
160 | showCaret,
|
161 | openDirection,
|
162 | showDefaultInputIcon,
|
163 | inputIconPosition,
|
164 | customInputIcon,
|
165 | customArrowIcon,
|
166 | customCloseIcon,
|
167 | isFocused,
|
168 | phrases,
|
169 | isRTL,
|
170 | noBorder,
|
171 | block,
|
172 | verticalSpacing,
|
173 | small,
|
174 | regular,
|
175 | styles,
|
176 | }) {
|
177 | const calendarIcon = customInputIcon || (
|
178 | <CalendarIcon {...css(styles.DateRangePickerInput_calendarIcon_svg)} />
|
179 | );
|
180 |
|
181 | let arrowIcon = customArrowIcon || <RightArrow {...css(styles.DateRangePickerInput_arrow_svg)} />;
|
182 | if (isRTL) arrowIcon = <LeftArrow {...css(styles.DateRangePickerInput_arrow_svg)} />;
|
183 | if (small) arrowIcon = '-';
|
184 |
|
185 | const closeIcon = customCloseIcon || (
|
186 | <CloseButton
|
187 | {...css(
|
188 | styles.DateRangePickerInput_clearDates_svg,
|
189 | small && styles.DateRangePickerInput_clearDates_svg__small,
|
190 | )}
|
191 | />
|
192 | );
|
193 |
|
194 | const screenReaderStartDateText = screenReaderMessage
|
195 | || phrases.keyboardForwardNavigationInstructions;
|
196 | const screenReaderEndDateText = screenReaderMessage
|
197 | || phrases.keyboardBackwardNavigationInstructions;
|
198 |
|
199 | const inputIcon = (showDefaultInputIcon || customInputIcon !== null) && (
|
200 | <button
|
201 | {...css(styles.DateRangePickerInput_calendarIcon)}
|
202 | type="button"
|
203 | disabled={disabled}
|
204 | aria-label={phrases.focusStartDate}
|
205 | onClick={onKeyDownArrowDown}
|
206 | >
|
207 | {calendarIcon}
|
208 | </button>
|
209 | );
|
210 |
|
211 | const startDateDisabled = disabled === START_DATE || disabled === true;
|
212 | const endDateDisabled = disabled === END_DATE || disabled === true;
|
213 |
|
214 | return (
|
215 | <div
|
216 | {...css(
|
217 | styles.DateRangePickerInput,
|
218 | disabled && styles.DateRangePickerInput__disabled,
|
219 | isRTL && styles.DateRangePickerInput__rtl,
|
220 | !noBorder && styles.DateRangePickerInput__withBorder,
|
221 | block && styles.DateRangePickerInput__block,
|
222 | showClearDates && styles.DateRangePickerInput__showClearDates,
|
223 | )}
|
224 | >
|
225 | {inputIconPosition === ICON_BEFORE_POSITION && inputIcon}
|
226 |
|
227 | <DateInput
|
228 | id={startDateId}
|
229 | placeholder={startDatePlaceholderText}
|
230 | ariaLabel={startDateAriaLabel}
|
231 | displayValue={startDate}
|
232 | screenReaderMessage={screenReaderStartDateText}
|
233 | focused={isStartDateFocused}
|
234 | isFocused={isFocused}
|
235 | disabled={startDateDisabled}
|
236 | required={required}
|
237 | readOnly={readOnly}
|
238 | showCaret={showCaret}
|
239 | openDirection={openDirection}
|
240 | onChange={onStartDateChange}
|
241 | onFocus={onStartDateFocus}
|
242 | onKeyDownShiftTab={onStartDateShiftTab}
|
243 | onKeyDownArrowDown={onKeyDownArrowDown}
|
244 | onKeyDownQuestionMark={onKeyDownQuestionMark}
|
245 | verticalSpacing={verticalSpacing}
|
246 | small={small}
|
247 | regular={regular}
|
248 | />
|
249 |
|
250 | {children}
|
251 |
|
252 | {
|
253 | <div
|
254 | {...css(styles.DateRangePickerInput_arrow)}
|
255 | aria-hidden="true"
|
256 | role="presentation"
|
257 | >
|
258 | {arrowIcon}
|
259 | </div>
|
260 | }
|
261 |
|
262 | <DateInput
|
263 | id={endDateId}
|
264 | placeholder={endDatePlaceholderText}
|
265 | ariaLabel={endDateAriaLabel}
|
266 | displayValue={endDate}
|
267 | screenReaderMessage={screenReaderEndDateText}
|
268 | focused={isEndDateFocused}
|
269 | isFocused={isFocused}
|
270 | disabled={endDateDisabled}
|
271 | required={required}
|
272 | readOnly={readOnly}
|
273 | showCaret={showCaret}
|
274 | openDirection={openDirection}
|
275 | onChange={onEndDateChange}
|
276 | onFocus={onEndDateFocus}
|
277 | onKeyDownArrowDown={onKeyDownArrowDown}
|
278 | onKeyDownQuestionMark={onKeyDownQuestionMark}
|
279 | onKeyDownTab={onEndDateTab}
|
280 | verticalSpacing={verticalSpacing}
|
281 | small={small}
|
282 | regular={regular}
|
283 | />
|
284 |
|
285 |
|
286 | {showClearDates && (
|
287 | <button
|
288 | type="button"
|
289 | aria-label={phrases.clearDates}
|
290 | {...css(
|
291 | styles.DateRangePickerInput_clearDates,
|
292 | small && styles.DateRangePickerInput_clearDates__small,
|
293 | !customCloseIcon && styles.DateRangePickerInput_clearDates_default,
|
294 | !(startDate || endDate) && styles.DateRangePickerInput_clearDates__hide,
|
295 | )}
|
296 | onClick={onClearDates}
|
297 | disabled={disabled}
|
298 | >
|
299 | {closeIcon}
|
300 | </button>
|
301 | )}
|
302 |
|
303 | {inputIconPosition === ICON_AFTER_POSITION && inputIcon}
|
304 | </div>
|
305 | );
|
306 | }
|
307 |
|
308 | DateRangePickerInput.propTypes = propTypes;
|
309 | DateRangePickerInput.defaultProps = defaultProps;
|
310 |
|
311 | export default withStyles(({ reactDates: { border, color, sizing } }) => ({
|
312 | DateRangePickerInput: {
|
313 | backgroundColor: color.background,
|
314 | display: 'inline-block',
|
315 | },
|
316 |
|
317 | DateRangePickerInput__disabled: {
|
318 | background: color.disabled,
|
319 | },
|
320 |
|
321 | DateRangePickerInput__withBorder: {
|
322 | borderColor: color.border,
|
323 | borderWidth: border.pickerInput.borderWidth,
|
324 | borderStyle: border.pickerInput.borderStyle,
|
325 | borderRadius: border.pickerInput.borderRadius,
|
326 | },
|
327 |
|
328 | DateRangePickerInput__rtl: {
|
329 | direction: noflip('rtl'),
|
330 | },
|
331 |
|
332 | DateRangePickerInput__block: {
|
333 | display: 'block',
|
334 | },
|
335 |
|
336 | DateRangePickerInput__showClearDates: {
|
337 | paddingRight: 30, // TODO: should be noflip wrapped and handled by an isRTL prop
|
338 | },
|
339 |
|
340 | DateRangePickerInput_arrow: {
|
341 | display: 'inline-block',
|
342 | verticalAlign: 'middle',
|
343 | color: color.text,
|
344 | },
|
345 |
|
346 | DateRangePickerInput_arrow_svg: {
|
347 | verticalAlign: 'middle',
|
348 | fill: color.text,
|
349 | height: sizing.arrowWidth,
|
350 | width: sizing.arrowWidth,
|
351 | },
|
352 |
|
353 | DateRangePickerInput_clearDates: {
|
354 | background: 'none',
|
355 | border: 0,
|
356 | color: 'inherit',
|
357 | font: 'inherit',
|
358 | lineHeight: 'normal',
|
359 | overflow: 'visible',
|
360 |
|
361 | cursor: 'pointer',
|
362 | padding: 10,
|
363 | margin: '0 10px 0 5px', // TODO: should be noflip wrapped and handled by an isRTL prop
|
364 | position: 'absolute',
|
365 | right: 0, // TODO: should be noflip wrapped and handled by an isRTL prop
|
366 | top: '50%',
|
367 | transform: 'translateY(-50%)',
|
368 | },
|
369 |
|
370 | DateRangePickerInput_clearDates__small: {
|
371 | padding: 6,
|
372 | },
|
373 |
|
374 | DateRangePickerInput_clearDates_default: {
|
375 | ':focus': {
|
376 | background: color.core.border,
|
377 | borderRadius: '50%',
|
378 | },
|
379 |
|
380 | ':hover': {
|
381 | background: color.core.border,
|
382 | borderRadius: '50%',
|
383 | },
|
384 | },
|
385 |
|
386 | DateRangePickerInput_clearDates__hide: {
|
387 | visibility: 'hidden',
|
388 | },
|
389 |
|
390 | DateRangePickerInput_clearDates_svg: {
|
391 | fill: color.core.grayLight,
|
392 | height: 12,
|
393 | width: 15,
|
394 | verticalAlign: 'middle',
|
395 | },
|
396 |
|
397 | DateRangePickerInput_clearDates_svg__small: {
|
398 | height: 9,
|
399 | },
|
400 |
|
401 | DateRangePickerInput_calendarIcon: {
|
402 | background: 'none',
|
403 | border: 0,
|
404 | color: 'inherit',
|
405 | font: 'inherit',
|
406 | lineHeight: 'normal',
|
407 | overflow: 'visible',
|
408 |
|
409 | cursor: 'pointer',
|
410 | display: 'inline-block',
|
411 | verticalAlign: 'middle',
|
412 | padding: 10,
|
413 | margin: '0 5px 0 10px', // TODO: should be noflip wrapped and handled by an isRTL prop
|
414 | },
|
415 |
|
416 | DateRangePickerInput_calendarIcon_svg: {
|
417 | fill: color.core.grayLight,
|
418 | height: 15,
|
419 | width: 14,
|
420 | verticalAlign: 'middle',
|
421 | },
|
422 | }), { pureComponent: typeof React.PureComponent !== 'undefined' })(DateRangePickerInput);
|