Skip to content

Commit ba29384

Browse files
committed
Add support for the InputRenderer
1 parent 9acb2a5 commit ba29384

File tree

6 files changed

+546
-440
lines changed

6 files changed

+546
-440
lines changed

README.md

+31
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,37 @@
1010

1111
> An easily internationalizable, accessible, mobile-friendly datepicker library for the web.
1212
13+
I was very frustrated that such a popular library doesn't allow set a custom component, so I came up with this solution.
14+
15+
It's exactly the same `react-dates`, but with the custom component support in the mix and typings out of the box. That's how you can use it:
16+
17+
```js
18+
import React, { useState } from 'react'
19+
import TextInput from '@ui/text-input'
20+
import { SingleDatePicker } from 'react-dates-custom-input'
21+
22+
const render = () => {
23+
const [currentDate, setCurrentDate] = useState(null)
24+
const [isFocused, setFocused] = useState(false)
25+
26+
return (
27+
<SingleDatePicker
28+
date={currentDate}
29+
onDateChange={newDate => setCurrentDate(newDate)}
30+
id='date'
31+
focused={isFocused}
32+
onFocusChange={({ focused }) => setFocused(focused)}
33+
inputRenderer={({ initInput, ...inputProps }) => (
34+
<TextInput
35+
ref={initInput}
36+
{...inputProps}
37+
/>
38+
)}
39+
/>
40+
)
41+
}
42+
```
43+
1344
![react-dates in action](https://raw.githubusercontent.com/airbnb/react-dates/master/react-dates-demo.gif)
1445

1546
## Live Playground

src/components/DateInput.jsx

+153-124
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ import {
1818
MODIFIER_KEY_NAMES,
1919
} from '../constants';
2020

21-
const FANG_PATH_TOP = `M0,${FANG_HEIGHT_PX} ${FANG_WIDTH_PX},${FANG_HEIGHT_PX} ${FANG_WIDTH_PX / 2},0z`;
22-
const FANG_STROKE_TOP = `M0,${FANG_HEIGHT_PX} ${FANG_WIDTH_PX / 2},0 ${FANG_WIDTH_PX},${FANG_HEIGHT_PX}`;
23-
const FANG_PATH_BOTTOM = `M0,0 ${FANG_WIDTH_PX},0 ${FANG_WIDTH_PX / 2},${FANG_HEIGHT_PX}z`;
24-
const FANG_STROKE_BOTTOM = `M0,0 ${FANG_WIDTH_PX / 2},${FANG_HEIGHT_PX} ${FANG_WIDTH_PX},0`;
21+
const FANG_PATH_TOP = `M0,${FANG_HEIGHT_PX} ${FANG_WIDTH_PX},${FANG_HEIGHT_PX} ${FANG_WIDTH_PX
22+
/ 2},0z`;
23+
const FANG_STROKE_TOP = `M0,${FANG_HEIGHT_PX} ${FANG_WIDTH_PX
24+
/ 2},0 ${FANG_WIDTH_PX},${FANG_HEIGHT_PX}`;
25+
const FANG_PATH_BOTTOM = `M0,0 ${FANG_WIDTH_PX},0 ${FANG_WIDTH_PX
26+
/ 2},${FANG_HEIGHT_PX}z`;
27+
const FANG_STROKE_BOTTOM = `M0,0 ${FANG_WIDTH_PX
28+
/ 2},${FANG_HEIGHT_PX} ${FANG_WIDTH_PX},0`;
2529

2630
const propTypes = forbidExtraProps({
2731
...withStylesPropTypes,
@@ -49,10 +53,21 @@ const propTypes = forbidExtraProps({
4953
onKeyDownArrowDown: PropTypes.func,
5054
onKeyDownQuestionMark: PropTypes.func,
5155

56+
// not sure what types should I set there,
57+
// cuz I haven't used prop-types in a decade
58+
inputRenderer: PropTypes.func,
5259
// accessibility
5360
isFocused: PropTypes.bool, // describes actual DOM focus
5461
});
5562

63+
const defaultInputRenderer = ({ initInput, ...otherProps }) => (
64+
<input ref={initInput} {...otherProps} />
65+
);
66+
67+
defaultInputRenderer.propTypes = {
68+
initInput: PropTypes.func.isRequired,
69+
};
70+
5671
const defaultProps = {
5772
placeholder: 'Select Date',
5873
displayValue: '',
@@ -77,6 +92,8 @@ const defaultProps = {
7792
onKeyDownArrowDown() {},
7893
onKeyDownQuestionMark() {},
7994

95+
inputRenderer: defaultInputRenderer,
96+
8097
// accessibility
8198
isFocused: false,
8299
};
@@ -93,7 +110,9 @@ class DateInput extends React.PureComponent {
93110
this.onChange = this.onChange.bind(this);
94111
this.onKeyDown = this.onKeyDown.bind(this);
95112
this.setInputRef = this.setInputRef.bind(this);
96-
this.throttledKeyDown = throttle(this.onFinalKeyDown, 300, { trailing: false });
113+
this.throttledKeyDown = throttle(this.onFinalKeyDown, 300, {
114+
trailing: false,
115+
});
97116
}
98117

99118
componentDidMount() {
@@ -111,7 +130,9 @@ class DateInput extends React.PureComponent {
111130

112131
componentDidUpdate(prevProps) {
113132
const { focused, isFocused } = this.props;
114-
if (prevProps.focused === focused && prevProps.isFocused === isFocused) return;
133+
if (prevProps.focused === focused && prevProps.isFocused === isFocused) {
134+
return;
135+
}
115136

116137
if (focused && isFocused) {
117138
this.inputRef.focus();
@@ -167,10 +188,7 @@ class DateInput extends React.PureComponent {
167188
}
168189

169190
render() {
170-
const {
171-
dateString,
172-
isTouchDevice: isTouch,
173-
} = this.state;
191+
const { dateString, isTouchDevice: isTouch } = this.state;
174192
const {
175193
id,
176194
placeholder,
@@ -190,6 +208,7 @@ class DateInput extends React.PureComponent {
190208
block,
191209
styles,
192210
theme: { reactDates },
211+
inputRenderer: InputRenderer,
193212
} = this.props;
194213

195214
const value = dateString || displayValue || '';
@@ -211,7 +230,7 @@ class DateInput extends React.PureComponent {
211230
withFang && openDirection === OPEN_UP && styles.DateInput__openUp,
212231
)}
213232
>
214-
<input
233+
<InputRenderer
215234
{...css(
216235
styles.DateInput_input,
217236
small && styles.DateInput_input__small,
@@ -224,7 +243,7 @@ class DateInput extends React.PureComponent {
224243
type="text"
225244
id={id}
226245
name={id}
227-
ref={this.setInputRef}
246+
initInput={this.setInputRef}
228247
value={value}
229248
onChange={this.onChange}
230249
onKeyDown={this.onKeyDown}
@@ -257,13 +276,20 @@ class DateInput extends React.PureComponent {
257276
/>
258277
<path
259278
{...css(styles.DateInput_fangStroke)}
260-
d={openDirection === OPEN_DOWN ? FANG_STROKE_TOP : FANG_STROKE_BOTTOM}
279+
d={
280+
openDirection === OPEN_DOWN
281+
? FANG_STROKE_TOP
282+
: FANG_STROKE_BOTTOM
283+
}
261284
/>
262285
</svg>
263286
)}
264287

265288
{screenReaderMessage && (
266-
<p {...css(styles.DateInput_screenReaderMessage)} id={screenReaderMessageId}>
289+
<p
290+
{...css(styles.DateInput_screenReaderMessage)}
291+
id={screenReaderMessageId}
292+
>
267293
{screenReaderMessage}
268294
</p>
269295
)}
@@ -275,113 +301,116 @@ class DateInput extends React.PureComponent {
275301
DateInput.propTypes = propTypes;
276302
DateInput.defaultProps = defaultProps;
277303

278-
export default withStyles(({
279-
reactDates: {
280-
border, color, sizing, spacing, font, zIndex,
281-
},
282-
}) => ({
283-
DateInput: {
284-
margin: 0,
285-
padding: spacing.inputPadding,
286-
background: color.background,
287-
position: 'relative',
288-
display: 'inline-block',
289-
width: sizing.inputWidth,
290-
verticalAlign: 'middle',
291-
},
292-
293-
DateInput__small: {
294-
width: sizing.inputWidth_small,
295-
},
296-
297-
DateInput__block: {
298-
width: '100%',
299-
},
300-
301-
DateInput__disabled: {
302-
background: color.disabled,
303-
color: color.textDisabled,
304-
},
305-
306-
DateInput_input: {
307-
fontWeight: font.input.weight,
308-
fontSize: font.input.size,
309-
lineHeight: font.input.lineHeight,
310-
color: color.text,
311-
backgroundColor: color.background,
312-
width: '100%',
313-
padding: `${spacing.displayTextPaddingVertical}px ${spacing.displayTextPaddingHorizontal}px`,
314-
paddingTop: spacing.displayTextPaddingTop,
315-
paddingBottom: spacing.displayTextPaddingBottom,
316-
paddingLeft: noflip(spacing.displayTextPaddingLeft),
317-
paddingRight: noflip(spacing.displayTextPaddingRight),
318-
border: border.input.border,
319-
borderTop: border.input.borderTop,
320-
borderRight: noflip(border.input.borderRight),
321-
borderBottom: border.input.borderBottom,
322-
borderLeft: noflip(border.input.borderLeft),
323-
borderRadius: border.input.borderRadius,
324-
},
325-
326-
DateInput_input__small: {
327-
fontSize: font.input.size_small,
328-
lineHeight: font.input.lineHeight_small,
329-
letterSpacing: font.input.letterSpacing_small,
330-
padding: `${spacing.displayTextPaddingVertical_small}px ${spacing.displayTextPaddingHorizontal_small}px`,
331-
paddingTop: spacing.displayTextPaddingTop_small,
332-
paddingBottom: spacing.displayTextPaddingBottom_small,
333-
paddingLeft: noflip(spacing.displayTextPaddingLeft_small),
334-
paddingRight: noflip(spacing.displayTextPaddingRight_small),
335-
},
336-
337-
DateInput_input__regular: {
338-
fontWeight: 'auto',
339-
},
340-
341-
DateInput_input__readOnly: {
342-
userSelect: 'none',
343-
},
344-
345-
DateInput_input__focused: {
346-
outline: border.input.outlineFocused,
347-
background: color.backgroundFocused,
348-
border: border.input.borderFocused,
349-
borderTop: border.input.borderTopFocused,
350-
borderRight: noflip(border.input.borderRightFocused),
351-
borderBottom: border.input.borderBottomFocused,
352-
borderLeft: noflip(border.input.borderLeftFocused),
353-
},
354-
355-
DateInput_input__disabled: {
356-
background: color.disabled,
357-
fontStyle: font.input.styleDisabled,
358-
},
359-
360-
DateInput_screenReaderMessage: {
361-
border: 0,
362-
clip: 'rect(0, 0, 0, 0)',
363-
height: 1,
364-
margin: -1,
365-
overflow: 'hidden',
366-
padding: 0,
367-
position: 'absolute',
368-
width: 1,
369-
},
370-
371-
DateInput_fang: {
372-
position: 'absolute',
373-
width: FANG_WIDTH_PX,
374-
height: FANG_HEIGHT_PX,
375-
left: 22, // TODO: should be noflip wrapped and handled by an isRTL prop
376-
zIndex: zIndex + 2,
377-
},
378-
379-
DateInput_fangShape: {
380-
fill: color.background,
381-
},
382-
383-
DateInput_fangStroke: {
384-
stroke: color.core.border,
385-
fill: 'transparent',
386-
},
387-
}), { pureComponent: typeof React.PureComponent !== 'undefined' })(DateInput);
304+
export default withStyles(
305+
({
306+
reactDates: {
307+
border, color, sizing, spacing, font, zIndex,
308+
},
309+
}) => ({
310+
DateInput: {
311+
margin: 0,
312+
padding: spacing.inputPadding,
313+
background: color.background,
314+
position: 'relative',
315+
display: 'inline-block',
316+
width: sizing.inputWidth,
317+
verticalAlign: 'middle',
318+
},
319+
320+
DateInput__small: {
321+
width: sizing.inputWidth_small,
322+
},
323+
324+
DateInput__block: {
325+
width: '100%',
326+
},
327+
328+
DateInput__disabled: {
329+
background: color.disabled,
330+
color: color.textDisabled,
331+
},
332+
333+
DateInput_input: {
334+
fontWeight: font.input.weight,
335+
fontSize: font.input.size,
336+
lineHeight: font.input.lineHeight,
337+
color: color.text,
338+
backgroundColor: color.background,
339+
width: '100%',
340+
padding: `${spacing.displayTextPaddingVertical}px ${spacing.displayTextPaddingHorizontal}px`,
341+
paddingTop: spacing.displayTextPaddingTop,
342+
paddingBottom: spacing.displayTextPaddingBottom,
343+
paddingLeft: noflip(spacing.displayTextPaddingLeft),
344+
paddingRight: noflip(spacing.displayTextPaddingRight),
345+
border: border.input.border,
346+
borderTop: border.input.borderTop,
347+
borderRight: noflip(border.input.borderRight),
348+
borderBottom: border.input.borderBottom,
349+
borderLeft: noflip(border.input.borderLeft),
350+
borderRadius: border.input.borderRadius,
351+
},
352+
353+
DateInput_input__small: {
354+
fontSize: font.input.size_small,
355+
lineHeight: font.input.lineHeight_small,
356+
letterSpacing: font.input.letterSpacing_small,
357+
padding: `${spacing.displayTextPaddingVertical_small}px ${spacing.displayTextPaddingHorizontal_small}px`,
358+
paddingTop: spacing.displayTextPaddingTop_small,
359+
paddingBottom: spacing.displayTextPaddingBottom_small,
360+
paddingLeft: noflip(spacing.displayTextPaddingLeft_small),
361+
paddingRight: noflip(spacing.displayTextPaddingRight_small),
362+
},
363+
364+
DateInput_input__regular: {
365+
fontWeight: 'auto',
366+
},
367+
368+
DateInput_input__readOnly: {
369+
userSelect: 'none',
370+
},
371+
372+
DateInput_input__focused: {
373+
outline: border.input.outlineFocused,
374+
background: color.backgroundFocused,
375+
border: border.input.borderFocused,
376+
borderTop: border.input.borderTopFocused,
377+
borderRight: noflip(border.input.borderRightFocused),
378+
borderBottom: border.input.borderBottomFocused,
379+
borderLeft: noflip(border.input.borderLeftFocused),
380+
},
381+
382+
DateInput_input__disabled: {
383+
background: color.disabled,
384+
fontStyle: font.input.styleDisabled,
385+
},
386+
387+
DateInput_screenReaderMessage: {
388+
border: 0,
389+
clip: 'rect(0, 0, 0, 0)',
390+
height: 1,
391+
margin: -1,
392+
overflow: 'hidden',
393+
padding: 0,
394+
position: 'absolute',
395+
width: 1,
396+
},
397+
398+
DateInput_fang: {
399+
position: 'absolute',
400+
width: FANG_WIDTH_PX,
401+
height: FANG_HEIGHT_PX,
402+
left: 22, // TODO: should be noflip wrapped and handled by an isRTL prop
403+
zIndex: zIndex + 2,
404+
},
405+
406+
DateInput_fangShape: {
407+
fill: color.background,
408+
},
409+
410+
DateInput_fangStroke: {
411+
stroke: color.core.border,
412+
fill: 'transparent',
413+
},
414+
}),
415+
{ pureComponent: typeof React.PureComponent !== 'undefined' },
416+
)(DateInput);

0 commit comments

Comments
 (0)