Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #2430 from teamleadercrm/FRAF-1154
Browse files Browse the repository at this point in the history
Making DatePickerInput typeable (again)
  • Loading branch information
qubis741 authored Nov 9, 2022
2 parents 820253f + bda1602 commit 575b11f
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 170 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

### Added

- `Popover`: `withFocusTrap` prop to allow control over focus ([@qubis741](https://github.com/qubis741)) in [#2430](https://github.com/teamleadercrm/ui/pull/2430))
- `DatePickerInput`: `typeable` and `errorText` props to allow typing date ([@qubis741](https://github.com/qubis741)) in [#2430](https://github.com/teamleadercrm/ui/pull/2430))

### Changed

### Deprecated
Expand Down
7 changes: 6 additions & 1 deletion src/components/datepicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import DayPicker, { DayModifiers, DayPickerProps } from 'react-day-picker';
import Box, { pickBoxProps } from '../box';
import NavigationBar from './NavigationBar';
Expand Down Expand Up @@ -43,6 +43,11 @@ const DatePicker: GenericComponent<DatePickerProps> = ({
const [selectedDate, setSelectedDate] = useState<Date | undefined>(others.selectedDate);
const [selectedMonth, setSelectedMonth] = useState<Date>();

useEffect(() => {
setSelectedDate(others.selectedDate);
setSelectedMonth(others.selectedDate);
}, [others.selectedDate]);

const handleDayClick = (day: Date, modifiers: DayModifiers) => {
if (modifiers[theme['disabled']]) {
return;
Expand Down
71 changes: 55 additions & 16 deletions src/components/datepicker/DatePickerInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ import Input from '../input';
import { InputProps } from '../input/Input';
import Popover from '../popover';
import { PopoverProps } from '../popover/Popover';
import { formatDate } from './localeUtils';
import { formatDate, parseMultiFormatsDate } from './localeUtils';
import theme from './theme.css';

const DEFAULT_FORMAT = 'dd/MM/yyyy';
const ALLOWED_DATE_FORMATS = [DEFAULT_FORMAT, 'd/M/yyyy', 'dd.MM.yyyy', 'd.M.yyyy', 'dd-MM-yyyy', 'd-M-yyyy'];

export interface DatePickerInputProps extends Omit<BoxProps, 'size' | 'onChange'> {
/** A class name for the wrapper to give custom styles. */
className?: string;
/** Object with props for the DatePicker component. */
dayPickerProps?: DayPickerProps;
/** A footer component, rendered at the bottom of the date picker */
footer?: ReactNode;
/** A custom function to format a date. */
/** A custom function to format a date if input is not typeable */
formatDate?: (selectedDate: Date, locale: string) => string;
/** Object with props for the Input component. */
inputProps?: InputProps;
Expand All @@ -47,6 +50,10 @@ export interface DatePickerInputProps extends Omit<BoxProps, 'size' | 'onChange'
openPickerOnFocus?: boolean;
/** Whether the input should have button for value clearing. False by default. */
clearable?: boolean;
/** Whether user is able to type into the input. True by default. */
typeable?: boolean;
/** Error text that is displayed when typed date is invalid. */
errorText?: string;
}

interface DayPickerProps extends Omit<ReactDayPickerProps, 'modifiers'> {
Expand All @@ -72,29 +79,35 @@ const DatePickerInput: GenericComponent<DatePickerInputProps> = ({
clearable = false,
onChange,
onBlur,
typeable = true,
errorText,
...others
}) => {
const [isPopoverActive, setIsPopoverActive] = useState(false);
const [popoverAnchorEl, setPopoverAnchorEl] = useState<Element | null>(null);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(others.selectedDate);

const getFormattedDate = () => {
if (!selectedDate) {
const getFormattedDateString = (date: Date) => {
if (!date) {
return '';
}

if (!customFormatDate) {
return formatDate(selectedDate, locale);
if (typeable || !customFormatDate) {
return formatDate(date, DEFAULT_FORMAT, locale);
}

return customFormatDate(selectedDate, locale);
return customFormatDate(date, locale);
};
const [isPopoverActive, setIsPopoverActive] = useState(false);
const [popoverAnchorEl, setPopoverAnchorEl] = useState<Element | null>(null);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(others.selectedDate);
const [displayError, setDisplayError] = useState(false);
const [inputValue, setInputValue] = useState(others.selectedDate ? getFormattedDateString(others.selectedDate) : '');
const handleInputValueChange = (value: string) => {
setDisplayError(false);
setInputValue(value);
};

const handleInputFocus = (event: React.FocusEvent<HTMLElement>) => {
if (inputProps?.readOnly) {
return;
}

if (openPickerOnFocus) {
setPopoverAnchorEl(event.currentTarget);
setIsPopoverActive(true);
Expand All @@ -110,10 +123,30 @@ const DatePickerInput: GenericComponent<DatePickerInputProps> = ({
setIsPopoverActive(true);
inputProps?.onFocus && inputProps.onFocus(event as unknown as React.FocusEvent<HTMLElement>);
}

inputProps?.onClick && inputProps.onClick(event);
};

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const value = event.target.value;
const date = parseMultiFormatsDate(value, ALLOWED_DATE_FORMATS, locale);
if (date) {
setSelectedDate(date);
}
handleInputValueChange(value);
};

const handleInputBlur = (event: React.FocusEvent<HTMLElement>) => {
inputProps?.onBlur && inputProps.onBlur(event);
if (typeable && !customFormatDate && inputValue) {
const date = parseMultiFormatsDate(inputValue, ALLOWED_DATE_FORMATS, locale);
if (date) {
handleInputValueChange(getFormattedDateString(date));
} else {
setDisplayError(true);
}
}
};

const handlePopoverClose = () => {
onBlur && onBlur();
setIsPopoverActive(false);
Expand All @@ -122,6 +155,7 @@ const DatePickerInput: GenericComponent<DatePickerInputProps> = ({
const handleDatePickerDateChange = (date: Date) => {
setIsPopoverActive(false);
setSelectedDate(date);
handleInputValueChange(getFormattedDateString(date));
onChange && onChange(date);
};

Expand All @@ -138,6 +172,7 @@ const DatePickerInput: GenericComponent<DatePickerInputProps> = ({
event.preventDefault();
setIsPopoverActive(false);
setSelectedDate(undefined);
handleInputValueChange('');
onChange && onChange(undefined);
};

Expand All @@ -159,21 +194,24 @@ const DatePickerInput: GenericComponent<DatePickerInputProps> = ({
}, [others.selectedDate]);

const boxProps = pickBoxProps(others);

const inputError = displayError ? errorText || true : false;
return (
<Box className={className} {...boxProps}>
<Input
inverse={inverse}
prefix={renderIcon()}
suffix={renderClearIcon()}
size={inputSize || size}
value={getFormattedDate()}
width="120px"
noInputStyling={dayPickerProps && dayPickerProps.withMonthPicker}
noInputStyling={!typeable}
error={inputError}
{...inputProps}
onClick={handleInputClick}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
className={theme['date-picker-input']}
value={inputValue}
onChange={handleInputChange}
/>
<Popover
active={isPopoverActive}
Expand All @@ -185,6 +223,7 @@ const DatePickerInput: GenericComponent<DatePickerInputProps> = ({
position="end"
offsetCorrection={30}
returnFocusToSource={false}
withFocusTrap={!typeable}
{...popoverProps}
>
<Box overflowY="auto">
Expand Down
151 changes: 1 addition & 150 deletions src/components/datepicker/datePicker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,7 @@ import { ComponentMeta, ComponentStory } from '@storybook/react';
import { DateTime } from 'luxon';
import React from 'react';
import { addStoryInGroup, LOW_LEVEL_BLOCKS } from '../../../.storybook/utils';
import { DatePicker, DatePickerInput, Toggle } from '../../index';

const languages = [
'da-DK',
'de-DE',
'en-GB',
'en-US',
'es-ES',
'fi-FI',
'fr-BE',
'fr-FR',
'it-IT',
'nb-NO',
'nl-BE',
'nl-NL',
'pl-PL',
'pt-PT',
'sv-SE',
'tr-TR',
];
const sizes = ['small', 'medium', 'large'];

const optionalSizes = {
small: 'small',
medium: 'medium',
large: 'large',
none: null,
};

const customFormatDate = (date: Date, locale: string) => {
return DateTime.fromJSDate(date).setLocale(locale).toLocaleString(DateTime.DATETIME_HUGE);
};

const inputPlaceholderToday = (locale: string = 'nl-BE') => {
return DateTime.fromJSDate(new Date()).setLocale(locale).toLocaleString(DateTime.DATE_SHORT);
};
import { DatePicker } from '../../index';

const preSelectedDate = DateTime.local().plus({ days: 3 }).toJSDate();

Expand Down Expand Up @@ -81,117 +46,3 @@ singleDate.parameters = {
},
],
};

export const inputSingleDate: ComponentStory<typeof DatePickerInput> = (args) => {
const handleOnChange = (selectedDate: Date | undefined) => {
console.log('Selected date', selectedDate);
};

return (
<DatePickerInput
{...args}
dayPickerProps={{ ...args.dayPickerProps }}
inputProps={{ ...args.inputProps, placeholder: inputPlaceholderToday(args.locale) }}
footer={<Toggle label="Lorem ipsum dolor sit amet, suspendisse faucibus nunc et pellentesque" size="small" />}
formatDate={customFormatDate}
onChange={handleOnChange}
selectedDate={preSelectedDate}
/>
);
};

inputSingleDate.storyName = 'Input single date';
inputSingleDate.args = {
dayPickerProps: {
numberOfMonths: 1,
showOutsideDays: true,
showWeekNumbers: true,
withMonthPicker: false,
},
inputProps: {
bold: false,
disable: false,
error: '',
helpText: 'Pick a date',
inverse: false,
warning: '',
readOnly: false,
width: '',
},
locale: 'nl-BE',
size: 'medium',
inputSize: undefined,
datePickerSize: undefined,
};
inputSingleDate.argTypes = {
locale: {
control: 'select',
options: languages,
},
size: {
control: 'select',
options: sizes,
},
inputSize: {
control: 'select',
options: optionalSizes,
},
datePickerSize: {
control: 'select',
options: optionalSizes,
},
};
inputSingleDate.parameters = {
design: [
{
name: 'Figma',
type: 'figma',
url: 'https://www.figma.com/file/LHH25GN90ljQaBEUNMsdJn/Desktop-components?node-id=980%3A0',
},
{
name: 'Vendor docs',
type: 'iframe',
url: 'http://react-day-picker.js.org/api/DayPickerInput',
},
],
};

export const clearableInputSingleDate: ComponentStory<typeof DatePickerInput> = (args) => {
const handleOnChange = (selectedDate: Date | undefined) => {
console.log('Selected date', selectedDate);
};
return (
<DatePickerInput
{...args}
dayPickerProps={{ ...args.dayPickerProps }}
inputProps={{ ...args.inputProps, placeholder: inputPlaceholderToday(args.locale) }}
footer={<Toggle label="Lorem ipsum dolor sit amet, suspendisse faucibus nunc et pellentesque" size="small" />}
formatDate={customFormatDate}
onChange={handleOnChange}
selectedDate={preSelectedDate}
clearable
/>
);
};
clearableInputSingleDate.args = {
dayPickerProps: {
numberOfMonths: 1,
showOutsideDays: true,
showWeekNumbers: true,
withMonthPicker: false,
},
inputProps: {
bold: false,
disable: false,
error: '',
helpText: 'Pick a date',
inverse: false,
warning: '',
readOnly: false,
width: '',
},
locale: 'nl-BE',
size: 'medium',
inputSize: undefined,
datePickerSize: undefined,
};
Loading

0 comments on commit 575b11f

Please sign in to comment.