@snack-uikit/fields 0.31.0 → 0.32.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 (134) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +179 -147
  3. package/dist/cjs/components/FieldDate/FieldDate.d.ts +15 -24
  4. package/dist/cjs/components/FieldDate/FieldDate.js +53 -41
  5. package/dist/cjs/components/FieldDate/index.d.ts +0 -1
  6. package/dist/cjs/components/FieldDate/index.js +1 -9
  7. package/dist/cjs/components/FieldSecure/FieldSecure.d.ts +1 -1
  8. package/dist/cjs/components/FieldSelect/hooks.d.ts +2 -2
  9. package/dist/cjs/components/FieldSelect/hooks.js +7 -3
  10. package/dist/cjs/components/FieldSlider/FieldSlider.d.ts +1 -1
  11. package/dist/cjs/components/FieldText/FieldText.d.ts +1 -1
  12. package/dist/cjs/components/FieldTextArea/FieldTextArea.d.ts +1 -1
  13. package/dist/cjs/components/FieldTime/FieldTime.d.ts +30 -0
  14. package/dist/cjs/components/FieldTime/FieldTime.js +298 -0
  15. package/dist/cjs/components/FieldTime/index.d.ts +1 -0
  16. package/dist/cjs/components/{FieldDate/hooks → FieldTime}/index.js +1 -1
  17. package/dist/cjs/components/FieldTime/styles.module.css +27 -0
  18. package/dist/cjs/components/index.d.ts +6 -5
  19. package/dist/cjs/components/index.js +6 -5
  20. package/dist/cjs/constants/dateFields.d.ts +24 -0
  21. package/dist/cjs/constants/dateFields.js +152 -0
  22. package/dist/cjs/constants/index.d.ts +2 -0
  23. package/dist/cjs/constants/index.js +26 -0
  24. package/dist/cjs/hooks/dateHandlers/index.d.ts +3 -0
  25. package/dist/cjs/hooks/dateHandlers/index.js +27 -0
  26. package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.d.ts +13 -5
  27. package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.js +49 -34
  28. package/dist/cjs/hooks/dateHandlers/useDateFieldHelpersForMode.d.ts +18 -0
  29. package/dist/cjs/hooks/dateHandlers/useDateFieldHelpersForMode.js +113 -0
  30. package/dist/cjs/hooks/index.d.ts +1 -0
  31. package/dist/cjs/hooks/index.js +1 -0
  32. package/dist/cjs/hooks/useCopyButton.js +1 -1
  33. package/dist/cjs/{types.d.ts → types/allFields.d.ts} +1 -1
  34. package/dist/cjs/types/dateFields.d.ts +11 -0
  35. package/dist/cjs/types/index.d.ts +2 -0
  36. package/dist/cjs/types/index.js +26 -0
  37. package/dist/cjs/utils/dateFields.d.ts +10 -0
  38. package/dist/cjs/utils/dateFields.js +71 -0
  39. package/dist/esm/components/FieldDate/FieldDate.d.ts +15 -24
  40. package/dist/esm/components/FieldDate/FieldDate.js +39 -31
  41. package/dist/esm/components/FieldDate/index.d.ts +0 -1
  42. package/dist/esm/components/FieldDate/index.js +0 -1
  43. package/dist/esm/components/FieldSecure/FieldSecure.d.ts +1 -1
  44. package/dist/esm/components/FieldSelect/hooks.d.ts +2 -2
  45. package/dist/esm/components/FieldSelect/hooks.js +9 -3
  46. package/dist/esm/components/FieldSlider/FieldSlider.d.ts +1 -1
  47. package/dist/esm/components/FieldText/FieldText.d.ts +1 -1
  48. package/dist/esm/components/FieldTextArea/FieldTextArea.d.ts +1 -1
  49. package/dist/esm/components/FieldTime/FieldTime.d.ts +30 -0
  50. package/dist/esm/components/FieldTime/FieldTime.js +161 -0
  51. package/dist/esm/components/FieldTime/index.d.ts +1 -0
  52. package/dist/esm/components/FieldTime/index.js +1 -0
  53. package/dist/esm/components/FieldTime/styles.module.css +27 -0
  54. package/dist/esm/components/index.d.ts +6 -5
  55. package/dist/esm/components/index.js +6 -5
  56. package/dist/esm/constants/dateFields.d.ts +24 -0
  57. package/dist/esm/constants/dateFields.js +103 -0
  58. package/dist/esm/constants/index.d.ts +2 -0
  59. package/dist/esm/constants/index.js +2 -0
  60. package/dist/esm/hooks/dateHandlers/index.d.ts +3 -0
  61. package/dist/esm/hooks/dateHandlers/index.js +3 -0
  62. package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.d.ts +13 -5
  63. package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.js +48 -35
  64. package/dist/esm/hooks/dateHandlers/useDateFieldHelpersForMode.d.ts +18 -0
  65. package/dist/esm/hooks/dateHandlers/useDateFieldHelpersForMode.js +95 -0
  66. package/dist/esm/hooks/index.d.ts +1 -0
  67. package/dist/esm/hooks/index.js +1 -0
  68. package/dist/esm/hooks/useCopyButton.js +1 -1
  69. package/dist/esm/{types.d.ts → types/allFields.d.ts} +1 -1
  70. package/dist/esm/types/dateFields.d.ts +11 -0
  71. package/dist/esm/types/index.d.ts +2 -0
  72. package/dist/esm/types/index.js +2 -0
  73. package/dist/esm/utils/dateFields.d.ts +10 -0
  74. package/dist/esm/utils/dateFields.js +59 -0
  75. package/package.json +10 -10
  76. package/src/components/FieldDate/FieldDate.tsx +72 -52
  77. package/src/components/FieldDate/index.ts +0 -1
  78. package/src/components/FieldSelect/hooks.ts +15 -3
  79. package/src/components/FieldTime/FieldTime.tsx +350 -0
  80. package/src/components/FieldTime/index.ts +1 -0
  81. package/src/components/FieldTime/styles.module.scss +41 -0
  82. package/src/components/index.ts +6 -5
  83. package/src/constants/dateFields.ts +127 -0
  84. package/src/constants/index.ts +2 -0
  85. package/src/hooks/dateHandlers/index.ts +3 -0
  86. package/src/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.ts +93 -47
  87. package/src/hooks/dateHandlers/useDateFieldHelpersForMode.ts +145 -0
  88. package/src/hooks/index.ts +1 -0
  89. package/src/hooks/useCopyButton.tsx +1 -1
  90. package/src/{types.ts → types/allFields.ts} +1 -1
  91. package/src/types/dateFields.ts +14 -0
  92. package/src/types/index.ts +2 -0
  93. package/src/utils/dateFields.ts +75 -0
  94. package/dist/cjs/components/FieldDate/constants.d.ts +0 -10
  95. package/dist/cjs/components/FieldDate/constants.js +0 -49
  96. package/dist/cjs/components/FieldDate/hooks/index.d.ts +0 -1
  97. package/dist/cjs/components/FieldDate/hooks/useDateFieldHelpers.d.ts +0 -10
  98. package/dist/cjs/components/FieldDate/hooks/useDateFieldHelpers.js +0 -82
  99. package/dist/cjs/components/FieldDate/types.d.ts +0 -6
  100. package/dist/cjs/components/FieldDate/utils.d.ts +0 -9
  101. package/dist/cjs/components/FieldDate/utils.js +0 -56
  102. package/dist/esm/components/FieldDate/constants.d.ts +0 -10
  103. package/dist/esm/components/FieldDate/constants.js +0 -28
  104. package/dist/esm/components/FieldDate/hooks/index.d.ts +0 -1
  105. package/dist/esm/components/FieldDate/hooks/index.js +0 -1
  106. package/dist/esm/components/FieldDate/hooks/useDateFieldHelpers.d.ts +0 -10
  107. package/dist/esm/components/FieldDate/hooks/useDateFieldHelpers.js +0 -66
  108. package/dist/esm/components/FieldDate/types.d.ts +0 -6
  109. package/dist/esm/components/FieldDate/utils.d.ts +0 -9
  110. package/dist/esm/components/FieldDate/utils.js +0 -43
  111. package/src/components/FieldDate/constants.ts +0 -33
  112. package/src/components/FieldDate/hooks/index.ts +0 -1
  113. package/src/components/FieldDate/hooks/useDateFieldHelpers.ts +0 -96
  114. package/src/components/FieldDate/types.ts +0 -6
  115. package/src/components/FieldDate/utils.ts +0 -49
  116. /package/dist/cjs/{constants.d.ts → constants/allFields.d.ts} +0 -0
  117. /package/dist/cjs/{constants.js → constants/allFields.js} +0 -0
  118. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.d.ts +0 -0
  119. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.js +0 -0
  120. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.d.ts +0 -0
  121. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.js +0 -0
  122. /package/dist/cjs/{components/FieldDate/types.js → types/allFields.js} +0 -0
  123. /package/dist/cjs/{types.js → types/dateFields.js} +0 -0
  124. /package/dist/esm/{constants.d.ts → constants/allFields.d.ts} +0 -0
  125. /package/dist/esm/{constants.js → constants/allFields.js} +0 -0
  126. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.d.ts +0 -0
  127. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.js +0 -0
  128. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.d.ts +0 -0
  129. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.js +0 -0
  130. /package/dist/esm/{components/FieldDate/types.js → types/allFields.js} +0 -0
  131. /package/dist/esm/{types.js → types/dateFields.js} +0 -0
  132. /package/src/{constants.ts → constants/allFields.ts} +0 -0
  133. /package/src/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.ts +0 -0
  134. /package/src/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.ts +0 -0
@@ -0,0 +1,41 @@
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
2
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
3
+
4
+ $sizes: 's', 'm', 'l';
5
+ $icons-sizes: (
6
+ 's': styles-tokens-element.$icon-xs,
7
+ 'm': styles-tokens-element.$icon-s,
8
+ 'l': styles-tokens-element.$icon-s
9
+ );
10
+
11
+ .triggerClassName {
12
+ --offset: #{styles-tokens-fields.$space-drop-list-drop-offset};
13
+
14
+ display: block;
15
+ width: 100%;
16
+ }
17
+
18
+ .container {
19
+ .calendarIcon {
20
+ color: styles-tokens-fields.$sys-neutral-text-light;
21
+
22
+ @each $size in $sizes {
23
+ &[data-size='#{$size}'] {
24
+ width: styles-tokens-fields.simple-var($icons-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
25
+ height: styles-tokens-fields.simple-var($icons-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
26
+ }
27
+ }
28
+ }
29
+
30
+ &:hover, &:focus-within, &[data-focused] {
31
+ .calendarIcon {
32
+ color: styles-tokens-fields.$sys-neutral-text-support;
33
+ }
34
+ }
35
+
36
+ &[data-disabled], &[data-readonly] {
37
+ .calendarIcon {
38
+ color: styles-tokens-fields.$sys-neutral-text-disabled;
39
+ }
40
+ }
41
+ }
@@ -1,9 +1,10 @@
1
+ export * from './FieldColor';
2
+ export * from './FieldDate';
1
3
  export * from './FieldDecorator';
2
- export * from './FieldText';
3
- export * from './FieldTextArea';
4
4
  export * from './FieldSecure';
5
- export * from './FieldDate';
6
5
  export * from './FieldSelect';
7
- export * from './FieldStepper';
8
6
  export * from './FieldSlider';
9
- export * from './FieldColor';
7
+ export * from './FieldStepper';
8
+ export * from './FieldText';
9
+ export * from './FieldTextArea';
10
+ export * from './FieldTime';
@@ -0,0 +1,127 @@
1
+ import { Mode, NoSecondsMode, Slot, TimeMode } from '../types';
2
+
3
+ export enum SlotKey {
4
+ Day = 'D',
5
+ Month = 'M',
6
+ Year = 'Y',
7
+ Hours = 'h',
8
+ Minutes = 'm',
9
+ Seconds = 's',
10
+ }
11
+
12
+ export const MODES = {
13
+ Date: 'date',
14
+ DateTime: 'date-time',
15
+ } as const;
16
+
17
+ export const TIME_MODES = {
18
+ FullTime: 'full-time',
19
+ NoSeconds: 'no-seconds',
20
+ } as const;
21
+
22
+ export const NO_SECONDS_MODE = 'date-time-no-sec';
23
+
24
+ export const MASK: Record<Mode | TimeMode | NoSecondsMode, Record<string, string>> = {
25
+ [MODES.Date]: {
26
+ 'ru-RU': 'ДД.ММ.ГГГГ',
27
+ 'en-US': 'DD.MM.YYYY',
28
+ },
29
+ [MODES.DateTime]: {
30
+ 'ru-RU': 'ДД.ММ.ГГГГ, чч:мм:сс',
31
+ 'en-US': 'DD.MM.YYYY, hh:mm:ss',
32
+ },
33
+ [NO_SECONDS_MODE]: {
34
+ 'ru-RU': 'ДД.ММ.ГГГГ, чч:мм',
35
+ 'en-US': 'DD.MM.YYYY, hh:mm',
36
+ },
37
+ [TIME_MODES.FullTime]: {
38
+ 'ru-RU': 'чч:мм:сс',
39
+ 'en-US': 'hh:mm:ss',
40
+ },
41
+ [TIME_MODES.NoSeconds]: {
42
+ 'ru-RU': 'чч:мм',
43
+ 'en-US': 'hh:mm',
44
+ },
45
+ };
46
+
47
+ export const DEFAULT_LOCALE = new Intl.Locale('ru-RU');
48
+
49
+ const DATE_SLOTS = {
50
+ [SlotKey.Day]: { start: 0, end: 2, max: 31, min: 1 },
51
+ [SlotKey.Month]: { start: 3, end: 5, max: 12, min: 1 },
52
+ [SlotKey.Year]: { start: 6, end: 10, max: 2100, min: 1900 },
53
+ };
54
+
55
+ const TIME_SLOTS = (shift: number, showSeconds: boolean) => ({
56
+ [SlotKey.Hours]: { start: shift, end: shift + 2, max: 23, min: 0 },
57
+ [SlotKey.Minutes]: { start: shift + 3, end: shift + 5, max: 59, min: 0 },
58
+ ...(showSeconds ? { [SlotKey.Seconds]: { start: shift + 6, end: shift + 8, max: 59, min: 0 } } : {}),
59
+ });
60
+
61
+ export const SLOTS: Record<Mode | TimeMode | NoSecondsMode, Record<SlotKey | string, Slot>> = {
62
+ [MODES.Date]: DATE_SLOTS,
63
+ [MODES.DateTime]: { ...DATE_SLOTS, ...TIME_SLOTS(12, true) },
64
+ [NO_SECONDS_MODE]: { ...DATE_SLOTS, ...TIME_SLOTS(12, false) },
65
+ [TIME_MODES.FullTime]: TIME_SLOTS(0, true),
66
+ [TIME_MODES.NoSeconds]: TIME_SLOTS(0, false),
67
+ };
68
+
69
+ export type FocusSlot = SlotKey | 'auto';
70
+
71
+ export const SLOT_ORDER: Record<Mode | TimeMode | NoSecondsMode, SlotKey[]> = {
72
+ [MODES.Date]: [SlotKey.Day, SlotKey.Month, SlotKey.Year],
73
+ [MODES.DateTime]: [SlotKey.Day, SlotKey.Month, SlotKey.Year, SlotKey.Hours, SlotKey.Minutes, SlotKey.Seconds],
74
+ [NO_SECONDS_MODE]: [SlotKey.Day, SlotKey.Month, SlotKey.Year, SlotKey.Hours, SlotKey.Minutes],
75
+ [TIME_MODES.FullTime]: [SlotKey.Hours, SlotKey.Minutes, SlotKey.Seconds],
76
+ [TIME_MODES.NoSeconds]: [SlotKey.Hours, SlotKey.Minutes],
77
+ };
78
+
79
+ const RU_DATE_SLOTS_PLACEHOLDER = {
80
+ [SlotKey.Day]: 'ДД',
81
+ [SlotKey.Month]: 'ММ',
82
+ [SlotKey.Year]: 'ГГГГ',
83
+ };
84
+
85
+ const RU_TIME_SLOTS_PLACEHOLDER = {
86
+ [SlotKey.Hours]: 'чч',
87
+ [SlotKey.Minutes]: 'мм',
88
+ [SlotKey.Seconds]: 'сс',
89
+ };
90
+
91
+ const EN_DATE_SLOTS_PLACEHOLDER = {
92
+ [SlotKey.Day]: 'DD',
93
+ [SlotKey.Month]: 'MM',
94
+ [SlotKey.Year]: 'YYYY',
95
+ };
96
+
97
+ const EN_TIME_SLOTS_PLACEHOLDER = {
98
+ [SlotKey.Hours]: 'hh',
99
+ [SlotKey.Minutes]: 'mm',
100
+ [SlotKey.Seconds]: 'ss',
101
+ };
102
+
103
+ export const SLOTS_PLACEHOLDER: Record<
104
+ Mode | TimeMode | NoSecondsMode,
105
+ Record<string, Partial<Record<SlotKey, string>>>
106
+ > = {
107
+ [MODES.Date]: {
108
+ 'ru-RU': RU_DATE_SLOTS_PLACEHOLDER,
109
+ 'en-US': EN_DATE_SLOTS_PLACEHOLDER,
110
+ },
111
+ [MODES.DateTime]: {
112
+ 'ru-RU': { ...RU_DATE_SLOTS_PLACEHOLDER, ...RU_TIME_SLOTS_PLACEHOLDER },
113
+ 'en-US': { ...EN_DATE_SLOTS_PLACEHOLDER, ...EN_TIME_SLOTS_PLACEHOLDER },
114
+ },
115
+ [NO_SECONDS_MODE]: {
116
+ 'ru-RU': { ...RU_DATE_SLOTS_PLACEHOLDER, ...RU_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
117
+ 'en-US': { ...EN_DATE_SLOTS_PLACEHOLDER, ...EN_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
118
+ },
119
+ [TIME_MODES.FullTime]: {
120
+ 'ru-RU': RU_TIME_SLOTS_PLACEHOLDER,
121
+ 'en-US': EN_TIME_SLOTS_PLACEHOLDER,
122
+ },
123
+ [TIME_MODES.NoSeconds]: {
124
+ 'ru-RU': { ...RU_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
125
+ 'en-US': { ...EN_TIME_SLOTS_PLACEHOLDER, [SlotKey.Seconds]: undefined },
126
+ },
127
+ };
@@ -0,0 +1,2 @@
1
+ export * from './allFields';
2
+ export * from './dateFields';
@@ -0,0 +1,3 @@
1
+ export * from './useDateField';
2
+ export * from './useFocusHandlers';
3
+ export * from './useHandlers';
@@ -1,32 +1,71 @@
1
1
  import { ChangeEvent, FocusEventHandler, KeyboardEvent, RefObject, useCallback, useMemo, useRef } from 'react';
2
2
 
3
+ import { TimePickerProps } from '@snack-uikit/calendar';
3
4
  import { isBrowser } from '@snack-uikit/utils';
4
5
 
5
- import { DEFAULT_LOCALE, MASK, SlotKey, SLOTS, SLOTS_PLACEHOLDER } from '../constants';
6
- import { getNextSlotKey, getPrevSlotKey, getSlotKey } from '../utils';
7
- import { useDateFieldHelpers } from './useDateFieldHelpers';
8
-
9
- type UseDateFieldProps = {
6
+ import {
7
+ DEFAULT_LOCALE,
8
+ FocusSlot,
9
+ MASK,
10
+ MODES,
11
+ NO_SECONDS_MODE,
12
+ SLOT_ORDER,
13
+ SlotKey,
14
+ SLOTS,
15
+ SLOTS_PLACEHOLDER,
16
+ } from '../../constants';
17
+ import { Mode, TimeMode } from '../../types';
18
+ import { parseDate } from '../../utils/dateFields';
19
+ import { useDateFieldHelpersForMode } from './useDateFieldHelpersForMode';
20
+
21
+ type BaseProps = {
10
22
  inputRef: RefObject<HTMLInputElement>;
11
- onChange?(value: string): void;
12
23
  readonly?: boolean;
13
24
  locale?: Intl.Locale;
14
25
  setIsOpen(v: boolean): void;
26
+ showSeconds?: boolean;
15
27
  };
16
28
 
17
- type FocusSlot = SlotKey.Day | SlotKey.Year | 'auto';
18
-
19
- export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LOCALE, setIsOpen }: UseDateFieldProps) {
20
- const { setFocus, updateSlot, getSlot, isLikeDate, isAllSelected, isValidInput, tryToCompleteInput } =
21
- useDateFieldHelpers(inputRef);
22
- const focusSlotRef = useRef<FocusSlot>(SlotKey.Day);
23
-
24
- const mask = useMemo(() => MASK[locale.baseName] || MASK[DEFAULT_LOCALE.baseName], [locale]);
25
-
26
- const slotsPlaceHolder = useMemo(
27
- () => SLOTS_PLACEHOLDER[locale.baseName] || SLOTS_PLACEHOLDER[DEFAULT_LOCALE.baseName],
28
- [locale.baseName],
29
- );
29
+ type UseDateFieldProps =
30
+ | ({
31
+ mode: Mode;
32
+ onChange?(value: Date | undefined): void;
33
+ } & BaseProps)
34
+ | ({
35
+ mode: TimeMode;
36
+ onChange?: TimePickerProps['onChangeValue'];
37
+ } & BaseProps);
38
+
39
+ export function useDateField({
40
+ inputRef,
41
+ onChange,
42
+ readonly,
43
+ locale = DEFAULT_LOCALE,
44
+ setIsOpen,
45
+ mode,
46
+ showSeconds,
47
+ }: UseDateFieldProps) {
48
+ const dateTimeMode = mode === MODES.DateTime && !showSeconds ? NO_SECONDS_MODE : mode;
49
+ const slotsInfo = SLOTS[dateTimeMode];
50
+ const mask = MASK[dateTimeMode][locale.baseName] || MASK[dateTimeMode][DEFAULT_LOCALE.baseName];
51
+ const slotsPlaceholder =
52
+ SLOTS_PLACEHOLDER[dateTimeMode][locale.baseName] || SLOTS_PLACEHOLDER[dateTimeMode][DEFAULT_LOCALE.baseName];
53
+ const slotOrder = SLOT_ORDER[dateTimeMode];
54
+ const {
55
+ getNextSlotKey,
56
+ getPrevSlotKey,
57
+ getSlotKeyFromIndex,
58
+ setFocus,
59
+ updateSlot,
60
+ getSlot,
61
+ isLikeDate,
62
+ isAllSelected,
63
+ tryToCompleteInput,
64
+ isValidInput,
65
+ } = useDateFieldHelpersForMode({ inputRef, mode: dateTimeMode });
66
+
67
+ const focusSlotKey = useMemo(() => slotOrder[0], [slotOrder]);
68
+ const focusSlotRef = useRef<FocusSlot>(focusSlotKey);
30
69
 
31
70
  const setInputFocus = useCallback(
32
71
  (focusSlot?: FocusSlot) => {
@@ -35,20 +74,20 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
35
74
  }
36
75
 
37
76
  if (isBrowser() && document.activeElement !== inputRef.current) {
38
- focusSlotRef.current = focusSlot || SlotKey.Day;
77
+ focusSlotRef.current = focusSlot || focusSlotKey;
39
78
  inputRef.current.focus();
40
79
  return;
41
80
  }
42
81
 
43
82
  const focusSlotValue = focusSlot || focusSlotRef.current;
44
83
 
45
- if (isLikeDate() && focusSlotValue === SlotKey.Day) {
84
+ if (isLikeDate() && focusSlotValue === focusSlotKey) {
46
85
  return;
47
86
  }
48
87
 
49
88
  if (!inputRef.current.value) {
50
89
  inputRef.current.value = mask;
51
- setFocus(SlotKey.Day);
90
+ setFocus(focusSlotKey);
52
91
  return;
53
92
  }
54
93
 
@@ -57,14 +96,14 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
57
96
  return;
58
97
  }
59
98
 
60
- const slotKey = getSlotKey(inputRef.current.selectionStart);
99
+ const slotKey = getSlotKeyFromIndex(inputRef.current.selectionStart);
61
100
 
62
101
  if (slotKey) {
63
- const { start, end } = SLOTS[slotKey];
102
+ const { start, end } = slotsInfo[slotKey];
64
103
  inputRef.current.setSelectionRange(start, end);
65
104
  }
66
105
  },
67
- [inputRef, isLikeDate, mask, readonly, setFocus],
106
+ [inputRef, readonly, isLikeDate, focusSlotKey, getSlotKeyFromIndex, mask, setFocus, slotsInfo],
68
107
  );
69
108
 
70
109
  const handleClick = useCallback(() => {
@@ -72,12 +111,12 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
72
111
  }, [setInputFocus]);
73
112
 
74
113
  const handleChange: (value: string, e?: ChangeEvent<HTMLInputElement> | undefined) => void = () => {
75
- onChange && isLikeDate() && onChange(inputRef.current?.value || '');
114
+ onChange && isLikeDate() && onChange(parseDate(inputRef.current?.value || ''));
76
115
  };
77
116
 
78
117
  const checkInputAndGoNext = useCallback(
79
- (slotKey: string) => {
80
- if (slotKey === SlotKey.Year && tryToCompleteInput()) {
118
+ (slotKey: SlotKey | undefined) => {
119
+ if (slotKey === slotOrder[slotOrder.length - 1] && tryToCompleteInput()) {
81
120
  return;
82
121
  }
83
122
 
@@ -88,19 +127,19 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
88
127
 
89
128
  switch (slotKey) {
90
129
  case SlotKey.Day:
91
- updateSlot(SlotKey.Month, slotsPlaceHolder[SlotKey.Month]);
130
+ updateSlot(SlotKey.Month, slotsPlaceholder?.[SlotKey.Month] ?? '');
92
131
  setFocus(SlotKey.Month);
93
132
  return;
94
133
  case SlotKey.Year:
95
134
  case SlotKey.Month:
96
- updateSlot(SlotKey.Day, slotsPlaceHolder[SlotKey.Day]);
135
+ updateSlot(SlotKey.Day, slotsPlaceholder?.[SlotKey.Day] ?? '');
97
136
  setFocus(SlotKey.Day);
98
137
  return;
99
138
  default:
100
139
  setFocus(getNextSlotKey(slotKey));
101
140
  }
102
141
  },
103
- [tryToCompleteInput, isValidInput, setFocus, slotsPlaceHolder, updateSlot],
142
+ [slotOrder, tryToCompleteInput, isValidInput, setFocus, getNextSlotKey, updateSlot, slotsPlaceholder],
104
143
  );
105
144
 
106
145
  const handleKeyDown = useCallback(
@@ -125,16 +164,16 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
125
164
  }
126
165
 
127
166
  const clickIndex = inputRef.current.selectionStart;
128
- const slotKey = getSlotKey(clickIndex);
167
+ const slotKey = getSlotKeyFromIndex(clickIndex);
129
168
 
130
169
  if (slotKey) {
131
170
  const value = getSlot(slotKey);
132
- const { max } = SLOTS[slotKey];
171
+ const { max } = slotsInfo[slotKey];
133
172
 
134
173
  const numberValue = Number(value) || 0;
135
174
 
136
175
  if (e.key === 'ArrowRight') {
137
- if (isAllSelected() || slotKey === SlotKey.Year) {
176
+ if (isAllSelected() || slotKey === slotOrder[slotOrder.length - 1]) {
138
177
  inputRef.current.selectionStart = inputRef.current.value.length;
139
178
  return;
140
179
  }
@@ -150,9 +189,9 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
150
189
  if (e.key === 'Backspace') {
151
190
  if (isAllSelected()) {
152
191
  inputRef.current.value = mask;
153
- setFocus(SlotKey.Day);
192
+ setFocus(focusSlotKey);
154
193
  } else {
155
- updateSlot(slotKey, slotsPlaceHolder[slotKey]);
194
+ updateSlot(slotKey, slotsPlaceholder[slotKey] ?? '');
156
195
  }
157
196
  }
158
197
 
@@ -189,24 +228,31 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
189
228
  }
190
229
  }
191
230
  }
192
- onChange?.(isLikeDate() ? inputRef.current.value : '');
231
+ const newDate = parseDate(isLikeDate() ? inputRef.current.value : '');
232
+ onChange?.(newDate);
193
233
  }
194
234
  }
195
235
  },
196
236
  [
197
- checkInputAndGoNext,
198
- getSlot,
199
237
  inputRef,
200
- isAllSelected,
201
- isLikeDate,
202
- mask,
203
- onChange,
204
238
  readonly,
205
- setFocus,
239
+ getSlotKeyFromIndex,
206
240
  setIsOpen,
207
- slotsPlaceHolder,
208
241
  tryToCompleteInput,
242
+ getSlot,
243
+ slotsInfo,
244
+ isLikeDate,
245
+ onChange,
246
+ isAllSelected,
247
+ slotOrder,
248
+ setFocus,
249
+ getNextSlotKey,
250
+ getPrevSlotKey,
251
+ mask,
252
+ focusSlotKey,
209
253
  updateSlot,
254
+ slotsPlaceholder,
255
+ checkInputAndGoNext,
210
256
  ],
211
257
  );
212
258
 
@@ -214,8 +260,8 @@ export function useDateField({ inputRef, onChange, readonly, locale = DEFAULT_LO
214
260
  if (!readonly && inputRef.current?.value === mask) {
215
261
  inputRef.current.value = '';
216
262
  }
217
- focusSlotRef.current = SlotKey.Day;
218
- }, [inputRef, mask, readonly]);
263
+ focusSlotRef.current = focusSlotKey;
264
+ }, [inputRef, mask, readonly, focusSlotKey]);
219
265
 
220
266
  return {
221
267
  handleKeyDown,
@@ -0,0 +1,145 @@
1
+ import { RefObject, useCallback, useMemo } from 'react';
2
+
3
+ import { MODES, NO_SECONDS_MODE, SlotKey, SLOTS, TIME_MODES } from '../../constants';
4
+ import { Mode, NoSecondsMode, TimeMode } from '../../types';
5
+ import { getNextSlotKeyHandler, getPrevSlotKeyHandler, getSlotKeyFromIndexHandler } from '../../utils/dateFields';
6
+
7
+ export function useDateFieldHelpersForMode({
8
+ inputRef,
9
+ mode,
10
+ }: {
11
+ inputRef: RefObject<HTMLInputElement>;
12
+ mode: Mode | TimeMode | NoSecondsMode;
13
+ }) {
14
+ const setFocus = useCallback(
15
+ (slotKey: string) => {
16
+ if (inputRef.current) {
17
+ const { start, end } = SLOTS[mode][slotKey];
18
+
19
+ inputRef.current.setSelectionRange(start, end);
20
+ }
21
+ },
22
+ [mode, inputRef],
23
+ );
24
+
25
+ const isAllSelected = useCallback(
26
+ () => inputRef.current?.value.length === inputRef.current?.selectionEnd && inputRef.current?.selectionStart === 0,
27
+ [inputRef],
28
+ );
29
+
30
+ const getSlot = useCallback(
31
+ (slotKey: string) => {
32
+ if (inputRef.current) {
33
+ return inputRef.current.value.slice(SLOTS[mode][slotKey].start, SLOTS[mode][slotKey].end);
34
+ }
35
+
36
+ return '';
37
+ },
38
+ [mode, inputRef],
39
+ );
40
+
41
+ const isLikeDate = useCallback(() => {
42
+ if (inputRef.current) {
43
+ return Object.keys(SLOTS[mode]).every(slotKey => getSlot(slotKey) && Number.isInteger(Number(getSlot(slotKey))));
44
+ }
45
+ return false;
46
+ }, [mode, getSlot, inputRef]);
47
+
48
+ const isValidInput = useCallback((): boolean => {
49
+ const isTimeMode = Object.values(TIME_MODES).includes(mode as TimeMode);
50
+
51
+ if (isTimeMode) {
52
+ return true;
53
+ }
54
+
55
+ const day = parseInt(getSlot(SlotKey.Day), 10);
56
+ const month = parseInt(getSlot(SlotKey.Month), 10);
57
+ const year = parseInt(getSlot(SlotKey.Year), 10);
58
+
59
+ if (!month || !day) {
60
+ return true;
61
+ }
62
+
63
+ const date = new Date(year || /* високосный год = */ 2020, month - 1, day);
64
+
65
+ return date.getDate() === day;
66
+ }, [getSlot, mode]);
67
+
68
+ const tryToCompleteInput = useCallback((): boolean => {
69
+ let isCompleted: boolean;
70
+
71
+ const parsedSlotsData = Object.keys(SLOTS[mode]).reduce(
72
+ (res, key) => {
73
+ const slotKey = key as SlotKey;
74
+ res[slotKey] = parseInt(getSlot(slotKey), 10);
75
+ return res;
76
+ },
77
+ {} as Record<SlotKey, number>,
78
+ );
79
+
80
+ const {
81
+ [SlotKey.Day]: day,
82
+ [SlotKey.Month]: month,
83
+ [SlotKey.Year]: year,
84
+ [SlotKey.Hours]: hours,
85
+ [SlotKey.Minutes]: minutes,
86
+ [SlotKey.Seconds]: seconds,
87
+ } = parsedSlotsData;
88
+
89
+ const yearSlotMeta = SLOTS[mode][SlotKey.Year];
90
+ const isDateCompleted = Boolean(day && month && year >= yearSlotMeta?.min && year <= yearSlotMeta?.max);
91
+ const isTimeCompleted = [
92
+ hours,
93
+ minutes,
94
+ ...(mode === MODES.DateTime || mode === TIME_MODES.FullTime ? [seconds] : []),
95
+ ].every(value => value !== undefined);
96
+
97
+ if (mode === MODES.DateTime || mode === NO_SECONDS_MODE) {
98
+ isCompleted = isDateCompleted && isTimeCompleted;
99
+ } else if (mode === TIME_MODES.FullTime || mode === TIME_MODES.NoSeconds) {
100
+ isCompleted = isTimeCompleted;
101
+ } else {
102
+ isCompleted = isDateCompleted;
103
+ }
104
+
105
+ if (isCompleted && inputRef.current) {
106
+ const lastPosition = inputRef.current?.value.length;
107
+ inputRef.current.selectionStart = lastPosition;
108
+ inputRef.current.selectionEnd = lastPosition;
109
+ }
110
+
111
+ return isCompleted;
112
+ }, [getSlot, inputRef, mode]);
113
+
114
+ const updateSlot = useCallback(
115
+ (slotKey: string, slotValue: number | string) => {
116
+ if (inputRef.current) {
117
+ const { start, end, max } = SLOTS[mode][slotKey];
118
+
119
+ inputRef.current.value =
120
+ inputRef.current.value.slice(0, start) +
121
+ slotValue.toString().padStart(max.toString().length, '0') +
122
+ inputRef.current.value.slice(end);
123
+ setFocus(slotKey);
124
+ }
125
+ },
126
+ [inputRef, setFocus, mode],
127
+ );
128
+
129
+ const getNextSlotKey = useMemo(() => getNextSlotKeyHandler(mode), [mode]);
130
+ const getPrevSlotKey = useMemo(() => getPrevSlotKeyHandler(mode), [mode]);
131
+ const getSlotKeyFromIndex = useMemo(() => getSlotKeyFromIndexHandler(mode), [mode]);
132
+
133
+ return {
134
+ isAllSelected,
135
+ isValidInput,
136
+ tryToCompleteInput,
137
+ getSlot,
138
+ updateSlot,
139
+ setFocus,
140
+ isLikeDate,
141
+ getNextSlotKey,
142
+ getPrevSlotKey,
143
+ getSlotKeyFromIndex,
144
+ };
145
+ }
@@ -1,3 +1,4 @@
1
+ export * from './dateHandlers';
1
2
  export * from './useCopyButton';
2
3
  export * from './useHideButton';
3
4
  export * from './usePostfix';
@@ -36,7 +36,7 @@ export function useCopyButton({
36
36
  <ButtonCopyValue
37
37
  {...props}
38
38
  size={BUTTON_SIZE_MAP[size]}
39
- valueToCopy={prefix + valueToCopy + postfix}
39
+ valueToCopy={(prefix ?? '') + valueToCopy + (postfix ?? '')}
40
40
  onValueRequest={onValueRequest}
41
41
  disabled={disabled}
42
42
  />
@@ -3,7 +3,7 @@ import { ReactNode } from 'react';
3
3
  import { DroplistProps, SelectionSingleState } from '@snack-uikit/list';
4
4
  import { ValueOf } from '@snack-uikit/utils';
5
5
 
6
- import { BUTTON_VARIANT, CONTAINER_VARIANT, VALIDATION_STATE } from './constants';
6
+ import { BUTTON_VARIANT, CONTAINER_VARIANT, VALIDATION_STATE } from '../constants';
7
7
 
8
8
  export type ValidationState = ValueOf<typeof VALIDATION_STATE>;
9
9
 
@@ -0,0 +1,14 @@
1
+ import { ValueOf } from '@snack-uikit/utils';
2
+
3
+ import { MODES, NO_SECONDS_MODE, TIME_MODES } from '../constants';
4
+
5
+ export type Slot = {
6
+ start: number;
7
+ end: number;
8
+ max: number;
9
+ min: number;
10
+ };
11
+
12
+ export type Mode = ValueOf<typeof MODES>;
13
+ export type TimeMode = ValueOf<typeof TIME_MODES>;
14
+ export type NoSecondsMode = typeof NO_SECONDS_MODE;
@@ -0,0 +1,2 @@
1
+ export * from './allFields';
2
+ export * from './dateFields';