From 044f7e0a4d100ab2aadd3f902cae2047b03d5bea Mon Sep 17 00:00:00 2001 From: qubis741 Date: Wed, 16 Nov 2022 14:48:41 +0100 Subject: [PATCH 01/18] allow selectedDate to be controlled by prop --- src/components/datepicker/DatePickerInput.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index 6f91c6f88..c7bdd43d6 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -194,6 +194,7 @@ function DatePickerInput({ useEffect(() => { setSelectedDate(others.selectedDate); + handleInputValueChange(others.selectedDate ? getFormattedDateString(others.selectedDate) : ''); }, [others.selectedDate]); const boxProps = pickBoxProps(others); From ab3aba0ed18fe3a34bafc83f215cc8eeebe1e4a0 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Wed, 16 Nov 2022 16:41:45 +0100 Subject: [PATCH 02/18] moved useEffect to top of component so its more visible --- src/components/datepicker/DatePickerInput.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index c7bdd43d6..55e7a04f8 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -106,6 +106,11 @@ function DatePickerInput({ setInputValue(value); }; + useEffect(() => { + setSelectedDate(others.selectedDate); + handleInputValueChange(others.selectedDate ? getFormattedDateString(others.selectedDate) : ''); + }, [others.selectedDate]); + const handleInputFocus = (event: React.FocusEvent) => { if (inputProps?.readOnly) { return; @@ -192,11 +197,6 @@ function DatePickerInput({ ) : null; }; - useEffect(() => { - setSelectedDate(others.selectedDate); - handleInputValueChange(others.selectedDate ? getFormattedDateString(others.selectedDate) : ''); - }, [others.selectedDate]); - const boxProps = pickBoxProps(others); const inputError = displayError ? errorText || true : false; return ( From 2ae4d3a6f289bb0a6215cac3cb804b7ca214274f Mon Sep 17 00:00:00 2001 From: qubis741 Date: Wed, 16 Nov 2022 16:42:16 +0100 Subject: [PATCH 03/18] give external error higher priority than internal --- src/components/datepicker/DatePickerInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index 55e7a04f8..f8ad0c0d3 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -198,7 +198,7 @@ function DatePickerInput({ }; const boxProps = pickBoxProps(others); - const inputError = displayError ? errorText || true : false; + const internalError = displayError ? errorText || true : false; return ( ({ size={inputSize || size} width="120px" noInputStyling={!typeable} - error={inputError} {...inputProps} + error={inputProps?.error || internalError} onClick={handleInputClick} onFocus={handleInputFocus} onBlur={handleInputBlur} From 30cb112787c1ac0f3101df2e2198b13bf6db412d Mon Sep 17 00:00:00 2001 From: qubis741 Date: Wed, 16 Nov 2022 17:32:27 +0100 Subject: [PATCH 04/18] return string in onChange, if invalid date --- src/components/datepicker/DatePickerInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index f8ad0c0d3..62819b1ba 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -33,7 +33,7 @@ export interface DatePickerInputProps extends /** The language ISO locale code ('en-GB', 'nl-BE', 'fr-FR',...). */ locale?: string; /** Callback function that is fired when the date has changed. */ - onChange?: (selectedDate: Date | undefined) => void; + onChange?: (selectedDate: Date | undefined | string) => void; /** Callback function that is fired when the popover with the calendar gets closed (unfocused) */ onBlur?: () => void; /** Object with props for the Popover component. */ From b50ae46905bdfb5567dff411ff89527538c35211 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Wed, 16 Nov 2022 17:37:55 +0100 Subject: [PATCH 05/18] adapt tests to new type definition of onChange --- src/components/datepicker/datePickerInput.stories.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/datepicker/datePickerInput.stories.tsx b/src/components/datepicker/datePickerInput.stories.tsx index 4743c9dc3..7147940dc 100644 --- a/src/components/datepicker/datePickerInput.stories.tsx +++ b/src/components/datepicker/datePickerInput.stories.tsx @@ -20,7 +20,7 @@ export default { } as ComponentMeta; export const defaultStory: ComponentStory = (args) => { - const handleOnChange = (selectedDate: Date | undefined) => { + const handleOnChange = (selectedDate: Date | undefined | string) => { console.log('Selected date', selectedDate); }; @@ -28,7 +28,7 @@ export const defaultStory: ComponentStory = (args) => { }; export const clearableInputSingleDate: ComponentStory = () => { - const handleOnChange = (selectedDate: Date | undefined) => { + const handleOnChange = (selectedDate: Date | undefined | string) => { console.log('Selected date', selectedDate); }; @@ -55,7 +55,7 @@ export const clearableInputSingleDate: ComponentStory = }; export const inputSingleDateWithCustomFormat: ComponentStory = () => { - const handleOnChange = (selectedDate: Date | undefined) => { + const handleOnChange = (selectedDate: Date | undefined | string) => { console.log('Selected date', selectedDate); }; @@ -83,7 +83,7 @@ export const inputSingleDateWithCustomFormat: ComponentStory = () => { - const handleOnChange = (selectedDate: Date | undefined) => { + const handleOnChange = (selectedDate: Date | undefined | string) => { console.log('Selected date', selectedDate); }; From 72c371f31a8be77502e2500ec7f4cd60bfbdac03 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Wed, 16 Nov 2022 17:38:35 +0100 Subject: [PATCH 06/18] call onChange on closing of popover, so proper argument is passed --- src/components/datepicker/DatePickerInput.tsx | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index 62819b1ba..5cfaba9a6 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -1,5 +1,5 @@ import { IconCalendarSmallOutline, IconCloseBadgedSmallFilled } from '@teamleader/ui-icons'; -import React, { ReactNode, useEffect, useState } from 'react'; +import React, { ReactNode, useCallback, useEffect, useState } from 'react'; import { DayPickerProps as ReactDayPickerProps, Modifier } from 'react-day-picker'; import DatePicker from '.'; import { SIZES } from '../../constants'; @@ -105,7 +105,36 @@ function DatePickerInput({ setDisplayError(false); setInputValue(value); }; - + // Special handling of closing popover, where we also call onChange prop to ensure proper value is in argument + const closePopover = useCallback( + (value: Date | undefined | false) => { + setIsPopoverActive(false); + // Date - on day click + if (value) { + onChange && onChange(value); + return; + } + // Clear click + if (value === undefined) { + onChange && onChange(undefined); + return; + } + // Blurred from input, not focused on datepicker + if (value === false) { + if (typeable && !customFormatDate && inputValue) { + const date = parseMultiFormatsDate(inputValue, ALLOWED_DATE_FORMATS, locale); + if (date && isAllowedDate(date, dayPickerProps?.disabledDays)) { + onChange && onChange(date); + } else { + onChange && onChange(inputValue); + } + } else { + onChange && onChange(selectedDate); + } + } + }, + [inputValue], + ); useEffect(() => { setSelectedDate(others.selectedDate); handleInputValueChange(others.selectedDate ? getFormattedDateString(others.selectedDate) : ''); @@ -148,7 +177,6 @@ function DatePickerInput({ const date = parseMultiFormatsDate(inputValue, ALLOWED_DATE_FORMATS, locale); if (date && isAllowedDate(date, dayPickerProps?.disabledDays)) { handleInputValueChange(getFormattedDateString(date)); - onChange && onChange(date); } else { setDisplayError(true); } @@ -157,14 +185,13 @@ function DatePickerInput({ const handlePopoverClose = () => { onBlur && onBlur(); - setIsPopoverActive(false); + closePopover(false); }; const handleDatePickerDateChange = (date: Date) => { - setIsPopoverActive(false); + closePopover(date); setSelectedDate(date); handleInputValueChange(getFormattedDateString(date)); - onChange && onChange(date); }; const renderIcon = () => { @@ -178,10 +205,8 @@ function DatePickerInput({ const handleClear = (event: MouseEvent) => { // Prevents opening datepicker on clicking of this event.preventDefault(); - setIsPopoverActive(false); - setSelectedDate(undefined); + closePopover(undefined); handleInputValueChange(''); - onChange && onChange(undefined); }; const renderClearIcon = () => { From 7736dff2d1ff3c4338d26331ebdf98e92930c6f0 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 12:55:43 +0100 Subject: [PATCH 07/18] changed others.selectedDate to preselectedDate for easier readability --- src/components/datepicker/DatePickerInput.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index 5cfaba9a6..c12c0f92a 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -83,6 +83,7 @@ function DatePickerInput({ onBlur, typeable = true as IsTypeable, errorText, + selectedDate: preselectedDate, ...others }: DatePickerInputProps) { const getFormattedDateString = (date: Date) => { @@ -96,11 +97,12 @@ function DatePickerInput({ return customFormatDate(date, locale); }; + const [isPopoverActive, setIsPopoverActive] = useState(false); const [popoverAnchorEl, setPopoverAnchorEl] = useState(null); - const [selectedDate, setSelectedDate] = useState(others.selectedDate); + const [selectedDate, setSelectedDate] = useState(preselectedDate); const [displayError, setDisplayError] = useState(false); - const [inputValue, setInputValue] = useState(others.selectedDate ? getFormattedDateString(others.selectedDate) : ''); + const [inputValue, setInputValue] = useState(preselectedDate ? getFormattedDateString(preselectedDate) : ''); const handleInputValueChange = (value: string) => { setDisplayError(false); setInputValue(value); @@ -259,7 +261,7 @@ function DatePickerInput({ From b9770bae64b5cfd0e48577d2bd68936b995b43fa Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 12:56:04 +0100 Subject: [PATCH 08/18] added isValidDate util --- src/components/datepicker/localeUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/datepicker/localeUtils.ts b/src/components/datepicker/localeUtils.ts index d94af3e38..bbe0d8b8c 100644 --- a/src/components/datepicker/localeUtils.ts +++ b/src/components/datepicker/localeUtils.ts @@ -107,3 +107,5 @@ export default { getMonths, parseDate, }; + +export const isValidDate = (date: Date) => DateTime.fromJSDate(date).isValid; From aaa81ad5690fd0215cc9014bebdafbb25c671f08 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 12:57:14 +0100 Subject: [PATCH 09/18] improved DatePickerInput useEffect to properly assign preselectedDate in different cases --- src/components/datepicker/DatePickerInput.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index c12c0f92a..ee2a6497d 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -10,7 +10,7 @@ import Input from '../input'; import { InputProps } from '../input/Input'; import Popover from '../popover'; import { PopoverProps } from '../popover/Popover'; -import { formatDate, parseMultiFormatsDate } from './localeUtils'; +import { formatDate, isValidDate, parseMultiFormatsDate } from './localeUtils'; import theme from './theme.css'; import { isAllowedDate } from './utils'; @@ -138,9 +138,17 @@ function DatePickerInput({ [inputValue], ); useEffect(() => { - setSelectedDate(others.selectedDate); - handleInputValueChange(others.selectedDate ? getFormattedDateString(others.selectedDate) : ''); - }, [others.selectedDate]); + if (!preselectedDate) { + handleInputValueChange(''); + setSelectedDate(preselectedDate); + // If preseleced invalid date happens when typed date is invalid and value is passed from form in codebase + } else if (isValidDate(preselectedDate)) { + handleInputValueChange(getFormattedDateString(preselectedDate)); + setSelectedDate(preselectedDate); + } else { + setSelectedDate(undefined); + } + }, [preselectedDate]); const handleInputFocus = (event: React.FocusEvent) => { if (inputProps?.readOnly) { From 70f176f7c146dd1b133711d5e276abaa6f2cefaa Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 12:57:44 +0100 Subject: [PATCH 10/18] added conditional typed argument to DatePickerInput onChange --- src/components/datepicker/DatePickerInput.tsx | 3 ++- src/components/datepicker/datePickerInput.stories.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index ee2a6497d..3a44bb12c 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -33,7 +33,7 @@ export interface DatePickerInputProps extends /** The language ISO locale code ('en-GB', 'nl-BE', 'fr-FR',...). */ locale?: string; /** Callback function that is fired when the date has changed. */ - onChange?: (selectedDate: Date | undefined | string) => void; + onChange?: (selectedDate: IsTypeable extends true ? Date | string | undefined : Date | undefined) => void; /** Callback function that is fired when the popover with the calendar gets closed (unfocused) */ onBlur?: () => void; /** Object with props for the Popover component. */ @@ -128,6 +128,7 @@ function DatePickerInput({ if (date && isAllowedDate(date, dayPickerProps?.disabledDays)) { onChange && onChange(date); } else { + // @ts-ignore onChange && onChange(inputValue); } } else { diff --git a/src/components/datepicker/datePickerInput.stories.tsx b/src/components/datepicker/datePickerInput.stories.tsx index 7147940dc..e51a7261e 100644 --- a/src/components/datepicker/datePickerInput.stories.tsx +++ b/src/components/datepicker/datePickerInput.stories.tsx @@ -55,7 +55,7 @@ export const clearableInputSingleDate: ComponentStory = }; export const inputSingleDateWithCustomFormat: ComponentStory = () => { - const handleOnChange = (selectedDate: Date | undefined | string) => { + const handleOnChange = (selectedDate: Date | undefined) => { console.log('Selected date', selectedDate); }; From 74f3a60715064c70d2ccceff8a925f21ef6a53a1 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 16:52:28 +0100 Subject: [PATCH 11/18] properly set date if preselectedDate is invalid --- src/components/datepicker/DatePickerInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index 3a44bb12c..7a3a5ab13 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -141,7 +141,7 @@ function DatePickerInput({ useEffect(() => { if (!preselectedDate) { handleInputValueChange(''); - setSelectedDate(preselectedDate); + setSelectedDate(undefined); // If preseleced invalid date happens when typed date is invalid and value is passed from form in codebase } else if (isValidDate(preselectedDate)) { handleInputValueChange(getFormattedDateString(preselectedDate)); From 3f8d079c1f43ef0483afcea09f1a935223d6af23 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 16:53:58 +0100 Subject: [PATCH 12/18] added isValid locale util --- src/components/datepicker/localeUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/datepicker/localeUtils.ts b/src/components/datepicker/localeUtils.ts index bbe0d8b8c..92891fb4a 100644 --- a/src/components/datepicker/localeUtils.ts +++ b/src/components/datepicker/localeUtils.ts @@ -108,4 +108,4 @@ export default { parseDate, }; -export const isValidDate = (date: Date) => DateTime.fromJSDate(date).isValid; +export const isValidDate = (date: any): date is Date => DateTime.fromJSDate(date).isValid; From 1ba76153dda5a2eee1a6f7277b04fde5a120608a Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 16:55:32 +0100 Subject: [PATCH 13/18] allow string type in selectedDate prop to match reality and adapt code accordingly --- src/components/datepicker/DatePickerInput.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index 7a3a5ab13..afc76e7a6 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -38,8 +38,8 @@ export interface DatePickerInputProps extends onBlur?: () => void; /** Object with props for the Popover component. */ popoverProps?: PopoverProps; - /** The current selected date. */ - selectedDate?: Date; + /** The current selected value. */ + selectedDate?: IsTypeable extends true ? Date | string : Date; /** Size of the Input & DatePicker components. */ size?: Exclude; /** Overridable size of the Input component. */ @@ -100,9 +100,14 @@ function DatePickerInput({ const [isPopoverActive, setIsPopoverActive] = useState(false); const [popoverAnchorEl, setPopoverAnchorEl] = useState(null); - const [selectedDate, setSelectedDate] = useState(preselectedDate); + const [selectedDate, setSelectedDate] = useState( + preselectedDate ? new Date(preselectedDate) : undefined, + ); const [displayError, setDisplayError] = useState(false); - const [inputValue, setInputValue] = useState(preselectedDate ? getFormattedDateString(preselectedDate) : ''); + const [inputValue, setInputValue] = useState( + preselectedDate ? (isValidDate(preselectedDate) ? getFormattedDateString(preselectedDate) : preselectedDate) : '', + ); + const handleInputValueChange = (value: string) => { setDisplayError(false); setInputValue(value); From d4847f94ab0ac3f8e1460a15e523c2ad75967353 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 16:56:57 +0100 Subject: [PATCH 14/18] added comment to explain ts-ignore --- src/components/datepicker/DatePickerInput.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/datepicker/DatePickerInput.tsx b/src/components/datepicker/DatePickerInput.tsx index afc76e7a6..6d789de17 100644 --- a/src/components/datepicker/DatePickerInput.tsx +++ b/src/components/datepicker/DatePickerInput.tsx @@ -133,6 +133,7 @@ function DatePickerInput({ if (date && isAllowedDate(date, dayPickerProps?.disabledDays)) { onChange && onChange(date); } else { + // Conditional typing of arguments somehow doesn't work inside of component // @ts-ignore onChange && onChange(inputValue); } From cb37d274ac5aac48df4067f9723908089abd3963 Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 17:00:43 +0100 Subject: [PATCH 15/18] bump version --- CHANGELOG.md | 14 ++++++++++---- package.json | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a1a93c41..0f81cf45d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ### Added -- `MarketingMenuItem`: Added new component ([@lorgan3](https://github.com/lorgan3)) in ([#2452](https://github.com/teamleadercrm/ui/pull/2452)) - ### Changed ### Deprecated @@ -12,10 +10,18 @@ ### Fixed -- `NumericInput`: Make sure the stepper `onMouseDown` loops stop when the minimum or maximum value is reached ([@kristofcolpaert](https://github.com/kristofcolpaert)) in ([#2451](https://github.com/teamleadercrm/ui/pull/2451)) - ### Dependency updates +## [17.0.3] - 2022-11-21 + +### Added + +- `MarketingMenuItem`: Added new component ([@lorgan3](https://github.com/lorgan3)) in ([#2452](https://github.com/teamleadercrm/ui/pull/2452)) +### Fixed + +- `NumericInput`: Make sure the stepper `onMouseDown` loops stop when the minimum or maximum value is reached ([@kristofcolpaert](https://github.com/kristofcolpaert)) in ([#2451](https://github.com/teamleadercrm/ui/pull/2451)) +- `DatePickerInput`: overall functionality ([@qubis741](https://github.com/qubis741)) in [#2448](https://github.com/teamleadercrm/ui/pull/2448)) + ## [17.0.2] - 2022-11-16 ### Fixed diff --git a/package.json b/package.json index ef2b190c2..9ccb64b22 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@teamleader/ui", "description": "Teamleader UI library", - "version": "17.0.2", + "version": "17.0.3", "author": "Teamleader ", "bugs": { "url": "https://github.com/teamleadercrm/ui/issues" From 3525dc7cc6c9a0c8612ae30fb8679d8232527a4a Mon Sep 17 00:00:00 2001 From: qubis741 Date: Fri, 18 Nov 2022 18:03:09 +0100 Subject: [PATCH 16/18] improve detecting focus trap exception in DatePicker --- src/components/datepicker/DatePicker.tsx | 2 +- src/components/select/Select.tsx | 8 +------- src/utils/useFocusTrap/useFocusTrap.tsx | 8 +++++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/components/datepicker/DatePicker.tsx b/src/components/datepicker/DatePicker.tsx index 716c1d6d8..019533eb6 100644 --- a/src/components/datepicker/DatePicker.tsx +++ b/src/components/datepicker/DatePicker.tsx @@ -85,7 +85,7 @@ const DatePicker: GenericComponent = ({ ); return ( - + ( ); }; -// For setting data attribute that is detected in `useFocusTrap` -const Input =