@dhis2-ui/calendar 9.9.0-alpha.2 → 9.9.0-alpha.4
Sign up to get free protection for your applications and to get access to all the features.
- package/build/cjs/calendar/calendar-container.js +17 -5
- package/build/cjs/calendar/calendar-table-cell.js +5 -2
- package/build/cjs/calendar/calendar-table.js +5 -2
- package/build/cjs/calendar/navigation-container.js +8 -2
- package/build/cjs/calendar-input/__tests__/calendar-input.test.js +57 -0
- package/build/cjs/calendar-input/calendar-input.js +18 -3
- package/build/es/calendar/calendar-container.js +18 -5
- package/build/es/calendar/calendar-table-cell.js +5 -2
- package/build/es/calendar/calendar-table.js +5 -2
- package/build/es/calendar/navigation-container.js +8 -2
- package/build/es/calendar-input/__tests__/calendar-input.test.js +43 -0
- package/build/es/calendar-input/calendar-input.js +19 -4
- package/package.json +9 -9
@@ -23,6 +23,8 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
|
|
23
23
|
|
24
24
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
25
25
|
|
26
|
+
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
27
|
+
|
26
28
|
const wrapperBorderColor = _uiConstants.colors.grey300;
|
27
29
|
const backgroundColor = 'none';
|
28
30
|
|
@@ -39,7 +41,9 @@ const CalendarContainer = _ref => {
|
|
39
41
|
nextYear,
|
40
42
|
prevMonth,
|
41
43
|
prevYear,
|
42
|
-
languageDirection
|
44
|
+
languageDirection,
|
45
|
+
excludedRef,
|
46
|
+
unfocusable
|
43
47
|
} = _ref;
|
44
48
|
const navigationProps = (0, _react.useMemo)(() => {
|
45
49
|
return {
|
@@ -58,13 +62,19 @@ const CalendarContainer = _ref => {
|
|
58
62
|
dir: languageDirection,
|
59
63
|
"data-test": "calendar",
|
60
64
|
className: _style.default.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]]) + " " + "calendar-wrapper"
|
61
|
-
}, /*#__PURE__*/_react.default.createElement(
|
65
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
66
|
+
ref: excludedRef,
|
67
|
+
className: _style.default.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]])
|
68
|
+
}, /*#__PURE__*/_react.default.createElement(_navigationContainer.NavigationContainer, _extends({}, navigationProps, {
|
69
|
+
unfocusable: unfocusable
|
70
|
+
})), /*#__PURE__*/_react.default.createElement(_calendarTable.CalendarTable, {
|
62
71
|
selectedDate: date,
|
63
72
|
calendarWeekDays: calendarWeekDays,
|
64
73
|
weekDayLabels: weekDayLabels,
|
65
74
|
cellSize: cellSize,
|
66
|
-
width: width
|
67
|
-
|
75
|
+
width: width,
|
76
|
+
unfocusable: unfocusable
|
77
|
+
}))), /*#__PURE__*/_react.default.createElement(_style.default, {
|
68
78
|
id: "2823859047",
|
69
79
|
dynamic: [backgroundColor, wrapperBorderColor, width]
|
70
80
|
}, [".calendar-wrapper.__jsx-style-dynamic-selector{font-family:Roboto,sans-serif;font-weight:400;font-size:14px;background-color:".concat(backgroundColor, ";display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;border:1px solid ").concat(wrapperBorderColor, ";border-radius:3px;min-width:").concat(width, ";width:-webkit-max-content;width:-moz-max-content;width:max-content;box-shadow:0px 4px 6px -2px #2129340d;box-shadow:0px 10px 15px -3px #2129341a;}")]));
|
@@ -73,11 +83,13 @@ const CalendarContainer = _ref => {
|
|
73
83
|
exports.CalendarContainer = CalendarContainer;
|
74
84
|
CalendarContainer.defaultProps = {
|
75
85
|
cellSize: '32px',
|
76
|
-
width: '240px'
|
86
|
+
width: '240px',
|
87
|
+
unfocusable: false
|
77
88
|
};
|
78
89
|
CalendarContainer.propTypes = {
|
79
90
|
/** the currently selected date using an iso-like format YYYY-MM-DD, in the calendar system provided (not iso8601) */
|
80
91
|
date: _propTypes.default.string,
|
92
|
+
unfocusable: _propTypes.default.bool,
|
81
93
|
..._calendarTable.CalendarTableProps,
|
82
94
|
..._navigationContainer.NavigationContainerProps
|
83
95
|
};
|
@@ -21,7 +21,8 @@ const CalendarTableCell = _ref => {
|
|
21
21
|
let {
|
22
22
|
day,
|
23
23
|
cellSize,
|
24
|
-
selectedDate
|
24
|
+
selectedDate,
|
25
|
+
unfocusable
|
25
26
|
} = _ref;
|
26
27
|
const dayHoverBackgroundColor = _uiConstants.colors.grey200;
|
27
28
|
const selectedDayBackgroundColor = _uiConstants.colors.teal700;
|
@@ -31,6 +32,7 @@ const CalendarTableCell = _ref => {
|
|
31
32
|
className: _style.default.dynamic([["2052411850", [cellSize, cellSize, cellSize, cellSize, _uiConstants.colors.grey900, dayHoverBackgroundColor, _uiConstants.colors.grey300, selectedDayBackgroundColor, _uiConstants.colors.teal600, _uiConstants.colors.teal200, _uiConstants.colors.grey600]]])
|
32
33
|
}, /*#__PURE__*/_react.default.createElement("button", {
|
33
34
|
name: "day",
|
35
|
+
tabIndex: unfocusable ? -1 : 0,
|
34
36
|
className: _style.default.dynamic([["2052411850", [cellSize, cellSize, cellSize, cellSize, _uiConstants.colors.grey900, dayHoverBackgroundColor, _uiConstants.colors.grey300, selectedDayBackgroundColor, _uiConstants.colors.teal600, _uiConstants.colors.teal200, _uiConstants.colors.grey600]]]) + " " + ((0, _classnames.default)('day', {
|
35
37
|
isSelected: selectedDate === (day === null || day === void 0 ? void 0 : day.calendarDate),
|
36
38
|
isToday: day.isToday,
|
@@ -53,5 +55,6 @@ CalendarTableCell.propTypes = {
|
|
53
55
|
label: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
|
54
56
|
onClick: _propTypes.default.func
|
55
57
|
}),
|
56
|
-
selectedDate: _propTypes.default.string
|
58
|
+
selectedDate: _propTypes.default.string,
|
59
|
+
unfocusable: _propTypes.default.bool
|
57
60
|
};
|
@@ -25,7 +25,8 @@ const CalendarTable = _ref => {
|
|
25
25
|
calendarWeekDays,
|
26
26
|
width,
|
27
27
|
cellSize,
|
28
|
-
selectedDate
|
28
|
+
selectedDate,
|
29
|
+
unfocusable
|
29
30
|
} = _ref;
|
30
31
|
return /*#__PURE__*/_react.default.createElement("div", {
|
31
32
|
className: _style.default.dynamic([["452536960", [_uiConstants.spacers.dp4, _uiConstants.spacers.dp4]]]) + " " + "calendar-table-wrapper"
|
@@ -43,7 +44,8 @@ const CalendarTable = _ref => {
|
|
43
44
|
day: day,
|
44
45
|
key: day === null || day === void 0 ? void 0 : day.calendarDate,
|
45
46
|
cellSize: cellSize,
|
46
|
-
width: width
|
47
|
+
width: width,
|
48
|
+
unfocusable: unfocusable
|
47
49
|
})))))), /*#__PURE__*/_react.default.createElement(_style.default, {
|
48
50
|
id: "452536960",
|
49
51
|
dynamic: [_uiConstants.spacers.dp4, _uiConstants.spacers.dp4]
|
@@ -63,6 +65,7 @@ const CalendarTableProps = {
|
|
63
65
|
}).isRequired).isRequired).isRequired,
|
64
66
|
cellSize: _propTypes.default.string,
|
65
67
|
selectedDate: _propTypes.default.string,
|
68
|
+
unfocusable: _propTypes.default.bool,
|
66
69
|
weekDayLabels: _propTypes.default.arrayOf(_propTypes.default.string),
|
67
70
|
width: _propTypes.default.string
|
68
71
|
};
|
@@ -32,7 +32,8 @@ const NavigationContainer = _ref => {
|
|
32
32
|
nextMonth,
|
33
33
|
nextYear,
|
34
34
|
prevMonth,
|
35
|
-
prevYear
|
35
|
+
prevYear,
|
36
|
+
unfocusable
|
36
37
|
} = _ref;
|
37
38
|
const PreviousIcon = languageDirection === 'ltr' ? _uiIcons.IconChevronLeft16 : _uiIcons.IconChevronRight16;
|
38
39
|
const NextIcon = languageDirection === 'ltr' ? _uiIcons.IconChevronRight16 : _uiIcons.IconChevronLeft16; // Ethiopic years - when localised to English - add the era (i.e. 2015 ERA1), which is redundant in practice (like writing AD for gregorian years)
|
@@ -51,6 +52,7 @@ const NavigationContainer = _ref => {
|
|
51
52
|
"data-test": "calendar-previous-month",
|
52
53
|
"aria-label": "".concat(_index.default.t("Go to ".concat(prevMonth.label))),
|
53
54
|
type: "button",
|
55
|
+
tabIndex: unfocusable ? -1 : 0,
|
54
56
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
55
57
|
}, /*#__PURE__*/_react.default.createElement(PreviousIcon, {
|
56
58
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -66,6 +68,7 @@ const NavigationContainer = _ref => {
|
|
66
68
|
name: "next-month",
|
67
69
|
"aria-label": "".concat(_index.default.t("Go to ".concat(nextMonth.label))),
|
68
70
|
type: "button",
|
71
|
+
tabIndex: unfocusable ? -1 : 0,
|
69
72
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
70
73
|
}, /*#__PURE__*/_react.default.createElement(NextIcon, {
|
71
74
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -78,6 +81,7 @@ const NavigationContainer = _ref => {
|
|
78
81
|
name: "previous-year",
|
79
82
|
"aria-label": "".concat(_index.default.t('Go to previous year')),
|
80
83
|
type: "button",
|
84
|
+
tabIndex: unfocusable ? -1 : 0,
|
81
85
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
82
86
|
}, /*#__PURE__*/_react.default.createElement(PreviousIcon, {
|
83
87
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -93,6 +97,7 @@ const NavigationContainer = _ref => {
|
|
93
97
|
name: "next-year",
|
94
98
|
"aria-label": "".concat(_index.default.t('Go to next year')),
|
95
99
|
type: "button",
|
100
|
+
tabIndex: unfocusable ? -1 : 0,
|
96
101
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
97
102
|
}, /*#__PURE__*/_react.default.createElement(NextIcon, {
|
98
103
|
className: _style.default.dynamic([["3883083596", [_uiConstants.spacers.dp4, _uiConstants.colors.grey600, _uiConstants.colors.grey200, _uiConstants.colors.grey900, _uiConstants.colors.grey300, _uiConstants.spacers.dp8, _uiConstants.spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -126,7 +131,8 @@ const NavigationContainerProps = {
|
|
126
131
|
prevYear: _propTypes.default.shape({
|
127
132
|
label: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number]),
|
128
133
|
navigateTo: _propTypes.default.func
|
129
|
-
})
|
134
|
+
}),
|
135
|
+
unfocusable: _propTypes.default.bool
|
130
136
|
};
|
131
137
|
exports.NavigationContainerProps = NavigationContainerProps;
|
132
138
|
NavigationContainer.propTypes = NavigationContainerProps;
|
@@ -0,0 +1,57 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
var _react = require("@testing-library/react");
|
4
|
+
|
5
|
+
var _react2 = _interopRequireDefault(require("react"));
|
6
|
+
|
7
|
+
var _calendarInput = require("../calendar-input.js");
|
8
|
+
|
9
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
10
|
+
|
11
|
+
describe('Calendar Input', () => {
|
12
|
+
it('allow selection of a date through the calendar widget', async () => {
|
13
|
+
const onDateSelectMock = jest.fn();
|
14
|
+
const screen = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(_calendarInput.CalendarInput, {
|
15
|
+
calendar: "gregory",
|
16
|
+
onDateSelect: onDateSelectMock
|
17
|
+
}));
|
18
|
+
const dateInput = (0, _react.within)(screen.getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
19
|
+
|
20
|
+
_react.fireEvent.focus(dateInput);
|
21
|
+
|
22
|
+
const calendar = await screen.findByTestId('calendar');
|
23
|
+
expect(calendar).toBeInTheDocument();
|
24
|
+
const todayString = new Date().toISOString().slice(0, -14);
|
25
|
+
const today = (0, _react.within)(calendar).getByTestId(todayString);
|
26
|
+
|
27
|
+
_react.fireEvent.click(today);
|
28
|
+
|
29
|
+
await (0, _react.waitFor)(() => {
|
30
|
+
expect(calendar).not.toBeInTheDocument();
|
31
|
+
});
|
32
|
+
expect(onDateSelectMock).toHaveBeenCalledWith(expect.objectContaining({
|
33
|
+
calendarDateString: todayString
|
34
|
+
}));
|
35
|
+
});
|
36
|
+
it('allow selection of a date through the input', async () => {
|
37
|
+
const onDateSelectMock = jest.fn();
|
38
|
+
const screen = (0, _react.render)( /*#__PURE__*/_react2.default.createElement(_calendarInput.CalendarInput, {
|
39
|
+
calendar: "gregory",
|
40
|
+
onDateSelect: onDateSelectMock
|
41
|
+
}));
|
42
|
+
const dateInputString = '2024/10/12';
|
43
|
+
const dateInput = (0, _react.within)(screen.getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
44
|
+
|
45
|
+
_react.fireEvent.change(dateInput, {
|
46
|
+
target: {
|
47
|
+
value: dateInputString
|
48
|
+
}
|
49
|
+
});
|
50
|
+
|
51
|
+
_react.fireEvent.blur(dateInput);
|
52
|
+
|
53
|
+
expect(onDateSelectMock).toHaveBeenCalledWith(expect.objectContaining({
|
54
|
+
calendarDateString: dateInputString
|
55
|
+
}));
|
56
|
+
});
|
57
|
+
});
|
@@ -66,6 +66,9 @@ const CalendarInput = function () {
|
|
66
66
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
67
67
|
const ref = (0, _react.useRef)();
|
68
68
|
const [open, setOpen] = (0, _react.useState)(false);
|
69
|
+
const [partialDate, setPartialDate] = (0, _react.useState)(date);
|
70
|
+
const excludeRef = (0, _react.useRef)(null);
|
71
|
+
(0, _react.useEffect)(() => setPartialDate(date), [date]);
|
69
72
|
const useDatePickerOptions = (0, _react.useMemo)(() => ({
|
70
73
|
calendar,
|
71
74
|
locale,
|
@@ -88,9 +91,17 @@ const CalendarInput = function () {
|
|
88
91
|
});
|
89
92
|
|
90
93
|
const handleChange = e => {
|
94
|
+
setPartialDate(e.value);
|
95
|
+
};
|
96
|
+
|
97
|
+
const handleBlur = (_, e) => {
|
91
98
|
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
92
|
-
calendarDateString:
|
99
|
+
calendarDateString: partialDate
|
93
100
|
});
|
101
|
+
|
102
|
+
if (excludeRef.current && !excludeRef.current.contains(e.relatedTarget)) {
|
103
|
+
setOpen(false);
|
104
|
+
}
|
94
105
|
};
|
95
106
|
|
96
107
|
const onFocus = () => {
|
@@ -123,8 +134,9 @@ const CalendarInput = function () {
|
|
123
134
|
}, rest, {
|
124
135
|
type: "text",
|
125
136
|
onFocus: onFocus,
|
126
|
-
value:
|
137
|
+
value: partialDate,
|
127
138
|
onChange: handleChange,
|
139
|
+
onBlur: handleBlur,
|
128
140
|
validationText: pickerResults.errorMessage || pickerResults.warningMessage,
|
129
141
|
error: !!pickerResults.errorMessage,
|
130
142
|
warning: !!pickerResults.warningMessage
|
@@ -149,7 +161,10 @@ const CalendarInput = function () {
|
|
149
161
|
reference: ref,
|
150
162
|
placement: "bottom-start",
|
151
163
|
modifiers: [offsetModifier]
|
152
|
-
}, /*#__PURE__*/_react.default.createElement(_card.Card, null, /*#__PURE__*/_react.default.createElement(_calendarContainer.CalendarContainer,
|
164
|
+
}, /*#__PURE__*/_react.default.createElement(_card.Card, null, /*#__PURE__*/_react.default.createElement(_calendarContainer.CalendarContainer, _extends({}, calendarProps, {
|
165
|
+
excludedRef: excludeRef,
|
166
|
+
unfocusable: true
|
167
|
+
}))))), /*#__PURE__*/_react.default.createElement(_style.default, {
|
153
168
|
id: "633677374"
|
154
169
|
}, [".calendar-input-wrapper.jsx-633677374{position:relative;}", ".calendar-clear-button.jsx-633677374{position:absolute;inset-inline-end:6px;-webkit-inset-block-start:27px;-ms-intb-rlock-start:27px;inset-block-start:27px;}", ".calendar-clear-button.with-icon.jsx-633677374{inset-inline-end:36px;}", ".calendar-clear-button.with-dense-wrapper.jsx-633677374{-webkit-inset-block-start:23px;-ms-intb-rlock-start:23px;inset-block-start:23px;}"]));
|
155
170
|
};
|
@@ -1,4 +1,7 @@
|
|
1
1
|
import _JSXStyle from "styled-jsx/style";
|
2
|
+
|
3
|
+
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
4
|
+
|
2
5
|
import { colors } from '@dhis2/ui-constants';
|
3
6
|
import PropTypes from 'prop-types';
|
4
7
|
import React, { useMemo } from 'react';
|
@@ -19,7 +22,9 @@ export const CalendarContainer = _ref => {
|
|
19
22
|
nextYear,
|
20
23
|
prevMonth,
|
21
24
|
prevYear,
|
22
|
-
languageDirection
|
25
|
+
languageDirection,
|
26
|
+
excludedRef,
|
27
|
+
unfocusable
|
23
28
|
} = _ref;
|
24
29
|
const navigationProps = useMemo(() => {
|
25
30
|
return {
|
@@ -38,24 +43,32 @@ export const CalendarContainer = _ref => {
|
|
38
43
|
dir: languageDirection,
|
39
44
|
"data-test": "calendar",
|
40
45
|
className: _JSXStyle.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]]) + " " + "calendar-wrapper"
|
41
|
-
}, /*#__PURE__*/React.createElement(
|
46
|
+
}, /*#__PURE__*/React.createElement("div", {
|
47
|
+
ref: excludedRef,
|
48
|
+
className: _JSXStyle.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]])
|
49
|
+
}, /*#__PURE__*/React.createElement(NavigationContainer, _extends({}, navigationProps, {
|
50
|
+
unfocusable: unfocusable
|
51
|
+
})), /*#__PURE__*/React.createElement(CalendarTable, {
|
42
52
|
selectedDate: date,
|
43
53
|
calendarWeekDays: calendarWeekDays,
|
44
54
|
weekDayLabels: weekDayLabels,
|
45
55
|
cellSize: cellSize,
|
46
|
-
width: width
|
47
|
-
|
56
|
+
width: width,
|
57
|
+
unfocusable: unfocusable
|
58
|
+
}))), /*#__PURE__*/React.createElement(_JSXStyle, {
|
48
59
|
id: "2823859047",
|
49
60
|
dynamic: [backgroundColor, wrapperBorderColor, width]
|
50
61
|
}, [".calendar-wrapper.__jsx-style-dynamic-selector{font-family:Roboto,sans-serif;font-weight:400;font-size:14px;background-color:".concat(backgroundColor, ";display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;border:1px solid ").concat(wrapperBorderColor, ";border-radius:3px;min-width:").concat(width, ";width:-webkit-max-content;width:-moz-max-content;width:max-content;box-shadow:0px 4px 6px -2px #2129340d;box-shadow:0px 10px 15px -3px #2129341a;}")]));
|
51
62
|
};
|
52
63
|
CalendarContainer.defaultProps = {
|
53
64
|
cellSize: '32px',
|
54
|
-
width: '240px'
|
65
|
+
width: '240px',
|
66
|
+
unfocusable: false
|
55
67
|
};
|
56
68
|
CalendarContainer.propTypes = {
|
57
69
|
/** the currently selected date using an iso-like format YYYY-MM-DD, in the calendar system provided (not iso8601) */
|
58
70
|
date: PropTypes.string,
|
71
|
+
unfocusable: PropTypes.bool,
|
59
72
|
...CalendarTableProps,
|
60
73
|
...NavigationContainerProps
|
61
74
|
};
|
@@ -7,7 +7,8 @@ export const CalendarTableCell = _ref => {
|
|
7
7
|
let {
|
8
8
|
day,
|
9
9
|
cellSize,
|
10
|
-
selectedDate
|
10
|
+
selectedDate,
|
11
|
+
unfocusable
|
11
12
|
} = _ref;
|
12
13
|
const dayHoverBackgroundColor = colors.grey200;
|
13
14
|
const selectedDayBackgroundColor = colors.teal700;
|
@@ -17,6 +18,7 @@ export const CalendarTableCell = _ref => {
|
|
17
18
|
className: _JSXStyle.dynamic([["2052411850", [cellSize, cellSize, cellSize, cellSize, colors.grey900, dayHoverBackgroundColor, colors.grey300, selectedDayBackgroundColor, colors.teal600, colors.teal200, colors.grey600]]])
|
18
19
|
}, /*#__PURE__*/React.createElement("button", {
|
19
20
|
name: "day",
|
21
|
+
tabIndex: unfocusable ? -1 : 0,
|
20
22
|
className: _JSXStyle.dynamic([["2052411850", [cellSize, cellSize, cellSize, cellSize, colors.grey900, dayHoverBackgroundColor, colors.grey300, selectedDayBackgroundColor, colors.teal600, colors.teal200, colors.grey600]]]) + " " + (cx('day', {
|
21
23
|
isSelected: selectedDate === (day === null || day === void 0 ? void 0 : day.calendarDate),
|
22
24
|
isToday: day.isToday,
|
@@ -37,5 +39,6 @@ CalendarTableCell.propTypes = {
|
|
37
39
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
38
40
|
onClick: PropTypes.func
|
39
41
|
}),
|
40
|
-
selectedDate: PropTypes.string
|
42
|
+
selectedDate: PropTypes.string,
|
43
|
+
unfocusable: PropTypes.bool
|
41
44
|
};
|
@@ -10,7 +10,8 @@ export const CalendarTable = _ref => {
|
|
10
10
|
calendarWeekDays,
|
11
11
|
width,
|
12
12
|
cellSize,
|
13
|
-
selectedDate
|
13
|
+
selectedDate,
|
14
|
+
unfocusable
|
14
15
|
} = _ref;
|
15
16
|
return /*#__PURE__*/React.createElement("div", {
|
16
17
|
className: _JSXStyle.dynamic([["452536960", [spacers.dp4, spacers.dp4]]]) + " " + "calendar-table-wrapper"
|
@@ -28,7 +29,8 @@ export const CalendarTable = _ref => {
|
|
28
29
|
day: day,
|
29
30
|
key: day === null || day === void 0 ? void 0 : day.calendarDate,
|
30
31
|
cellSize: cellSize,
|
31
|
-
width: width
|
32
|
+
width: width,
|
33
|
+
unfocusable: unfocusable
|
32
34
|
})))))), /*#__PURE__*/React.createElement(_JSXStyle, {
|
33
35
|
id: "452536960",
|
34
36
|
dynamic: [spacers.dp4, spacers.dp4]
|
@@ -46,6 +48,7 @@ export const CalendarTableProps = {
|
|
46
48
|
}).isRequired).isRequired).isRequired,
|
47
49
|
cellSize: PropTypes.string,
|
48
50
|
selectedDate: PropTypes.string,
|
51
|
+
unfocusable: PropTypes.bool,
|
49
52
|
weekDayLabels: PropTypes.arrayOf(PropTypes.string),
|
50
53
|
width: PropTypes.string
|
51
54
|
};
|
@@ -16,7 +16,8 @@ export const NavigationContainer = _ref => {
|
|
16
16
|
nextMonth,
|
17
17
|
nextYear,
|
18
18
|
prevMonth,
|
19
|
-
prevYear
|
19
|
+
prevYear,
|
20
|
+
unfocusable
|
20
21
|
} = _ref;
|
21
22
|
const PreviousIcon = languageDirection === 'ltr' ? IconChevronLeft16 : IconChevronRight16;
|
22
23
|
const NextIcon = languageDirection === 'ltr' ? IconChevronRight16 : IconChevronLeft16; // Ethiopic years - when localised to English - add the era (i.e. 2015 ERA1), which is redundant in practice (like writing AD for gregorian years)
|
@@ -35,6 +36,7 @@ export const NavigationContainer = _ref => {
|
|
35
36
|
"data-test": "calendar-previous-month",
|
36
37
|
"aria-label": "".concat(i18n.t("Go to ".concat(prevMonth.label))),
|
37
38
|
type: "button",
|
39
|
+
tabIndex: unfocusable ? -1 : 0,
|
38
40
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
39
41
|
}, /*#__PURE__*/React.createElement(PreviousIcon, {
|
40
42
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -50,6 +52,7 @@ export const NavigationContainer = _ref => {
|
|
50
52
|
name: "next-month",
|
51
53
|
"aria-label": "".concat(i18n.t("Go to ".concat(nextMonth.label))),
|
52
54
|
type: "button",
|
55
|
+
tabIndex: unfocusable ? -1 : 0,
|
53
56
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
54
57
|
}, /*#__PURE__*/React.createElement(NextIcon, {
|
55
58
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -62,6 +65,7 @@ export const NavigationContainer = _ref => {
|
|
62
65
|
name: "previous-year",
|
63
66
|
"aria-label": "".concat(i18n.t('Go to previous year')),
|
64
67
|
type: "button",
|
68
|
+
tabIndex: unfocusable ? -1 : 0,
|
65
69
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
66
70
|
}, /*#__PURE__*/React.createElement(PreviousIcon, {
|
67
71
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -77,6 +81,7 @@ export const NavigationContainer = _ref => {
|
|
77
81
|
name: "next-year",
|
78
82
|
"aria-label": "".concat(i18n.t('Go to next year')),
|
79
83
|
type: "button",
|
84
|
+
tabIndex: unfocusable ? -1 : 0,
|
80
85
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
81
86
|
}, /*#__PURE__*/React.createElement(NextIcon, {
|
82
87
|
className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
|
@@ -108,6 +113,7 @@ export const NavigationContainerProps = {
|
|
108
113
|
prevYear: PropTypes.shape({
|
109
114
|
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
110
115
|
navigateTo: PropTypes.func
|
111
|
-
})
|
116
|
+
}),
|
117
|
+
unfocusable: PropTypes.bool
|
112
118
|
};
|
113
119
|
NavigationContainer.propTypes = NavigationContainerProps;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { fireEvent, render, waitFor, within } from '@testing-library/react';
|
2
|
+
import React from 'react';
|
3
|
+
import { CalendarInput } from '../calendar-input.js';
|
4
|
+
describe('Calendar Input', () => {
|
5
|
+
it('allow selection of a date through the calendar widget', async () => {
|
6
|
+
const onDateSelectMock = jest.fn();
|
7
|
+
const screen = render( /*#__PURE__*/React.createElement(CalendarInput, {
|
8
|
+
calendar: "gregory",
|
9
|
+
onDateSelect: onDateSelectMock
|
10
|
+
}));
|
11
|
+
const dateInput = within(screen.getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
12
|
+
fireEvent.focus(dateInput);
|
13
|
+
const calendar = await screen.findByTestId('calendar');
|
14
|
+
expect(calendar).toBeInTheDocument();
|
15
|
+
const todayString = new Date().toISOString().slice(0, -14);
|
16
|
+
const today = within(calendar).getByTestId(todayString);
|
17
|
+
fireEvent.click(today);
|
18
|
+
await waitFor(() => {
|
19
|
+
expect(calendar).not.toBeInTheDocument();
|
20
|
+
});
|
21
|
+
expect(onDateSelectMock).toHaveBeenCalledWith(expect.objectContaining({
|
22
|
+
calendarDateString: todayString
|
23
|
+
}));
|
24
|
+
});
|
25
|
+
it('allow selection of a date through the input', async () => {
|
26
|
+
const onDateSelectMock = jest.fn();
|
27
|
+
const screen = render( /*#__PURE__*/React.createElement(CalendarInput, {
|
28
|
+
calendar: "gregory",
|
29
|
+
onDateSelect: onDateSelectMock
|
30
|
+
}));
|
31
|
+
const dateInputString = '2024/10/12';
|
32
|
+
const dateInput = within(screen.getByTestId('dhis2-uicore-input')).getByRole('textbox');
|
33
|
+
fireEvent.change(dateInput, {
|
34
|
+
target: {
|
35
|
+
value: dateInputString
|
36
|
+
}
|
37
|
+
});
|
38
|
+
fireEvent.blur(dateInput);
|
39
|
+
expect(onDateSelectMock).toHaveBeenCalledWith(expect.objectContaining({
|
40
|
+
calendarDateString: dateInputString
|
41
|
+
}));
|
42
|
+
});
|
43
|
+
});
|
@@ -9,7 +9,7 @@ import { Layer } from '@dhis2-ui/layer';
|
|
9
9
|
import { Popper } from '@dhis2-ui/popper';
|
10
10
|
import { useDatePicker, useResolvedDirection } from '@dhis2/multi-calendar-dates';
|
11
11
|
import cx from 'classnames';
|
12
|
-
import React, { useRef, useState, useMemo } from 'react';
|
12
|
+
import React, { useRef, useState, useMemo, useEffect } from 'react';
|
13
13
|
import { CalendarContainer } from '../calendar/calendar-container.js';
|
14
14
|
import { CalendarProps } from '../calendar/calendar.js';
|
15
15
|
import i18n from '../locales/index.js';
|
@@ -41,6 +41,9 @@ export const CalendarInput = function () {
|
|
41
41
|
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
42
42
|
const ref = useRef();
|
43
43
|
const [open, setOpen] = useState(false);
|
44
|
+
const [partialDate, setPartialDate] = useState(date);
|
45
|
+
const excludeRef = useRef(null);
|
46
|
+
useEffect(() => setPartialDate(date), [date]);
|
44
47
|
const useDatePickerOptions = useMemo(() => ({
|
45
48
|
calendar,
|
46
49
|
locale,
|
@@ -63,9 +66,17 @@ export const CalendarInput = function () {
|
|
63
66
|
});
|
64
67
|
|
65
68
|
const handleChange = e => {
|
69
|
+
setPartialDate(e.value);
|
70
|
+
};
|
71
|
+
|
72
|
+
const handleBlur = (_, e) => {
|
66
73
|
parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
|
67
|
-
calendarDateString:
|
74
|
+
calendarDateString: partialDate
|
68
75
|
});
|
76
|
+
|
77
|
+
if (excludeRef.current && !excludeRef.current.contains(e.relatedTarget)) {
|
78
|
+
setOpen(false);
|
79
|
+
}
|
69
80
|
};
|
70
81
|
|
71
82
|
const onFocus = () => {
|
@@ -98,8 +109,9 @@ export const CalendarInput = function () {
|
|
98
109
|
}, rest, {
|
99
110
|
type: "text",
|
100
111
|
onFocus: onFocus,
|
101
|
-
value:
|
112
|
+
value: partialDate,
|
102
113
|
onChange: handleChange,
|
114
|
+
onBlur: handleBlur,
|
103
115
|
validationText: pickerResults.errorMessage || pickerResults.warningMessage,
|
104
116
|
error: !!pickerResults.errorMessage,
|
105
117
|
warning: !!pickerResults.warningMessage
|
@@ -124,7 +136,10 @@ export const CalendarInput = function () {
|
|
124
136
|
reference: ref,
|
125
137
|
placement: "bottom-start",
|
126
138
|
modifiers: [offsetModifier]
|
127
|
-
}, /*#__PURE__*/React.createElement(Card, null, /*#__PURE__*/React.createElement(CalendarContainer,
|
139
|
+
}, /*#__PURE__*/React.createElement(Card, null, /*#__PURE__*/React.createElement(CalendarContainer, _extends({}, calendarProps, {
|
140
|
+
excludedRef: excludeRef,
|
141
|
+
unfocusable: true
|
142
|
+
}))))), /*#__PURE__*/React.createElement(_JSXStyle, {
|
128
143
|
id: "633677374"
|
129
144
|
}, [".calendar-input-wrapper.jsx-633677374{position:relative;}", ".calendar-clear-button.jsx-633677374{position:absolute;inset-inline-end:6px;-webkit-inset-block-start:27px;-ms-intb-rlock-start:27px;inset-block-start:27px;}", ".calendar-clear-button.with-icon.jsx-633677374{inset-inline-end:36px;}", ".calendar-clear-button.with-dense-wrapper.jsx-633677374{-webkit-inset-block-start:23px;-ms-intb-rlock-start:23px;inset-block-start:23px;}"]));
|
130
145
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@dhis2-ui/calendar",
|
3
|
-
"version": "9.9.0-alpha.
|
3
|
+
"version": "9.9.0-alpha.4",
|
4
4
|
"description": "UI Calendar",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
@@ -33,15 +33,15 @@
|
|
33
33
|
"styled-jsx": "^4"
|
34
34
|
},
|
35
35
|
"dependencies": {
|
36
|
-
"@dhis2-ui/button": "9.9.0-alpha.
|
37
|
-
"@dhis2-ui/card": "9.9.0-alpha.
|
38
|
-
"@dhis2-ui/input": "9.9.0-alpha.
|
39
|
-
"@dhis2-ui/layer": "9.9.0-alpha.
|
40
|
-
"@dhis2-ui/popper": "9.9.0-alpha.
|
41
|
-
"@dhis2/multi-calendar-dates": "v1.0.0-alpha.
|
36
|
+
"@dhis2-ui/button": "9.9.0-alpha.4",
|
37
|
+
"@dhis2-ui/card": "9.9.0-alpha.4",
|
38
|
+
"@dhis2-ui/input": "9.9.0-alpha.4",
|
39
|
+
"@dhis2-ui/layer": "9.9.0-alpha.4",
|
40
|
+
"@dhis2-ui/popper": "9.9.0-alpha.4",
|
41
|
+
"@dhis2/multi-calendar-dates": "v1.0.0-alpha.27",
|
42
42
|
"@dhis2/prop-types": "^3.1.2",
|
43
|
-
"@dhis2/ui-constants": "9.9.0-alpha.
|
44
|
-
"@dhis2/ui-icons": "9.9.0-alpha.
|
43
|
+
"@dhis2/ui-constants": "9.9.0-alpha.4",
|
44
|
+
"@dhis2/ui-icons": "9.9.0-alpha.4",
|
45
45
|
"classnames": "^2.3.1",
|
46
46
|
"prop-types": "^15.7.2"
|
47
47
|
},
|