@navinc/base-react-components
Version:
Nav's Pattern Library
183 lines (167 loc) • 4.7 kB
JavaScript
import React from 'react'
import Input from './input.js'
import Select from './select.js'
import Copy from './copy.js'
import { Errors, Err } from './form-elements/shared.js'
import VMasker from 'vanilla-masker'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import { noop } from '@navinc/utils'
export const DatePickerWrapper = styled.div`
div > ${Copy} {
text-align: left;
}
`
const StyledCopy = styled(Copy)`
margin-bottom: 8px;
`
const InputWrapper = styled.div`
display: grid;
grid-gap: 12px;
grid-template-columns: ${({ fieldsToShow = [] }) => {
if (fieldsToShow.length === 3) {
return '5fr 2fr 2.5fr'
} else if (fieldsToShow.length === 2) {
return '1fr 1fr'
} else {
return '1fr'
}
}};
${Errors} {
display: none;
}
`
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
]
export function isDateValid(dateIn) {
if (!dateIn || !dateIn.year || !dateIn.month || !dateIn.day) return false
const { year, month, day } = dateIn
if (year.toString().length !== 4) {
return false
}
const date = new Date(year, month - 1, day)
return date.getFullYear() === Number(year) && date.getMonth() + 1 === Number(month) && date.getDate() === Number(day)
}
export const DatePicker = ({
errors = [],
fieldsToShow = ['day', 'month', 'year'],
hasSpaceForErrors,
isInvalid,
label,
name,
onBlur,
onChange = noop,
value = {},
...rest
}) => {
const onChangeWrapper = (key, val) => {
onChange({
target: {
value: {
...value,
[key]: val,
},
},
})
}
const handleMonthChange = (event) => {
const monthNumber = months.indexOf(event.target.value) + 1
const val = monthNumber < 10 ? `0${monthNumber}` : `${monthNumber}`
onChangeWrapper('month', val)
}
const handleDayChange = (event) => {
onChangeWrapper('day', VMasker.toPattern(event.target.value, '99'))
}
const handleYearChange = (event) => {
onChangeWrapper('year', VMasker.toPattern(event.target.value, '9999'))
}
const isInvalidOrHasErrors = isInvalid || errors.length
return (
<DatePickerWrapper {...rest}>
{label && (
<div>
<StyledCopy light size="sm">
{label}
</StyledCopy>
</div>
)}
<InputWrapper fieldsToShow={fieldsToShow}>
{fieldsToShow.includes('month') && (
<Select
data-testid="date_picker:month"
isInvalid={isInvalidOrHasErrors}
label="Month"
name={name}
options={months}
onBlur={onBlur}
onChange={handleMonthChange}
value={months[parseInt(value.month, 10) - 1] ?? 0}
/>
)}
{fieldsToShow.includes('day') && (
<Input
data-testid="date_picker:day"
isInvalid={isInvalidOrHasErrors}
label="Day"
onBlur={onBlur}
onChange={handleDayChange}
// chrome 73 onWheel events became passive
onWheel={(e) => {
e.preventDefault()
e.target.blur()
}}
type="number"
value={value.day || ''}
/>
)}
{fieldsToShow.includes('year') && (
<Input
data-testid="date_picker:year"
isInvalid={isInvalidOrHasErrors}
label="Year"
onBlur={onBlur}
onChange={handleYearChange}
// chrome 73 onWheel events became passive
onWheel={(e) => {
e.preventDefault()
e.target.blur()
}}
type="number"
value={value.year || ''}
/>
)}
</InputWrapper>
<Errors hasSpaceForErrors={hasSpaceForErrors} data-testid="date_picker:errors">
{!!errors.length && errors.map((err, i) => <Err key={`err-${i}`}>{err}</Err>)}
</Errors>
</DatePickerWrapper>
)
}
DatePicker.propTypes = {
errors: PropTypes.arrayOf(PropTypes.string),
fieldsToShow: PropTypes.arrayOf(PropTypes.oneOf(['day', 'month', 'year'])),
hasSpaceForErrors: PropTypes.bool,
isInvalid: PropTypes.bool,
label: PropTypes.string,
onBlur: PropTypes.func,
onChange: PropTypes.func,
value: PropTypes.shape({
day: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
month: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
year: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}),
}
const StyledDatePicker = styled(DatePicker)``
export default StyledDatePicker