@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.
@@ -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(_navigationContainer.NavigationContainer, navigationProps), /*#__PURE__*/_react.default.createElement(_calendarTable.CalendarTable, {
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
- })), /*#__PURE__*/_react.default.createElement(_style.default, {
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: e.value
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: date,
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, calendarProps)))), /*#__PURE__*/_react.default.createElement(_style.default, {
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(NavigationContainer, navigationProps), /*#__PURE__*/React.createElement(CalendarTable, {
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
- })), /*#__PURE__*/React.createElement(_JSXStyle, {
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: e.value
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: date,
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, calendarProps)))), /*#__PURE__*/React.createElement(_JSXStyle, {
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.2",
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.2",
37
- "@dhis2-ui/card": "9.9.0-alpha.2",
38
- "@dhis2-ui/input": "9.9.0-alpha.2",
39
- "@dhis2-ui/layer": "9.9.0-alpha.2",
40
- "@dhis2-ui/popper": "9.9.0-alpha.2",
41
- "@dhis2/multi-calendar-dates": "v1.0.0-alpha.26",
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.2",
44
- "@dhis2/ui-icons": "9.9.0-alpha.2",
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
  },