Skip to content

Commit 00e69fd

Browse files
authored
fix: add clearing state (#1960)
<!-- Hello 👋 Thank you for submitting a pull request. To help us merge your PR, make sure to follow the instructions below: - Create or update the tests - Create or update the documentation at https://github.com/strapi/documentation - Refer to the issue you are closing in the PR description: Fix #issue - Specify if the PR is ready to be merged or work in progress (by opening a draft PR) Please ensure you read the Contributing Guide: https://github.com/strapi/strapi/blob/master/CONTRIBUTING.md --> ### What does it do? Clearing the `DateTimePicker` now clears on the first click without flashing the old value. I added a “clearing” state so the internal calendar resets before the next paint while the controlled value prop catches up. added test with it ### Why is it needed? this should fix #1883 ### How to test it? launch stroybook, go to `DateTimePicker`, clear button should be clearing date at first click now. ### Related issue(s)/PR(s) #1883 dx-2218
2 parents 553cbeb + 09ecec5 commit 00e69fd

File tree

3 files changed

+83
-11
lines changed

3 files changed

+83
-11
lines changed

.changeset/eight-foxes-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@strapi/design-system': major
3+
---
4+
5+
fix clear button for DateTimePicker

packages/design-system/src/components/DateTimePicker/DateTimePicker.test.tsx

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as React from 'react';
2+
13
import { render as renderRTL } from '@test/utils';
24

35
import { Field } from '../Field';
@@ -135,6 +137,42 @@ describe('DateTimePicker', () => {
135137
expect(onClear).toHaveBeenCalled();
136138
});
137139

140+
it('should clear a controlled value the first time the clear button is clicked', async () => {
141+
const Controlled = () => {
142+
const [value, setValue] = React.useState<Date | null>(new Date('12/01/2023 10:00'));
143+
144+
return (
145+
<DateTimePicker
146+
locale="en-GB"
147+
value={value ?? undefined}
148+
onChange={(date) => setValue(date ?? null)}
149+
onClear={() => setValue(null)}
150+
/>
151+
);
152+
};
153+
154+
const { getByRole, user } = renderRTL(<Controlled />);
155+
156+
const dateInput = getByRole('combobox', { name: 'Choose date' });
157+
const timeInput = getByRole('combobox', { name: 'Choose time' });
158+
159+
await user.click(dateInput);
160+
await user.click(getByRole('gridcell', { name: /15/ }));
161+
162+
expect(dateInput).toHaveValue('15/12/2023');
163+
expect(timeInput).toHaveValue('10:00');
164+
165+
await user.click(timeInput);
166+
await user.click(getByRole('option', { name: /12:30/ }));
167+
168+
expect(timeInput).toHaveValue('12:30');
169+
170+
await user.click(getByRole('button', { name: 'clear date' }));
171+
172+
expect(dateInput).toHaveValue('');
173+
expect(timeInput).toHaveValue('');
174+
});
175+
138176
it('should reset the value of the TimePicker to 00:00 when clicking on the clear button but not clear the DatePicker and not call onClear', async () => {
139177
const onClear = jest.fn();
140178
const { getByRole, user } = render({ onClear });
@@ -218,7 +256,9 @@ describe('DateTimePicker', () => {
218256
await user.click(getByRole('combobox', { name: 'Choose time' }));
219257
await user.click(getByRole('option', { name: /12:00/ }));
220258

221-
expect(onChange).toHaveBeenNthCalledWith(2, new Date('12/15/2023 12:00'));
259+
expect(onChange).toHaveBeenNthCalledWith(2, new Date('12/15/2023'));
260+
expect(onChange).toHaveBeenNthCalledWith(3, new Date('12/15/2023 12:00'));
261+
expect(onChange).toHaveBeenCalledTimes(3);
222262
});
223263
});
224264
});

packages/design-system/src/components/DateTimePicker/DateTimePicker.tsx

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { CalendarDateTime, parseAbsoluteToLocal, toCalendarDateTime, getLocalTim
55
import { useComposedRefs } from '../../hooks/useComposeRefs';
66
import { useControllableState } from '../../hooks/useControllableState';
77
import { useDateFormatter } from '../../hooks/useDateFormatter';
8+
import { useIsomorphicLayoutEffect } from '../../hooks/useIsomorphicLayoutEffect';
89
import { Flex } from '../../primitives/Flex';
910
import { useDesignSystem } from '../../utilities/DesignSystemProvider';
1011
import { DatePicker, DatePickerProps, DatePickerElement } from '../DatePicker/DatePicker';
@@ -52,9 +53,13 @@ export const DateTimePicker = React.forwardRef<DatePickerElement, DateTimePicker
5253
) => {
5354
const DatePickerElement = React.useRef<HTMLInputElement>(null!);
5455

56+
const [isClearing, setIsClearing] = React.useState(false);
57+
58+
const externalCalendarDateValue = value ? convertUTCDateToCalendarDateTime(value, false) : undefined;
59+
5560
const [dateValue, setDateValue] = useControllableState<CalendarDateTime | undefined>({
5661
defaultProp: initialDate ? convertUTCDateToCalendarDateTime(initialDate, false) : undefined,
57-
prop: value ? convertUTCDateToCalendarDateTime(value, false) : value ?? undefined,
62+
prop: isClearing ? undefined : externalCalendarDateValue,
5863
onChange(date) {
5964
if (onChange) {
6065
onChange(date?.toDate(getLocalTimeZone()));
@@ -72,20 +77,42 @@ export const DateTimePicker = React.forwardRef<DatePickerElement, DateTimePicker
7277

7378
const timeValue = dateValue ? timeFormatter.format(dateValue.toDate(getLocalTimeZone())) : '';
7479

80+
const previousPropValueRef = React.useRef<DateTimePickerProps['value']>(value);
81+
82+
useIsomorphicLayoutEffect(() => {
83+
if (isClearing && dateValue) {
84+
setDateValue(undefined);
85+
}
86+
}, [isClearing, dateValue, setDateValue]);
87+
88+
useIsomorphicLayoutEffect(() => {
89+
const previousValue = previousPropValueRef.current;
90+
const hasClearedExternally = value == null && previousValue != null;
91+
92+
if (hasClearedExternally && dateValue) {
93+
setDateValue(undefined);
94+
}
95+
96+
if (isClearing && value !== previousValue) {
97+
setIsClearing(false);
98+
}
99+
100+
previousPropValueRef.current = value;
101+
}, [value, dateValue, isClearing, setDateValue, setIsClearing]);
102+
103+
useIsomorphicLayoutEffect(() => {
104+
if (isClearing && dateValue === undefined && value == null) {
105+
setIsClearing(false);
106+
}
107+
}, [isClearing, dateValue, value, setIsClearing]);
108+
75109
// React.useEffect(() => {
76110
// setTimeTextValue((s) => (s === timeValue ? s : timeValue));
77111
// }, [timeValue]);
78112

79113
const handleDateChange = (date: Date | undefined) => {
80114
let newDate = date ? convertUTCDateToCalendarDateTime(date) : undefined;
81115

82-
/**
83-
* If the date hasn't changed, don't do anything.
84-
*/
85-
if (newDate && dateValue && newDate.compare(dateValue) === 0) {
86-
return;
87-
}
88-
89116
if (timeValue && newDate) {
90117
const [hours, minutes] = timeValue.split(':');
91118
newDate = newDate.set({ hour: parseInt(hours, 10), minute: parseInt(minutes, 10) });
@@ -111,8 +138,8 @@ export const DateTimePicker = React.forwardRef<DatePickerElement, DateTimePicker
111138
};
112139

113140
const handleDateClear: DatePickerProps['onClear'] = (e) => {
114-
setDateValue(undefined);
115-
// setTimeTextValue('');
141+
e.preventDefault();
142+
setIsClearing(true);
116143

117144
if (onClear) {
118145
onClear(e);

0 commit comments

Comments
 (0)