@hero-design/rn 8.34.0 → 8.34.1

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.
@@ -4,5 +4,5 @@ $ rollup -c
4
4
  src/index.ts → lib/index.js, es/index.js...
5
5
  (!) Plugin replace: @rollup/plugin-replace: 'preventAssignment' currently defaults to false. It is recommended to set this option to `true`, as the next major version will default this option to `true`.
6
6
  (!) Plugin node-resolve: preferring built-in module 'events' over local alternative at '/root/hero-design/node_modules/events/events.js', pass 'preferBuiltins: false' to disable this behavior or 'preferBuiltins: true' to disable this warning
7
- created lib/index.js, es/index.js in 28.6s
7
+ created lib/index.js, es/index.js in 25.1s
8
8
  $ tsc --noEmit false --emitDeclarationOnly --project tsconfig.prod.json
package/es/index.js CHANGED
@@ -3,6 +3,7 @@ import { Platform, Dimensions, StyleSheet as StyleSheet$1, Animated, View, UIMan
3
3
  import React, { useContext, createContext, createElement, useMemo, forwardRef, useEffect, useCallback, useRef, useState, isValidElement, useImperativeHandle, useLayoutEffect } from 'react';
4
4
  import { createIconSet } from 'react-native-vector-icons';
5
5
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
6
+ import { MonthYearPickerViewIOS, MonthYearPickerDialogueAndroid } from '@hero-design/react-native-month-year-picker';
6
7
  import DateTimePicker from '@react-native-community/datetimepicker';
7
8
  import RootSiblings, { RootSiblingParent } from 'react-native-root-siblings';
8
9
  import RnSlider from '@react-native-community/slider';
@@ -2009,7 +2010,9 @@ var getCalendarTheme = function getCalendarTheme(theme) {
2009
2010
  var space = {
2010
2011
  rowVerticalPadding: theme.space.medium,
2011
2012
  headerVerticalPadding: theme.space.medium,
2012
- headerHorizontalPadding: theme.space.smallMedium
2013
+ headerHorizontalPadding: theme.space.smallMedium,
2014
+ headerMarginRight: theme.space.small,
2015
+ iosPickerMarginVertical: theme.space['5xlarge']
2013
2016
  };
2014
2017
  var radii = {
2015
2018
  "default": theme.radii.rounded
@@ -11262,13 +11265,27 @@ var Calendar = function Calendar(_ref) {
11262
11265
  maxDate = _ref.maxDate,
11263
11266
  _ref$markedDates = _ref.markedDates,
11264
11267
  markedDates = _ref$markedDates === void 0 ? [] : _ref$markedDates,
11265
- testID = _ref.testID;
11268
+ testID = _ref.testID,
11269
+ _ref$onMonthChange = _ref.onMonthChange,
11270
+ onMonthChange = _ref$onMonthChange === void 0 ? noop$1 : _ref$onMonthChange,
11271
+ monthPickerConfirmLabel = _ref.monthPickerConfirmLabel,
11272
+ monthPickerCancelLabel = _ref.monthPickerCancelLabel;
11273
+ var theme = useTheme();
11266
11274
  var currentMonth = visibleDate.getMonth();
11267
11275
  var currentYear = visibleDate.getFullYear();
11268
11276
  var now = new Date();
11269
11277
  var parsedMaskedDate = markedDates.reduce(function (current, markedDate) {
11270
11278
  return _objectSpread2(_objectSpread2({}, current), {}, _defineProperty({}, markedDate.toDateString(), true));
11271
11279
  }, {});
11280
+ var _React$useState = React.useState(false),
11281
+ _React$useState2 = _slicedToArray(_React$useState, 2),
11282
+ monthPickerVisible = _React$useState2[0],
11283
+ setMonthPickerVisible = _React$useState2[1];
11284
+ var _React$useState3 = React.useState(0),
11285
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
11286
+ contentHeight = _React$useState4[0],
11287
+ setContentHeight = _React$useState4[1];
11288
+ var useMonthPicker = onMonthChange !== noop$1;
11272
11289
  var firstDateOfMonth = new Date(currentYear, currentMonth, 1);
11273
11290
  var lastDateOfMonth = new Date(currentYear, currentMonth + 1, 0);
11274
11291
  var lastDateOfPreviousMonth = new Date(currentYear, currentMonth, 0);
@@ -11296,14 +11313,49 @@ var Calendar = function Calendar(_ref) {
11296
11313
  return /*#__PURE__*/React.createElement(StyledContainer$5, {
11297
11314
  testID: testID
11298
11315
  }, /*#__PURE__*/React.createElement(StyledCalendarHeader, null, /*#__PURE__*/React.createElement(ContentNavigator, {
11299
- value: formatTime('MMMM yyyy', visibleDate),
11316
+ value: !useMonthPicker ? formatTime('MMMM yyyy', visibleDate) : /*#__PURE__*/React.createElement(TouchableOpacity, {
11317
+ testID: "calendar-month-picker",
11318
+ onPress: function onPress() {
11319
+ return setMonthPickerVisible(!monthPickerVisible);
11320
+ }
11321
+ }, /*#__PURE__*/React.createElement(Box, {
11322
+ flexDirection: "row",
11323
+ justifyContent: "center",
11324
+ alignItems: "center"
11325
+ }, /*#__PURE__*/React.createElement(Typography.Title, {
11326
+ level: "h5",
11327
+ style: {
11328
+ textAlign: 'center',
11329
+ marginRight: theme.__hd__.calendar.space.headerMarginRight
11330
+ }
11331
+ }, formatTime('MMMM yyyy', visibleDate)), /*#__PURE__*/React.createElement(Icon, {
11332
+ icon: monthPickerVisible ? 'arrow-up' : 'arrow-down',
11333
+ size: "small"
11334
+ }))),
11300
11335
  onPreviousPress: onPreviousPress,
11301
11336
  onNextPress: onNextPress,
11302
- onPress: onTitlePress,
11337
+ onPress: useMonthPicker ? undefined : onTitlePress,
11303
11338
  previousDisabled: disablePrevButton,
11304
11339
  nextDisabled: disableNextButton,
11305
11340
  fontSize: "large"
11306
- })), /*#__PURE__*/React.createElement(StyledCalendarRow, null, DAYS_OF_WEEK.map(function (day) {
11341
+ })), Platform.OS === 'ios' && monthPickerVisible ? /*#__PURE__*/React.createElement(Box, {
11342
+ style: {
11343
+ overflow: 'hidden'
11344
+ }
11345
+ }, /*#__PURE__*/React.createElement(MonthYearPickerViewIOS, {
11346
+ value: value,
11347
+ minimumDate: minDate,
11348
+ maximumDate: maxDate,
11349
+ onChange: onMonthChange,
11350
+ style: {
11351
+ height: contentHeight + theme.__hd__.calendar.space.iosPickerMarginVertical * 2,
11352
+ marginVertical: -theme.__hd__.calendar.space.iosPickerMarginVertical
11353
+ }
11354
+ })) : /*#__PURE__*/React.createElement(Box, {
11355
+ onLayout: Platform.OS === 'ios' ? function (e) {
11356
+ return setContentHeight(e.nativeEvent.layout.height);
11357
+ } : undefined
11358
+ }, /*#__PURE__*/React.createElement(StyledCalendarRow, null, DAYS_OF_WEEK.map(function (day) {
11307
11359
  return /*#__PURE__*/React.createElement(StyledCalendarRowItem, {
11308
11360
  key: day
11309
11361
  }, /*#__PURE__*/React.createElement(StyledCalendarDayNameCell, null, /*#__PURE__*/React.createElement(Typography.Body, {
@@ -11350,6 +11402,18 @@ var Calendar = function Calendar(_ref) {
11350
11402
  }) : /*#__PURE__*/React.createElement(StyledDisabledCalendarRowItem, {
11351
11403
  testID: "calendar-disabled-cell"
11352
11404
  });
11405
+ })), Platform.OS === 'android' && monthPickerVisible && /*#__PURE__*/React.createElement(MonthYearPickerDialogueAndroid, {
11406
+ doneButtonLabel: monthPickerConfirmLabel,
11407
+ cancelButtonLabel: monthPickerCancelLabel,
11408
+ value: value,
11409
+ minimumDate: minDate,
11410
+ maximumDate: maxDate,
11411
+ onChange: function onChange(action, date) {
11412
+ setMonthPickerVisible(false);
11413
+ if (action === 'dateSetAction' && !!date) {
11414
+ onMonthChange(date);
11415
+ }
11416
+ }
11353
11417
  })));
11354
11418
  };
11355
11419
 
@@ -12433,7 +12497,9 @@ var InternalCalendar = function InternalCalendar(_ref) {
12433
12497
  var minDate = _ref.minDate,
12434
12498
  maxDate = _ref.maxDate,
12435
12499
  value = _ref.value,
12436
- _onChange = _ref.onChange;
12500
+ _onChange = _ref.onChange,
12501
+ monthPickerConfirmLabel = _ref.monthPickerConfirmLabel,
12502
+ monthPickerCancelLabel = _ref.monthPickerCancelLabel;
12437
12503
  var _useState = useState(value || new Date()),
12438
12504
  _useState2 = _slicedToArray(_useState, 2),
12439
12505
  visibleDate = _useState2[0],
@@ -12459,7 +12525,12 @@ var InternalCalendar = function InternalCalendar(_ref) {
12459
12525
  return setVisibleDate(new Date(visibleDate.getFullYear(), visibleDate.getMonth() + 1, 1));
12460
12526
  },
12461
12527
  minDate: minDate,
12462
- maxDate: maxDate
12528
+ maxDate: maxDate,
12529
+ onMonthChange: function onMonthChange(date) {
12530
+ return setVisibleDate(date);
12531
+ },
12532
+ monthPickerConfirmLabel: monthPickerConfirmLabel,
12533
+ monthPickerCancelLabel: monthPickerCancelLabel
12463
12534
  });
12464
12535
  };
12465
12536
  var DatePickerCalendar = function DatePickerCalendar(_ref2) {
@@ -12480,7 +12551,9 @@ var DatePickerCalendar = function DatePickerCalendar(_ref2) {
12480
12551
  error = _ref2.error,
12481
12552
  helpText = _ref2.helpText,
12482
12553
  style = _ref2.style,
12483
- testID = _ref2.testID;
12554
+ testID = _ref2.testID,
12555
+ monthPickerConfirmLabel = _ref2.monthPickerConfirmLabel,
12556
+ monthPickerCancelLabel = _ref2.monthPickerCancelLabel;
12484
12557
  var _useState5 = useState(false),
12485
12558
  _useState6 = _slicedToArray(_useState5, 2),
12486
12559
  open = _useState6[0],
@@ -12529,7 +12602,9 @@ var DatePickerCalendar = function DatePickerCalendar(_ref2) {
12529
12602
  minDate: minDate,
12530
12603
  maxDate: maxDate,
12531
12604
  value: value,
12532
- onChange: setSelectingDate
12605
+ onChange: setSelectingDate,
12606
+ monthPickerConfirmLabel: monthPickerConfirmLabel,
12607
+ monthPickerCancelLabel: monthPickerCancelLabel
12533
12608
  })));
12534
12609
  };
12535
12610
 
package/lib/index.js CHANGED
@@ -6,6 +6,7 @@ var reactNative = require('react-native');
6
6
  var React = require('react');
7
7
  var reactNativeVectorIcons = require('react-native-vector-icons');
8
8
  var reactNativeSafeAreaContext = require('react-native-safe-area-context');
9
+ var reactNativeMonthYearPicker = require('@hero-design/react-native-month-year-picker');
9
10
  var DateTimePicker = require('@react-native-community/datetimepicker');
10
11
  var RootSiblings = require('react-native-root-siblings');
11
12
  var RnSlider = require('@react-native-community/slider');
@@ -2040,7 +2041,9 @@ var getCalendarTheme = function getCalendarTheme(theme) {
2040
2041
  var space = {
2041
2042
  rowVerticalPadding: theme.space.medium,
2042
2043
  headerVerticalPadding: theme.space.medium,
2043
- headerHorizontalPadding: theme.space.smallMedium
2044
+ headerHorizontalPadding: theme.space.smallMedium,
2045
+ headerMarginRight: theme.space.small,
2046
+ iosPickerMarginVertical: theme.space['5xlarge']
2044
2047
  };
2045
2048
  var radii = {
2046
2049
  "default": theme.radii.rounded
@@ -11293,13 +11296,27 @@ var Calendar = function Calendar(_ref) {
11293
11296
  maxDate = _ref.maxDate,
11294
11297
  _ref$markedDates = _ref.markedDates,
11295
11298
  markedDates = _ref$markedDates === void 0 ? [] : _ref$markedDates,
11296
- testID = _ref.testID;
11299
+ testID = _ref.testID,
11300
+ _ref$onMonthChange = _ref.onMonthChange,
11301
+ onMonthChange = _ref$onMonthChange === void 0 ? noop$1 : _ref$onMonthChange,
11302
+ monthPickerConfirmLabel = _ref.monthPickerConfirmLabel,
11303
+ monthPickerCancelLabel = _ref.monthPickerCancelLabel;
11304
+ var theme = useTheme();
11297
11305
  var currentMonth = visibleDate.getMonth();
11298
11306
  var currentYear = visibleDate.getFullYear();
11299
11307
  var now = new Date();
11300
11308
  var parsedMaskedDate = markedDates.reduce(function (current, markedDate) {
11301
11309
  return _objectSpread2(_objectSpread2({}, current), {}, _defineProperty({}, markedDate.toDateString(), true));
11302
11310
  }, {});
11311
+ var _React$useState = React__default["default"].useState(false),
11312
+ _React$useState2 = _slicedToArray(_React$useState, 2),
11313
+ monthPickerVisible = _React$useState2[0],
11314
+ setMonthPickerVisible = _React$useState2[1];
11315
+ var _React$useState3 = React__default["default"].useState(0),
11316
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
11317
+ contentHeight = _React$useState4[0],
11318
+ setContentHeight = _React$useState4[1];
11319
+ var useMonthPicker = onMonthChange !== noop$1;
11303
11320
  var firstDateOfMonth = new Date(currentYear, currentMonth, 1);
11304
11321
  var lastDateOfMonth = new Date(currentYear, currentMonth + 1, 0);
11305
11322
  var lastDateOfPreviousMonth = new Date(currentYear, currentMonth, 0);
@@ -11327,14 +11344,49 @@ var Calendar = function Calendar(_ref) {
11327
11344
  return /*#__PURE__*/React__default["default"].createElement(StyledContainer$5, {
11328
11345
  testID: testID
11329
11346
  }, /*#__PURE__*/React__default["default"].createElement(StyledCalendarHeader, null, /*#__PURE__*/React__default["default"].createElement(ContentNavigator, {
11330
- value: formatTime('MMMM yyyy', visibleDate),
11347
+ value: !useMonthPicker ? formatTime('MMMM yyyy', visibleDate) : /*#__PURE__*/React__default["default"].createElement(reactNative.TouchableOpacity, {
11348
+ testID: "calendar-month-picker",
11349
+ onPress: function onPress() {
11350
+ return setMonthPickerVisible(!monthPickerVisible);
11351
+ }
11352
+ }, /*#__PURE__*/React__default["default"].createElement(Box, {
11353
+ flexDirection: "row",
11354
+ justifyContent: "center",
11355
+ alignItems: "center"
11356
+ }, /*#__PURE__*/React__default["default"].createElement(Typography.Title, {
11357
+ level: "h5",
11358
+ style: {
11359
+ textAlign: 'center',
11360
+ marginRight: theme.__hd__.calendar.space.headerMarginRight
11361
+ }
11362
+ }, formatTime('MMMM yyyy', visibleDate)), /*#__PURE__*/React__default["default"].createElement(Icon, {
11363
+ icon: monthPickerVisible ? 'arrow-up' : 'arrow-down',
11364
+ size: "small"
11365
+ }))),
11331
11366
  onPreviousPress: onPreviousPress,
11332
11367
  onNextPress: onNextPress,
11333
- onPress: onTitlePress,
11368
+ onPress: useMonthPicker ? undefined : onTitlePress,
11334
11369
  previousDisabled: disablePrevButton,
11335
11370
  nextDisabled: disableNextButton,
11336
11371
  fontSize: "large"
11337
- })), /*#__PURE__*/React__default["default"].createElement(StyledCalendarRow, null, DAYS_OF_WEEK.map(function (day) {
11372
+ })), reactNative.Platform.OS === 'ios' && monthPickerVisible ? /*#__PURE__*/React__default["default"].createElement(Box, {
11373
+ style: {
11374
+ overflow: 'hidden'
11375
+ }
11376
+ }, /*#__PURE__*/React__default["default"].createElement(reactNativeMonthYearPicker.MonthYearPickerViewIOS, {
11377
+ value: value,
11378
+ minimumDate: minDate,
11379
+ maximumDate: maxDate,
11380
+ onChange: onMonthChange,
11381
+ style: {
11382
+ height: contentHeight + theme.__hd__.calendar.space.iosPickerMarginVertical * 2,
11383
+ marginVertical: -theme.__hd__.calendar.space.iosPickerMarginVertical
11384
+ }
11385
+ })) : /*#__PURE__*/React__default["default"].createElement(Box, {
11386
+ onLayout: reactNative.Platform.OS === 'ios' ? function (e) {
11387
+ return setContentHeight(e.nativeEvent.layout.height);
11388
+ } : undefined
11389
+ }, /*#__PURE__*/React__default["default"].createElement(StyledCalendarRow, null, DAYS_OF_WEEK.map(function (day) {
11338
11390
  return /*#__PURE__*/React__default["default"].createElement(StyledCalendarRowItem, {
11339
11391
  key: day
11340
11392
  }, /*#__PURE__*/React__default["default"].createElement(StyledCalendarDayNameCell, null, /*#__PURE__*/React__default["default"].createElement(Typography.Body, {
@@ -11381,6 +11433,18 @@ var Calendar = function Calendar(_ref) {
11381
11433
  }) : /*#__PURE__*/React__default["default"].createElement(StyledDisabledCalendarRowItem, {
11382
11434
  testID: "calendar-disabled-cell"
11383
11435
  });
11436
+ })), reactNative.Platform.OS === 'android' && monthPickerVisible && /*#__PURE__*/React__default["default"].createElement(reactNativeMonthYearPicker.MonthYearPickerDialogueAndroid, {
11437
+ doneButtonLabel: monthPickerConfirmLabel,
11438
+ cancelButtonLabel: monthPickerCancelLabel,
11439
+ value: value,
11440
+ minimumDate: minDate,
11441
+ maximumDate: maxDate,
11442
+ onChange: function onChange(action, date) {
11443
+ setMonthPickerVisible(false);
11444
+ if (action === 'dateSetAction' && !!date) {
11445
+ onMonthChange(date);
11446
+ }
11447
+ }
11384
11448
  })));
11385
11449
  };
11386
11450
 
@@ -12464,7 +12528,9 @@ var InternalCalendar = function InternalCalendar(_ref) {
12464
12528
  var minDate = _ref.minDate,
12465
12529
  maxDate = _ref.maxDate,
12466
12530
  value = _ref.value,
12467
- _onChange = _ref.onChange;
12531
+ _onChange = _ref.onChange,
12532
+ monthPickerConfirmLabel = _ref.monthPickerConfirmLabel,
12533
+ monthPickerCancelLabel = _ref.monthPickerCancelLabel;
12468
12534
  var _useState = React.useState(value || new Date()),
12469
12535
  _useState2 = _slicedToArray(_useState, 2),
12470
12536
  visibleDate = _useState2[0],
@@ -12490,7 +12556,12 @@ var InternalCalendar = function InternalCalendar(_ref) {
12490
12556
  return setVisibleDate(new Date(visibleDate.getFullYear(), visibleDate.getMonth() + 1, 1));
12491
12557
  },
12492
12558
  minDate: minDate,
12493
- maxDate: maxDate
12559
+ maxDate: maxDate,
12560
+ onMonthChange: function onMonthChange(date) {
12561
+ return setVisibleDate(date);
12562
+ },
12563
+ monthPickerConfirmLabel: monthPickerConfirmLabel,
12564
+ monthPickerCancelLabel: monthPickerCancelLabel
12494
12565
  });
12495
12566
  };
12496
12567
  var DatePickerCalendar = function DatePickerCalendar(_ref2) {
@@ -12511,7 +12582,9 @@ var DatePickerCalendar = function DatePickerCalendar(_ref2) {
12511
12582
  error = _ref2.error,
12512
12583
  helpText = _ref2.helpText,
12513
12584
  style = _ref2.style,
12514
- testID = _ref2.testID;
12585
+ testID = _ref2.testID,
12586
+ monthPickerConfirmLabel = _ref2.monthPickerConfirmLabel,
12587
+ monthPickerCancelLabel = _ref2.monthPickerCancelLabel;
12515
12588
  var _useState5 = React.useState(false),
12516
12589
  _useState6 = _slicedToArray(_useState5, 2),
12517
12590
  open = _useState6[0],
@@ -12560,7 +12633,9 @@ var DatePickerCalendar = function DatePickerCalendar(_ref2) {
12560
12633
  minDate: minDate,
12561
12634
  maxDate: maxDate,
12562
12635
  value: value,
12563
- onChange: setSelectingDate
12636
+ onChange: setSelectingDate,
12637
+ monthPickerConfirmLabel: monthPickerConfirmLabel,
12638
+ monthPickerCancelLabel: monthPickerCancelLabel
12564
12639
  })));
12565
12640
  };
12566
12641
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.34.0",
3
+ "version": "8.34.1",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -21,12 +21,14 @@
21
21
  "dependencies": {
22
22
  "@emotion/native": "^11.9.3",
23
23
  "@emotion/react": "^11.9.3",
24
- "@hero-design/colors": "8.34.0",
24
+ "@hero-design/colors": "8.34.1",
25
+ "@hero-design/react-native-month-year-picker": "^8.34.1",
25
26
  "date-fns": "^2.16.1",
26
27
  "events": "^3.2.0",
27
28
  "hero-editor": "^1.9.21"
28
29
  },
29
30
  "peerDependencies": {
31
+ "@hero-design/react-native-month-year-picker": "^8.34.1",
30
32
  "@react-native-community/datetimepicker": "^3.5.2",
31
33
  "@react-native-community/slider": "4.1.12",
32
34
  "react": "18.0.0",
@@ -46,7 +48,7 @@
46
48
  "@babel/preset-typescript": "^7.17.12",
47
49
  "@babel/runtime": "^7.18.9",
48
50
  "@emotion/jest": "^11.9.3",
49
- "@hero-design/eslint-plugin": "8.34.0",
51
+ "@hero-design/eslint-plugin": "8.34.1",
50
52
  "@react-native-community/datetimepicker": "^3.5.2",
51
53
  "@react-native-community/slider": "4.1.12",
52
54
  "@rollup/plugin-babel": "^5.3.1",
@@ -62,10 +64,10 @@
62
64
  "@types/react-native": "^0.67.7",
63
65
  "@types/react-native-vector-icons": "^6.4.10",
64
66
  "babel-plugin-inline-import": "^3.0.0",
65
- "eslint-config-hd": "8.34.0",
67
+ "eslint-config-hd": "8.34.1",
66
68
  "eslint-plugin-import": "^2.27.5",
67
69
  "jest": "^27.3.1",
68
- "prettier-config-hd": "8.34.0",
70
+ "prettier-config-hd": "8.34.1",
69
71
  "react": "18.0.0",
70
72
  "react-native": "0.69.7",
71
73
  "react-native-gesture-handler": "~2.5.0",
package/rollup.config.js CHANGED
@@ -34,6 +34,7 @@ export default {
34
34
  'react-native-pager-view',
35
35
  'react-native-vector-icons',
36
36
  'react-native-linear-gradient',
37
+ '@hero-design/react-native-month-year-picker',
37
38
  'react-native-root-siblings',
38
39
  ],
39
40
  plugins: [
@@ -92,4 +92,51 @@ describe('Calendar', () => {
92
92
  fireEvent.press(getByText('26'));
93
93
  expect(onChange).toBeCalledWith(new Date(2022, 10, 26));
94
94
  });
95
+
96
+ it('disables onTitlePress when onMonthChange is passed', () => {
97
+ const onTitlePress = jest.fn();
98
+ const { getByTestId } = renderWithTheme(
99
+ <Calendar
100
+ value={new Date(2022, 10, 5)}
101
+ visibleDate={new Date(2022, 10, 5)}
102
+ onTitlePress={onTitlePress}
103
+ minDate={new Date(2022, 10, 3)}
104
+ maxDate={new Date(2022, 10, 27)}
105
+ onMonthChange={jest.fn()}
106
+ />
107
+ );
108
+
109
+ fireEvent.press(getByTestId('calendar-month-picker'));
110
+ expect(onTitlePress).toHaveBeenCalledTimes(0);
111
+ });
112
+
113
+ it.each`
114
+ platform
115
+ ${'ios'}
116
+ ${'android'}
117
+ `('renders correct picker on $platform', ({ platform }) => {
118
+ jest.mock('react-native/Libraries/Utilities/Platform', () => ({
119
+ OS: platform,
120
+ select: () => null,
121
+ }));
122
+
123
+ const { getByTestId, queryByText } = renderWithTheme(
124
+ <Calendar
125
+ value={new Date(2022, 10, 5)}
126
+ visibleDate={new Date(2022, 10, 5)}
127
+ minDate={new Date(2022, 10, 3)}
128
+ maxDate={new Date(2022, 10, 27)}
129
+ onMonthChange={jest.fn()}
130
+ />
131
+ );
132
+
133
+ fireEvent.press(getByTestId('calendar-month-picker'));
134
+
135
+ // Pickers are mocked at packages/rn/testUtils/setup.tsx
136
+ if (platform === 'ios') {
137
+ expect(queryByText('IOS picker')).toBeDefined();
138
+ } else {
139
+ expect(queryByText('Android picker')).toBeDefined();
140
+ }
141
+ });
95
142
  });
@@ -1,7 +1,17 @@
1
- import React from 'react';
1
+ import {
2
+ MonthYearPickerDialogueAndroid,
3
+ MonthYearPickerViewIOS,
4
+ } from '@hero-design/react-native-month-year-picker';
2
5
  import format from 'date-fns/fp/format';
6
+ import React from 'react';
7
+ import { Platform, TouchableOpacity } from 'react-native';
8
+ import { useTheme } from '../../theme';
9
+ import { noop } from '../../utils/functions';
10
+ import Box from '../Box';
3
11
  import ContentNavigator from '../ContentNavigator';
12
+ import Icon from '../Icon';
4
13
  import Typography from '../Typography';
14
+ import CalendarRowItem from './CalendarRowItem';
5
15
  import {
6
16
  StyledCalendarDayNameCell,
7
17
  StyledCalendarHeader,
@@ -10,9 +20,7 @@ import {
10
20
  StyledContainer,
11
21
  StyledDisabledCalendarRowItem,
12
22
  } from './StyledCalendar';
13
- import CalendarRowItem from './CalendarRowItem';
14
23
  import { getValidDate, initArray, isEqDate } from './helpers';
15
- import { noop } from '../../utils/functions';
16
24
 
17
25
  const DAYS_OF_WEEK = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
18
26
  // Sunday first column => 0
@@ -68,6 +76,18 @@ export interface CalendarProps {
68
76
  * Testing id of the component.
69
77
  */
70
78
  testID?: string;
79
+ /**
80
+ * Callback when header month is changed, passing this prop will disable onTitlePress.
81
+ */
82
+ onMonthChange?: (date: Date) => void;
83
+ /**
84
+ * Label for the confirm button of the month picker, Android only.
85
+ */
86
+ monthPickerConfirmLabel?: string;
87
+ /**
88
+ * Label for the cancel button of the month picker, Android only.
89
+ */
90
+ monthPickerCancelLabel?: string;
71
91
  }
72
92
 
73
93
  const Calendar = ({
@@ -81,7 +101,11 @@ const Calendar = ({
81
101
  maxDate,
82
102
  markedDates = [],
83
103
  testID,
104
+ onMonthChange = noop,
105
+ monthPickerConfirmLabel,
106
+ monthPickerCancelLabel,
84
107
  }: CalendarProps) => {
108
+ const theme = useTheme();
85
109
  const currentMonth = visibleDate.getMonth();
86
110
  const currentYear = visibleDate.getFullYear();
87
111
  const now = new Date();
@@ -92,6 +116,10 @@ const Calendar = ({
92
116
  }),
93
117
  {}
94
118
  );
119
+ const [monthPickerVisible, setMonthPickerVisible] = React.useState(false);
120
+ const [contentHeight, setContentHeight] = React.useState(0);
121
+
122
+ const useMonthPicker = onMonthChange !== noop;
95
123
 
96
124
  const firstDateOfMonth = new Date(currentYear, currentMonth, 1);
97
125
  const lastDateOfMonth = new Date(currentYear, currentMonth + 1, 0);
@@ -152,70 +180,144 @@ const Calendar = ({
152
180
  <StyledContainer testID={testID}>
153
181
  <StyledCalendarHeader>
154
182
  <ContentNavigator
155
- value={format('MMMM yyyy', visibleDate)}
183
+ value={
184
+ !useMonthPicker ? (
185
+ format('MMMM yyyy', visibleDate)
186
+ ) : (
187
+ <TouchableOpacity
188
+ testID="calendar-month-picker"
189
+ onPress={() => setMonthPickerVisible(!monthPickerVisible)}
190
+ >
191
+ <Box
192
+ flexDirection="row"
193
+ justifyContent="center"
194
+ alignItems="center"
195
+ >
196
+ <Typography.Title
197
+ level="h5"
198
+ style={{
199
+ textAlign: 'center',
200
+ marginRight:
201
+ theme.__hd__.calendar.space.headerMarginRight,
202
+ }}
203
+ >
204
+ {format('MMMM yyyy', visibleDate)}
205
+ </Typography.Title>
206
+
207
+ <Icon
208
+ icon={monthPickerVisible ? 'arrow-up' : 'arrow-down'}
209
+ size="small"
210
+ />
211
+ </Box>
212
+ </TouchableOpacity>
213
+ )
214
+ }
156
215
  onPreviousPress={onPreviousPress}
157
216
  onNextPress={onNextPress}
158
- onPress={onTitlePress}
217
+ onPress={useMonthPicker ? undefined : onTitlePress}
159
218
  previousDisabled={disablePrevButton}
160
219
  nextDisabled={disableNextButton}
161
220
  fontSize="large"
162
221
  />
163
222
  </StyledCalendarHeader>
164
- <StyledCalendarRow>
165
- {DAYS_OF_WEEK.map((day) => (
166
- <StyledCalendarRowItem key={day}>
167
- <StyledCalendarDayNameCell>
168
- <Typography.Body variant="small">{day}</Typography.Body>
169
- </StyledCalendarDayNameCell>
170
- </StyledCalendarRowItem>
171
- ))}
172
- </StyledCalendarRow>
173
- <StyledCalendarRow>
174
- {daysOfPreviousMonth.map((date) =>
175
- date ? (
176
- <CalendarRowItem
177
- key={date.toDateString()}
178
- date={date}
179
- isCurrent={isEqDate(now, date)}
180
- isSelected={isEqDate(value, date)}
181
- onPress={() => onChange?.(date)}
182
- textIntent="subdued"
183
- marked={parsedMaskedDate[date.toDateString()]}
184
- />
185
- ) : (
186
- <StyledDisabledCalendarRowItem testID="calendar-disabled-cell" />
187
- )
188
- )}
189
- {daysOfCurrentMonth.map((date) =>
190
- date ? (
191
- <CalendarRowItem
192
- key={date.toDateString()}
193
- date={date}
194
- isCurrent={isEqDate(now, date)}
195
- isSelected={isEqDate(value, date)}
196
- onPress={() => onChange?.(date)}
197
- marked={parsedMaskedDate[date.toDateString()]}
198
- />
199
- ) : (
200
- <StyledDisabledCalendarRowItem testID="calendar-disabled-cell" />
201
- )
202
- )}
203
- {daysOfNextMonth.map((date) =>
204
- date ? (
205
- <CalendarRowItem
206
- key={date.toDateString()}
207
- date={date}
208
- isCurrent={isEqDate(now, date)}
209
- isSelected={isEqDate(value, date)}
210
- onPress={() => onChange?.(date)}
211
- textIntent="subdued"
212
- marked={parsedMaskedDate[date.toDateString()]}
223
+ {Platform.OS === 'ios' && monthPickerVisible ? (
224
+ <Box style={{ overflow: 'hidden' }}>
225
+ <MonthYearPickerViewIOS
226
+ value={value}
227
+ minimumDate={minDate}
228
+ maximumDate={maxDate}
229
+ onChange={onMonthChange}
230
+ style={{
231
+ height:
232
+ contentHeight +
233
+ theme.__hd__.calendar.space.iosPickerMarginVertical * 2,
234
+ marginVertical:
235
+ -theme.__hd__.calendar.space.iosPickerMarginVertical,
236
+ }}
237
+ />
238
+ </Box>
239
+ ) : (
240
+ <Box
241
+ onLayout={
242
+ Platform.OS === 'ios'
243
+ ? (e) => setContentHeight(e.nativeEvent.layout.height)
244
+ : undefined
245
+ }
246
+ >
247
+ <StyledCalendarRow>
248
+ {DAYS_OF_WEEK.map((day) => (
249
+ <StyledCalendarRowItem key={day}>
250
+ <StyledCalendarDayNameCell>
251
+ <Typography.Body variant="small">{day}</Typography.Body>
252
+ </StyledCalendarDayNameCell>
253
+ </StyledCalendarRowItem>
254
+ ))}
255
+ </StyledCalendarRow>
256
+ <StyledCalendarRow>
257
+ {daysOfPreviousMonth.map((date) =>
258
+ date ? (
259
+ <CalendarRowItem
260
+ key={date.toDateString()}
261
+ date={date}
262
+ isCurrent={isEqDate(now, date)}
263
+ isSelected={isEqDate(value, date)}
264
+ onPress={() => onChange?.(date)}
265
+ textIntent="subdued"
266
+ marked={parsedMaskedDate[date.toDateString()]}
267
+ />
268
+ ) : (
269
+ <StyledDisabledCalendarRowItem testID="calendar-disabled-cell" />
270
+ )
271
+ )}
272
+ {daysOfCurrentMonth.map((date) =>
273
+ date ? (
274
+ <CalendarRowItem
275
+ key={date.toDateString()}
276
+ date={date}
277
+ isCurrent={isEqDate(now, date)}
278
+ isSelected={isEqDate(value, date)}
279
+ onPress={() => onChange?.(date)}
280
+ marked={parsedMaskedDate[date.toDateString()]}
281
+ />
282
+ ) : (
283
+ <StyledDisabledCalendarRowItem testID="calendar-disabled-cell" />
284
+ )
285
+ )}
286
+ {daysOfNextMonth.map((date) =>
287
+ date ? (
288
+ <CalendarRowItem
289
+ key={date.toDateString()}
290
+ date={date}
291
+ isCurrent={isEqDate(now, date)}
292
+ isSelected={isEqDate(value, date)}
293
+ onPress={() => onChange?.(date)}
294
+ textIntent="subdued"
295
+ marked={parsedMaskedDate[date.toDateString()]}
296
+ />
297
+ ) : (
298
+ <StyledDisabledCalendarRowItem testID="calendar-disabled-cell" />
299
+ )
300
+ )}
301
+ </StyledCalendarRow>
302
+
303
+ {Platform.OS === 'android' && monthPickerVisible && (
304
+ <MonthYearPickerDialogueAndroid
305
+ doneButtonLabel={monthPickerConfirmLabel}
306
+ cancelButtonLabel={monthPickerCancelLabel}
307
+ value={value}
308
+ minimumDate={minDate}
309
+ maximumDate={maxDate}
310
+ onChange={(action, date) => {
311
+ setMonthPickerVisible(false);
312
+
313
+ if (action === 'dateSetAction' && !!date) {
314
+ onMonthChange(date);
315
+ }
316
+ }}
213
317
  />
214
- ) : (
215
- <StyledDisabledCalendarRowItem testID="calendar-disabled-cell" />
216
- )
217
- )}
218
- </StyledCalendarRow>
318
+ )}
319
+ </Box>
320
+ )}
219
321
  </StyledContainer>
220
322
  );
221
323
  };
@@ -20,7 +20,10 @@ const DatePickerAndroid = ({
20
20
  helpText,
21
21
  style,
22
22
  testID,
23
- }: Omit<DatePickerProps, 'variant'>) => {
23
+ }: Omit<
24
+ DatePickerProps,
25
+ 'variant' | 'monthPickerConfirmLabel' | 'monthPickerCancelLabel'
26
+ >) => {
24
27
  const [open, setOpen] = useState(false);
25
28
 
26
29
  const displayValue = value ? formatDate(displayFormat, value) : '';
@@ -13,7 +13,17 @@ const InternalCalendar = ({
13
13
  maxDate,
14
14
  value,
15
15
  onChange,
16
- }: Pick<CalendarProps, 'minDate' | 'maxDate' | 'value' | 'onChange'>) => {
16
+ monthPickerConfirmLabel,
17
+ monthPickerCancelLabel,
18
+ }: Pick<
19
+ CalendarProps,
20
+ | 'minDate'
21
+ | 'maxDate'
22
+ | 'value'
23
+ | 'onChange'
24
+ | 'monthPickerConfirmLabel'
25
+ | 'monthPickerCancelLabel'
26
+ >) => {
17
27
  const [visibleDate, setVisibleDate] = useState(value || new Date());
18
28
  const [selectingDate, setSelectingDate] = useState<Date>(value || new Date());
19
29
  return (
@@ -39,6 +49,9 @@ const InternalCalendar = ({
39
49
  }
40
50
  minDate={minDate}
41
51
  maxDate={maxDate}
52
+ onMonthChange={(date) => setVisibleDate(date)}
53
+ monthPickerConfirmLabel={monthPickerConfirmLabel}
54
+ monthPickerCancelLabel={monthPickerCancelLabel}
42
55
  />
43
56
  );
44
57
  };
@@ -58,6 +71,8 @@ const DatePickerCalendar = ({
58
71
  helpText,
59
72
  style,
60
73
  testID,
74
+ monthPickerConfirmLabel,
75
+ monthPickerCancelLabel,
61
76
  }: Omit<DatePickerProps, 'variant'>) => {
62
77
  const [open, setOpen] = useState(false);
63
78
 
@@ -101,6 +116,8 @@ const DatePickerCalendar = ({
101
116
  maxDate={maxDate}
102
117
  value={value}
103
118
  onChange={setSelectingDate}
119
+ monthPickerConfirmLabel={monthPickerConfirmLabel}
120
+ monthPickerCancelLabel={monthPickerCancelLabel}
104
121
  />
105
122
  </BottomSheet>
106
123
  </TouchableOpacity>
@@ -41,7 +41,10 @@ const DatePickerIOS = ({
41
41
  helpText,
42
42
  style,
43
43
  testID,
44
- }: Omit<DatePickerProps, 'variant'>) => {
44
+ }: Omit<
45
+ DatePickerProps,
46
+ 'variant' | 'monthPickerConfirmLabel' | 'monthPickerCancelLabel'
47
+ >) => {
45
48
  const [selectingDate, setSelectingDate] = useState<Date>(
46
49
  getInitialDateValue(value || new Date(), minDate, maxDate)
47
50
  );
@@ -58,4 +58,43 @@ describe('DatePickerCalendar', () => {
58
58
 
59
59
  expect(getByText('This is help text')).toBeTruthy();
60
60
  });
61
+
62
+ it.each`
63
+ platform
64
+ ${'ios'}
65
+ ${'android'}
66
+ `('renders month year picker when pressing on title', ({ platform }) => {
67
+ jest.mock('react-native/Libraries/Utilities/Platform', () => ({
68
+ OS: platform,
69
+ select: () => null,
70
+ }));
71
+
72
+ const { queryByText, getByText, getByTestId } = renderWithTheme(
73
+ <DatePickerCalendar
74
+ value={new Date('December 21, 1995')}
75
+ label="Start date"
76
+ confirmLabel="Confirm"
77
+ helpText="This is help text"
78
+ onChange={jest.fn()}
79
+ />
80
+ );
81
+
82
+ fireEvent.press(getByText('Start date'));
83
+ fireEvent.press(getByText('December 1995'));
84
+
85
+ if (platform === 'ios') {
86
+ expect(queryByText('IOS picker')).toBeDefined();
87
+ } else {
88
+ expect(queryByText('Android picker')).toBeDefined();
89
+ }
90
+
91
+ // Selecting month
92
+ fireEvent(
93
+ getByTestId('calendar'),
94
+ 'onMonthChange',
95
+ new Date('January 17, 1993')
96
+ );
97
+
98
+ expect(queryByText('January 1993')).toBeDefined();
99
+ });
61
100
  });
@@ -64,4 +64,12 @@ export interface DatePickerProps {
64
64
  * Testing id of the component.
65
65
  */
66
66
  testID?: string;
67
+ /**
68
+ * Calendar variant prop. Label for the confirm button of the month picker, Android only.
69
+ */
70
+ monthPickerConfirmLabel?: string;
71
+ /**
72
+ * Calendar variant prop. Label for the cancel button of the month picker, Android only.
73
+ */
74
+ monthPickerCancelLabel?: string;
67
75
  }
@@ -262,7 +262,9 @@ Object {
262
262
  },
263
263
  "space": Object {
264
264
  "headerHorizontalPadding": 12,
265
+ "headerMarginRight": 8,
265
266
  "headerVerticalPadding": 16,
267
+ "iosPickerMarginVertical": 64,
266
268
  "rowVerticalPadding": 16,
267
269
  },
268
270
  },
@@ -24,6 +24,8 @@ const getCalendarTheme = (theme: GlobalTheme) => {
24
24
  rowVerticalPadding: theme.space.medium,
25
25
  headerVerticalPadding: theme.space.medium,
26
26
  headerHorizontalPadding: theme.space.smallMedium,
27
+ headerMarginRight: theme.space.small,
28
+ iosPickerMarginVertical: theme.space['5xlarge'],
27
29
  };
28
30
 
29
31
  const radii = {
@@ -107,6 +107,25 @@ jest.mock('react-native-gesture-handler', () => {
107
107
  };
108
108
  });
109
109
 
110
+ jest.mock("@hero-design/react-native-month-year-picker", () => {
111
+ const React = jest.requireActual("react");
112
+ const { View, Text } = jest.requireActual("react-native");
113
+
114
+ return {
115
+ MonthYearPickerDialogueAndroid: () => (
116
+ <View>
117
+ <Text>Android picker</Text>
118
+ </View>
119
+ ),
120
+ MonthYearPickerViewIOS: () => (
121
+ <View>
122
+ <Text>IOS picker</Text>
123
+ </View>
124
+ ),
125
+ };
126
+ });
127
+
128
+
110
129
  jest.mock('react-native/Libraries/Utilities/BackHandler', () => {
111
130
  return jest.requireActual(
112
131
  'react-native/Libraries/Utilities/__mocks__/BackHandler.js',
@@ -39,6 +39,18 @@ export interface CalendarProps {
39
39
  * Testing id of the component.
40
40
  */
41
41
  testID?: string;
42
+ /**
43
+ * Callback when header month is changed, passing this prop will disable onTitlePress.
44
+ */
45
+ onMonthChange?: (date: Date) => void;
46
+ /**
47
+ * Label for the confirm button of the month picker, Android only.
48
+ */
49
+ monthPickerConfirmLabel?: string;
50
+ /**
51
+ * Label for the cancel button of the month picker, Android only.
52
+ */
53
+ monthPickerCancelLabel?: string;
42
54
  }
43
- declare const Calendar: ({ value, visibleDate, onChange, onPreviousPress, onNextPress, onTitlePress, minDate, maxDate, markedDates, testID, }: CalendarProps) => JSX.Element;
55
+ declare const Calendar: ({ value, visibleDate, onChange, onPreviousPress, onNextPress, onTitlePress, minDate, maxDate, markedDates, testID, onMonthChange, monthPickerConfirmLabel, monthPickerCancelLabel, }: CalendarProps) => JSX.Element;
44
56
  export default Calendar;
@@ -1,3 +1,3 @@
1
1
  import type { DatePickerProps } from './types';
2
- declare const DatePickerAndroid: ({ value, minDate, maxDate, label, placeholder, onChange, displayFormat, disabled, required, error, helpText, style, testID, }: Omit<DatePickerProps, 'variant'>) => JSX.Element;
2
+ declare const DatePickerAndroid: ({ value, minDate, maxDate, label, placeholder, onChange, displayFormat, disabled, required, error, helpText, style, testID, }: Omit<DatePickerProps, 'variant' | 'monthPickerConfirmLabel' | 'monthPickerCancelLabel'>) => JSX.Element;
3
3
  export default DatePickerAndroid;
@@ -1,3 +1,3 @@
1
1
  import type { DatePickerProps } from './types';
2
- declare const DatePickerCalendar: ({ value, minDate, maxDate, label, placeholder, onChange, confirmLabel, displayFormat, disabled, required, error, helpText, style, testID, }: Omit<DatePickerProps, 'variant'>) => JSX.Element;
2
+ declare const DatePickerCalendar: ({ value, minDate, maxDate, label, placeholder, onChange, confirmLabel, displayFormat, disabled, required, error, helpText, style, testID, monthPickerConfirmLabel, monthPickerCancelLabel, }: Omit<DatePickerProps, 'variant'>) => JSX.Element;
3
3
  export default DatePickerCalendar;
@@ -1,4 +1,4 @@
1
1
  import type { DatePickerProps } from './types';
2
2
  export declare const getInitialDateValue: (value: Date, minDate?: Date | undefined, maxDate?: Date | undefined) => Date;
3
- declare const DatePickerIOS: ({ value, minDate, maxDate, label, placeholder, onChange, confirmLabel, displayFormat, disabled, required, error, helpText, style, testID, }: Omit<DatePickerProps, 'variant'>) => JSX.Element;
3
+ declare const DatePickerIOS: ({ value, minDate, maxDate, label, placeholder, onChange, confirmLabel, displayFormat, disabled, required, error, helpText, style, testID, }: Omit<DatePickerProps, 'variant' | 'monthPickerConfirmLabel' | 'monthPickerCancelLabel'>) => JSX.Element;
4
4
  export default DatePickerIOS;
@@ -63,4 +63,12 @@ export interface DatePickerProps {
63
63
  * Testing id of the component.
64
64
  */
65
65
  testID?: string;
66
+ /**
67
+ * Calendar variant prop. Label for the confirm button of the month picker, Android only.
68
+ */
69
+ monthPickerConfirmLabel?: string;
70
+ /**
71
+ * Calendar variant prop. Label for the cancel button of the month picker, Android only.
72
+ */
73
+ monthPickerCancelLabel?: string;
66
74
  }
@@ -21,6 +21,8 @@ declare const getCalendarTheme: (theme: GlobalTheme) => {
21
21
  rowVerticalPadding: number;
22
22
  headerVerticalPadding: number;
23
23
  headerHorizontalPadding: number;
24
+ headerMarginRight: number;
25
+ iosPickerMarginVertical: number;
24
26
  };
25
27
  };
26
28
  export default getCalendarTheme;