@dhis2-ui/calendar 9.11.3 → 9.12.0-alpha.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.default = exports.WithAnyCalendar = exports.Nepali = exports.Ethiopic = void 0;
6
+ exports.default = exports.WithAnyCalendar = exports.Nepali = exports.Ethiopic = exports.Basic = void 0;
7
7
  var _react = _interopRequireDefault(require("react"));
8
8
  var _calendar = require("../calendar/calendar.js");
9
9
  var _calendarStoryWrapper = require("./calendar-story-wrapper.js");
@@ -29,8 +29,35 @@ var _default = exports.default = {
29
29
  component: description
30
30
  }
31
31
  }
32
+ },
33
+ argTypes: {
34
+ calendar: {
35
+ control: 'select',
36
+ options: ['gregory', 'islamic', 'nepali', 'ethiopic', 'persian', 'indian']
37
+ },
38
+ weekDayFormat: {
39
+ control: 'select',
40
+ options: ['long', 'short', 'narrow']
41
+ },
42
+ locale: {
43
+ control: 'text'
44
+ },
45
+ numberingSystem: {
46
+ control: 'select',
47
+ options: ['latn', 'arab', 'ethi']
48
+ }
32
49
  }
33
50
  };
51
+ const Basic = args => {
52
+ return /*#__PURE__*/_react.default.createElement(_calendar.Calendar, args);
53
+ };
54
+ exports.Basic = Basic;
55
+ Basic.args = {
56
+ onDateSelect: date => console.log(date),
57
+ calendar: 'gregory',
58
+ weekDayFormat: 'narrow',
59
+ locale: 'en'
60
+ };
34
61
  const Ethiopic = args => {
35
62
  return /*#__PURE__*/_react.default.createElement(_calendarStoryWrapper.CalendarStoryWrapper, _extends({
36
63
  calendar: "ethiopic",
@@ -0,0 +1,72 @@
1
+ import _JSXStyle from "styled-jsx/style";
2
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
3
+ import { colors } from '@dhis2/ui-constants';
4
+ import PropTypes from 'prop-types';
5
+ import React, { useMemo } from 'react';
6
+ import { CalendarTable, CalendarTableProps } from './calendar-table.js';
7
+ import { NavigationContainer, NavigationContainerProps } from './navigation-container.js';
8
+ const wrapperBorderColor = colors.grey300;
9
+ const backgroundColor = 'none';
10
+ export const CalendarContainer = /*#__PURE__*/React.memo(function CalendarContainer(_ref) {
11
+ let {
12
+ date,
13
+ width,
14
+ cellSize,
15
+ calendarWeekDays,
16
+ weekDayLabels,
17
+ currMonth,
18
+ currYear,
19
+ nextMonth,
20
+ nextYear,
21
+ prevMonth,
22
+ prevYear,
23
+ languageDirection,
24
+ excludedRef,
25
+ unfocusable
26
+ } = _ref;
27
+ const navigationProps = useMemo(() => {
28
+ return {
29
+ currMonth,
30
+ currYear,
31
+ nextMonth,
32
+ nextYear,
33
+ prevMonth,
34
+ prevYear,
35
+ languageDirection
36
+ };
37
+ }, [currMonth, currYear, languageDirection, nextMonth, nextYear, prevMonth, prevYear]);
38
+ return /*#__PURE__*/React.createElement("div", {
39
+ className: _JSXStyle.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]])
40
+ }, /*#__PURE__*/React.createElement("div", {
41
+ dir: languageDirection,
42
+ "data-test": "calendar",
43
+ className: _JSXStyle.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]]) + " " + "calendar-wrapper"
44
+ }, /*#__PURE__*/React.createElement("div", {
45
+ ref: excludedRef,
46
+ className: _JSXStyle.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]])
47
+ }, /*#__PURE__*/React.createElement(NavigationContainer, _extends({}, navigationProps, {
48
+ unfocusable: unfocusable
49
+ })), /*#__PURE__*/React.createElement(CalendarTable, {
50
+ selectedDate: date,
51
+ calendarWeekDays: calendarWeekDays,
52
+ weekDayLabels: weekDayLabels,
53
+ cellSize: cellSize,
54
+ width: width,
55
+ unfocusable: unfocusable
56
+ }))), /*#__PURE__*/React.createElement(_JSXStyle, {
57
+ id: "2823859047",
58
+ dynamic: [backgroundColor, wrapperBorderColor, width]
59
+ }, [`.calendar-wrapper.__jsx-style-dynamic-selector{font-family:Roboto,sans-serif;font-weight:400;font-size:14px;background-color:${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 ${wrapperBorderColor};border-radius:3px;min-width:${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;}`]));
60
+ });
61
+ CalendarContainer.defaultProps = {
62
+ cellSize: '32px',
63
+ width: '240px',
64
+ unfocusable: false
65
+ };
66
+ CalendarContainer.propTypes = {
67
+ /** the currently selected date using an iso-like format YYYY-MM-DD, in the calendar system provided (not iso8601) */
68
+ date: PropTypes.string,
69
+ unfocusable: PropTypes.bool,
70
+ ...CalendarTableProps,
71
+ ...NavigationContainerProps
72
+ };
@@ -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,13 +29,14 @@ 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]
35
37
  }, [`.calendar-table.__jsx-style-dynamic-selector{border:none;border-collapse:collapse;width:100%;margin-block:${spacers.dp4};}`, ".calendar-table.__jsx-style-dynamic-selector tr.__jsx-style-dynamic-selector,.calendar-table.__jsx-style-dynamic-selector td.__jsx-style-dynamic-selector{border:none;}", `.calendar-table-wrapper.__jsx-style-dynamic-selector{padding-inline:${spacers.dp4};}`]));
36
38
  };
37
- CalendarTable.propTypes = {
39
+ export const CalendarTableProps = {
38
40
  calendarWeekDays: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.shape({
39
41
  calendarDate: PropTypes.string,
40
42
  isInCurrentMonth: PropTypes.bool,
@@ -46,6 +48,8 @@ CalendarTable.propTypes = {
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
+ };
55
+ CalendarTable.propTypes = CalendarTableProps;
@@ -1,10 +1,7 @@
1
- import _JSXStyle from "styled-jsx/style";
2
1
  import { useDatePicker, useResolvedDirection } from '@dhis2/multi-calendar-dates';
3
- import { colors } from '@dhis2/ui-constants';
4
2
  import PropTypes from 'prop-types';
5
- import React, { useState } from 'react';
6
- import { CalendarTable } from './calendar-table.js';
7
- import { NavigationContainer } from './navigation-container.js';
3
+ import React, { useMemo, useState } from 'react';
4
+ import { CalendarContainer } from './calendar-container.js';
8
5
  export const Calendar = _ref => {
9
6
  let {
10
7
  onDateSelect,
@@ -18,8 +15,6 @@ export const Calendar = _ref => {
18
15
  width,
19
16
  cellSize
20
17
  } = _ref;
21
- const wrapperBorderColor = colors.grey300;
22
- const backgroundColor = 'none';
23
18
  const [selectedDateString, setSelectedDateString] = useState(date);
24
19
  const languageDirection = useResolvedDirection(dir, locale);
25
20
  const options = {
@@ -29,7 +24,7 @@ export const Calendar = _ref => {
29
24
  numberingSystem,
30
25
  weekDayFormat
31
26
  };
32
- const pickerOptions = useDatePicker({
27
+ const pickerResults = useDatePicker({
33
28
  onDateSelect: result => {
34
29
  const {
35
30
  calendarDateString
@@ -40,29 +35,30 @@ export const Calendar = _ref => {
40
35
  date: selectedDateString,
41
36
  options
42
37
  });
43
- const {
44
- calendarWeekDays,
45
- weekDayLabels
46
- } = pickerOptions;
47
- return /*#__PURE__*/React.createElement("div", {
48
- className: _JSXStyle.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]])
49
- }, /*#__PURE__*/React.createElement("div", {
50
- dir: languageDirection,
51
- "data-test": "calendar",
52
- className: _JSXStyle.dynamic([["2823859047", [backgroundColor, wrapperBorderColor, width]]]) + " " + "calendar-wrapper"
53
- }, /*#__PURE__*/React.createElement(NavigationContainer, {
54
- pickerOptions: pickerOptions,
55
- languageDirection: languageDirection
56
- }), /*#__PURE__*/React.createElement(CalendarTable, {
57
- selectedDate: selectedDateString,
58
- calendarWeekDays: calendarWeekDays,
59
- weekDayLabels: weekDayLabels,
60
- cellSize: cellSize,
61
- width: width
62
- })), /*#__PURE__*/React.createElement(_JSXStyle, {
63
- id: "2823859047",
64
- dynamic: [backgroundColor, wrapperBorderColor, width]
65
- }, [`.calendar-wrapper.__jsx-style-dynamic-selector{font-family:Roboto,sans-serif;font-weight:400;font-size:14px;background-color:${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 ${wrapperBorderColor};border-radius:3px;min-width:${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;}`]));
38
+ const calendarProps = useMemo(() => {
39
+ return {
40
+ date,
41
+ dir,
42
+ locale,
43
+ width,
44
+ cellSize,
45
+ // minDate,
46
+ // maxDate,
47
+ // validation, // todo: clarify how we use validation props (and format) in Calendar (not CalendarInput)
48
+ // format,
49
+ isValid: pickerResults.isValid,
50
+ calendarWeekDays: pickerResults.calendarWeekDays,
51
+ weekDayLabels: pickerResults.weekDayLabels,
52
+ currMonth: pickerResults.currMonth,
53
+ currYear: pickerResults.currYear,
54
+ nextMonth: pickerResults.nextMonth,
55
+ nextYear: pickerResults.nextYear,
56
+ prevMonth: pickerResults.prevMonth,
57
+ prevYear: pickerResults.prevYear,
58
+ languageDirection
59
+ };
60
+ }, [cellSize, date, dir, locale, pickerResults, width, languageDirection]);
61
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(CalendarContainer, calendarProps));
66
62
  };
67
63
  Calendar.defaultProps = {
68
64
  cellSize: '32px',
@@ -10,18 +10,16 @@ export const NavigationContainer = _ref => {
10
10
  var _currYear$label;
11
11
  let {
12
12
  languageDirection,
13
- pickerOptions
14
- } = _ref;
15
- const PreviousIcon = languageDirection === 'ltr' ? IconChevronLeft16 : IconChevronRight16;
16
- const NextIcon = languageDirection === 'ltr' ? IconChevronRight16 : IconChevronLeft16;
17
- const {
18
13
  currMonth,
19
14
  currYear,
20
15
  nextMonth,
21
16
  nextYear,
22
17
  prevMonth,
23
- prevYear
24
- } = pickerOptions;
18
+ prevYear,
19
+ unfocusable
20
+ } = _ref;
21
+ const PreviousIcon = languageDirection === 'ltr' ? IconChevronLeft16 : IconChevronRight16;
22
+ const NextIcon = languageDirection === 'ltr' ? IconChevronRight16 : IconChevronLeft16;
25
23
 
26
24
  // Ethiopic years - when localised to English - add the era (i.e. 2015 ERA1), which is redundant in practice (like writing AD for gregorian years)
27
25
  // there is an ongoing discussion in JS-Temporal polyfill whether the era should be included or not, but for our case, it's safer to remove it
@@ -38,6 +36,7 @@ export const NavigationContainer = _ref => {
38
36
  "data-test": "calendar-previous-month",
39
37
  "aria-label": `${i18n.t(`Go to ${prevMonth.label}`)}`,
40
38
  type: "button",
39
+ tabIndex: unfocusable ? -1 : 0,
41
40
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
42
41
  }, /*#__PURE__*/React.createElement(PreviousIcon, {
43
42
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
@@ -53,6 +52,7 @@ export const NavigationContainer = _ref => {
53
52
  name: "next-month",
54
53
  "aria-label": `${i18n.t(`Go to ${nextMonth.label}`)}`,
55
54
  type: "button",
55
+ tabIndex: unfocusable ? -1 : 0,
56
56
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
57
57
  }, /*#__PURE__*/React.createElement(NextIcon, {
58
58
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
@@ -65,6 +65,7 @@ export const NavigationContainer = _ref => {
65
65
  name: "previous-year",
66
66
  "aria-label": `${i18n.t('Go to previous year')}`,
67
67
  type: "button",
68
+ tabIndex: unfocusable ? -1 : 0,
68
69
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
69
70
  }, /*#__PURE__*/React.createElement(PreviousIcon, {
70
71
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
@@ -80,6 +81,7 @@ export const NavigationContainer = _ref => {
80
81
  name: "next-year",
81
82
  "aria-label": `${i18n.t('Go to next year')}`,
82
83
  type: "button",
84
+ tabIndex: unfocusable ? -1 : 0,
83
85
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
84
86
  }, /*#__PURE__*/React.createElement(NextIcon, {
85
87
  className: _JSXStyle.dynamic([["3883083596", [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]]])
@@ -88,30 +90,30 @@ export const NavigationContainer = _ref => {
88
90
  dynamic: [spacers.dp4, colors.grey600, colors.grey200, colors.grey900, colors.grey300, spacers.dp8, spacers.dp4, wrapperBorderColor, headerBackground]
89
91
  }, ["button.__jsx-style-dynamic-selector{background:none;border:0;}", ".month.__jsx-style-dynamic-selector,.year.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;cursor:default;}", ".month.__jsx-style-dynamic-selector .curr.__jsx-style-dynamic-selector,.year.__jsx-style-dynamic-selector .curr.__jsx-style-dynamic-selector{-webkit-flex:2;-ms-flex:2;flex:2;white-space:nowrap;}", ".prev.__jsx-style-dynamic-selector,.next.__jsx-style-dynamic-selector{-webkit-flex:1;-ms-flex:1;flex:1;text-align:center;}", `.prev.__jsx-style-dynamic-selector button.__jsx-style-dynamic-selector,.next.__jsx-style-dynamic-selector button.__jsx-style-dynamic-selector{padding:${spacers.dp4};height:24px;width:24px;color:${colors.grey600};border-radius:3px;}`, ".prev.__jsx-style-dynamic-selector button.__jsx-style-dynamic-selector svg.__jsx-style-dynamic-selector,.next.__jsx-style-dynamic-selector button.__jsx-style-dynamic-selector svg.__jsx-style-dynamic-selector{width:16px;height:16px;}", `.prev.__jsx-style-dynamic-selector:hover button.__jsx-style-dynamic-selector,.next.__jsx-style-dynamic-selector:hover button.__jsx-style-dynamic-selector{background-color:${colors.grey200};color:${colors.grey900};cursor:pointer;}`, `.prev.__jsx-style-dynamic-selector button.__jsx-style-dynamic-selector:active,.next.__jsx-style-dynamic-selector button.__jsx-style-dynamic-selector:active{background-color:${colors.grey300};}`, ".label.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:4px 8px;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;min-height:16px;}", `.navigation-container.__jsx-style-dynamic-selector{gap:${spacers.dp8};padding:${spacers.dp4};display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;border-bottom:1px solid ${wrapperBorderColor};background-color:${headerBackground};font-size:1.08em;}`]));
90
92
  };
91
- NavigationContainer.propTypes = {
93
+ export const NavigationContainerProps = {
94
+ currMonth: PropTypes.shape({
95
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
96
+ }),
97
+ currYear: PropTypes.shape({
98
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
99
+ }),
92
100
  languageDirection: PropTypes.oneOf(['ltr', 'rtl']),
93
- pickerOptions: PropTypes.shape({
94
- currMonth: PropTypes.shape({
95
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
96
- }),
97
- currYear: PropTypes.shape({
98
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
99
- }),
100
- nextMonth: PropTypes.shape({
101
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
102
- navigateTo: PropTypes.func
103
- }),
104
- nextYear: PropTypes.shape({
105
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
106
- navigateTo: PropTypes.func
107
- }),
108
- prevMonth: PropTypes.shape({
109
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
110
- navigateTo: PropTypes.func
111
- }),
112
- prevYear: PropTypes.shape({
113
- label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
114
- navigateTo: PropTypes.func
115
- })
116
- })
117
- };
101
+ nextMonth: PropTypes.shape({
102
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
103
+ navigateTo: PropTypes.func
104
+ }),
105
+ nextYear: PropTypes.shape({
106
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
107
+ navigateTo: PropTypes.func
108
+ }),
109
+ prevMonth: PropTypes.shape({
110
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
111
+ navigateTo: PropTypes.func
112
+ }),
113
+ prevYear: PropTypes.shape({
114
+ label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
115
+ navigateTo: PropTypes.func
116
+ }),
117
+ unfocusable: PropTypes.bool
118
+ };
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
+ });
@@ -1,13 +1,15 @@
1
1
  import _JSXStyle from "styled-jsx/style";
2
2
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
3
+ import { useDatePicker, useResolvedDirection } from '@dhis2/multi-calendar-dates';
3
4
  import { Button } from '@dhis2-ui/button';
4
5
  import { Card } from '@dhis2-ui/card';
5
- import { InputField, InputFieldProps } from '@dhis2-ui/input';
6
+ import { InputField } from '@dhis2-ui/input';
6
7
  import { Layer } from '@dhis2-ui/layer';
7
8
  import { Popper } from '@dhis2-ui/popper';
8
9
  import cx from 'classnames';
9
- import React, { useRef, useState } from 'react';
10
- import { Calendar, CalendarProps } from '../calendar/calendar.js';
10
+ import React, { useRef, useState, useMemo, useEffect } from 'react';
11
+ import { CalendarContainer } from '../calendar/calendar-container.js';
12
+ import { CalendarProps } from '../calendar/calendar.js';
11
13
  import i18n from '../locales/index.js';
12
14
  const offsetModifier = {
13
15
  name: 'offset',
@@ -17,7 +19,7 @@ const offsetModifier = {
17
19
  };
18
20
  export const CalendarInput = function () {
19
21
  let {
20
- onDateSelect,
22
+ onDateSelect: parentOnDateSelect,
21
23
  calendar,
22
24
  date,
23
25
  dir,
@@ -28,45 +30,86 @@ export const CalendarInput = function () {
28
30
  width,
29
31
  cellSize,
30
32
  clearable,
33
+ minDate,
34
+ maxDate,
35
+ format,
36
+ // todo: props and types for format and validation
37
+ strictValidation,
31
38
  ...rest
32
39
  } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
33
40
  const ref = useRef();
34
41
  const [open, setOpen] = useState(false);
35
- const calendarProps = React.useMemo(() => {
36
- const onDateSelectWrapper = selectedDate => {
42
+ const [partialDate, setPartialDate] = useState(date);
43
+ const excludeRef = useRef(null);
44
+ useEffect(() => setPartialDate(date), [date]);
45
+ const useDatePickerOptions = useMemo(() => ({
46
+ calendar,
47
+ locale,
48
+ timeZone,
49
+ // todo: we probably shouldn't have had timezone here in the first place
50
+ numberingSystem,
51
+ weekDayFormat
52
+ }), [calendar, locale, numberingSystem, timeZone, weekDayFormat]);
53
+ const pickerResults = useDatePicker({
54
+ onDateSelect: result => {
37
55
  setOpen(false);
38
- onDateSelect === null || onDateSelect === void 0 ? void 0 : onDateSelect(selectedDate);
39
- };
56
+ parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect(result);
57
+ },
58
+ date,
59
+ minDate: minDate,
60
+ maxDate: maxDate,
61
+ strictValidation: strictValidation,
62
+ format: format,
63
+ options: useDatePickerOptions
64
+ });
65
+ const handleChange = e => {
66
+ setPartialDate(e.value);
67
+ };
68
+ const handleBlur = (_, e) => {
69
+ parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect({
70
+ calendarDateString: partialDate
71
+ });
72
+ if (excludeRef.current && !excludeRef.current.contains(e.relatedTarget)) {
73
+ setOpen(false);
74
+ }
75
+ };
76
+ const onFocus = () => {
77
+ setOpen(true);
78
+ };
79
+ const languageDirection = useResolvedDirection(dir, locale);
80
+ const calendarProps = useMemo(() => {
40
81
  return {
41
- onDateSelect: onDateSelectWrapper,
42
- calendar,
43
82
  date,
44
- dir,
45
- locale,
46
- numberingSystem,
47
- weekDayFormat,
48
- timeZone,
49
83
  width,
50
- cellSize
84
+ cellSize,
85
+ isValid: pickerResults.isValid,
86
+ calendarWeekDays: pickerResults.calendarWeekDays,
87
+ weekDayLabels: pickerResults.weekDayLabels,
88
+ currMonth: pickerResults.currMonth,
89
+ currYear: pickerResults.currYear,
90
+ nextMonth: pickerResults.nextMonth,
91
+ nextYear: pickerResults.nextYear,
92
+ prevMonth: pickerResults.prevMonth,
93
+ prevYear: pickerResults.prevYear,
94
+ languageDirection
51
95
  };
52
- }, [calendar, cellSize, date, dir, locale, numberingSystem, onDateSelect, timeZone, weekDayFormat, width]);
53
- const onFocus = () => {
54
- setOpen(true);
55
- };
96
+ }, [cellSize, date, pickerResults, width, languageDirection]);
56
97
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
57
98
  ref: ref,
58
- className: "jsx-862452676" + " " + "calendar-input-wrapper"
99
+ className: "jsx-633677374" + " " + "calendar-input-wrapper"
59
100
  }, /*#__PURE__*/React.createElement(InputField, _extends({
60
101
  label: i18n.t('Pick a date')
61
102
  }, rest, {
62
103
  type: "text",
63
104
  onFocus: onFocus,
64
- value: date
105
+ value: partialDate,
106
+ onChange: handleChange,
107
+ onBlur: handleBlur,
108
+ validationText: pickerResults.errorMessage || pickerResults.warningMessage,
109
+ error: !!pickerResults.errorMessage,
110
+ warning: !!pickerResults.warningMessage
65
111
  })), clearable && /*#__PURE__*/React.createElement("div", {
66
- className: "jsx-862452676" + " " + (cx('calendar-clear-button', {
67
- // ToDo: this is a workaround to show the clear button in the correct place when an icon is shown.
68
- // Long-term, we should abstract and share the logic multi-select uses for the input-wrapper
69
- // https://dhis2.atlassian.net/browse/DHIS2-14848
112
+ className: "jsx-633677374" + " " + (cx('calendar-clear-button', {
70
113
  'with-icon': rest.valid || rest.error || rest.warning || rest.loading,
71
114
  'with-dense-wrapper': rest.dense
72
115
  }) || "")
@@ -74,7 +117,9 @@ export const CalendarInput = function () {
74
117
  dataTest: "calendar-clear-button",
75
118
  secondary: true,
76
119
  small: true,
77
- onClick: () => calendarProps.onDateSelect(null),
120
+ onClick: () => {
121
+ parentOnDateSelect === null || parentOnDateSelect === void 0 ? void 0 : parentOnDateSelect(null);
122
+ },
78
123
  type: "button"
79
124
  }, i18n.t('Clear')))), open && /*#__PURE__*/React.createElement(Layer, {
80
125
  onBackdropClick: () => {
@@ -84,16 +129,16 @@ export const CalendarInput = function () {
84
129
  reference: ref,
85
130
  placement: "bottom-start",
86
131
  modifiers: [offsetModifier]
87
- }, /*#__PURE__*/React.createElement(Card, null, /*#__PURE__*/React.createElement(Calendar, _extends({}, calendarProps, {
88
- date: date
132
+ }, /*#__PURE__*/React.createElement(Card, null, /*#__PURE__*/React.createElement(CalendarContainer, _extends({}, calendarProps, {
133
+ excludedRef: excludeRef,
134
+ unfocusable: true
89
135
  }))))), /*#__PURE__*/React.createElement(_JSXStyle, {
90
- id: "862452676"
91
- }, [".calendar-input-wrapper.jsx-862452676{position:relative;}", ".calendar-clear-button.jsx-862452676{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-862452676{inset-inline-end:36px;}", ".calendar-clear-button.with-dense-wrapper.jsx-862452676{-webkit-inset-block-start:23px;-ms-intb-rlock-start:23px;inset-block-start:23px;}"]));
136
+ id: "633677374"
137
+ }, [".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;}"]));
92
138
  };
93
139
  CalendarInput.defaultProps = {
94
140
  dataTest: 'dhis2-uiwidgets-calendar-inputfield'
95
141
  };
96
142
  CalendarInput.propTypes = {
97
- ...CalendarProps,
98
- ...InputFieldProps
143
+ ...CalendarProps
99
144
  };
@@ -104,4 +104,21 @@ export const CalendarWithClearButton = _ref2 => {
104
104
  }), /*#__PURE__*/React.createElement("div", null, "value:", /*#__PURE__*/React.createElement("span", {
105
105
  "data-test": "storybook-calendar-date-value"
106
106
  }, date !== null && date !== void 0 ? date : 'undefined')));
107
- };
107
+ };
108
+ export function CalendarWithEditiableInput() {
109
+ const [date, setDate] = useState('2020-07-03');
110
+ return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CalendarInput, {
111
+ editable: true,
112
+ date: date,
113
+ calendar: "gregory",
114
+ onDateSelect: selectedDate => {
115
+ const date = selectedDate === null || selectedDate === void 0 ? void 0 : selectedDate.calendarDateString;
116
+ setDate(date);
117
+ },
118
+ width: '700px',
119
+ inputWidth: "900px",
120
+ timeZone: 'UTC',
121
+ minDate: '2020-07-01',
122
+ maxDate: '2020-07-09'
123
+ })));
124
+ }
@@ -22,8 +22,34 @@ export default {
22
22
  component: description
23
23
  }
24
24
  }
25
+ },
26
+ argTypes: {
27
+ calendar: {
28
+ control: 'select',
29
+ options: ['gregory', 'islamic', 'nepali', 'ethiopic', 'persian', 'indian']
30
+ },
31
+ weekDayFormat: {
32
+ control: 'select',
33
+ options: ['long', 'short', 'narrow']
34
+ },
35
+ locale: {
36
+ control: 'text'
37
+ },
38
+ numberingSystem: {
39
+ control: 'select',
40
+ options: ['latn', 'arab', 'ethi']
41
+ }
25
42
  }
26
43
  };
44
+ export const Basic = args => {
45
+ return /*#__PURE__*/React.createElement(Calendar, args);
46
+ };
47
+ Basic.args = {
48
+ onDateSelect: date => console.log(date),
49
+ calendar: 'gregory',
50
+ weekDayFormat: 'narrow',
51
+ locale: 'en'
52
+ };
27
53
  export const Ethiopic = args => {
28
54
  return /*#__PURE__*/React.createElement(CalendarStoryWrapper, _extends({
29
55
  calendar: "ethiopic",