@dhis2-ui/calendar 9.9.0-alpha.3 → 9.9.0-alpha.4

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.
@@ -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.3",
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.3",
37
- "@dhis2-ui/card": "9.9.0-alpha.3",
38
- "@dhis2-ui/input": "9.9.0-alpha.3",
39
- "@dhis2-ui/layer": "9.9.0-alpha.3",
40
- "@dhis2-ui/popper": "9.9.0-alpha.3",
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
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.3",
44
- "@dhis2/ui-icons": "9.9.0-alpha.3",
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
  },