@snack-uikit/fields 0.30.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 (150) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +184 -152
  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/FieldSelect/styles.module.css +6 -18
  11. package/dist/cjs/components/FieldSlider/FieldSlider.d.ts +1 -1
  12. package/dist/cjs/components/FieldText/FieldText.d.ts +1 -1
  13. package/dist/cjs/components/FieldTextArea/FieldTextArea.d.ts +1 -1
  14. package/dist/cjs/components/FieldTime/FieldTime.d.ts +30 -0
  15. package/dist/cjs/components/FieldTime/FieldTime.js +298 -0
  16. package/dist/cjs/components/FieldTime/index.d.ts +1 -0
  17. package/dist/cjs/components/{FieldDate/hooks → FieldTime}/index.js +1 -1
  18. package/dist/cjs/components/FieldTime/styles.module.css +27 -0
  19. package/dist/cjs/components/index.d.ts +6 -5
  20. package/dist/cjs/components/index.js +6 -5
  21. package/dist/cjs/constants/dateFields.d.ts +24 -0
  22. package/dist/cjs/constants/dateFields.js +152 -0
  23. package/dist/cjs/constants/index.d.ts +2 -0
  24. package/dist/cjs/constants/index.js +26 -0
  25. package/dist/cjs/hooks/dateHandlers/index.d.ts +3 -0
  26. package/dist/cjs/hooks/dateHandlers/index.js +27 -0
  27. package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.d.ts +13 -5
  28. package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.js +49 -34
  29. package/dist/cjs/hooks/dateHandlers/useDateFieldHelpersForMode.d.ts +18 -0
  30. package/dist/cjs/hooks/dateHandlers/useDateFieldHelpersForMode.js +113 -0
  31. package/dist/cjs/hooks/index.d.ts +1 -0
  32. package/dist/cjs/hooks/index.js +1 -0
  33. package/dist/cjs/hooks/useCopyButton.js +1 -1
  34. package/dist/cjs/{types.d.ts → types/allFields.d.ts} +1 -1
  35. package/dist/cjs/types/dateFields.d.ts +11 -0
  36. package/dist/cjs/types/index.d.ts +2 -0
  37. package/dist/cjs/types/index.js +26 -0
  38. package/dist/cjs/utils/dateFields.d.ts +10 -0
  39. package/dist/cjs/utils/dateFields.js +71 -0
  40. package/dist/esm/components/FieldDate/FieldDate.d.ts +15 -24
  41. package/dist/esm/components/FieldDate/FieldDate.js +39 -31
  42. package/dist/esm/components/FieldDate/index.d.ts +0 -1
  43. package/dist/esm/components/FieldDate/index.js +0 -1
  44. package/dist/esm/components/FieldSecure/FieldSecure.d.ts +1 -1
  45. package/dist/esm/components/FieldSelect/hooks.d.ts +2 -2
  46. package/dist/esm/components/FieldSelect/hooks.js +9 -3
  47. package/dist/esm/components/FieldSelect/styles.module.css +6 -18
  48. package/dist/esm/components/FieldSlider/FieldSlider.d.ts +1 -1
  49. package/dist/esm/components/FieldText/FieldText.d.ts +1 -1
  50. package/dist/esm/components/FieldTextArea/FieldTextArea.d.ts +1 -1
  51. package/dist/esm/components/FieldTime/FieldTime.d.ts +30 -0
  52. package/dist/esm/components/FieldTime/FieldTime.js +161 -0
  53. package/dist/esm/components/FieldTime/index.d.ts +1 -0
  54. package/dist/esm/components/FieldTime/index.js +1 -0
  55. package/dist/esm/components/FieldTime/styles.module.css +27 -0
  56. package/dist/esm/components/index.d.ts +6 -5
  57. package/dist/esm/components/index.js +6 -5
  58. package/dist/esm/constants/dateFields.d.ts +24 -0
  59. package/dist/esm/constants/dateFields.js +103 -0
  60. package/dist/esm/constants/index.d.ts +2 -0
  61. package/dist/esm/constants/index.js +2 -0
  62. package/dist/esm/hooks/dateHandlers/index.d.ts +3 -0
  63. package/dist/esm/hooks/dateHandlers/index.js +3 -0
  64. package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.d.ts +13 -5
  65. package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.js +48 -35
  66. package/dist/esm/hooks/dateHandlers/useDateFieldHelpersForMode.d.ts +18 -0
  67. package/dist/esm/hooks/dateHandlers/useDateFieldHelpersForMode.js +95 -0
  68. package/dist/esm/hooks/index.d.ts +1 -0
  69. package/dist/esm/hooks/index.js +1 -0
  70. package/dist/esm/hooks/useCopyButton.js +1 -1
  71. package/dist/esm/{types.d.ts → types/allFields.d.ts} +1 -1
  72. package/dist/esm/types/dateFields.d.ts +11 -0
  73. package/dist/esm/types/index.d.ts +2 -0
  74. package/dist/esm/types/index.js +2 -0
  75. package/dist/esm/utils/dateFields.d.ts +10 -0
  76. package/dist/esm/utils/dateFields.js +59 -0
  77. package/package.json +16 -16
  78. package/src/components/FieldColor/styles.module.scss +9 -10
  79. package/src/components/FieldDate/FieldDate.tsx +72 -52
  80. package/src/components/FieldDate/index.ts +0 -1
  81. package/src/components/FieldDate/styles.module.scss +10 -11
  82. package/src/components/FieldDecorator/styles.module.scss +44 -45
  83. package/src/components/FieldSelect/hooks.ts +15 -3
  84. package/src/components/FieldSelect/styles.module.scss +20 -20
  85. package/src/components/FieldSlider/styles.module.scss +4 -4
  86. package/src/components/FieldTextArea/styles.module.scss +18 -18
  87. package/src/components/FieldTime/FieldTime.tsx +350 -0
  88. package/src/components/FieldTime/index.ts +1 -0
  89. package/src/components/FieldTime/styles.module.scss +41 -0
  90. package/src/components/index.ts +6 -5
  91. package/src/constants/dateFields.ts +127 -0
  92. package/src/constants/index.ts +2 -0
  93. package/src/helperComponents/ButtonCopyValue/styles.module.scss +2 -2
  94. package/src/helperComponents/ButtonField/styles.module.scss +9 -9
  95. package/src/helperComponents/ButtonFieldList/styles.module.scss +2 -2
  96. package/src/helperComponents/ButtonHideValue/styles.module.scss +2 -2
  97. package/src/helperComponents/FieldContainerPrivate/styles.module.scss +24 -26
  98. package/src/helperComponents/TextArea/styles.module.scss +5 -5
  99. package/src/hooks/dateHandlers/index.ts +3 -0
  100. package/src/{components/FieldDate/hooks → hooks/dateHandlers}/useDateField.ts +93 -47
  101. package/src/hooks/dateHandlers/useDateFieldHelpersForMode.ts +145 -0
  102. package/src/hooks/index.ts +1 -0
  103. package/src/hooks/styles.module.scss +5 -5
  104. package/src/hooks/useCopyButton.tsx +1 -1
  105. package/src/styles.module.scss +15 -15
  106. package/src/{types.ts → types/allFields.ts} +1 -1
  107. package/src/types/dateFields.ts +14 -0
  108. package/src/types/index.ts +2 -0
  109. package/src/utils/dateFields.ts +75 -0
  110. package/dist/cjs/components/FieldDate/constants.d.ts +0 -10
  111. package/dist/cjs/components/FieldDate/constants.js +0 -49
  112. package/dist/cjs/components/FieldDate/hooks/index.d.ts +0 -1
  113. package/dist/cjs/components/FieldDate/hooks/useDateFieldHelpers.d.ts +0 -10
  114. package/dist/cjs/components/FieldDate/hooks/useDateFieldHelpers.js +0 -82
  115. package/dist/cjs/components/FieldDate/types.d.ts +0 -6
  116. package/dist/cjs/components/FieldDate/utils.d.ts +0 -9
  117. package/dist/cjs/components/FieldDate/utils.js +0 -56
  118. package/dist/esm/components/FieldDate/constants.d.ts +0 -10
  119. package/dist/esm/components/FieldDate/constants.js +0 -28
  120. package/dist/esm/components/FieldDate/hooks/index.d.ts +0 -1
  121. package/dist/esm/components/FieldDate/hooks/index.js +0 -1
  122. package/dist/esm/components/FieldDate/hooks/useDateFieldHelpers.d.ts +0 -10
  123. package/dist/esm/components/FieldDate/hooks/useDateFieldHelpers.js +0 -66
  124. package/dist/esm/components/FieldDate/types.d.ts +0 -6
  125. package/dist/esm/components/FieldDate/utils.d.ts +0 -9
  126. package/dist/esm/components/FieldDate/utils.js +0 -43
  127. package/src/components/FieldDate/constants.ts +0 -33
  128. package/src/components/FieldDate/hooks/index.ts +0 -1
  129. package/src/components/FieldDate/hooks/useDateFieldHelpers.ts +0 -96
  130. package/src/components/FieldDate/types.ts +0 -6
  131. package/src/components/FieldDate/utils.ts +0 -49
  132. /package/dist/cjs/{constants.d.ts → constants/allFields.d.ts} +0 -0
  133. /package/dist/cjs/{constants.js → constants/allFields.js} +0 -0
  134. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.d.ts +0 -0
  135. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.js +0 -0
  136. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.d.ts +0 -0
  137. /package/dist/cjs/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.js +0 -0
  138. /package/dist/cjs/{components/FieldDate/types.js → types/allFields.js} +0 -0
  139. /package/dist/cjs/{types.js → types/dateFields.js} +0 -0
  140. /package/dist/esm/{constants.d.ts → constants/allFields.d.ts} +0 -0
  141. /package/dist/esm/{constants.js → constants/allFields.js} +0 -0
  142. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.d.ts +0 -0
  143. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.js +0 -0
  144. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.d.ts +0 -0
  145. /package/dist/esm/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.js +0 -0
  146. /package/dist/esm/{components/FieldDate/types.js → types/allFields.js} +0 -0
  147. /package/dist/esm/{types.js → types/dateFields.js} +0 -0
  148. /package/src/{constants.ts → constants/allFields.ts} +0 -0
  149. /package/src/{components/FieldDate/hooks → hooks/dateHandlers}/useFocusHandlers.ts +0 -0
  150. /package/src/{components/FieldDate/hooks → hooks/dateHandlers}/useHandlers.ts +0 -0
@@ -1,18 +1,18 @@
1
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
2
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
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
3
 
4
4
  $sizes: 's', 'm', 'l';
5
5
  $containerVariant: 'single-line-container';
6
6
  $icons-sizes: (
7
- 's': $icon-xs,
8
- 'm': $icon-s,
9
- 'l': $icon-s,
7
+ 's': styles-tokens-element.$icon-xs,
8
+ 'm': styles-tokens-element.$icon-s,
9
+ 'l': styles-tokens-element.$icon-s,
10
10
  );
11
11
 
12
12
  $base-min-width: 4px;
13
13
 
14
14
  .triggerClassName {
15
- --offset: #{$space-drop-list-drop-offset};
15
+ --offset: #{styles-tokens-element.$space-drop-list-drop-offset};
16
16
 
17
17
  width: 100%;
18
18
  }
@@ -33,10 +33,10 @@ $base-min-width: 4px;
33
33
 
34
34
  @each $size in $sizes {
35
35
  &[data-size='#{$size}'] {
36
- @include composite-var($fields, 'select-multiple', $size);
36
+ @include styles-tokens-element.composite-var(styles-tokens-fields.$fields, 'select-multiple', $size);
37
37
 
38
38
  .contentWrapper {
39
- gap: simple-var($fields, 'select-multiple', $size, 'gap');
39
+ gap: styles-tokens-element.simple-var(styles-tokens-fields.$fields, 'select-multiple', $size, 'gap');
40
40
  }
41
41
  }
42
42
  }
@@ -49,27 +49,27 @@ $base-min-width: 4px;
49
49
  @each $size in $sizes {
50
50
  &[data-size='#{$size}'] {
51
51
  .inputPlug {
52
- @include composite-var($theme-variables, 'sans', 'body', $size);
52
+ @include styles-tokens-element.composite-var(styles-tokens-element.$theme-variables, 'sans', 'body', $size);
53
53
  }
54
54
 
55
55
  .arrowIcon {
56
- width: simple-var($icons-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
57
- height: simple-var($icons-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
58
- color: $sys-neutral-text-light;
56
+ width: styles-tokens-element.simple-var($icons-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
57
+ height: styles-tokens-element.simple-var($icons-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
58
+ color: styles-tokens-element.$sys-neutral-text-light;
59
59
  }
60
60
 
61
61
  &[data-variant='#{$containerVariant}'] {
62
62
  .displayValue {
63
- $button-width: simple-var($icons-sizes, $size);
64
- $postfix-width: calc(var(#{$space-fields-postfix-gap}) + $button-width * 2);
63
+ $button-width: styles-tokens-element.simple-var($icons-sizes, $size);
64
+ $postfix-width: calc(var(#{styles-tokens-element.$space-fields-postfix-gap}) + $button-width * 2);
65
65
  $margin-right: calc(
66
- #{simple-var($fields, $containerVariant, $size, 'padding-right')} + #{simple-var($fields, $containerVariant, $size, 'gap')} + #{$postfix-width}
66
+ #{styles-tokens-element.simple-var(styles-tokens-fields.$fields, $containerVariant, $size, 'padding-right')} + #{styles-tokens-element.simple-var(styles-tokens-fields.$fields, $containerVariant, $size, 'gap')} + #{$postfix-width}
67
67
  );
68
68
 
69
69
  width: calc(100% - $margin-right);
70
70
  margin-right: $margin-right;
71
- padding-left: simple-var($fields, $containerVariant, $size, 'padding-left');
72
- border-radius: simple-var($fields, $containerVariant, $size, 'border-radius');
71
+ padding-left: styles-tokens-element.simple-var(styles-tokens-fields.$fields, $containerVariant, $size, 'padding-left');
72
+ border-radius: styles-tokens-element.simple-var(styles-tokens-fields.$fields, $containerVariant, $size, 'border-radius');
73
73
  }
74
74
  }
75
75
  }
@@ -79,14 +79,14 @@ $base-min-width: 4px;
79
79
  &:focus-within,
80
80
  &[data-focused] {
81
81
  .arrowIcon {
82
- color: $sys-neutral-text-support;
82
+ color: styles-tokens-element.$sys-neutral-text-support;
83
83
  }
84
84
  }
85
85
 
86
86
  &[data-disabled],
87
87
  &[data-readonly] {
88
88
  .arrowIcon {
89
- color: $sys-neutral-text-disabled;
89
+ color: styles-tokens-element.$sys-neutral-text-disabled;
90
90
  }
91
91
  }
92
92
  }
@@ -125,7 +125,7 @@ $base-min-width: 4px;
125
125
  .postfix {
126
126
  display: inline-flex;
127
127
  flex-shrink: 0;
128
- gap: $space-fields-postfix-gap;
128
+ gap: styles-tokens-element.$space-fields-postfix-gap;
129
129
  }
130
130
 
131
131
  input.readonlyCursor {
@@ -1,5 +1,5 @@
1
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
2
- @import '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
2
+ @use '@snack-uikit/figma-tokens/build/scss/styles-theme-variables';
3
3
 
4
4
  $sizes: 's', 'm', 'l';
5
5
 
@@ -14,7 +14,7 @@ $sizes: 's', 'm', 'l';
14
14
 
15
15
  @each $size in $sizes {
16
16
  &[data-size='#{$size}'] {
17
- @include composite-var($fields, 'slider-wrap', $size);
17
+ @include styles-theme-variables.composite-var(styles-tokens-fields.$fields, 'slider-wrap', $size);
18
18
 
19
19
  height: inherit;
20
20
  }
@@ -24,6 +24,6 @@ $sizes: 's', 'm', 'l';
24
24
 
25
25
  .fieldContainer {
26
26
  svg {
27
- color: simple-var($theme-variables, 'sys', 'neutral', 'text-disabled');
27
+ color: styles-theme-variables.simple-var(styles-theme-variables.$theme-variables, 'sys', 'neutral', 'text-disabled');
28
28
  }
29
29
  }
@@ -1,25 +1,25 @@
1
- @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
1
+ @use '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
2
2
 
3
3
  $sizes: 's', 'm', 'l';
4
4
  $padding-right: (
5
- 's': $fields-buttons-s,
6
- 'm': $fields-buttons-m,
7
- 'l': $fields-buttons-m,
5
+ 's': styles-tokens-fields.$fields-buttons-s,
6
+ 'm': styles-tokens-fields.$fields-buttons-m,
7
+ 'l': styles-tokens-fields.$fields-buttons-m,
8
8
  );
9
9
  $scroll-bar-heights: (
10
- 's': $dimension-050m,
11
- 'm': calc($dimension-025m + $dimension-050m),
12
- 'l': $dimension-1m,
10
+ 's': styles-tokens-fields.$dimension-050m,
11
+ 'm': calc(styles-tokens-fields.$dimension-025m + styles-tokens-fields.$dimension-050m),
12
+ 'l': styles-tokens-fields.$dimension-1m,
13
13
  );
14
14
  $padding-right: (
15
- 's': $fields-buttons-s,
16
- 'm': $fields-buttons-m,
17
- 'l': $fields-buttons-m,
15
+ 's': styles-tokens-fields.$fields-buttons-s,
16
+ 'm': styles-tokens-fields.$fields-buttons-m,
17
+ 'l': styles-tokens-fields.$fields-buttons-m,
18
18
  );
19
19
  $scroll-bar-heights: (
20
- 's': $dimension-050m,
21
- 'm': calc($dimension-025m + $dimension-050m),
22
- 'l': $dimension-1m,
20
+ 's': styles-tokens-fields.$dimension-050m,
21
+ 'm': calc(styles-tokens-fields.$dimension-025m + styles-tokens-fields.$dimension-050m),
22
+ 'l': styles-tokens-fields.$dimension-1m,
23
23
  );
24
24
 
25
25
  .container {
@@ -37,18 +37,18 @@ $scroll-bar-heights: (
37
37
 
38
38
  @each $size in $sizes {
39
39
  &[data-size='#{$size}'] {
40
- --row-height: #{simple-var($theme-variables, 'sans', 'body', $size, 'line-height')};
41
- --horizontal-scroll-bar-height: #{simple-var($scroll-bar-heights, $size)};
40
+ --row-height: #{styles-tokens-fields.simple-var(styles-tokens-fields.$theme-variables, 'sans', 'body', $size, 'line-height')};
41
+ --horizontal-scroll-bar-height: #{styles-tokens-fields.simple-var($scroll-bar-heights, $size)};
42
42
 
43
43
  .textarea {
44
44
  overflow: hidden;
45
- padding-right: calc(simple-var($padding-right, $size, 'width') + simple-var($fields-scroll-bar-width, 'width'));
45
+ padding-right: calc(styles-tokens-fields.simple-var($padding-right, $size, 'width') + styles-tokens-fields.simple-var(styles-tokens-fields.$fields-scroll-bar-width, 'width'));
46
46
  }
47
47
 
48
48
  .postfix {
49
49
  position: absolute;
50
- top: simple-var($fields, 'multi-line-container', $size, 'padding-top');
51
- right: calc(simple-var($fields, 'multi-line-container', $size, 'padding-right') + simple-var($fields-scroll-bar-width, 'width'));
50
+ top: styles-tokens-fields.simple-var(styles-tokens-fields.$fields, 'multi-line-container', $size, 'padding-top');
51
+ right: calc(styles-tokens-fields.simple-var(styles-tokens-fields.$fields, 'multi-line-container', $size, 'padding-right') + styles-tokens-fields.simple-var(styles-tokens-fields.$fields-scroll-bar-width, 'width'));
52
52
  }
53
53
  }
54
54
  }
@@ -0,0 +1,350 @@
1
+ import mergeRefs from 'merge-refs';
2
+ import { FocusEvent, forwardRef, KeyboardEvent, MouseEvent, useCallback, useEffect, useMemo, useRef } from 'react';
3
+ import { useUncontrolledProp } from 'uncontrollable';
4
+
5
+ import { TimePicker, TimePickerProps } from '@snack-uikit/calendar';
6
+ import { Dropdown } from '@snack-uikit/dropdown';
7
+ import { WatchSVG } from '@snack-uikit/icons';
8
+ import {
9
+ ButtonProps,
10
+ ICON_SIZE,
11
+ InputPrivate,
12
+ InputPrivateProps,
13
+ runAfterRerender,
14
+ SIZE,
15
+ useButtonNavigation,
16
+ useClearButton,
17
+ } from '@snack-uikit/input-private';
18
+ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
19
+
20
+ import { CONTAINER_VARIANT, DEFAULT_LOCALE, SlotKey, TIME_MODES, VALIDATION_STATE } from '../../constants';
21
+ import { FieldContainerPrivate } from '../../helperComponents';
22
+ import { useCopyButton, useDateField, useFocusHandlers, useHandlers } from '../../hooks';
23
+ import { getValidationState } from '../../utils/getValidationState';
24
+ import { FieldDecorator, FieldDecoratorProps } from '../FieldDecorator';
25
+ import styles from './styles.module.scss';
26
+
27
+ type InputProps = Pick<InputPrivateProps, 'id' | 'name' | 'disabled' | 'readonly' | 'onFocus' | 'onBlur'>;
28
+
29
+ type WrapperProps = Pick<
30
+ FieldDecoratorProps,
31
+ | 'className'
32
+ | 'label'
33
+ | 'labelTooltip'
34
+ | 'required'
35
+ | 'caption'
36
+ | 'hint'
37
+ | 'showHintIcon'
38
+ | 'size'
39
+ | 'validationState'
40
+ | 'labelTooltipPlacement'
41
+ | 'error'
42
+ >;
43
+
44
+ type FieldTimeOwnProps = {
45
+ /** Открыт time-picker */
46
+ open?: boolean;
47
+ /** Колбек открытия пикера */
48
+ onOpenChange?(value: boolean): void;
49
+ /** Значение поля */
50
+ value?: TimePickerProps['value'];
51
+ /** Колбек смены значения */
52
+ onChange?: TimePickerProps['onChangeValue'];
53
+ /** Отображение кнопки копирования */
54
+ showCopyButton?: boolean;
55
+ /** Показывать ли секунды */
56
+ showSeconds?: boolean;
57
+ /**
58
+ * Отображение кнопки Очистки поля
59
+ * @default true
60
+ */
61
+ showClearButton?: boolean;
62
+ };
63
+
64
+ export type FieldTimeProps = WithSupportProps<FieldTimeOwnProps & InputProps & WrapperProps>;
65
+
66
+ const getStringTimeValue = (
67
+ time: TimePickerProps['value'],
68
+ { showSeconds, locale }: Pick<TimePickerProps, 'showSeconds'> & { locale: Intl.Locale },
69
+ ) => {
70
+ if (!time) {
71
+ return '';
72
+ }
73
+
74
+ const date = new Date();
75
+ date.setHours(time.hours ?? 0);
76
+ date.setMinutes(time.minutes ?? 0);
77
+ date.setSeconds(time.seconds ?? 0);
78
+
79
+ return date.toLocaleTimeString(locale, {
80
+ hour: 'numeric',
81
+ minute: 'numeric',
82
+ second: showSeconds ? 'numeric' : undefined,
83
+ });
84
+ };
85
+
86
+ export const FieldTime = forwardRef<HTMLInputElement, FieldTimeProps>(
87
+ (
88
+ {
89
+ id,
90
+ name,
91
+ value: valueProp,
92
+ disabled = false,
93
+ readonly = false,
94
+ showCopyButton: showCopyButtonProp = true,
95
+ showClearButton: showClearButtonProp = true,
96
+ open,
97
+ onOpenChange,
98
+ onChange,
99
+ onFocus,
100
+ onBlur: onBlurProp,
101
+ className,
102
+ label,
103
+ labelTooltip,
104
+ labelTooltipPlacement,
105
+ required = false,
106
+ caption,
107
+ hint,
108
+ showHintIcon,
109
+ showSeconds = true,
110
+ size = SIZE.S,
111
+ validationState = VALIDATION_STATE.Default,
112
+ error,
113
+ ...rest
114
+ },
115
+ ref,
116
+ ) => {
117
+ const [isOpen, setIsOpen] = useUncontrolledProp(open, false, onOpenChange);
118
+
119
+ const localRef = useRef<HTMLInputElement>(null);
120
+ const clearButtonRef = useRef<HTMLButtonElement>(null);
121
+ const copyButtonRef = useRef<HTMLButtonElement>(null);
122
+ const calendarIconSize = size === SIZE.S ? ICON_SIZE.Xs : ICON_SIZE.S;
123
+ const showDropList = isOpen && !readonly && !disabled;
124
+ const showAdditionalButton = Boolean(valueProp && !disabled);
125
+ const showClearButton = showClearButtonProp && showAdditionalButton && !readonly;
126
+ const showCopyButton = showCopyButtonProp && showAdditionalButton && readonly;
127
+ const fieldValidationState = getValidationState({ validationState, error });
128
+ const navigationStartRef: TimePickerProps['navigationStartRef'] = useRef(null);
129
+
130
+ const checkForLeavingFocus = useCallback(
131
+ <T extends HTMLInputElement | HTMLButtonElement>(event: KeyboardEvent<T>) => {
132
+ if (event.key === 'ArrowDown') {
133
+ setIsOpen(true);
134
+ setTimeout(() => navigationStartRef.current?.focus(), 0);
135
+ }
136
+ },
137
+ [setIsOpen],
138
+ );
139
+
140
+ const handleClear = useCallback(() => {
141
+ onChange && onChange(undefined);
142
+ if (localRef.current?.value) {
143
+ localRef.current.value = '';
144
+ }
145
+
146
+ if (required) {
147
+ localRef.current?.focus();
148
+ setIsOpen(true);
149
+ } else {
150
+ localRef.current?.blur();
151
+ setIsOpen(false);
152
+ }
153
+ }, [onChange, required, setIsOpen]);
154
+
155
+ const valueToCopy = getStringTimeValue(valueProp, { showSeconds, locale: DEFAULT_LOCALE });
156
+ const clearButtonSettings = useClearButton({ clearButtonRef, showClearButton, size, onClear: handleClear });
157
+ const copyButtonSettings = useCopyButton({ copyButtonRef, showCopyButton, size, valueToCopy });
158
+ const calendarIcon: ButtonProps = useMemo(
159
+ () => ({
160
+ active: false,
161
+ show: true,
162
+ id: 'watchIcon',
163
+ render: props => (
164
+ <WatchSVG {...props} size={calendarIconSize} className={styles.calendarIcon} data-size={size} />
165
+ ),
166
+ }),
167
+ [calendarIconSize, size],
168
+ );
169
+
170
+ const memorizedButtons = useMemo(
171
+ () => [clearButtonSettings, copyButtonSettings, calendarIcon],
172
+ [clearButtonSettings, copyButtonSettings, calendarIcon],
173
+ );
174
+
175
+ const {
176
+ value,
177
+ handleChange,
178
+ handleClick: timeInputClickHandler,
179
+ handleKeyDown: timeInputKeyDownHandler,
180
+ handleBlur: timeInputBlurHandler,
181
+ mask,
182
+ setInputFocus,
183
+ } = useDateField({
184
+ inputRef: localRef,
185
+ onChange,
186
+ readonly,
187
+ locale: DEFAULT_LOCALE,
188
+ setIsOpen,
189
+ mode: showSeconds ? TIME_MODES.FullTime : TIME_MODES.NoSeconds,
190
+ showSeconds,
191
+ });
192
+
193
+ const setInputFocusFromButtons = useCallback(() => setInputFocus(SlotKey.Seconds), [setInputFocus]);
194
+
195
+ const {
196
+ postfixButtons,
197
+ inputTabIndex,
198
+ onInputKeyDown: navigationInputKeyDownHandler,
199
+ setInitialTabIndices,
200
+ } = useButtonNavigation({
201
+ setInputFocus: setInputFocusFromButtons,
202
+ inputRef: localRef,
203
+ postfixButtons: memorizedButtons,
204
+ onButtonKeyDown: checkForLeavingFocus,
205
+ readonly,
206
+ submitKeys: ['Enter', 'Space', 'Tab'],
207
+ });
208
+
209
+ const handleSelectTime = (time: TimePickerProps['value']) => {
210
+ onChange && onChange(time);
211
+ localRef.current?.focus();
212
+ setIsOpen(false);
213
+
214
+ if (localRef.current) {
215
+ localRef.current.value = getStringTimeValue(time, { showSeconds, locale: DEFAULT_LOCALE });
216
+ }
217
+ };
218
+
219
+ const handleCalendarFocusLeave: TimePickerProps['onFocusLeave'] = () => {
220
+ setInitialTabIndices();
221
+ // TODO: find out why it works not as expected (focus is moved to the next element instead of the focused one)
222
+ // maybe floating-ui causes the problem
223
+ runAfterRerender(() => {
224
+ setInputFocus(SlotKey.Hours);
225
+ setIsOpen(false);
226
+ });
227
+ };
228
+
229
+ const handleInputKeyDown = useHandlers<KeyboardEvent<HTMLInputElement>>([
230
+ checkForLeavingFocus,
231
+ timeInputKeyDownHandler,
232
+ navigationInputKeyDownHandler,
233
+ ]);
234
+
235
+ useEffect(() => {
236
+ if (open) {
237
+ localRef.current?.focus();
238
+ }
239
+ }, [open]);
240
+
241
+ // TODO input ref - determine whether to update ref based on input/non-input state
242
+ useEffect(() => {
243
+ if (localRef.current && document.activeElement !== localRef.current) {
244
+ localRef.current.value = getStringTimeValue(valueProp, { showSeconds, locale: DEFAULT_LOCALE });
245
+ }
246
+ }, [showSeconds, valueProp]);
247
+
248
+ const onFocusByKeyboard = useCallback(
249
+ (e: FocusEvent<HTMLInputElement>) => {
250
+ setInputFocus();
251
+ onFocus?.(e);
252
+ },
253
+ [onFocus, setInputFocus],
254
+ );
255
+
256
+ const inputHandlers = useFocusHandlers({
257
+ onFocusByClick: onFocus,
258
+ onFocusByKeyboard,
259
+ });
260
+
261
+ const onBlur = useHandlers([timeInputBlurHandler, inputHandlers.onBlur, onBlurProp]);
262
+
263
+ const onClick = useCallback(
264
+ (e: MouseEvent<HTMLInputElement>) => {
265
+ timeInputClickHandler();
266
+ if (isOpen) {
267
+ // stop the event because want picker to stay opened
268
+ e.stopPropagation();
269
+ }
270
+ },
271
+ [timeInputClickHandler, isOpen],
272
+ );
273
+
274
+ return (
275
+ <FieldDecorator
276
+ className={className}
277
+ label={label}
278
+ labelTooltip={labelTooltip}
279
+ labelTooltipPlacement={labelTooltipPlacement}
280
+ labelFor={id}
281
+ required={required}
282
+ caption={caption}
283
+ hint={hint}
284
+ disabled={disabled}
285
+ readonly={readonly}
286
+ showHintIcon={showHintIcon}
287
+ size={size}
288
+ error={error}
289
+ validationState={fieldValidationState}
290
+ {...extractSupportProps(rest)}
291
+ >
292
+ <Dropdown
293
+ trigger='click'
294
+ triggerClassName={styles.triggerClassName}
295
+ widthStrategy='auto'
296
+ {...(readonly || disabled
297
+ ? { open: false }
298
+ : {
299
+ open: showDropList,
300
+ onOpenChange: setIsOpen,
301
+ })}
302
+ content={
303
+ <TimePicker
304
+ size={size}
305
+ value={valueProp}
306
+ onChangeValue={handleSelectTime}
307
+ navigationStartRef={navigationStartRef}
308
+ onFocusLeave={handleCalendarFocusLeave}
309
+ data-test-id='field-time__timepicker'
310
+ fitToContainer={false}
311
+ showSeconds={showSeconds}
312
+ />
313
+ }
314
+ >
315
+ <FieldContainerPrivate
316
+ className={styles.container}
317
+ size={size}
318
+ validationState={fieldValidationState}
319
+ disabled={disabled}
320
+ readonly={readonly}
321
+ variant={CONTAINER_VARIANT.SingleLine}
322
+ focused={showDropList}
323
+ inputRef={localRef}
324
+ postfix={postfixButtons}
325
+ >
326
+ <InputPrivate
327
+ ref={mergeRefs(ref, localRef)}
328
+ data-size={size}
329
+ value={value || ''}
330
+ placeholder={mask}
331
+ onChange={handleChange}
332
+ onFocus={inputHandlers.onFocus}
333
+ onMouseDown={inputHandlers.onMouseDown}
334
+ onBlur={onBlur}
335
+ onKeyDown={handleInputKeyDown}
336
+ onClick={onClick}
337
+ disabled={disabled}
338
+ readonly={readonly}
339
+ tabIndex={inputTabIndex}
340
+ type='text'
341
+ id={id}
342
+ name={name}
343
+ data-test-id='field-time__input'
344
+ />
345
+ </FieldContainerPrivate>
346
+ </Dropdown>
347
+ </FieldDecorator>
348
+ );
349
+ },
350
+ );
@@ -0,0 +1 @@
1
+ export * from './FieldTime';
@@ -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';