@snack-uikit/calendar 0.9.0 → 0.11.0

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +20 -5
  3. package/dist/cjs/components/Calendar/Calendar.d.ts +22 -8
  4. package/dist/cjs/components/Calendar/Calendar.js +3 -3
  5. package/dist/cjs/components/Calendar/utils.d.ts +1 -2
  6. package/dist/cjs/components/Calendar/utils.js +15 -20
  7. package/dist/cjs/components/TimePicker/TimePicker.d.ts +34 -0
  8. package/dist/cjs/components/TimePicker/TimePicker.js +149 -0
  9. package/dist/cjs/components/TimePicker/index.d.ts +1 -0
  10. package/dist/cjs/components/TimePicker/index.js +25 -0
  11. package/dist/cjs/components/TimePicker/styles.module.css +12 -0
  12. package/dist/cjs/components/index.d.ts +1 -0
  13. package/dist/cjs/components/index.js +2 -1
  14. package/dist/cjs/constants.d.ts +5 -0
  15. package/dist/cjs/constants.js +7 -2
  16. package/dist/cjs/helperComponents/Button/Button.js +7 -6
  17. package/dist/cjs/helperComponents/Button/styles.module.css +13 -13
  18. package/dist/cjs/helperComponents/CalendarBase/CalendarBase.d.ts +6 -3
  19. package/dist/cjs/helperComponents/CalendarBase/CalendarBase.js +86 -38
  20. package/dist/cjs/helperComponents/CalendarBase/hooks.d.ts +10 -1
  21. package/dist/cjs/helperComponents/CalendarBase/hooks.js +30 -0
  22. package/dist/cjs/helperComponents/CalendarBase/styles.module.css +36 -12
  23. package/dist/cjs/helperComponents/CalendarContext/CalendarContext.d.ts +23 -6
  24. package/dist/cjs/helperComponents/CalendarContext/CalendarContext.js +18 -2
  25. package/dist/cjs/helperComponents/ColumnLabels/styles.module.css +3 -3
  26. package/dist/cjs/helperComponents/Footer/Footer.d.ts +1 -0
  27. package/dist/cjs/helperComponents/Footer/Footer.js +126 -0
  28. package/dist/cjs/helperComponents/Footer/index.d.ts +1 -0
  29. package/dist/cjs/helperComponents/Footer/index.js +25 -0
  30. package/dist/cjs/helperComponents/Footer/styles.module.css +49 -0
  31. package/dist/cjs/helperComponents/Item/Item.js +3 -2
  32. package/dist/cjs/helperComponents/Item/hooks.d.ts +2 -2
  33. package/dist/cjs/helperComponents/Item/hooks.js +6 -4
  34. package/dist/cjs/helperComponents/MonthView/MonthView.js +38 -3
  35. package/dist/cjs/helperComponents/TimeList/TimeList.d.ts +15 -0
  36. package/dist/cjs/helperComponents/TimeList/TimeList.js +65 -0
  37. package/dist/cjs/helperComponents/TimeList/index.d.ts +1 -0
  38. package/dist/cjs/helperComponents/TimeList/index.js +25 -0
  39. package/dist/cjs/helperComponents/TimePickerBase/TimePickerBase.d.ts +3 -0
  40. package/dist/cjs/helperComponents/TimePickerBase/TimePickerBase.js +190 -0
  41. package/dist/cjs/helperComponents/TimePickerBase/index.d.ts +1 -0
  42. package/dist/cjs/helperComponents/TimePickerBase/index.js +25 -0
  43. package/dist/cjs/helperComponents/TimePickerBase/styles.module.css +103 -0
  44. package/dist/cjs/hooks.d.ts +17 -2
  45. package/dist/cjs/hooks.js +119 -9
  46. package/dist/cjs/types.d.ts +12 -0
  47. package/dist/cjs/utils.d.ts +1 -1
  48. package/dist/cjs/utils.js +6 -4
  49. package/dist/esm/components/Calendar/Calendar.d.ts +22 -8
  50. package/dist/esm/components/Calendar/Calendar.js +3 -3
  51. package/dist/esm/components/Calendar/utils.d.ts +1 -2
  52. package/dist/esm/components/Calendar/utils.js +11 -11
  53. package/dist/esm/components/TimePicker/TimePicker.d.ts +34 -0
  54. package/dist/esm/components/TimePicker/TimePicker.js +86 -0
  55. package/dist/esm/components/TimePicker/index.d.ts +1 -0
  56. package/dist/esm/components/TimePicker/index.js +1 -0
  57. package/dist/esm/components/TimePicker/styles.module.css +12 -0
  58. package/dist/esm/components/index.d.ts +1 -0
  59. package/dist/esm/components/index.js +1 -0
  60. package/dist/esm/constants.d.ts +5 -0
  61. package/dist/esm/constants.js +5 -0
  62. package/dist/esm/helperComponents/Button/Button.js +8 -7
  63. package/dist/esm/helperComponents/Button/styles.module.css +13 -13
  64. package/dist/esm/helperComponents/CalendarBase/CalendarBase.d.ts +6 -3
  65. package/dist/esm/helperComponents/CalendarBase/CalendarBase.js +37 -22
  66. package/dist/esm/helperComponents/CalendarBase/hooks.d.ts +10 -1
  67. package/dist/esm/helperComponents/CalendarBase/hooks.js +20 -0
  68. package/dist/esm/helperComponents/CalendarBase/styles.module.css +36 -12
  69. package/dist/esm/helperComponents/CalendarContext/CalendarContext.d.ts +23 -6
  70. package/dist/esm/helperComponents/CalendarContext/CalendarContext.js +15 -1
  71. package/dist/esm/helperComponents/ColumnLabels/styles.module.css +3 -3
  72. package/dist/esm/helperComponents/Footer/Footer.d.ts +1 -0
  73. package/dist/esm/helperComponents/Footer/Footer.js +63 -0
  74. package/dist/esm/helperComponents/Footer/index.d.ts +1 -0
  75. package/dist/esm/helperComponents/Footer/index.js +1 -0
  76. package/dist/esm/helperComponents/Footer/styles.module.css +49 -0
  77. package/dist/esm/helperComponents/Item/Item.js +2 -2
  78. package/dist/esm/helperComponents/Item/hooks.d.ts +2 -2
  79. package/dist/esm/helperComponents/Item/hooks.js +6 -5
  80. package/dist/esm/helperComponents/MonthView/MonthView.js +31 -3
  81. package/dist/esm/helperComponents/TimeList/TimeList.d.ts +15 -0
  82. package/dist/esm/helperComponents/TimeList/TimeList.js +37 -0
  83. package/dist/esm/helperComponents/TimeList/index.d.ts +1 -0
  84. package/dist/esm/helperComponents/TimeList/index.js +1 -0
  85. package/dist/esm/helperComponents/TimePickerBase/TimePickerBase.d.ts +3 -0
  86. package/dist/esm/helperComponents/TimePickerBase/TimePickerBase.js +113 -0
  87. package/dist/esm/helperComponents/TimePickerBase/index.d.ts +1 -0
  88. package/dist/esm/helperComponents/TimePickerBase/index.js +1 -0
  89. package/dist/esm/helperComponents/TimePickerBase/styles.module.css +103 -0
  90. package/dist/esm/hooks.d.ts +17 -2
  91. package/dist/esm/hooks.js +109 -8
  92. package/dist/esm/types.d.ts +12 -0
  93. package/dist/esm/utils.d.ts +1 -1
  94. package/dist/esm/utils.js +4 -2
  95. package/package.json +6 -3
  96. package/src/components/Calendar/Calendar.tsx +27 -12
  97. package/src/components/Calendar/utils.ts +17 -10
  98. package/src/components/TimePicker/TimePicker.tsx +166 -0
  99. package/src/components/TimePicker/index.ts +1 -0
  100. package/src/components/TimePicker/styles.module.scss +13 -0
  101. package/src/components/index.ts +1 -0
  102. package/src/constants.ts +7 -0
  103. package/src/helperComponents/Button/Button.tsx +8 -7
  104. package/src/helperComponents/Button/styles.module.scss +12 -12
  105. package/src/helperComponents/CalendarBase/CalendarBase.tsx +78 -43
  106. package/src/helperComponents/CalendarBase/hooks.ts +31 -1
  107. package/src/helperComponents/CalendarBase/styles.module.scss +37 -14
  108. package/src/helperComponents/CalendarContext/CalendarContext.tsx +47 -7
  109. package/src/helperComponents/ColumnLabel/styles.module.scss +6 -6
  110. package/src/helperComponents/ColumnLabels/styles.module.scss +2 -2
  111. package/src/helperComponents/Footer/Footer.tsx +129 -0
  112. package/src/helperComponents/Footer/index.ts +1 -0
  113. package/src/helperComponents/Footer/styles.module.scss +31 -0
  114. package/src/helperComponents/Item/Item.tsx +2 -1
  115. package/src/helperComponents/Item/hooks.ts +8 -6
  116. package/src/helperComponents/Item/styles.module.scss +29 -29
  117. package/src/helperComponents/MonthView/MonthView.tsx +43 -2
  118. package/src/helperComponents/TimeList/TimeList.tsx +69 -0
  119. package/src/helperComponents/TimeList/index.ts +1 -0
  120. package/src/helperComponents/TimePickerBase/TimePickerBase.tsx +202 -0
  121. package/src/helperComponents/TimePickerBase/index.ts +1 -0
  122. package/src/helperComponents/TimePickerBase/styles.module.scss +76 -0
  123. package/src/hooks.ts +139 -6
  124. package/src/types.ts +15 -0
  125. package/src/utils.ts +4 -3
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useContext, useMemo } from 'react';
3
+ import { Divider } from '@snack-uikit/divider';
4
+ import { getDefaultItemId } from '@snack-uikit/list';
5
+ import { useLocale } from '@snack-uikit/locale';
6
+ import { AUTOFOCUS, HOURS, MINUTES, SECONDS } from '../../constants';
7
+ import { CalendarContext } from '../CalendarContext';
8
+ import { TimeList } from '../TimeList';
9
+ import styles from './styles.module.css';
10
+ export function TimePickerBase({ showDivider = true }) {
11
+ const { t } = useLocale('Calendar');
12
+ const { size, mode, fitToContainer, showSeconds, dateAndTime, onTimeChange, applyButtonRef, currentButtonRef, hoursKeyboardNavigationRef, minutesKeyboardNavigationRef, secondsKeyboardNavigationRef, setFocus, getTestId, navigationStartRef, onFocusLeave, } = useContext(CalendarContext);
13
+ const hours = dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.hours;
14
+ const minutes = dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.minutes;
15
+ const seconds = dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.seconds;
16
+ const getTimeChangeHandler = useCallback((propName) => (value) => {
17
+ var _a;
18
+ const timeValues = Object.assign({ hours: 0, minutes: 0, seconds: 0 }, dateAndTime);
19
+ onTimeChange(Object.assign(Object.assign({}, timeValues), { [propName]: (_a = value !== null && value !== void 0 ? value : dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime[propName]) !== null && _a !== void 0 ? _a : 0 }));
20
+ }, [dateAndTime, onTimeChange]);
21
+ const onHoursChange = useMemo(() => getTimeChangeHandler('hours'), [getTimeChangeHandler]);
22
+ const onMinutesChange = useMemo(() => getTimeChangeHandler('minutes'), [getTimeChangeHandler]);
23
+ const onSecondsChange = useMemo(() => getTimeChangeHandler('seconds'), [getTimeChangeHandler]);
24
+ const onHourKeyDownGetter = useCallback(id => event => {
25
+ var _a, _b;
26
+ switch (event.key) {
27
+ case 'Tab':
28
+ if (mode === 'time' && event.shiftKey) {
29
+ onFocusLeave === null || onFocusLeave === void 0 ? void 0 : onFocusLeave('prev');
30
+ break;
31
+ }
32
+ event.stopPropagation();
33
+ event.preventDefault();
34
+ if (event.shiftKey) {
35
+ setFocus(AUTOFOCUS);
36
+ }
37
+ else {
38
+ (_a = minutesKeyboardNavigationRef.current) === null || _a === void 0 ? void 0 : _a.focusItem(getDefaultItemId(minutes !== null && minutes !== void 0 ? minutes : 0));
39
+ }
40
+ break;
41
+ case 'Enter':
42
+ (_b = minutesKeyboardNavigationRef.current) === null || _b === void 0 ? void 0 : _b.focusItem(getDefaultItemId(minutes !== null && minutes !== void 0 ? minutes : 0));
43
+ break;
44
+ case 'ArrowUp':
45
+ if (mode === 'time' && id === 0) {
46
+ onFocusLeave === null || onFocusLeave === void 0 ? void 0 : onFocusLeave('prev');
47
+ break;
48
+ }
49
+ break;
50
+ default:
51
+ break;
52
+ }
53
+ }, [minutes, minutesKeyboardNavigationRef, mode, onFocusLeave, setFocus]);
54
+ const onMinuteKeyDownGetter = useCallback(() => event => {
55
+ var _a, _b, _c, _d, _e;
56
+ switch (event.key) {
57
+ case 'Tab':
58
+ event.stopPropagation();
59
+ event.preventDefault();
60
+ if (event.shiftKey) {
61
+ (_a = hoursKeyboardNavigationRef.current) === null || _a === void 0 ? void 0 : _a.focusItem(getDefaultItemId(hours !== null && hours !== void 0 ? hours : 0));
62
+ }
63
+ else {
64
+ if (showSeconds) {
65
+ (_b = secondsKeyboardNavigationRef.current) === null || _b === void 0 ? void 0 : _b.focusItem(getDefaultItemId(seconds !== null && seconds !== void 0 ? seconds : 0));
66
+ }
67
+ else {
68
+ (_c = currentButtonRef.current) === null || _c === void 0 ? void 0 : _c.focus();
69
+ }
70
+ }
71
+ break;
72
+ case 'Enter':
73
+ if (showSeconds) {
74
+ (_d = secondsKeyboardNavigationRef.current) === null || _d === void 0 ? void 0 : _d.focusItem(getDefaultItemId(seconds !== null && seconds !== void 0 ? seconds : 0));
75
+ }
76
+ else {
77
+ (_e = applyButtonRef.current) === null || _e === void 0 ? void 0 : _e.focus();
78
+ }
79
+ break;
80
+ default:
81
+ break;
82
+ }
83
+ }, [
84
+ applyButtonRef,
85
+ currentButtonRef,
86
+ hours,
87
+ hoursKeyboardNavigationRef,
88
+ seconds,
89
+ secondsKeyboardNavigationRef,
90
+ showSeconds,
91
+ ]);
92
+ const onSecondKeyDownGetter = useCallback(() => event => {
93
+ var _a, _b, _c;
94
+ switch (event.key) {
95
+ case 'Tab':
96
+ event.stopPropagation();
97
+ event.preventDefault();
98
+ if (event.shiftKey) {
99
+ (_a = minutesKeyboardNavigationRef.current) === null || _a === void 0 ? void 0 : _a.focusItem(getDefaultItemId(minutes !== null && minutes !== void 0 ? minutes : 0));
100
+ }
101
+ else {
102
+ (_b = currentButtonRef.current) === null || _b === void 0 ? void 0 : _b.focus();
103
+ }
104
+ break;
105
+ case 'Enter':
106
+ (_c = applyButtonRef.current) === null || _c === void 0 ? void 0 : _c.focus();
107
+ break;
108
+ default:
109
+ break;
110
+ }
111
+ }, [applyButtonRef, currentButtonRef, minutes, minutesKeyboardNavigationRef]);
112
+ return (_jsxs(_Fragment, { children: [showDivider && _jsx(Divider, { orientation: 'vertical', className: styles.divider }), _jsxs("div", { className: styles.timePicker, "data-size": size, "data-fit-to-container": fitToContainer || undefined, children: [_jsx("div", { className: styles.header, "data-size": size, children: _jsx("span", { className: styles.title, children: t('time') }) }), _jsxs("div", { className: styles.timeListsWrapper, "data-size": size, "data-show-seconds": showSeconds || undefined, children: [_jsx(TimeList, { value: hours, onChange: onHoursChange, "data-test-id": getTestId('hours'), numberOfItems: HOURS, onKeyDownGetter: onHourKeyDownGetter, keyboardNavigationRef: hoursKeyboardNavigationRef, navigationStartRef: mode === 'time' ? navigationStartRef : undefined }), _jsx(Divider, { className: styles.divider, orientation: 'vertical' }), _jsx(TimeList, { value: minutes, onChange: onMinutesChange, "data-test-id": getTestId('minutes'), numberOfItems: MINUTES, onKeyDownGetter: onMinuteKeyDownGetter, keyboardNavigationRef: minutesKeyboardNavigationRef }), showSeconds && (_jsxs(_Fragment, { children: [_jsx(Divider, { className: styles.divider, orientation: 'vertical' }), _jsx(TimeList, { value: seconds, onChange: onSecondsChange, "data-test-id": getTestId('seconds'), numberOfItems: SECONDS, onKeyDownGetter: onSecondKeyDownGetter, keyboardNavigationRef: secondsKeyboardNavigationRef })] }))] })] })] }));
113
+ }
@@ -0,0 +1 @@
1
+ export * from './TimePickerBase';
@@ -0,0 +1 @@
1
+ export * from './TimePickerBase';
@@ -0,0 +1,103 @@
1
+ .divider{
2
+ flex-shrink:0;
3
+ height:auto;
4
+ }
5
+
6
+ .timePicker{
7
+ overflow:hidden;
8
+ display:flex;
9
+ flex-direction:column;
10
+ box-sizing:border-box;
11
+ max-width:100%;
12
+ }
13
+ .timePicker[data-size=s]{
14
+ min-width:var(--size-calendar-time-min-width-s, 120px);
15
+ max-height:var(--size-calendar-container-min-height-s, 256px);
16
+ }
17
+ .timePicker[data-size=s][data-fit-to-container]{
18
+ max-height:100%;
19
+ }
20
+ .timePicker[data-size=m]{
21
+ min-width:var(--size-calendar-time-min-width-m, 144px);
22
+ max-height:var(--size-calendar-container-min-height-m, 320px);
23
+ }
24
+ .timePicker[data-size=m][data-fit-to-container]{
25
+ max-height:100%;
26
+ }
27
+ .timePicker[data-size=l]{
28
+ min-width:var(--size-calendar-time-min-width-l, 168px);
29
+ max-height:var(--size-calendar-container-min-height-l, 384px);
30
+ }
31
+ .timePicker[data-size=l][data-fit-to-container]{
32
+ max-height:100%;
33
+ }
34
+
35
+ .timeListsWrapper{
36
+ display:grid;
37
+ grid-template-columns:1fr 1px 1fr;
38
+ min-height:0;
39
+ }
40
+ .timeListsWrapper[data-show-seconds]{
41
+ grid-template-columns:1fr 1px 1fr 1px 1fr;
42
+ }
43
+ .timeListsWrapper[data-size=s]{
44
+ max-height:calc(100% - var(--size-calendar-container-header-lines-height-s, 32px));
45
+ }
46
+ .timeListsWrapper[data-size=m]{
47
+ max-height:calc(100% - var(--size-calendar-container-header-lines-height-m, 40px));
48
+ }
49
+ .timeListsWrapper[data-size=l]{
50
+ max-height:calc(100% - var(--size-calendar-container-header-lines-height-l, 48px));
51
+ }
52
+
53
+ .title{
54
+ display:flex;
55
+ align-items:center;
56
+ }
57
+
58
+ .header{
59
+ display:flex;
60
+ flex-grow:0;
61
+ flex-shrink:0;
62
+ align-items:center;
63
+ color:var(--sys-neutral-text-main, #41424e);
64
+ }
65
+ .header[data-size=s]{
66
+ padding-left:var(--space-calendar-container-s, 8px);
67
+ padding-right:var(--space-calendar-container-s, 8px);
68
+ font-family:var(--sans-label-m-font-family, SB Sans Interface);
69
+ font-weight:var(--sans-label-m-font-weight, Semibold);
70
+ line-height:var(--sans-label-m-line-height, 16px);
71
+ font-size:var(--sans-label-m-font-size, 12px);
72
+ letter-spacing:var(--sans-label-m-letter-spacing, 0px);
73
+ paragraph-spacing:var(--sans-label-m-paragraph-spacing, 6.6px);
74
+ }
75
+ .header[data-size=s] .title{
76
+ height:var(--size-calendar-container-header-lines-height-s, 32px);
77
+ }
78
+ .header[data-size=m]{
79
+ padding-left:var(--space-calendar-container-m, 8px);
80
+ padding-right:var(--space-calendar-container-m, 8px);
81
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
82
+ font-weight:var(--sans-label-l-font-weight, Semibold);
83
+ line-height:var(--sans-label-l-line-height, 20px);
84
+ font-size:var(--sans-label-l-font-size, 14px);
85
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
86
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
87
+ }
88
+ .header[data-size=m] .title{
89
+ height:var(--size-calendar-container-header-lines-height-m, 40px);
90
+ }
91
+ .header[data-size=l]{
92
+ padding-left:var(--space-calendar-container-l, 8px);
93
+ padding-right:var(--space-calendar-container-l, 8px);
94
+ font-family:var(--sans-label-l-font-family, SB Sans Interface);
95
+ font-weight:var(--sans-label-l-font-weight, Semibold);
96
+ line-height:var(--sans-label-l-line-height, 20px);
97
+ font-size:var(--sans-label-l-font-size, 14px);
98
+ letter-spacing:var(--sans-label-l-letter-spacing, 0px);
99
+ paragraph-spacing:var(--sans-label-l-paragraph-spacing, 7.7px);
100
+ }
101
+ .header[data-size=l] .title{
102
+ height:var(--size-calendar-container-header-lines-height-l, 48px);
103
+ }
@@ -1,4 +1,5 @@
1
- import { BaseGrid, Cell } from './types';
1
+ import { KeyboardEventHandler } from 'react';
2
+ import { BaseGrid, Cell, DateAndTime, Range, TimeValue } from './types';
2
3
  export type UseGridParams = {
3
4
  buildGrid(viewDate: Date): BaseGrid;
4
5
  isTheSameItem(date1: Date, date2: Date): boolean;
@@ -7,5 +8,19 @@ export type UseGridParams = {
7
8
  onSelect?(date: Date): void;
8
9
  onPreselect?(date: Date): void;
9
10
  onLeave?(): void;
11
+ onKeyDown?: KeyboardEventHandler;
12
+ };
13
+ export declare function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameItem, getItemLabel, isInPeriod, onKeyDown, }: UseGridParams): Cell[][];
14
+ export declare function useDateAndTime({ showSeconds, value, }: {
15
+ showSeconds?: boolean;
16
+ value: Range | TimeValue | undefined;
17
+ }): {
18
+ dateAndTime: DateAndTime;
19
+ setDateAndTime: import("react").Dispatch<import("react").SetStateAction<DateAndTime>>;
20
+ isDateAndTimeFilled: () => boolean;
21
+ isTimeFilled: () => boolean;
22
+ isDateFilled: () => boolean;
23
+ onDateChange: (value: Pick<DateAndTime, "year" | "month" | "day"> | Date) => void;
24
+ onTimeChange: (value: TimeValue | Date) => void;
25
+ onDateAndTimeChange: (value: DateAndTime | Date) => void;
10
26
  };
11
- export declare function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameItem, getItemLabel, isInPeriod, }: UseGridParams): Cell[][];
package/dist/esm/hooks.js CHANGED
@@ -1,15 +1,16 @@
1
- import { useContext, useMemo } from 'react';
1
+ import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
2
+ import { CALENDAR_MODE, IN_RANGE_POSITION } from './constants';
2
3
  import { CalendarContext } from './helperComponents/CalendarContext';
3
4
  import { stringifyAddress } from './helperComponents/Item/utils';
4
5
  import { getInRangePosition, isWeekend } from './utils';
5
- export function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameItem, getItemLabel, isInPeriod, }) {
6
- const { today, showHolidays, preselectedRange, value, viewDate, viewMode, focus, buildCellProps, firstNotDisableCell, } = useContext(CalendarContext);
6
+ export function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameItem, getItemLabel, isInPeriod, onKeyDown, }) {
7
+ const { today, showHolidays, preselectedRange, value, dateAndTime, mode, viewDate, viewMode, focus, buildCellProps, firstNotDisableCell, isDateFilled, } = useContext(CalendarContext);
7
8
  return useMemo(() => {
8
9
  let hasFocusInGrid = false;
9
10
  let currentItem;
10
11
  let hasFoundFirstNotDisableCell = false;
11
12
  const result = buildGrid(viewDate).map(row => row.map(({ date, address }) => {
12
- var _a;
13
+ var _a, _b, _c, _d;
13
14
  let isDisabled = false;
14
15
  let isHoliday = undefined;
15
16
  let cellProps = { isDisabled, isHoliday };
@@ -27,12 +28,20 @@ export function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameIt
27
28
  hasFoundFirstNotDisableCell = true;
28
29
  }
29
30
  }
30
- const inRangePosition = getInRangePosition(date, viewMode, preselectedRange || value);
31
- const isSelectedValue = value && !preselectedRange ? isTheSameItem(value[0], date) || isTheSameItem(value[1], date) : false;
31
+ const dateTimeSelectedValue = isDateFilled()
32
+ ? new Date((_b = dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.year) !== null && _b !== void 0 ? _b : 0, (_c = dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.month) !== null && _c !== void 0 ? _c : 0, (_d = dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.day) !== null && _d !== void 0 ? _d : 0)
33
+ : undefined;
34
+ const inRangePosition = mode === CALENDAR_MODE.Range
35
+ ? getInRangePosition(date, viewMode, preselectedRange || value)
36
+ : IN_RANGE_POSITION.Out;
37
+ const isSelectedValue = value && !preselectedRange && !dateTimeSelectedValue
38
+ ? isTheSameItem(value[0], date) || isTheSameItem(value[1], date)
39
+ : false;
32
40
  const isPreselected = preselectedRange ? isTheSameItem(preselectedRange[0], date) : false;
41
+ const isDateTimeValueSelected = dateTimeSelectedValue ? isTheSameItem(dateTimeSelectedValue, date) : false;
33
42
  const tabIndex = focus && stringifyAddress(address) === focus ? 0 : -1;
34
43
  hasFocusInGrid = tabIndex === 0 || hasFocusInGrid;
35
- const isCurrent = isTheSameItem(today, date);
44
+ const isCurrent = isTheSameItem(today || new Date(), date);
36
45
  const cell = {
37
46
  date,
38
47
  onLeave,
@@ -45,8 +54,9 @@ export function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameIt
45
54
  onPreselect,
46
55
  inRangePosition,
47
56
  label: getItemLabel(date),
48
- isSelected: isSelectedValue || isPreselected,
57
+ isSelected: isSelectedValue || isPreselected || isDateTimeValueSelected,
49
58
  isInCurrentLevelPeriod: isInPeriod(viewDate, date),
59
+ onKeyDown,
50
60
  };
51
61
  if (isCurrent) {
52
62
  currentItem = cell;
@@ -60,11 +70,17 @@ export function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameIt
60
70
  }, [
61
71
  buildCellProps,
62
72
  buildGrid,
73
+ dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.day,
74
+ dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.month,
75
+ dateAndTime === null || dateAndTime === void 0 ? void 0 : dateAndTime.year,
63
76
  firstNotDisableCell,
64
77
  focus,
65
78
  getItemLabel,
79
+ isDateFilled,
66
80
  isInPeriod,
67
81
  isTheSameItem,
82
+ mode,
83
+ onKeyDown,
68
84
  onLeave,
69
85
  onPreselect,
70
86
  onSelect,
@@ -76,3 +92,88 @@ export function useGrid({ onSelect, onPreselect, onLeave, buildGrid, isTheSameIt
76
92
  viewMode,
77
93
  ]);
78
94
  }
95
+ export function useDateAndTime({ showSeconds, value, }) {
96
+ const [dateAndTime, setDateAndTime] = useState(() => {
97
+ if (Array.isArray(value)) {
98
+ const initialValue = value[0];
99
+ return {
100
+ year: initialValue.getFullYear(),
101
+ month: initialValue.getMonth(),
102
+ day: initialValue.getDate(),
103
+ hours: initialValue.getHours(),
104
+ minutes: initialValue.getMinutes(),
105
+ seconds: initialValue.getSeconds(),
106
+ };
107
+ }
108
+ return {
109
+ year: undefined,
110
+ month: undefined,
111
+ day: undefined,
112
+ hours: value === null || value === void 0 ? void 0 : value.hours,
113
+ minutes: value === null || value === void 0 ? void 0 : value.minutes,
114
+ seconds: value === null || value === void 0 ? void 0 : value.seconds,
115
+ };
116
+ });
117
+ const isDateFilled = useCallback(() => {
118
+ const { year, month, day } = dateAndTime;
119
+ return [year, month, day].every(value => value !== undefined);
120
+ }, [dateAndTime]);
121
+ const isTimeFilled = useCallback(() => {
122
+ const { hours, minutes, seconds } = dateAndTime;
123
+ return [hours, minutes, ...(showSeconds ? [seconds] : [])].every(value => value !== undefined);
124
+ }, [dateAndTime, showSeconds]);
125
+ const isDateAndTimeFilled = useCallback(() => isTimeFilled() && isDateFilled(), [isDateFilled, isTimeFilled]);
126
+ const onDateChange = useCallback((value) => {
127
+ if (value instanceof Date) {
128
+ setDateAndTime(prevDate => (Object.assign(Object.assign({}, prevDate), { year: value.getFullYear(), month: value.getMonth(), day: value.getDate() })));
129
+ }
130
+ else {
131
+ setDateAndTime(prevDate => (Object.assign(Object.assign({}, prevDate), value)));
132
+ }
133
+ }, []);
134
+ const onTimeChange = useCallback((value) => {
135
+ if (value instanceof Date) {
136
+ setDateAndTime(prevDate => (Object.assign(Object.assign({}, prevDate), { hours: value.getHours(), minutes: value.getMinutes(), seconds: value.getSeconds() })));
137
+ }
138
+ else {
139
+ setDateAndTime(prevDate => (Object.assign(Object.assign({}, prevDate), value)));
140
+ }
141
+ }, []);
142
+ const onDateAndTimeChange = useCallback((value) => {
143
+ if (value instanceof Date) {
144
+ setDateAndTime({
145
+ year: value.getFullYear(),
146
+ month: value.getMonth(),
147
+ day: value.getDate(),
148
+ hours: value.getHours(),
149
+ minutes: value.getMinutes(),
150
+ seconds: value.getSeconds(),
151
+ });
152
+ }
153
+ else {
154
+ setDateAndTime(value);
155
+ }
156
+ }, []);
157
+ useEffect(() => {
158
+ if (!value) {
159
+ setDateAndTime({});
160
+ return;
161
+ }
162
+ if (Array.isArray(value)) {
163
+ onDateAndTimeChange(value[0]);
164
+ }
165
+ else {
166
+ onTimeChange(value);
167
+ }
168
+ }, [onDateAndTimeChange, onTimeChange, value]);
169
+ return {
170
+ dateAndTime,
171
+ setDateAndTime,
172
+ isDateAndTimeFilled,
173
+ isTimeFilled,
174
+ isDateFilled,
175
+ onDateChange,
176
+ onTimeChange,
177
+ onDateAndTimeChange,
178
+ };
179
+ }
@@ -1,3 +1,4 @@
1
+ import { KeyboardEventHandler } from 'react';
1
2
  import { ValueOf } from '@snack-uikit/utils';
2
3
  import { CALENDAR_MODE, IN_RANGE_POSITION, SIZE, VIEW_MODE } from './constants';
3
4
  export type InRangePosition = ValueOf<typeof IN_RANGE_POSITION>;
@@ -24,6 +25,7 @@ export type Cell = {
24
25
  onSelect?(date: Date): void;
25
26
  onPreselect?(date: Date): void;
26
27
  onLeave?(): void;
28
+ onKeyDown?: KeyboardEventHandler;
27
29
  };
28
30
  export type FocusDirection = 'prev' | 'next';
29
31
  export type BuildCellProps = {
@@ -31,3 +33,13 @@ export type BuildCellProps = {
31
33
  isHoliday?: boolean;
32
34
  };
33
35
  export type BuildCellPropsFunction = (date: Date, viewMode: ViewMode) => BuildCellProps;
36
+ export type TimeValue = {
37
+ hours?: number;
38
+ minutes?: number;
39
+ seconds?: number;
40
+ };
41
+ export type DateAndTime = TimeValue & {
42
+ year?: number;
43
+ month?: number;
44
+ day?: number;
45
+ };
@@ -3,7 +3,7 @@ import { InRangePosition, Range, ViewMode } from './types';
3
3
  export declare const isTheSameDecade: (date1: Date, date2: Date) => boolean;
4
4
  export declare const isTheSameYear: (date1: Date, date2: Date) => boolean;
5
5
  export declare const isTheSameMonth: (date1: Date, date2: Date) => boolean;
6
- export declare const isTheSameDate: (date1: Date, date2: Date) => boolean;
6
+ export declare function isTheSameDate(date1: Date, date2: Date): boolean;
7
7
  export declare const capitalize: (str: string) => string;
8
8
  export declare const getMonthName: (date: Date, locale?: Intl.Locale) => string;
9
9
  export declare const getDateLabel: (date: Date) => string;
package/dist/esm/utils.js CHANGED
@@ -3,7 +3,9 @@ import { IN_RANGE_POSITION, VIEW_MODE } from './constants';
3
3
  export const isTheSameDecade = (date1, date2) => Math.floor(date1.getFullYear() / 10) === Math.floor(date2.getFullYear() / 10);
4
4
  export const isTheSameYear = (date1, date2) => date1.getFullYear() === date2.getFullYear();
5
5
  export const isTheSameMonth = (date1, date2) => isTheSameYear(date1, date2) && date1.getMonth() === date2.getMonth();
6
- export const isTheSameDate = (date1, date2) => isTheSameMonth(date1, date2) && date1.getDate() === date2.getDate();
6
+ export function isTheSameDate(date1, date2) {
7
+ return isTheSameMonth(date1, date2) && date1.getDate() === date2.getDate();
8
+ }
7
9
  export const capitalize = (str) => str.substring(0, 1).toUpperCase() + str.substring(1);
8
10
  export const getMonthName = (date, locale) => {
9
11
  const monthName = date.toLocaleString(locale, { month: 'long' });
@@ -52,7 +54,7 @@ export const getInRangePosition = (date, viewMode, range) => {
52
54
  };
53
55
  export const getEndOfTheDay = (date) => new Date(new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).valueOf() - 1);
54
56
  export const getTestIdBuilder = (testId) => (prefix) => (testId ? `${prefix}-${testId}` : undefined);
55
- export const getLocale = ({ localeProp, ctxLang } = {}) => { var _a; return localeProp || new Intl.Locale(ctxLang ? ctxLang.replace('_', '-') : (_a = navigator === null || navigator === void 0 ? void 0 : navigator.language) !== null && _a !== void 0 ? _a : 'ru-RU'); };
57
+ export const getLocale = ({ localeProp, ctxLang } = {}) => { var _a; return localeProp || new Intl.Locale(ctxLang ? ctxLang.replace('_', '-') : ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.language) !== null && _a !== void 0 ? _a : 'ru-RU')); };
56
58
  export const getStartOfWeek = (locale) => getWeekStartByLocale(locale.language);
57
59
  export const isWeekend = (date, viewMode) => {
58
60
  if (viewMode === 'month') {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Calendar",
7
- "version": "0.9.0",
7
+ "version": "0.11.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -36,7 +36,10 @@
36
36
  "license": "Apache-2.0",
37
37
  "scripts": {},
38
38
  "dependencies": {
39
- "@snack-uikit/icons": "0.23.0",
39
+ "@snack-uikit/button": "0.19.1",
40
+ "@snack-uikit/divider": "3.2.0",
41
+ "@snack-uikit/icons": "0.24.0",
42
+ "@snack-uikit/list": "0.21.0",
40
43
  "@snack-uikit/utils": "3.5.0",
41
44
  "classnames": "2.3.2",
42
45
  "uncontrollable": "8.0.2",
@@ -45,5 +48,5 @@
45
48
  "peerDependencies": {
46
49
  "@snack-uikit/locale": "*"
47
50
  },
48
- "gitHead": "62cc21d8606a61a78f80e3a7455b20d6f38474fa"
51
+ "gitHead": "b7163c6f939105eb34cabec64c9e983ac7958c26"
49
52
  }
@@ -1,11 +1,11 @@
1
- import { CSSProperties, RefCallback, useCallback } from 'react';
1
+ import { CSSProperties, RefObject, useCallback } from 'react';
2
2
 
3
3
  import { WithSupportProps } from '@snack-uikit/utils';
4
4
 
5
5
  import { CALENDAR_MODE } from '../../constants';
6
6
  import { CalendarBase } from '../../helperComponents/CalendarBase';
7
7
  import { BuildCellPropsFunction, FocusDirection, Range, Size } from '../../types';
8
- import { getNormalizedDefaultValue, getNormalizedValue } from './utils';
8
+ import { getNormalizedValue } from './utils';
9
9
 
10
10
  type CommonCalendarProps = {
11
11
  /**
@@ -47,8 +47,8 @@ type CommonCalendarProps = {
47
47
  locale?: Intl.Locale;
48
48
  /** Колбек потери фокуса. Вызывается со значением `next`, когда фокус покидает компонент, передвигаясь вперед, по клавише `tab`. Со значением `prev` - по клавише стрелки вверх или `shift + tab`. */
49
49
  onFocusLeave?(direction: FocusDirection): void;
50
- /** Ref-callback на первый доступный интерактивный элемент */
51
- navigationStartRef?: RefCallback<HTMLButtonElement>;
50
+ /** Ссылка на управление первым элементом навигации */
51
+ navigationStartRef?: RefObject<{ focus(): void }>;
52
52
  };
53
53
 
54
54
  type DateCalendarProps = CommonCalendarProps & {
@@ -63,16 +63,29 @@ type DateCalendarProps = CommonCalendarProps & {
63
63
  };
64
64
 
65
65
  type MonthCalendarProps = CommonCalendarProps & {
66
- /** Режим работы календаря: <br> - `month` - режим выбора месяца */
66
+ /** <br> - `month` - режим выбора месяца */
67
67
  mode: typeof CALENDAR_MODE.Month;
68
- /** Выбранное значение.<br> - в режиме month тип `Date` */
68
+ /** <br> - в режиме month тип `Date` */
69
69
  value?: Date;
70
- /** Значение по-умолчанию для uncontrolled.<br> - в режиме month тип `Date` */
70
+ /** <br> - в режиме month тип `Date` */
71
71
  defaultValue?: Date;
72
- /** Колбек выбора значения.<br> - в режиме month принимает тип `Date` */
72
+ /** <br> - в режиме month принимает тип `Date` */
73
73
  onChangeValue?(value: Date): void;
74
74
  };
75
75
 
76
+ type DateTimeCalendarProps = CommonCalendarProps & {
77
+ /** <br> - `date-time` - режим выбора даты и времени */
78
+ mode: typeof CALENDAR_MODE.DateTime;
79
+ /** <br> - в режиме date-time тип `Date` */
80
+ value?: Date;
81
+ /** <br> - в режиме date-time тип `Date` */
82
+ defaultValue?: Date;
83
+ /** <br> - в режиме date-time принимает тип `Date` */
84
+ onChangeValue?(value: Date): void;
85
+ /** Показывать ли секунды (только в режиме date-time) */
86
+ showSeconds?: boolean;
87
+ };
88
+
76
89
  type RangeCalendarProps = CommonCalendarProps & {
77
90
  /** <br> - `range` - режим выбора периода */
78
91
  mode: typeof CALENDAR_MODE.Range;
@@ -84,14 +97,16 @@ type RangeCalendarProps = CommonCalendarProps & {
84
97
  onChangeValue?(value: Range): void;
85
98
  };
86
99
 
87
- export type CalendarProps = WithSupportProps<DateCalendarProps | RangeCalendarProps | MonthCalendarProps>;
100
+ export type CalendarProps = WithSupportProps<
101
+ DateCalendarProps | RangeCalendarProps | MonthCalendarProps | DateTimeCalendarProps
102
+ >;
88
103
 
89
104
  export function Calendar(props: CalendarProps) {
90
105
  const { className, onChangeValue, buildCellProps, mode, ...rest } = props;
91
106
 
92
107
  const changeValueHandler = useCallback(
93
108
  (value: Range) => {
94
- if (mode === CALENDAR_MODE.Date || mode === CALENDAR_MODE.Month) {
109
+ if (mode === CALENDAR_MODE.Date || mode === CALENDAR_MODE.Month || mode === CALENDAR_MODE.DateTime) {
95
110
  const [date] = value;
96
111
  onChangeValue?.(date);
97
112
  return;
@@ -106,8 +121,8 @@ export function Calendar(props: CalendarProps) {
106
121
  {...rest}
107
122
  mode={mode}
108
123
  className={className}
109
- value={getNormalizedValue(props)}
110
- defaultValue={getNormalizedDefaultValue(props)}
124
+ value={getNormalizedValue(props.value)}
125
+ defaultValue={getNormalizedValue(props.defaultValue)}
111
126
  onChangeValue={changeValueHandler}
112
127
  buildCellProps={buildCellProps}
113
128
  />
@@ -1,15 +1,22 @@
1
- import { CALENDAR_MODE } from '../../constants';
2
1
  import { Range } from '../../types';
3
2
  import { CalendarProps } from './Calendar';
4
3
 
5
- export const getNormalizedValue = ({ value, mode }: CalendarProps): Range | undefined => {
6
- if (!value) return value;
7
- if (mode === CALENDAR_MODE.Date || mode === CALENDAR_MODE.Month) return [value, value];
8
- return value;
9
- };
4
+ function isRangeValue(value: CalendarProps['value']): value is Range {
5
+ return Array.isArray(value) && value.length === 2 && value[0] instanceof Date && value[1] instanceof Date;
6
+ }
7
+
8
+ function isDateValue(value: CalendarProps['value']): value is Date {
9
+ return value instanceof Date;
10
+ }
10
11
 
11
- export const getNormalizedDefaultValue = ({ defaultValue, mode }: CalendarProps): Range | undefined => {
12
- if (!defaultValue) return defaultValue;
13
- if (mode === CALENDAR_MODE.Date || mode === CALENDAR_MODE.Month) return [defaultValue, defaultValue];
14
- return defaultValue;
12
+ export const getNormalizedValue = (value: CalendarProps['value']): Range | undefined => {
13
+ if (isRangeValue(value)) {
14
+ return value;
15
+ }
16
+
17
+ if (isDateValue(value)) {
18
+ return [value, value];
19
+ }
20
+
21
+ return value;
15
22
  };