Skip to content
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

Allow user to set date by changing text input #379

Merged
merged 3 commits into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/form/day-picker/day-picker-README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const render = () => {
timeTitle = "Time"
dir="ltr"
timeInputErrorText="Please, enter valid time"
manualInput
inputMask={[/[0-1]/, /[0-9]/, '/', /[0-3]/, /[0-9]/, '/', /[0-9]/, /[0-9]/, /[0-9]/, /[0-9]/]}
dayPickerProps={{
disabled: [{ before: new Date()}, { after: new Date()}],
locale: es,
Expand Down Expand Up @@ -69,3 +71,5 @@ move forward with **tab-button**, back with **shift+tab-button**, **enter** to s
- timeTitle - title for time input;
- dir - direction, default is **ltr**, can be **rtl**
- timeInputErrorText - error label for time input error, default is 'Please, enter valid time'
- manualInput - allows user to set day by changing text input value, optional property, requires **inputMask** property to be set, false by default
- inputMask - text input mask that follows text input format, required for manualInput usage
93 changes: 70 additions & 23 deletions src/components/form/day-picker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
setHours,
setMinutes,
isDate,
isValid,
} from 'date-fns';
import { DayPicker as ReactDayPicker, DayPickerSingleProps } from 'react-day-picker';
import createAutoCorrectedDatePipe from 'text-mask-addons/dist/createAutoCorrectedDatePipe';
Expand All @@ -22,8 +23,11 @@ import { useTheme } from 'utils/hooks';
// eslint-disable-next-line import/no-duplicates
import en from 'date-fns/locale/en-GB';
import FocusLock from 'react-focus-lock';
import { useEffect, useMemo, useState } from 'react';
import { StyledDayPicker } from './style';

const defaultMask = [/[0-1]/, /[0-9]/, '/', /[0-3]/, /[0-9]/, '/', /[0-9]/, /[0-9]/, /[0-9]/, /[0-9]/];

const transformTime = (date = new Date()): string => {
const hours = date.getHours() > 9 ? date.getHours() : `0${date.getHours()}`;
const minutes = date.getMinutes() > 9 ? date.getMinutes() : `0${date.getMinutes()}`;
Expand All @@ -48,8 +52,10 @@ export interface DayPickerProps extends InputComponent, DayPickerSingleProps {
defaultDay?: Date;
format?: string;
iconAfter?: HTMLObjectElement | JSX.Element | string;
inputMask?: (string | RegExp)[];
isMobile?: boolean;
isTimePicker?: boolean;
manualInput?: boolean;
onChange?: (value: any) => void;
overlayComponentProps?: any;
placeholder?: string;
Expand Down Expand Up @@ -88,20 +94,46 @@ export const DayPicker = ({
dir = 'ltr',
timeInputErrorText = 'Please, enter valid time',
preventClosingOnDaySelect,
manualInput = false,
inputMask = defaultMask,
}: DayPickerProps): React.ReactElement => {
const theme = useTheme();

const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
const [date, setDate] = React.useState<Date>(value || defaultDay || new Date());
const [dateTime, setDateTime] = React.useState(transformTime(new Date(date)));
const [isTimeValid, setIsTimeValid] = React.useState<boolean>(true);
const defaultDate = value || defaultDay || new Date();
const [isCalendarOpen, setIsCalendarOpen] = useState(false);
const [date, setDate] = useState<Date>(defaultDate);
const [dateTime, setDateTime] = useState(transformTime(new Date(date)));
const [isTimeValid, setIsTimeValid] = useState<boolean>(true);

const inputValue = useMemo(() => formatDate(date, format), [date, format]);

const [month, setMonth] = useState<Date>(defaultDate);

const [textInputValue, setTextInputValue] = useState<string>(inputValue);

React.useEffect(() => {
if (onChange) onChange(date);
}, [date]);

useEffect(() => {
// get parsed date from text input
const parsedDate = parseDate(textInputValue, format);

// check if text input has string which is valid date
const isParsedDateValid = isValid(parsedDate);

if (isParsedDateValid && parsedDate) {
setDate(parsedDate);
setMonth(parsedDate);
}
}, [textInputValue]);

const calendarRef = React.useRef<HTMLDivElement>(null);

const onTextInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setTextInputValue(event.target.value);
};

const closeCalendar = () => {
if (isTimeValid) setIsCalendarOpen(false);
};
Expand All @@ -118,6 +150,8 @@ export const DayPicker = ({
if (isTimeValid && !preventClosingOnDaySelect) {
closeCalendar();
}

setTextInputValue(formatDate(transformedDate, format));
} else if (!selectedDate && isTimeValid) closeCalendar();
};

Expand All @@ -140,6 +174,7 @@ export const DayPicker = ({
if (!isTimeValid) setIsTimeValid(true);
setDateTime(e.target.value);
setDate(setHours(setMinutes(Number(date), minutes), hours));
setTextInputValue(formatDate(setHours(setMinutes(Number(date), minutes), hours), format));
} else {
setDateTime(e.target.value.replace(/[_:]/g, ''));
setIsTimeValid(false);
Expand Down Expand Up @@ -181,26 +216,36 @@ export const DayPicker = ({

return (
<StyledDayPicker theme={theme} error={error} className='dayPickerWrapper' {...wrapperProps}>
<FocusLock returnFocus autoFocus disabled={!isCalendarOpen}>
<TextInput
title={title}
subTitle={subTitle}
onClick={showCalendar}
onChange={showCalendar}
onKeyUp={(event) => {
if (event.key === 'Enter') {
showCalendar();
}
}}
disabled={isCalendarOpen || dateInputProps?.disable}
type='text'
<FocusLock returnFocus autoFocus disabled={manualInput || !isCalendarOpen}>
<MaskedInput
id='date-input-id'
key='date-input-id'
render={(customRef, restProps): JSX.Element => (
<TextInput
title={title}
subTitle={subTitle}
onClick={showCalendar}
onKeyUp={(event) => {
if (event.key === 'Enter') {
showCalendar();
}
}}
disabled={(isCalendarOpen && !manualInput) || dateInputProps?.disable}
type='text'
iconAfter={iconAfter}
error={error}
dir={dayPickerProps?.dir || dir}
inputRef={customRef}
{...dateInputProps}
{...restProps}
/>
)}
mask={manualInput ? inputMask : false}
placeholder={placeholder}
iconAfter={iconAfter}
value={formatDate(date, format)}
error={error}
dir={dayPickerProps?.dir || dir}
{...dateInputProps}
onChange={onTextInputChange}
value={textInputValue}
/>

<div className='calendar-wrapper'>
{isCalendarOpen && (
<div
Expand All @@ -213,9 +258,11 @@ export const DayPicker = ({
{...overlayComponentProps}
>
<ReactDayPicker
initialFocus={isCalendarOpen}
initialFocus={isCalendarOpen && !manualInput}
mode='single'
defaultMonth={date}
onMonthChange={setMonth}
month={month}
selected={date}
onSelect={handleDaySelect}
parseDate={parseDate}
Expand Down
6 changes: 4 additions & 2 deletions stories/day-picker/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ export default decorator('DayPicker', DayPickerDocs, README).add('DayPicker', ()
<Row style={{ justifyContent: 'space-around', alignItems: 'flex-start', marginBottom: '10px' }}>
<Col xs='12' md='12' lg='12'>
<DayPicker
manualInput
title='standard'
value={value}
onChange={handleChange}
format='MM/dd/yyyy'
placeholder='mm/dd/yy'
format='MM-dd-yyyy'
placeholder='mm-dd-yy'
inputMask={[/[0-1]/, /[0-9]/, '-', /[0-3]/, /[0-9]/, '-', /[0-9]/, /[0-9]/, /[0-9]/, /[0-9]/]}
dayPickerProps={{
disabled: {
before: new Date(),
Expand Down