Skip to content

Commit

Permalink
fix(DateRangeInput): Treat array of nulls as empty value to set start…
Browse files Browse the repository at this point in the history
… date on click (#6215) (#6404)

- Правим пример в сторибуке, чтобы DateRangeInput реагировал на изменения дат.
- исправляем поведение при выборе даты, если value={[null, null]}. В этом случае onChange возвращал всегда [null, null], что бы мы не выбрали в DateRangeInput.
Временное решение без этого фикса это передавать `undefined`. Будет актуально для v5, если не выпустим там патч.
- Меняем тип value c Array на Tuple, так как value может содержать только два элемента.
  • Loading branch information
mendrew authored Jan 19, 2024
1 parent 43288a8 commit a481dd0
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useArgs } from '@storybook/preview-api';
import { Meta, StoryObj } from '@storybook/react';
import { CanvasFullLayout, DisableCartesianParam } from '../../storybook/constants';
import { CalendarRange, CalendarRangeProps } from './CalendarRange';
Expand Down Expand Up @@ -40,10 +41,26 @@ export default story;
type Story = StoryObj<StoryCalendarRangeProps>;

export const Playground: Story = {
render: ({ value, startDate, endDate, ...args }) => {
render: function Render() {
const [{ value, startDate, endDate, ...args }, updateArgs] = useArgs();

const handleDateRangeUpdate: CalendarRangeProps['onChange'] = (updatedValue) => {
const [changedStartDate, changedEndDate] = updatedValue || [null, null];
updateArgs({
startDate: changedStartDate ? new Date(changedStartDate) : null,
endDate: changedEndDate ? new Date(changedEndDate) : null,
});
};

const parsedStartDate = startDate ? new Date(startDate) : null;
const parsedEndDate = endDate ? new Date(endDate) : null;

return <CalendarRange value={[parsedStartDate, parsedEndDate]} {...args} />;
return (
<CalendarRange
{...args}
value={[parsedStartDate, parsedEndDate]}
onChange={handleDateRangeUpdate}
/>
);
},
};
15 changes: 15 additions & 0 deletions packages/vkui/src/components/CalendarRange/CalendarRange.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { baselineComponent } from '../../testing/utils';
import { CalendarRange } from './CalendarRange';

Expand All @@ -7,4 +9,17 @@ describe('CalendarRange', () => {
// Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."
a11y: false,
});

it('calls onChange when initial value is [null, null]', () => {
const onChangeStub = jest.fn();
render(
<CalendarRange data-testid="calendar-range" onChange={onChangeStub} value={[null, null]} />,
);

fireEvent.click(screen.getAllByText('6')[0]);
expect(onChangeStub).not.toHaveBeenLastCalledWith([null, null]);

fireEvent.click(screen.getAllByText('6')[1]);
expect(onChangeStub).not.toHaveBeenLastCalledWith([null, null]);
});
});
15 changes: 9 additions & 6 deletions packages/vkui/src/components/CalendarRange/CalendarRange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { CalendarHeader, CalendarHeaderProps } from '../CalendarHeader/CalendarH
import { RootComponent } from '../RootComponent/RootComponent';
import styles from './CalendarRange.module.css';

export type DateRangeType = [Date | null, Date | null];

export interface CalendarRangeProps
extends Omit<HTMLAttributesWithRootRef<HTMLDivElement>, 'onChange'>,
Pick<
Expand All @@ -29,18 +31,18 @@ export interface CalendarRangeProps
| 'nextMonthIcon'
>,
Pick<CalendarDaysProps, 'listenDayChangesForUpdate'> {
value?: Array<Date | null>;
value?: DateRangeType;
disablePast?: boolean;
disableFuture?: boolean;
disablePickers?: boolean;
changeDayAriaLabel?: string;
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
onChange?(value?: Array<Date | null>): void;
onChange?(value?: DateRangeType): void;
shouldDisableDate?(value: Date): boolean;
onClose?(): void;
}

const getIsDaySelected = (day: Date, value?: Array<Date | null>) => {
const getIsDaySelected = (day: Date, value?: DateRangeType) => {
if (!value?.[0] || !value[1]) {
return false;
}
Expand Down Expand Up @@ -81,7 +83,7 @@ export const CalendarRange = ({
isDayDisabled,
resetSelectedDay,
} = useCalendar({ value, disableFuture, disablePast, shouldDisableDate });
const [hintedDate, setHintedDate] = React.useState<Array<Date | null>>();
const [hintedDate, setHintedDate] = React.useState<DateRangeType>();
const secondViewDate = addMonths(viewDate, 1);

const handleKeyDown = React.useCallback(
Expand All @@ -105,8 +107,9 @@ export const CalendarRange = ({
);

const getNewValue = React.useCallback(
(date: Date) => {
if (!value) {
(date: Date): DateRangeType => {
const isValueEmpty = !value || (value[0] === null && value[1] === null);
if (isValueEmpty) {
return [date, null];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useArgs } from '@storybook/preview-api';
import { Meta, StoryObj } from '@storybook/react';
import { CanvasFullLayout, DisableCartesianParam } from '../../storybook/constants';
import { DateRangeInput, DateRangeInputProps } from './DateRangeInput';
Expand Down Expand Up @@ -40,10 +41,26 @@ export default story;
type Story = StoryObj<StoryDateRangeInputProps>;

export const Playground: Story = {
render: ({ value, startDate, endDate, ...args }) => {
render: function Render() {
const [{ value, startDate, endDate, ...args }, updateArgs] = useArgs();

const handleDateRangeUpdate: DateRangeInputProps['onChange'] = (updatedValue) => {
const [changedStartDate, changedEndDate] = updatedValue || [null, null];
updateArgs({
startDate: changedStartDate ? new Date(changedStartDate) : null,
endDate: changedEndDate ? new Date(changedEndDate) : null,
});
};

const parsedStartDate = startDate ? new Date(startDate) : null;
const parsedEndDate = endDate ? new Date(endDate) : null;

return <DateRangeInput value={[parsedStartDate, parsedEndDate]} {...args} />;
return (
<DateRangeInput
{...args}
value={[parsedStartDate, parsedEndDate]}
onChange={handleDateRangeUpdate}
/>
);
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { callMultiple } from '../../lib/callMultiple';
import { format, isAfter, isMatch, parse } from '../../lib/date';
import type { PlacementWithAuto } from '../../lib/floating';
import { HasRootRef } from '../../types';
import { CalendarRange, CalendarRangeProps } from '../CalendarRange/CalendarRange';
import { CalendarRange, CalendarRangeProps, DateRangeType } from '../CalendarRange/CalendarRange';
import { FormField, FormFieldProps } from '../FormField/FormField';
import { IconButton } from '../IconButton/IconButton';
import { InputLike } from '../InputLike/InputLike';
Expand Down Expand Up @@ -221,7 +221,7 @@ export const DateRangeInput = ({
const handleRootRef = useExternRef(rootRef, getRootRef);

const onCalendarChange = React.useCallback(
(newValue?: Array<Date | null> | undefined) => {
(newValue?: DateRangeType) => {
onChange?.(newValue);
if (closeOnChange && newValue?.[1] && newValue[1] !== value?.[1]) {
removeFocusFromField();
Expand Down

0 comments on commit a481dd0

Please sign in to comment.