UNPKG

11 kBJSXView Raw
1import React from 'react';
2import PropTypes from 'prop-types';
3import { forbidExtraProps, nonNegativeInteger } from 'airbnb-prop-types';
4import { withStyles, withStylesPropTypes } from 'react-with-styles';
5
6import { DateRangePickerInputPhrases } from '../defaultPhrases';
7import getPhrasePropTypes from '../utils/getPhrasePropTypes';
8import noflip from '../utils/noflip';
9import openDirectionShape from '../shapes/OpenDirectionShape';
10
11import DateInput from './DateInput';
12import IconPositionShape from '../shapes/IconPositionShape';
13import DisabledShape from '../shapes/DisabledShape';
14
15import RightArrow from './RightArrow';
16import LeftArrow from './LeftArrow';
17import CloseButton from './CloseButton';
18import CalendarIcon from './CalendarIcon';
19
20import {
21 START_DATE,
22 END_DATE,
23 ICON_BEFORE_POSITION,
24 ICON_AFTER_POSITION,
25 OPEN_DOWN,
26} from '../constants';
27
28const 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 // accessibility
75 isFocused: PropTypes.bool, // describes actual DOM focus
76
77 // i18n
78 phrases: PropTypes.shape(getPhrasePropTypes(DateRangePickerInputPhrases)),
79
80 isRTL: PropTypes.bool,
81});
82
83const 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 // accessibility
125 isFocused: false,
126
127 // i18n
128 phrases: DateRangePickerInputPhrases,
129
130 isRTL: false,
131};
132
133function 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
308DateRangePickerInput.propTypes = propTypes;
309DateRangePickerInput.defaultProps = defaultProps;
310
311export 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);