@indico-data/design-system 2.41.0 → 2.42.0
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/iconNames.ts +3 -0
- 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/icons/constants.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/scripts/generateIconTypes.js +26 -0
- package/src/assets/indicons/api-doc.svg +3 -0
- package/src/assets/indicons/application-squares.svg +3 -0
- package/src/assets/indicons/arrow-dots.svg +3 -0
- package/src/assets/indicons/arrow-down.svg +9 -0
- package/src/assets/indicons/arrow-up-circle.svg +3 -0
- package/src/assets/indicons/arrows-cursor.svg +3 -0
- package/src/assets/indicons/bookmark-saved.svg +3 -0
- package/src/assets/indicons/check-circle.svg +3 -0
- package/src/assets/indicons/chevron-down.svg +3 -0
- package/src/assets/indicons/chevron-left.svg +3 -0
- package/src/assets/indicons/chevron-right.svg +3 -0
- package/src/assets/indicons/chevron-up.svg +3 -0
- package/src/assets/indicons/choose-library.svg +11 -0
- package/src/assets/indicons/circle-help.svg +3 -0
- package/src/assets/indicons/classification-document.svg +3 -0
- package/src/assets/indicons/classification-image.svg +3 -0
- package/src/assets/indicons/classification-page.svg +3 -0
- package/src/assets/indicons/classify-and-unbundle.svg +3 -0
- package/src/assets/indicons/coffee-1.svg +3 -0
- package/src/assets/indicons/coffee-2.svg +3 -0
- package/src/assets/indicons/coffee-fail.svg +3 -0
- package/src/assets/indicons/company-name.svg +3 -0
- package/src/assets/indicons/confidence-bar-1.svg +3 -0
- package/src/assets/indicons/confidence-bar-2.svg +3 -0
- package/src/assets/indicons/confidence-bar-3.svg +3 -0
- package/src/assets/indicons/confidence-bar-4.svg +3 -0
- package/src/assets/indicons/confidence-bars.svg +3 -0
- package/src/assets/indicons/data-and-access.svg +3 -0
- package/src/assets/indicons/elmos-fire.svg +3 -0
- package/src/assets/indicons/exclamation-circle-stroke.svg +13 -0
- package/src/assets/indicons/filter-outline.svg +7 -0
- package/src/assets/indicons/find-documents.svg +10 -0
- package/src/assets/indicons/gen-ai.svg +3 -0
- package/src/assets/indicons/grid-view.svg +3 -0
- package/src/assets/indicons/help-solid.svg +3 -0
- package/src/assets/indicons/highlight-outline.svg +10 -0
- package/src/assets/indicons/indico-logo-white.svg +3 -0
- package/src/assets/indicons/indico-o-white.svg +3 -0
- package/src/assets/indicons/indico-o.svg +3 -0
- package/src/assets/indicons/layout-complex.svg +3 -0
- package/src/assets/indicons/layout-simple.svg +3 -0
- package/src/assets/indicons/libraries-app.svg +15 -0
- package/src/assets/indicons/list-view.svg +3 -0
- package/src/assets/indicons/model-import.svg +3 -0
- package/src/assets/indicons/model-starter.svg +3 -0
- package/src/assets/indicons/no-collections.svg +10 -0
- package/src/assets/indicons/no-format.svg +3 -0
- package/src/assets/indicons/no-libraries.svg +11 -0
- package/src/assets/indicons/object-detection.svg +11 -0
- package/src/assets/indicons/page-thumbnail.svg +3 -0
- package/src/assets/indicons/pay-as-you-go.svg +3 -0
- package/src/assets/indicons/preview-view.svg +3 -0
- package/src/assets/indicons/radio-button.svg +5 -0
- package/src/assets/indicons/search-insights.svg +3 -0
- package/src/assets/indicons/search-thin.svg +3 -0
- package/src/assets/indicons/sort-down.svg +3 -0
- package/src/assets/indicons/square-plus.svg +3 -0
- package/src/assets/indicons/step-forward.svg +3 -0
- package/src/assets/indicons/surround-outline.svg +12 -0
- package/src/assets/indicons/thumbs-down.svg +3 -0
- package/src/assets/indicons/thumbs-up.svg +3 -0
- package/src/assets/indicons/warning-stroke.svg +17 -0
- package/src/assets/indicons/x-close.svg +3 -0
- package/src/assets/indicons/zoom-in.svg +10 -0
- package/src/assets/indicons/zoom-out.svg +9 -0
- 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/icons/constants.ts +47 -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,55 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { DatePicker } from '../DatePicker';
|
|
3
|
+
|
|
4
|
+
describe('DatePicker', () => {
|
|
5
|
+
describe('Single Date Picker', () => {
|
|
6
|
+
it('Renders the default mode for date picker (single)', () => {
|
|
7
|
+
render(<DatePicker data-testid="date-picker" />);
|
|
8
|
+
expect(screen.getByTestId('date-picker')).toHaveAttribute('data-mode', 'single');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('renders the date picker with a default of 2 months if no months are provided', () => {
|
|
12
|
+
render(<DatePicker data-testid="date-picker" />);
|
|
13
|
+
expect(document.getElementsByClassName('rdp-month')).toHaveLength(1);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('renders the date picker with a custom number of months', () => {
|
|
17
|
+
render(<DatePicker numberOfMonths={3} data-testid="date-picker" />);
|
|
18
|
+
expect(document.getElementsByClassName('rdp-month')).toHaveLength(3);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Range Date Picker', () => {
|
|
23
|
+
it('Renders the range mode for date picker', () => {
|
|
24
|
+
render(<DatePicker mode="range" data-testid="date-picker" />);
|
|
25
|
+
expect(screen.getByTestId('date-picker')).toHaveAttribute('data-mode', 'range');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders the date range picker with a default of 2 months if no months are provided', () => {
|
|
29
|
+
render(<DatePicker mode="range" data-testid="date-picker" />);
|
|
30
|
+
expect(document.getElementsByClassName('rdp-month')).toHaveLength(2);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('renders the date range picker with a custom number of months', () => {
|
|
34
|
+
render(<DatePicker mode="range" numberOfMonths={3} data-testid="date-picker" />);
|
|
35
|
+
expect(document.getElementsByClassName('rdp-month')).toHaveLength(3);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('Multiple Date Picker', () => {
|
|
40
|
+
it('Renders the multiple mode for date picker', () => {
|
|
41
|
+
render(<DatePicker mode="multiple" data-testid="date-picker" />);
|
|
42
|
+
expect(screen.getByTestId('date-picker')).toHaveAttribute('data-mode', 'multiple');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders the date picker with a default of 2 months if no months are provided', () => {
|
|
46
|
+
render(<DatePicker mode="multiple" data-testid="date-picker" />);
|
|
47
|
+
expect(document.getElementsByClassName('rdp-month')).toHaveLength(1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('renders the date picker with a custom number of months', () => {
|
|
51
|
+
render(<DatePicker mode="multiple" numberOfMonths={3} data-testid="date-picker" />);
|
|
52
|
+
expect(document.getElementsByClassName('rdp-month')).toHaveLength(3);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -4,7 +4,7 @@ export const getCommonProps = (props: CommonProps) => ({
|
|
|
4
4
|
className: props.className,
|
|
5
5
|
id: props.id,
|
|
6
6
|
month: props.month,
|
|
7
|
-
captionLayout: props.captionLayout,
|
|
7
|
+
captionLayout: props.captionLayout ?? 'dropdown',
|
|
8
8
|
defaultMonth: props.defaultMonth,
|
|
9
9
|
startMonth: props.startMonth,
|
|
10
10
|
endMonth: props.endMonth,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DatePicker } from './DatePicker';
|
|
@@ -5,7 +5,7 @@ import { Row, Col } from '../../../grid/index';
|
|
|
5
5
|
<Meta title="Forms/DatePicker/IconTriggerDatePicker" name="IconTriggerDatePicker" />
|
|
6
6
|
|
|
7
7
|
# IconTriggerDatePicker
|
|
8
|
-
The
|
|
8
|
+
The Icon Trigger Date Picker is to be used when we need to open the datepicker with a simple icon and dont care about the user typing in a value. It leverages our DatePicker component inside of a popper window.
|
|
9
9
|
<Canvas of={IconTriggerDatePicker.Default} />
|
|
10
10
|
|
|
11
11
|
## Props
|
|
@@ -54,16 +54,6 @@ const meta: Meta<typeof IconTriggerDatePicker> = {
|
|
|
54
54
|
},
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
|
-
month: {
|
|
58
|
-
control: 'date',
|
|
59
|
-
description: 'The month to display.',
|
|
60
|
-
table: {
|
|
61
|
-
category: 'Props',
|
|
62
|
-
type: {
|
|
63
|
-
summary: 'Date',
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
57
|
triggerIcon: {
|
|
68
58
|
control: 'text',
|
|
69
59
|
description: 'The icon to use as a trigger.',
|
|
@@ -12,14 +12,14 @@ interface Props {
|
|
|
12
12
|
isDisabled?: boolean;
|
|
13
13
|
id: string;
|
|
14
14
|
label?: string;
|
|
15
|
-
onSelect?: (selected: Date | DateRange |
|
|
16
|
-
|
|
17
|
-
selected?: Date | DateRange | Date[] | undefined;
|
|
15
|
+
onSelect?: (selected: Date | DateRange | undefined) => void;
|
|
16
|
+
selected?: Date | DateRange | undefined;
|
|
18
17
|
triggerIcon: IconName;
|
|
19
18
|
triggerIconSize: IconSizes;
|
|
20
19
|
isOpen?: boolean;
|
|
21
20
|
clearOnClose?: boolean;
|
|
22
21
|
className?: string;
|
|
22
|
+
initialMonth?: Date;
|
|
23
23
|
'data-testid'?: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -30,7 +30,6 @@ export const IconTriggerDatePicker = (props: Props) => {
|
|
|
30
30
|
isDisabled,
|
|
31
31
|
disableBeforeDate,
|
|
32
32
|
disableAfterDate,
|
|
33
|
-
month,
|
|
34
33
|
id,
|
|
35
34
|
label,
|
|
36
35
|
onSelect,
|
|
@@ -40,27 +39,24 @@ export const IconTriggerDatePicker = (props: Props) => {
|
|
|
40
39
|
mode,
|
|
41
40
|
isOpen,
|
|
42
41
|
clearOnClose,
|
|
42
|
+
initialMonth,
|
|
43
43
|
...rest
|
|
44
44
|
} = props;
|
|
45
45
|
|
|
46
|
-
const [
|
|
47
|
-
selected || undefined,
|
|
48
|
-
);
|
|
46
|
+
const [localMonth, setLocalMonth] = useState<Date>(initialMonth ?? new Date());
|
|
49
47
|
|
|
50
48
|
const handleSelect: OnSelectHandler<Date> = (date) => {
|
|
51
49
|
if (!date) {
|
|
52
50
|
onSelect && onSelect(undefined);
|
|
53
|
-
setLocalSelected(undefined);
|
|
54
51
|
} else {
|
|
55
52
|
onSelect && onSelect(date);
|
|
56
|
-
setLocalSelected(date);
|
|
57
53
|
}
|
|
58
54
|
};
|
|
59
55
|
|
|
60
56
|
// clear selection if clear on close is true
|
|
61
57
|
useEffect(() => {
|
|
62
58
|
if (!isOpen && clearOnClose) {
|
|
63
|
-
|
|
59
|
+
onSelect && onSelect(undefined);
|
|
64
60
|
}
|
|
65
61
|
}, [isOpen, clearOnClose]);
|
|
66
62
|
|
|
@@ -77,11 +73,12 @@ export const IconTriggerDatePicker = (props: Props) => {
|
|
|
77
73
|
<DatePicker
|
|
78
74
|
isDisabled={isDisabled}
|
|
79
75
|
mode={mode}
|
|
80
|
-
|
|
81
|
-
selected={localSelected as Date | DateRange | undefined}
|
|
76
|
+
selected={selected}
|
|
82
77
|
onSelect={handleSelect}
|
|
78
|
+
month={localMonth}
|
|
83
79
|
startMonth={disableBeforeDate}
|
|
84
80
|
endMonth={disableAfterDate}
|
|
81
|
+
onMonthChange={setLocalMonth}
|
|
85
82
|
{...rest}
|
|
86
83
|
/>
|
|
87
84
|
</FloatUI>
|
package/src/components/forms/date/iconTriggerDatePicker/__tests__/IconTriggerDatePicker.test.tsx
CHANGED
|
@@ -1,127 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
// const day28 = screen.getByText('28');
|
|
50
|
-
// await userEvent.click(day28);
|
|
51
|
-
// expect(mockOnChange).toHaveBeenCalledWith(
|
|
52
|
-
// new Date(today.getFullYear(), today.getMonth(), 28),
|
|
53
|
-
// );
|
|
54
|
-
// });
|
|
55
|
-
|
|
56
|
-
// it('closes date picker on single selection', async () => {
|
|
57
|
-
// await userEvent.click(trigger);
|
|
58
|
-
// const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
|
|
59
|
-
// expect(datePickerDialogContainer).toBeVisible();
|
|
60
|
-
|
|
61
|
-
// const day28 = screen.getByText('28');
|
|
62
|
-
// await userEvent.click(day28);
|
|
63
|
-
// expect(mockOnChange).toHaveBeenCalledWith(
|
|
64
|
-
// new Date(today.getFullYear(), today.getMonth(), 28),
|
|
65
|
-
// );
|
|
66
|
-
// expect(datePickerDialogContainer).not.toBeVisible();
|
|
67
|
-
// });
|
|
68
|
-
// });
|
|
69
|
-
|
|
70
|
-
// describe('range', () => {
|
|
71
|
-
// beforeEach(() => {
|
|
72
|
-
// render(
|
|
73
|
-
// <NoInputDatePicker
|
|
74
|
-
// id={'date-picker'}
|
|
75
|
-
// label={'Pick a date'}
|
|
76
|
-
// value={today}
|
|
77
|
-
// isRangePicker={true}
|
|
78
|
-
// triggerIcon="calendar"
|
|
79
|
-
// triggerIconSize={[20]}
|
|
80
|
-
// onChange={(date: Date | DateRange | undefined) => {
|
|
81
|
-
// mockOnChange(date);
|
|
82
|
-
// }}
|
|
83
|
-
// />,
|
|
84
|
-
// );
|
|
85
|
-
|
|
86
|
-
// trigger = screen.getByTestId('datepicker-trigger-for-date-picker');
|
|
87
|
-
// });
|
|
88
|
-
|
|
89
|
-
// it('shows ranged date picker', async () => {
|
|
90
|
-
// await userEvent.click(trigger);
|
|
91
|
-
// expect(screen.getByTestId('datepicker-dialog')).toBeVisible();
|
|
92
|
-
// expect(screen.getByTestId('range-datepicker')).toBeVisible();
|
|
93
|
-
// });
|
|
94
|
-
|
|
95
|
-
// it('emits the right date picked for range', async () => {
|
|
96
|
-
// await userEvent.click(trigger);
|
|
97
|
-
// const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
|
|
98
|
-
// expect(datePickerDialogContainer).toBeVisible();
|
|
99
|
-
|
|
100
|
-
// const day27 = screen.getByText('27');
|
|
101
|
-
// await userEvent.click(day27);
|
|
102
|
-
// const day28 = screen.getByText('28');
|
|
103
|
-
// await userEvent.click(day28);
|
|
104
|
-
|
|
105
|
-
// expect(mockOnChange).toHaveBeenCalledWith({
|
|
106
|
-
// from: new Date(today.getFullYear(), today.getMonth(), 27),
|
|
107
|
-
// to: new Date(today.getFullYear(), today.getMonth(), 28),
|
|
108
|
-
// });
|
|
109
|
-
// });
|
|
110
|
-
// it('closes date picker on both ranged selection', async () => {
|
|
111
|
-
// await userEvent.click(trigger);
|
|
112
|
-
// const datePickerDialogContainer = screen.getByTestId('datepicker-dialog');
|
|
113
|
-
// expect(datePickerDialogContainer).toBeVisible();
|
|
114
|
-
|
|
115
|
-
// const day27 = screen.getByText('27');
|
|
116
|
-
// await userEvent.click(day27);
|
|
117
|
-
// const day28 = screen.getByText('28');
|
|
118
|
-
// await userEvent.click(day28);
|
|
119
|
-
|
|
120
|
-
// expect(mockOnChange).toHaveBeenCalledWith({
|
|
121
|
-
// from: new Date(today.getFullYear(), today.getMonth(), 27),
|
|
122
|
-
// to: new Date(today.getFullYear(), today.getMonth(), 28),
|
|
123
|
-
// });
|
|
124
|
-
// expect(datePickerDialogContainer).not.toBeVisible();
|
|
125
|
-
// });
|
|
126
|
-
// });
|
|
127
|
-
// });
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { IconTriggerDatePicker } from '../IconTriggerDatePicker';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
|
|
5
|
+
describe('DatePicker', () => {
|
|
6
|
+
it('The date picker opens when the icon is clicked', async () => {
|
|
7
|
+
render(
|
|
8
|
+
<IconTriggerDatePicker
|
|
9
|
+
ariaLabel={''}
|
|
10
|
+
id={'date-picker'}
|
|
11
|
+
triggerIcon={'checkbox'}
|
|
12
|
+
triggerIconSize={'xs'}
|
|
13
|
+
data-testid={'date-picker-testid'}
|
|
14
|
+
/>,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const icon = screen.getByTestId('datepicker-trigger-for-date-picker');
|
|
18
|
+
expect(icon).toBeInTheDocument();
|
|
19
|
+
await userEvent.click(icon);
|
|
20
|
+
expect(screen.getByTestId('date-picker-testid')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('handles onSelect with the correct value', async () => {
|
|
24
|
+
const onSelect = jest.fn();
|
|
25
|
+
render(
|
|
26
|
+
<IconTriggerDatePicker
|
|
27
|
+
ariaLabel={''}
|
|
28
|
+
id={'date-picker'}
|
|
29
|
+
triggerIcon={'checkbox'}
|
|
30
|
+
triggerIconSize={'xs'}
|
|
31
|
+
data-testid={'date-picker-testid'}
|
|
32
|
+
onSelect={onSelect}
|
|
33
|
+
/>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const icon = screen.getByTestId('datepicker-trigger-for-date-picker');
|
|
37
|
+
expect(icon).toBeInTheDocument();
|
|
38
|
+
await userEvent.click(icon);
|
|
39
|
+
expect(screen.getByTestId('date-picker-testid')).toBeInTheDocument();
|
|
40
|
+
expect(onSelect).toHaveBeenCalledTimes(0);
|
|
41
|
+
const day = screen.getAllByRole('button', { name: /day/i })[0];
|
|
42
|
+
expect(day).toBeInTheDocument();
|
|
43
|
+
await userEvent.click(day);
|
|
44
|
+
// no need to be more specific as it will cause issues with pipelines having different time zones. This was verified manually at the time of writing
|
|
45
|
+
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { SingleInputDatePicker } from './SingleInputDatePicker';
|
|
2
|
+
import { SingleInputDatePicker, SingleInputDatePickerProps } from './SingleInputDatePicker';
|
|
3
3
|
import { useArgs } from '@storybook/preview-api';
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof SingleInputDatePicker> = {
|
|
@@ -23,6 +23,18 @@ const meta: Meta<typeof SingleInputDatePicker> = {
|
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
},
|
|
26
|
+
captionLayout: {
|
|
27
|
+
control: 'select',
|
|
28
|
+
options: ['dropdown', 'dropdown-months', 'dropdown-years', 'label'],
|
|
29
|
+
description:
|
|
30
|
+
'The layout of the caption. Enables you to add or remove the dropdown navigation for months/years',
|
|
31
|
+
table: {
|
|
32
|
+
category: 'Props',
|
|
33
|
+
type: {
|
|
34
|
+
summary: 'dropdown | dropdown-months | dropdown-years | label',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
26
38
|
className: {
|
|
27
39
|
control: false,
|
|
28
40
|
description: 'Accepts a CSS class name',
|
|
@@ -163,7 +175,7 @@ const meta: Meta<typeof SingleInputDatePicker> = {
|
|
|
163
175
|
},
|
|
164
176
|
},
|
|
165
177
|
},
|
|
166
|
-
|
|
178
|
+
initialMonth: {
|
|
167
179
|
control: 'date',
|
|
168
180
|
description: 'The month to display.',
|
|
169
181
|
table: {
|
|
@@ -196,9 +208,11 @@ export const SingleInput: Story = {
|
|
|
196
208
|
clearOnClose: false,
|
|
197
209
|
errorMessage: '',
|
|
198
210
|
ariaLabel: 'Date Picker',
|
|
211
|
+
selected: new Date(),
|
|
199
212
|
},
|
|
200
213
|
render: (args) => {
|
|
201
|
-
const [{ selected }, updateArgs] = useArgs();
|
|
214
|
+
const [{ selected }, updateArgs] = useArgs<SingleInputDatePickerProps>();
|
|
215
|
+
|
|
202
216
|
const handleSelect = (date: Date | undefined) => {
|
|
203
217
|
updateArgs({ selected: date });
|
|
204
218
|
};
|
|
@@ -1,21 +1,22 @@
|
|
|
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
|
-
import { DateRange } from 'react-day-picker';
|
|
7
6
|
import { FloatUI } from '../../../floatUI';
|
|
7
|
+
import { formatDateAsString } from './helpers';
|
|
8
8
|
|
|
9
|
-
interface SingleInputDatePickerProps {
|
|
9
|
+
export interface SingleInputDatePickerProps {
|
|
10
10
|
ariaLabel: string;
|
|
11
11
|
disableBeforeDate?: Date;
|
|
12
12
|
disableAfterDate?: Date;
|
|
13
13
|
isDisabled?: boolean;
|
|
14
|
+
captionLayout?: 'dropdown' | 'dropdown-months' | 'dropdown-years' | 'label';
|
|
14
15
|
id?: string;
|
|
15
16
|
label?: string;
|
|
16
|
-
onSelect
|
|
17
|
-
|
|
18
|
-
selected?: Date
|
|
17
|
+
onSelect: (selected: Date | undefined) => void;
|
|
18
|
+
initialMonth?: Date;
|
|
19
|
+
selected?: Date;
|
|
19
20
|
isOpen?: boolean;
|
|
20
21
|
clearOnClose?: boolean;
|
|
21
22
|
className?: string;
|
|
@@ -33,10 +34,10 @@ export function SingleInputDatePicker(props: SingleInputDatePickerProps) {
|
|
|
33
34
|
isDisabled,
|
|
34
35
|
disableBeforeDate,
|
|
35
36
|
disableAfterDate,
|
|
36
|
-
|
|
37
|
+
captionLayout,
|
|
38
|
+
initialMonth,
|
|
37
39
|
id,
|
|
38
40
|
label,
|
|
39
|
-
onSelect,
|
|
40
41
|
selected,
|
|
41
42
|
isOpen,
|
|
42
43
|
inputPlaceholder,
|
|
@@ -44,49 +45,48 @@ export function SingleInputDatePicker(props: SingleInputDatePickerProps) {
|
|
|
44
45
|
inputIconName,
|
|
45
46
|
isClearable,
|
|
46
47
|
errorMessage,
|
|
48
|
+
onSelect,
|
|
47
49
|
...rest
|
|
48
50
|
} = props;
|
|
49
51
|
|
|
50
52
|
const inputId = useId();
|
|
51
53
|
|
|
52
|
-
//
|
|
53
|
-
const [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
|
|
54
|
+
// The text value is assumed to be unneeded by the consumer.
|
|
55
|
+
const [localTextValue, setLocalTextValue] = useState<string>(
|
|
56
|
+
selected ? formatDateAsString(selected) : '',
|
|
57
|
+
);
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
const [inputValue, setInputValue] = useState('');
|
|
59
|
+
const [localMonth, setLocalMonth] = useState<Date>(initialMonth ?? selected ?? new Date());
|
|
60
60
|
|
|
61
|
+
// When the day picker is selected, update the text value.
|
|
61
62
|
const handleDayPickerSelect = (date: Date | undefined) => {
|
|
62
63
|
if (!date) {
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
setLocalTextValue('');
|
|
65
|
+
onSelect(undefined);
|
|
65
66
|
} else {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
setInputValue(format(date, 'MM/dd/yyyy'));
|
|
67
|
+
setLocalTextValue(formatDateAsString(date));
|
|
68
|
+
onSelect(date);
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
// When the text input is changed, update the selected date.
|
|
72
73
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
73
|
-
|
|
74
|
+
setLocalTextValue(e.target.value); // keep the input value in sync
|
|
74
75
|
|
|
75
76
|
const parsedDate = parse(e.target.value, 'MM/dd/yyyy', new Date());
|
|
76
77
|
|
|
77
78
|
if (isValid(parsedDate)) {
|
|
78
|
-
|
|
79
|
-
setLocalMonth(parsedDate);
|
|
79
|
+
onSelect(parsedDate);
|
|
80
80
|
} else {
|
|
81
|
-
|
|
81
|
+
onSelect(undefined);
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
// clear selection if clear on close is true
|
|
86
86
|
useEffect(() => {
|
|
87
87
|
if (!isOpen && clearOnClose) {
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
onSelect(undefined);
|
|
89
|
+
setLocalTextValue('');
|
|
90
90
|
}
|
|
91
91
|
}, [isOpen, clearOnClose]);
|
|
92
92
|
|
|
@@ -94,7 +94,7 @@ export function SingleInputDatePicker(props: SingleInputDatePickerProps) {
|
|
|
94
94
|
<FloatUI isOpen={isOpen} ariaLabel={ariaLabel}>
|
|
95
95
|
<Input
|
|
96
96
|
id={inputId}
|
|
97
|
-
value={
|
|
97
|
+
value={localTextValue}
|
|
98
98
|
placeholder={inputPlaceholder}
|
|
99
99
|
isDisabled={isDisabled}
|
|
100
100
|
iconName={inputIconName}
|
|
@@ -105,11 +105,12 @@ export function SingleInputDatePicker(props: SingleInputDatePickerProps) {
|
|
|
105
105
|
name={'Date Picker'}
|
|
106
106
|
/>
|
|
107
107
|
<DatePicker
|
|
108
|
-
|
|
109
|
-
onMonthChange={setLocalMonth}
|
|
108
|
+
captionLayout={captionLayout}
|
|
110
109
|
mode="single"
|
|
111
|
-
selected={
|
|
110
|
+
selected={selected}
|
|
112
111
|
onSelect={handleDayPickerSelect}
|
|
112
|
+
month={localMonth}
|
|
113
|
+
onMonthChange={setLocalMonth}
|
|
113
114
|
{...rest}
|
|
114
115
|
/>
|
|
115
116
|
</FloatUI>
|
|
@@ -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.
|