Skip to content

Add support for custom native input #281

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ Displays an input field complete with custom inputs, native input, and a calenda
|clearAriaLabel|`aria-label` for the clear button.|n/a|`"Clear value"`|
|clearIcon|Content of the clear button. Setting the value explicitly to `null` will hide the icon.|(default icon)|<ul><li>String: `"Clear"`</li><li>React element: `<ClearIcon />`</li></ul>|
|closeCalendar|Whether to close the calendar on value selection.|`true`|`false`|
|customInput|Custom input component function. Note that many attributes will be overriden to provide react-date-picker functionality. See customInputOverrides to control this.|<input />|<input />|
|customInputStyle|Custom input style property. This will be merged with the existing default style.|{}|{ color: 'red' }|
|customInputOverrides|Tell react-date-picker to not override these attributes with its own custom attributes. Note this may result in loss of functionality.|[]|['aria-label']|
|dayAriaLabel|`aria-label` for the day input.|n/a|`"Day"`|
|dayPlaceholder|`placeholder` for the day input.|`"--"`|`"dd"`|
|disabled|Whether the date picker should be disabled.|`false`|`true`|
Expand Down
17 changes: 9 additions & 8 deletions src/DateInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -540,20 +540,18 @@ export default class DateInput extends PureComponent {
}

renderNativeInput() {
const {
disabled,
maxDate,
minDate,
name,
nativeInputAriaLabel,
required,
} = this.props;
const { disabled, maxDate, minDate } = this.props;
const { name, nativeInputAriaLabel, required } = this.props;
const { customInput, customInputOverrides, customInputStyle } = this.props;
const { value } = this.state;

return (
<NativeInput
key="date"
ariaLabel={nativeInputAriaLabel}
customInput={customInput}
customInputOverrides={customInputOverrides}
customInputStyle={customInputStyle}
disabled={disabled}
maxDate={maxDate || defaultMaxDate}
minDate={minDate || defaultMinDate}
Expand Down Expand Up @@ -597,6 +595,9 @@ const isValue = PropTypes.oneOfType([
DateInput.propTypes = {
autoFocus: PropTypes.bool,
className: PropTypes.string.isRequired,
customInput: PropTypes.element,
customInputOverrides: PropTypes.arrayOf(PropTypes.string),
customInputStyle: PropTypes.objectOf(PropTypes.any),
dayAriaLabel: PropTypes.string,
dayPlaceholder: PropTypes.string,
disabled: PropTypes.bool,
Expand Down
110 changes: 60 additions & 50 deletions src/DateInput/NativeInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,40 @@ import {

import { isMaxDate, isMinDate, isValueType } from '../shared/propTypes';

const nativeInputType = (valueType) => {
switch (valueType) {
case 'decade':
case 'year':
return 'number';
case 'month':
return 'month';
case 'day':
return 'date';
default:
throw new Error('Invalid valueType.');
}
};

const nativeValueParser = (valueType) => {
switch (valueType) {
case 'century':
case 'decade':
case 'year':
return getYear;
case 'month':
return getISOLocalMonth;
case 'day':
return getISOLocalDate;
default:
throw new Error('Invalid valueType.');
}
};

export default function NativeInput({
ariaLabel,
customInput = <input />,
customInputStyle = {},
customInputOverrides = [],
disabled,
maxDate,
minDate,
Expand All @@ -19,63 +51,41 @@ export default function NativeInput({
value,
valueType,
}) {
const nativeInputType = (() => {
switch (valueType) {
case 'decade':
case 'year':
return 'number';
case 'month':
return 'month';
case 'day':
return 'date';
default:
throw new Error('Invalid valueType.');
}
})();
const inputStyle = {
visibility: 'hidden',
position: 'absolute',
top: '-9999px',
left: '-9999px',
...customInputStyle,
};

const nativeValueParser = (() => {
switch (valueType) {
case 'century':
case 'decade':
case 'year':
return getYear;
case 'month':
return getISOLocalMonth;
case 'day':
return getISOLocalDate;
default:
throw new Error('Invalid valueType.');
}
})();
const inputProps = {
'aria-label': ariaLabel,
disabled,
max: maxDate ? nativeValueParser(valueType)(maxDate) : null,
min: minDate ? nativeValueParser(valueType)(minDate) : null,
name,
onChange,
onFocus: event => event.stopPropagation(),
required,
type: nativeInputType(valueType),
value: value ? nativeValueParser(valueType)(value) : '',
};

function stopPropagation(event) {
event.stopPropagation();
}
const filteredInputProps = Object.keys(inputProps)
.reduce((obj, key) => (customInputOverrides && customInputOverrides.includes(key)
? { ...obj }
: { ...obj, [key]: inputProps[key] }),
{});

return (
<input
aria-label={ariaLabel}
disabled={disabled}
max={maxDate ? nativeValueParser(maxDate) : null}
min={minDate ? nativeValueParser(minDate) : null}
name={name}
onChange={onChange}
onFocus={stopPropagation}
required={required}
style={{
visibility: 'hidden',
position: 'absolute',
top: '-9999px',
left: '-9999px',
}}
type={nativeInputType}
value={value ? nativeValueParser(value) : ''}
/>
);
return <>{React.cloneElement(customInput, { ...filteredInputProps, style: inputStyle })}</>;
}

NativeInput.propTypes = {
ariaLabel: PropTypes.string,
customInput: PropTypes.element,
customInputOverrides: PropTypes.arrayOf(PropTypes.string),
customInputStyle: PropTypes.objectOf(PropTypes.any),
disabled: PropTypes.bool,
maxDate: isMaxDate,
minDate: isMinDate,
Expand Down