@indico-data/design-system 2.41.0 → 2.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/generated/iconTypes.ts +5 -0
- package/lib/components/forms/date/datePicker/DatePicker.d.ts +1 -2
- package/lib/components/forms/date/datePicker/__tests__/DatePicker.test.d.ts +1 -0
- package/lib/components/forms/date/datePicker/contants.d.ts +1 -1
- package/lib/components/forms/date/datePicker/index.d.ts +1 -0
- package/lib/components/forms/date/iconTriggerDatePicker/IconTriggerDatePicker.d.ts +3 -3
- package/lib/components/forms/date/iconTriggerDatePicker/__tests__/IconTriggerDatePicker.test.d.ts +1 -0
- package/lib/components/forms/date/inputDatePicker/SingleInputDatePicker.d.ts +5 -6
- package/lib/components/forms/date/inputDatePicker/__tests__/SingleInputDatePicker.test.d.ts +1 -0
- package/lib/components/forms/date/inputDatePicker/helpers.d.ts +1 -0
- package/lib/components/forms/date/inputDateRangePicker/InputDateRangePicker.d.ts +5 -6
- package/lib/components/forms/date/inputDateRangePicker/InputDateRangePicker.stories.d.ts +1 -1
- package/lib/components/forms/date/inputDateRangePicker/__tests__/InputDateRangePicker.test.d.ts +1 -0
- package/lib/components/index.d.ts +1 -0
- package/lib/index.d.ts +8 -7
- package/lib/index.esm.js +48 -47
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +48 -47
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/floatUI/{FloatUI.test.tsx → __tests__/FloatUI.test.tsx} +3 -3
- package/src/components/forms/date/datePicker/DatePicker.tsx +2 -2
- package/src/components/forms/date/datePicker/__tests__/DatePicker.test.tsx +55 -0
- package/src/components/forms/date/datePicker/contants.ts +1 -1
- package/src/components/forms/date/datePicker/index.ts +1 -0
- package/src/components/forms/date/iconTriggerDatePicker/IconTriggerDatePicker.mdx +1 -1
- package/src/components/forms/date/iconTriggerDatePicker/IconTriggerDatePicker.stories.tsx +0 -10
- package/src/components/forms/date/iconTriggerDatePicker/IconTriggerDatePicker.tsx +9 -12
- package/src/components/forms/date/iconTriggerDatePicker/__tests__/IconTriggerDatePicker.test.tsx +47 -127
- package/src/components/forms/date/inputDatePicker/SingleInputDatePicker.stories.tsx +17 -3
- package/src/components/forms/date/inputDatePicker/SingleInputDatePicker.tsx +31 -30
- package/src/components/forms/date/inputDatePicker/__tests__/SingleInputDatePicker.test.tsx +118 -0
- package/src/components/forms/date/inputDatePicker/helpers.ts +3 -0
- package/src/components/forms/date/inputDateRangePicker/InputDateRangePicker.mdx +9 -3
- package/src/components/forms/date/inputDateRangePicker/InputDateRangePicker.stories.tsx +41 -44
- package/src/components/forms/date/inputDateRangePicker/InputDateRangePicker.tsx +83 -49
- package/src/components/forms/date/inputDateRangePicker/__tests__/InputDateRangePicker.test.tsx +96 -0
- package/src/components/index.ts +1 -0
- /package/lib/components/floatUI/{FloatUI.test.d.ts → __tests__/FloatUI.test.d.ts} +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor, within } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { SingleInputDatePicker } from '../SingleInputDatePicker';
|
|
5
|
+
import { format, addDays } from 'date-fns';
|
|
6
|
+
|
|
7
|
+
const mockOnSelect = jest.fn();
|
|
8
|
+
|
|
9
|
+
describe('SingleInputDatePicker', () => {
|
|
10
|
+
const RealDate = Date;
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
global.Date = class extends RealDate {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('2024-01-01T00:00:00Z');
|
|
16
|
+
}
|
|
17
|
+
} as any;
|
|
18
|
+
Object.assign(global.Date, RealDate);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
global.Date = RealDate;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('opens the date picker and selects a date', async () => {
|
|
30
|
+
render(
|
|
31
|
+
<SingleInputDatePicker
|
|
32
|
+
ariaLabel="Single Input Date Picker"
|
|
33
|
+
data-testid="datepicker-testid"
|
|
34
|
+
captionLayout="label"
|
|
35
|
+
onSelect={mockOnSelect}
|
|
36
|
+
/>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const formInput = await screen.findByTestId('form-input-Date Picker');
|
|
40
|
+
expect(formInput).toBeInTheDocument();
|
|
41
|
+
|
|
42
|
+
await userEvent.click(formInput);
|
|
43
|
+
|
|
44
|
+
const datepicker = await screen.findByTestId('datepicker-testid');
|
|
45
|
+
expect(datepicker).toBeVisible();
|
|
46
|
+
|
|
47
|
+
const dayButtons = await within(datepicker).findAllByRole('button', { name: /\d+/ });
|
|
48
|
+
expect(dayButtons.length).toBeGreaterThan(0);
|
|
49
|
+
|
|
50
|
+
const lastDayButton = dayButtons[dayButtons.length - 1];
|
|
51
|
+
await userEvent.click(lastDayButton);
|
|
52
|
+
|
|
53
|
+
const expectedDate = new Date('2024-01-01T00:00:00Z');
|
|
54
|
+
const alternateDate = addDays(expectedDate, -1);
|
|
55
|
+
|
|
56
|
+
await waitFor(
|
|
57
|
+
() => {
|
|
58
|
+
const inputValue = (formInput as HTMLInputElement).value;
|
|
59
|
+
const formattedExpectedDate = format(expectedDate, 'MM/dd/yyyy');
|
|
60
|
+
const formattedAlternateDate = format(alternateDate, 'MM/dd/yyyy');
|
|
61
|
+
|
|
62
|
+
expect(inputValue === formattedExpectedDate || inputValue === formattedAlternateDate).toBe(
|
|
63
|
+
true,
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
{ timeout: 2000 },
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('updates the selected value in the dropdown to match user input', async () => {
|
|
71
|
+
render(
|
|
72
|
+
<SingleInputDatePicker
|
|
73
|
+
ariaLabel="Single Input Date Picker"
|
|
74
|
+
captionLayout="label"
|
|
75
|
+
data-testid="datepicker-testid"
|
|
76
|
+
onSelect={mockOnSelect}
|
|
77
|
+
/>,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const formInput = await screen.findByTestId('form-input-Date Picker');
|
|
81
|
+
expect(formInput).toBeInTheDocument();
|
|
82
|
+
|
|
83
|
+
await userEvent.click(formInput);
|
|
84
|
+
await userEvent.clear(formInput);
|
|
85
|
+
await userEvent.type(formInput, '12/31/2023');
|
|
86
|
+
|
|
87
|
+
await waitFor(() => {
|
|
88
|
+
expect(formInput).toHaveValue('12/31/2023');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
await userEvent.click(formInput);
|
|
92
|
+
|
|
93
|
+
const datepicker = await screen.findByTestId('datepicker-testid');
|
|
94
|
+
expect(datepicker).toBeVisible();
|
|
95
|
+
|
|
96
|
+
const dayButtons = await within(datepicker).findAllByRole('button', { name: /\d+/ });
|
|
97
|
+
expect(dayButtons.length).toBeGreaterThan(0);
|
|
98
|
+
|
|
99
|
+
const lastDayButton = dayButtons[dayButtons.length - 1];
|
|
100
|
+
await userEvent.click(lastDayButton);
|
|
101
|
+
|
|
102
|
+
const expectedDate = new Date('2024-01-01T00:00:00Z');
|
|
103
|
+
const alternateDate = addDays(expectedDate, -1);
|
|
104
|
+
|
|
105
|
+
await waitFor(
|
|
106
|
+
() => {
|
|
107
|
+
const inputValue = (formInput as HTMLInputElement).value;
|
|
108
|
+
const formattedExpectedDate = format(expectedDate, 'MM/dd/yyyy');
|
|
109
|
+
const formattedAlternateDate = format(alternateDate, 'MM/dd/yyyy');
|
|
110
|
+
// date hack to make sure pipeline timezone does not break test.
|
|
111
|
+
expect(inputValue === formattedExpectedDate || inputValue === formattedAlternateDate).toBe(
|
|
112
|
+
true,
|
|
113
|
+
);
|
|
114
|
+
},
|
|
115
|
+
{ timeout: 2000 },
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -7,12 +7,18 @@ import { Row, Col } from '../../../grid/index';
|
|
|
7
7
|
# Input Date Range Picker
|
|
8
8
|
The Input Date Range Picker is a visual representation of an input field that allows you to enter a date value to the input or select a date from the date picker. It leverages our DatePicker component inside of a FloatingUI window.
|
|
9
9
|
|
|
10
|
-
<Canvas of={InputDateRangePicker.
|
|
10
|
+
<Canvas of={InputDateRangePicker.RangeInput} />
|
|
11
11
|
|
|
12
12
|
## Props
|
|
13
13
|
In addition to the props listed below, it also accepts all props listed [here](story=?path=/docs/forms-datepicker--datepicker)
|
|
14
|
-
<Controls of={InputDateRangePicker.
|
|
14
|
+
<Controls of={InputDateRangePicker.RangeInput} />
|
|
15
15
|
|
|
16
|
+
# Notes
|
|
17
|
+
It is expected to show the same date in both input fields if only one date has been selected. This represents a start and end date on the same date. A usecase for this behavior would be a separate time on that same date. Expect the `to` input to update once a range has been selected (or the `from` input has been updated) to reflect the selected range.
|
|
16
18
|
|
|
17
19
|
## ToDo
|
|
18
|
-
-
|
|
20
|
+
- Clear on closed property
|
|
21
|
+
- Clear all selections (we need to think this one through, my initial thought is this clear would be handled by the parent component, not the date picker itself.)
|
|
22
|
+
- Better error handling and react-hook-form support
|
|
23
|
+
- Decide whether we want one icon prop or two.
|
|
24
|
+
- Decide if we want individual labels for each input or a single label for the range.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
2
|
import { InputDateRangePicker } from './InputDateRangePicker';
|
|
3
3
|
import { useArgs } from '@storybook/preview-api';
|
|
4
|
+
import { DateRange } from 'react-day-picker';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
4
6
|
|
|
5
7
|
const meta: Meta<typeof InputDateRangePicker> = {
|
|
6
8
|
component: InputDateRangePicker,
|
|
@@ -23,19 +25,21 @@ const meta: Meta<typeof InputDateRangePicker> = {
|
|
|
23
25
|
},
|
|
24
26
|
},
|
|
25
27
|
},
|
|
26
|
-
|
|
27
|
-
control:
|
|
28
|
-
|
|
28
|
+
captionLayout: {
|
|
29
|
+
control: 'select',
|
|
30
|
+
options: ['dropdown', 'dropdown-months', 'dropdown-years', 'label'],
|
|
31
|
+
description:
|
|
32
|
+
'The layout of the caption. Enables you to add or remove the dropdown navigation for months/years',
|
|
29
33
|
table: {
|
|
30
34
|
category: 'Props',
|
|
31
35
|
type: {
|
|
32
|
-
summary: '
|
|
36
|
+
summary: 'dropdown | dropdown-months | dropdown-years | label',
|
|
33
37
|
},
|
|
34
38
|
},
|
|
35
39
|
},
|
|
36
|
-
|
|
37
|
-
control:
|
|
38
|
-
description: '
|
|
40
|
+
className: {
|
|
41
|
+
control: false,
|
|
42
|
+
description: 'Accepts a CSS class name',
|
|
39
43
|
table: {
|
|
40
44
|
category: 'Props',
|
|
41
45
|
type: {
|
|
@@ -43,9 +47,10 @@ const meta: Meta<typeof InputDateRangePicker> = {
|
|
|
43
47
|
},
|
|
44
48
|
},
|
|
45
49
|
},
|
|
46
|
-
|
|
50
|
+
id: {
|
|
47
51
|
control: 'text',
|
|
48
|
-
description:
|
|
52
|
+
description:
|
|
53
|
+
'The id of the input fieldss. Each one has the id appended to it, For example an id of `holiday` would be `holiday-to` and `holiday-from`',
|
|
49
54
|
table: {
|
|
50
55
|
category: 'Props',
|
|
51
56
|
type: {
|
|
@@ -64,28 +69,18 @@ const meta: Meta<typeof InputDateRangePicker> = {
|
|
|
64
69
|
},
|
|
65
70
|
},
|
|
66
71
|
selected: {
|
|
67
|
-
control:
|
|
68
|
-
description: 'The selected date.',
|
|
72
|
+
control: false,
|
|
73
|
+
description: 'The selected date range.',
|
|
69
74
|
table: {
|
|
70
75
|
category: 'Props',
|
|
71
76
|
type: {
|
|
72
|
-
summary: '
|
|
77
|
+
summary: 'DateRange',
|
|
73
78
|
},
|
|
74
79
|
},
|
|
75
80
|
},
|
|
76
81
|
inputPlaceholder: {
|
|
77
82
|
control: 'text',
|
|
78
|
-
description: 'The placeholder text for the input
|
|
79
|
-
table: {
|
|
80
|
-
category: 'Props',
|
|
81
|
-
type: {
|
|
82
|
-
summary: 'string',
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
inputIconName: {
|
|
87
|
-
control: 'text',
|
|
88
|
-
description: 'The icon to display in the input field.',
|
|
83
|
+
description: 'The placeholder text for the input fields.',
|
|
89
84
|
table: {
|
|
90
85
|
category: 'Props',
|
|
91
86
|
type: {
|
|
@@ -93,9 +88,10 @@ const meta: Meta<typeof InputDateRangePicker> = {
|
|
|
93
88
|
},
|
|
94
89
|
},
|
|
95
90
|
},
|
|
96
|
-
|
|
97
|
-
control:
|
|
98
|
-
description:
|
|
91
|
+
isOpen: {
|
|
92
|
+
control: false,
|
|
93
|
+
description:
|
|
94
|
+
'Whether the floatingUI component wrapped around the date picker is opened or closed.',
|
|
99
95
|
table: {
|
|
100
96
|
category: 'Props',
|
|
101
97
|
type: {
|
|
@@ -103,29 +99,29 @@ const meta: Meta<typeof InputDateRangePicker> = {
|
|
|
103
99
|
},
|
|
104
100
|
},
|
|
105
101
|
},
|
|
106
|
-
|
|
107
|
-
control:
|
|
108
|
-
description: '
|
|
102
|
+
inputIconName: {
|
|
103
|
+
control: 'text',
|
|
104
|
+
description: 'The icon to display in the input fields.',
|
|
109
105
|
table: {
|
|
110
106
|
category: 'Props',
|
|
111
107
|
type: {
|
|
112
|
-
summary: '
|
|
108
|
+
summary: 'string',
|
|
113
109
|
},
|
|
114
110
|
},
|
|
115
111
|
},
|
|
116
|
-
|
|
117
|
-
control: '
|
|
118
|
-
description: '
|
|
112
|
+
toErrorMessage: {
|
|
113
|
+
control: 'text',
|
|
114
|
+
description: 'An error message to display on the `to` input.',
|
|
119
115
|
table: {
|
|
120
116
|
category: 'Props',
|
|
121
117
|
type: {
|
|
122
|
-
summary: '
|
|
118
|
+
summary: 'string',
|
|
123
119
|
},
|
|
124
120
|
},
|
|
125
121
|
},
|
|
126
|
-
|
|
122
|
+
fromErrorMessage: {
|
|
127
123
|
control: 'text',
|
|
128
|
-
description: 'An error message to display.',
|
|
124
|
+
description: 'An error message to display on the `from` input.',
|
|
129
125
|
table: {
|
|
130
126
|
category: 'Props',
|
|
131
127
|
type: {
|
|
@@ -155,7 +151,7 @@ const meta: Meta<typeof InputDateRangePicker> = {
|
|
|
155
151
|
},
|
|
156
152
|
isDisabled: {
|
|
157
153
|
control: 'boolean',
|
|
158
|
-
description: 'Disable the date picker.',
|
|
154
|
+
description: 'Disable the date picker and input fields.',
|
|
159
155
|
table: {
|
|
160
156
|
category: 'Props',
|
|
161
157
|
type: {
|
|
@@ -165,7 +161,7 @@ const meta: Meta<typeof InputDateRangePicker> = {
|
|
|
165
161
|
},
|
|
166
162
|
month: {
|
|
167
163
|
control: 'date',
|
|
168
|
-
description: 'The
|
|
164
|
+
description: 'The currently displayed month.',
|
|
169
165
|
table: {
|
|
170
166
|
category: 'Props',
|
|
171
167
|
type: {
|
|
@@ -185,21 +181,22 @@ export default meta;
|
|
|
185
181
|
|
|
186
182
|
type Story = StoryObj<typeof InputDateRangePicker>;
|
|
187
183
|
|
|
188
|
-
export const
|
|
184
|
+
export const RangeInput: Story = {
|
|
189
185
|
args: {
|
|
190
186
|
id: 'date-picker',
|
|
191
|
-
label: 'Pick a date:',
|
|
192
187
|
inputPlaceholder: 'MM/DD/YYYY',
|
|
193
188
|
inputIconName: 'calendar',
|
|
194
|
-
isClearable: true,
|
|
195
189
|
isDisabled: false,
|
|
196
|
-
|
|
197
|
-
|
|
190
|
+
toErrorMessage: '',
|
|
191
|
+
fromErrorMessage: '',
|
|
198
192
|
ariaLabel: 'Date Picker',
|
|
193
|
+
isOpen: false,
|
|
194
|
+
selected: { from: new Date(), to: new Date(new Date().getTime() + 5 * 24 * 60 * 60 * 1000) },
|
|
199
195
|
},
|
|
200
196
|
render: (args) => {
|
|
201
197
|
const [{ selected }, updateArgs] = useArgs();
|
|
202
|
-
|
|
198
|
+
|
|
199
|
+
const handleSelect = (date: DateRange | undefined) => {
|
|
203
200
|
updateArgs({ selected: date });
|
|
204
201
|
};
|
|
205
202
|
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { useId, useState, useEffect } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { isValid, parse } from 'date-fns';
|
|
3
3
|
import { DatePicker } from '../datePicker/DatePicker';
|
|
4
4
|
import { Input } from '../../input';
|
|
5
5
|
import { IconName } from '@/types';
|
|
6
6
|
import { DateRange } from 'react-day-picker';
|
|
7
7
|
import { FloatUI } from '../../../floatUI';
|
|
8
|
+
import { Col, Row } from '../../../grid';
|
|
9
|
+
import { formatDateAsString } from '../inputDatePicker/helpers';
|
|
8
10
|
|
|
9
11
|
interface InputDateRangePickerProps {
|
|
10
12
|
ariaLabel: string;
|
|
11
13
|
disableBeforeDate?: Date;
|
|
12
14
|
disableAfterDate?: Date;
|
|
15
|
+
captionLayout?: 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'label';
|
|
13
16
|
isDisabled?: boolean;
|
|
14
17
|
id?: string;
|
|
15
|
-
|
|
16
|
-
onSelect?: (selected: Date | undefined) => void;
|
|
18
|
+
onSelect: (selected: DateRange | undefined) => void;
|
|
17
19
|
month?: Date;
|
|
18
|
-
selected?:
|
|
20
|
+
selected?: DateRange | undefined;
|
|
19
21
|
isOpen?: boolean;
|
|
20
|
-
clearOnClose?: boolean;
|
|
21
22
|
className?: string;
|
|
22
23
|
inputIconName?: IconName;
|
|
23
|
-
isClearable?: boolean;
|
|
24
24
|
inputPlaceholder?: string;
|
|
25
|
-
|
|
25
|
+
toErrorMessage?: string | undefined;
|
|
26
|
+
fromErrorMessage?: string | undefined;
|
|
26
27
|
'data-testid'?: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -33,82 +34,115 @@ export function InputDateRangePicker(props: InputDateRangePickerProps) {
|
|
|
33
34
|
isDisabled,
|
|
34
35
|
disableBeforeDate,
|
|
35
36
|
disableAfterDate,
|
|
37
|
+
captionLayout,
|
|
36
38
|
month,
|
|
37
39
|
id,
|
|
38
|
-
label,
|
|
39
40
|
onSelect,
|
|
40
41
|
selected,
|
|
41
42
|
isOpen,
|
|
42
43
|
inputPlaceholder,
|
|
43
|
-
clearOnClose,
|
|
44
44
|
inputIconName,
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
toErrorMessage,
|
|
46
|
+
fromErrorMessage,
|
|
47
47
|
...rest
|
|
48
48
|
} = props;
|
|
49
49
|
|
|
50
50
|
const inputId = useId();
|
|
51
51
|
|
|
52
|
-
// Hold the
|
|
53
|
-
const [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const [
|
|
52
|
+
// Hold the input values in state
|
|
53
|
+
const [localTextValueFrom, setLocalTextValueFrom] = useState<string>(
|
|
54
|
+
selected?.from ? formatDateAsString(selected.from) : '',
|
|
55
|
+
);
|
|
56
|
+
const [localTextValueTo, setLocalTextValueTo] = useState<string>(
|
|
57
|
+
selected?.to ? formatDateAsString(selected.to) : '',
|
|
58
|
+
);
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
const [inputValue, setInputValue] = useState('');
|
|
60
|
+
const [localMonth, setLocalMonth] = useState<Date>(selected?.from || new Date());
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
// When the day picker is selected, update the text values.
|
|
63
|
+
const handleDayPickerSelect = (date: DateRange | undefined) => {
|
|
62
64
|
if (!date) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
setLocalTextValueFrom('');
|
|
66
|
+
setLocalTextValueTo('');
|
|
67
|
+
onSelect(undefined);
|
|
65
68
|
} else {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
setLocalTextValueFrom(date.from ? formatDateAsString(date.from) : '');
|
|
70
|
+
setLocalTextValueTo(date.to ? formatDateAsString(date.to) : '');
|
|
71
|
+
onSelect(date);
|
|
69
72
|
}
|
|
70
73
|
};
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
// When the text input is changed, update the selected date.
|
|
76
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, type: 'from' | 'to') => {
|
|
77
|
+
const value = e.target.value;
|
|
78
|
+
if (type === 'from') {
|
|
79
|
+
setLocalTextValueFrom(value);
|
|
80
|
+
} else {
|
|
81
|
+
setLocalTextValueTo(value);
|
|
82
|
+
}
|
|
74
83
|
|
|
75
|
-
const parsedDate = parse(
|
|
84
|
+
const parsedDate = parse(value, 'MM/dd/yyyy', new Date());
|
|
76
85
|
|
|
77
86
|
if (isValid(parsedDate)) {
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
onSelect({
|
|
88
|
+
from: type === 'from' ? parsedDate : selected?.from,
|
|
89
|
+
to: type === 'to' ? parsedDate : selected?.to,
|
|
90
|
+
});
|
|
80
91
|
} else {
|
|
81
|
-
|
|
92
|
+
onSelect({
|
|
93
|
+
from: type === 'from' ? undefined : selected?.from,
|
|
94
|
+
to: type === 'to' ? undefined : selected?.to,
|
|
95
|
+
});
|
|
82
96
|
}
|
|
83
97
|
};
|
|
84
98
|
|
|
85
99
|
// clear selection if clear on close is true
|
|
86
100
|
useEffect(() => {
|
|
87
|
-
if (!isOpen
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
if (!isOpen) {
|
|
102
|
+
onSelect(undefined);
|
|
103
|
+
setLocalTextValueFrom('');
|
|
104
|
+
setLocalTextValueTo('');
|
|
90
105
|
}
|
|
91
|
-
}, [isOpen
|
|
106
|
+
}, [isOpen]);
|
|
92
107
|
|
|
93
108
|
return (
|
|
94
109
|
<FloatUI isOpen={isOpen} ariaLabel={ariaLabel}>
|
|
95
|
-
<
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
<Row>
|
|
111
|
+
<Col>
|
|
112
|
+
<Input
|
|
113
|
+
id={`${inputId}-from`}
|
|
114
|
+
value={localTextValueFrom}
|
|
115
|
+
placeholder={inputPlaceholder}
|
|
116
|
+
isDisabled={isDisabled}
|
|
117
|
+
iconName={inputIconName}
|
|
118
|
+
onChange={(e) => handleInputChange(e, 'from')}
|
|
119
|
+
errorMessage={fromErrorMessage}
|
|
120
|
+
label={'From Date'}
|
|
121
|
+
name={'From Date'}
|
|
122
|
+
data-testid="date-picker-from"
|
|
123
|
+
/>
|
|
124
|
+
</Col>
|
|
125
|
+
<Col>
|
|
126
|
+
<Input
|
|
127
|
+
id={`${inputId}-to`}
|
|
128
|
+
value={localTextValueTo}
|
|
129
|
+
placeholder={inputPlaceholder}
|
|
130
|
+
isDisabled={isDisabled}
|
|
131
|
+
iconName={inputIconName}
|
|
132
|
+
onChange={(e) => handleInputChange(e, 'to')}
|
|
133
|
+
errorMessage={toErrorMessage}
|
|
134
|
+
label={'To Date'}
|
|
135
|
+
name={'To Date'}
|
|
136
|
+
data-testid="date-picker-to"
|
|
137
|
+
/>
|
|
138
|
+
</Col>
|
|
139
|
+
</Row>
|
|
107
140
|
<DatePicker
|
|
141
|
+
captionLayout={captionLayout}
|
|
108
142
|
month={localMonth}
|
|
109
|
-
onMonthChange={setLocalMonth}
|
|
110
|
-
mode="
|
|
111
|
-
selected={
|
|
143
|
+
onMonthChange={(date) => setLocalMonth(date)}
|
|
144
|
+
mode="range"
|
|
145
|
+
selected={selected}
|
|
112
146
|
onSelect={handleDayPickerSelect}
|
|
113
147
|
{...rest}
|
|
114
148
|
/>
|
package/src/components/forms/date/inputDateRangePicker/__tests__/InputDateRangePicker.test.tsx
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor, within } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { InputDateRangePicker } from '../InputDateRangePicker';
|
|
5
|
+
import { format } from 'date-fns';
|
|
6
|
+
|
|
7
|
+
const mockOnSelect = jest.fn();
|
|
8
|
+
|
|
9
|
+
describe('InputDateRangePicker', () => {
|
|
10
|
+
const RealDate = Date;
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
global.Date = class extends RealDate {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('2024-01-01T00:00:00Z');
|
|
16
|
+
}
|
|
17
|
+
} as any;
|
|
18
|
+
Object.assign(global.Date, RealDate);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterAll(() => {
|
|
22
|
+
global.Date = RealDate;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('opens the date picker and selects a date', async () => {
|
|
30
|
+
render(
|
|
31
|
+
<InputDateRangePicker
|
|
32
|
+
ariaLabel="Input Date Range Picker"
|
|
33
|
+
data-testid="datepicker-testid"
|
|
34
|
+
captionLayout="label"
|
|
35
|
+
onSelect={mockOnSelect}
|
|
36
|
+
/>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const formInput = await screen.findByTestId('date-picker-from');
|
|
40
|
+
expect(formInput).toBeInTheDocument();
|
|
41
|
+
|
|
42
|
+
await userEvent.click(formInput);
|
|
43
|
+
|
|
44
|
+
const datepicker = await screen.findByTestId('datepicker-testid');
|
|
45
|
+
expect(datepicker).toBeVisible();
|
|
46
|
+
|
|
47
|
+
const dayButtons = await within(datepicker).findAllByRole('button');
|
|
48
|
+
expect(dayButtons.length).toBeGreaterThan(0);
|
|
49
|
+
|
|
50
|
+
const lastDayButton = dayButtons[dayButtons.length - 1];
|
|
51
|
+
await userEvent.click(lastDayButton);
|
|
52
|
+
|
|
53
|
+
const initialDate = format(new Date('2024-01-01T00:00:00Z'), 'MM/dd/yyyy');
|
|
54
|
+
await waitFor(() => {
|
|
55
|
+
expect(formInput).toHaveValue(initialDate);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('updates the selected value in the dropdown to match user input', async () => {
|
|
60
|
+
render(
|
|
61
|
+
<InputDateRangePicker
|
|
62
|
+
ariaLabel="Input Date Range Picker"
|
|
63
|
+
captionLayout="label"
|
|
64
|
+
data-testid="datepicker-testid"
|
|
65
|
+
onSelect={mockOnSelect}
|
|
66
|
+
/>,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const formInput = await screen.findByTestId('date-picker-from');
|
|
70
|
+
expect(formInput).toBeInTheDocument();
|
|
71
|
+
|
|
72
|
+
await userEvent.click(formInput);
|
|
73
|
+
await userEvent.clear(formInput);
|
|
74
|
+
await userEvent.type(formInput, '12/31/2023');
|
|
75
|
+
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
expect(formInput).toHaveValue('12/31/2023');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await userEvent.click(formInput); // Ensure the date picker is open
|
|
81
|
+
|
|
82
|
+
const datepicker = await screen.findByTestId('datepicker-testid');
|
|
83
|
+
expect(datepicker).toBeVisible();
|
|
84
|
+
|
|
85
|
+
const dayButtons = await within(datepicker).findAllByRole('button');
|
|
86
|
+
expect(dayButtons.length).toBeGreaterThan(0);
|
|
87
|
+
|
|
88
|
+
const lastDayButton = dayButtons[dayButtons.length - 1];
|
|
89
|
+
await userEvent.click(lastDayButton);
|
|
90
|
+
|
|
91
|
+
const initialDate = format(new Date('2024-01-01T00:00:00Z'), 'MM/dd/yyyy');
|
|
92
|
+
await waitFor(() => {
|
|
93
|
+
expect(formInput).toHaveValue(initialDate);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/components/index.ts
CHANGED
|
@@ -17,3 +17,4 @@ export { Menu } from './menu';
|
|
|
17
17
|
export { DatePicker } from './forms/date/datePicker/DatePicker';
|
|
18
18
|
export { IconTriggerDatePicker } from './forms/date/iconTriggerDatePicker';
|
|
19
19
|
export { SingleInputDatePicker } from './forms/date/inputDatePicker';
|
|
20
|
+
export { InputDateRangePicker } from './forms/date/inputDateRangePicker';
|
|
File without changes
|