@snack-uikit/fields 0.28.0 → 0.29.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 (91) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +9 -0
  3. package/dist/components/FieldColor/FieldColor.d.ts +1 -1
  4. package/dist/components/FieldColor/FieldColor.js +3 -3
  5. package/dist/components/FieldDate/FieldDate.js +11 -5
  6. package/dist/components/FieldSecure/FieldSecure.d.ts +1 -1
  7. package/dist/components/FieldSecure/FieldSecure.js +3 -3
  8. package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +3 -1
  9. package/dist/components/FieldSelect/FieldSelectMultiple.js +7 -5
  10. package/dist/components/FieldSelect/FieldSelectSingle.d.ts +3 -1
  11. package/dist/components/FieldSelect/FieldSelectSingle.js +8 -6
  12. package/dist/components/FieldSelect/hooks.d.ts +1 -1
  13. package/dist/components/FieldSelect/hooks.js +3 -3
  14. package/dist/components/FieldSelect/types.d.ts +5 -1
  15. package/dist/components/FieldSlider/FieldSlider.d.ts +6 -2
  16. package/dist/components/FieldSlider/FieldSlider.js +13 -11
  17. package/dist/components/FieldStepper/FieldStepper.d.ts +5 -1
  18. package/dist/components/FieldStepper/FieldStepper.js +6 -4
  19. package/dist/components/FieldText/FieldText.d.ts +9 -2
  20. package/dist/components/FieldText/FieldText.js +46 -8
  21. package/dist/components/FieldText/helpers.d.ts +4 -0
  22. package/dist/components/FieldText/helpers.js +9 -0
  23. package/dist/components/FieldTextArea/FieldTextArea.d.ts +1 -1
  24. package/dist/components/FieldTextArea/FieldTextArea.js +3 -3
  25. package/dist/constants.d.ts +6 -0
  26. package/dist/constants.js +6 -0
  27. package/dist/helperComponents/ButtonField/ButtonField.d.ts +18 -0
  28. package/dist/helperComponents/ButtonField/ButtonField.js +10 -0
  29. package/dist/helperComponents/ButtonField/index.d.ts +1 -0
  30. package/dist/helperComponents/ButtonField/index.js +1 -0
  31. package/dist/helperComponents/ButtonField/styles.module.css +96 -0
  32. package/dist/helperComponents/ButtonFieldList/ButtonFieldList.d.ts +4 -0
  33. package/dist/helperComponents/ButtonFieldList/ButtonFieldList.js +27 -0
  34. package/dist/helperComponents/ButtonFieldList/helpers.d.ts +5 -0
  35. package/dist/helperComponents/ButtonFieldList/helpers.js +8 -0
  36. package/dist/helperComponents/ButtonFieldList/index.d.ts +1 -0
  37. package/dist/helperComponents/ButtonFieldList/index.js +1 -0
  38. package/dist/helperComponents/ButtonFieldList/styles.module.css +3 -0
  39. package/dist/helperComponents/FieldContainerPrivate/FieldContainerPrivate.d.ts +5 -4
  40. package/dist/helperComponents/FieldContainerPrivate/FieldContainerPrivate.js +2 -2
  41. package/dist/helperComponents/FieldContainerPrivate/styles.module.css +56 -5
  42. package/dist/helperComponents/index.d.ts +2 -0
  43. package/dist/helperComponents/index.js +2 -0
  44. package/dist/hooks/index.d.ts +4 -0
  45. package/dist/hooks/index.js +4 -0
  46. package/dist/hooks/styles.module.css +13 -0
  47. package/dist/hooks/useCopyButton.d.ts +3 -1
  48. package/dist/hooks/useCopyButton.js +4 -3
  49. package/dist/hooks/useHideButton.js +1 -0
  50. package/dist/hooks/usePostfix.d.ts +6 -0
  51. package/dist/hooks/usePostfix.js +11 -0
  52. package/dist/hooks/usePostfixButton.d.ts +11 -0
  53. package/dist/hooks/usePostfixButton.js +28 -0
  54. package/dist/hooks/usePrefix.d.ts +6 -0
  55. package/dist/hooks/usePrefix.js +11 -0
  56. package/dist/hooks/usePrefixButton.d.ts +11 -0
  57. package/dist/hooks/usePrefixButton.js +28 -0
  58. package/dist/types.d.ts +12 -1
  59. package/package.json +6 -5
  60. package/src/components/FieldColor/FieldColor.tsx +6 -3
  61. package/src/components/FieldDate/FieldDate.tsx +17 -10
  62. package/src/components/FieldSecure/FieldSecure.tsx +3 -3
  63. package/src/components/FieldSelect/FieldSelectMultiple.tsx +17 -4
  64. package/src/components/FieldSelect/FieldSelectSingle.tsx +17 -4
  65. package/src/components/FieldSelect/hooks.ts +3 -3
  66. package/src/components/FieldSelect/types.ts +10 -2
  67. package/src/components/FieldSlider/FieldSlider.tsx +30 -14
  68. package/src/components/FieldStepper/FieldStepper.tsx +40 -23
  69. package/src/components/FieldText/FieldText.tsx +78 -10
  70. package/src/components/FieldText/helpers.tsx +13 -0
  71. package/src/components/FieldTextArea/FieldTextArea.tsx +6 -3
  72. package/src/constants.ts +7 -0
  73. package/src/helperComponents/ButtonField/ButtonField.tsx +73 -0
  74. package/src/helperComponents/ButtonField/index.ts +1 -0
  75. package/src/helperComponents/ButtonField/styles.module.scss +57 -0
  76. package/src/helperComponents/ButtonFieldList/ButtonFieldList.tsx +36 -0
  77. package/src/helperComponents/ButtonFieldList/helpers.tsx +13 -0
  78. package/src/helperComponents/ButtonFieldList/index.ts +1 -0
  79. package/src/helperComponents/ButtonFieldList/styles.module.scss +5 -0
  80. package/src/helperComponents/FieldContainerPrivate/FieldContainerPrivate.tsx +6 -3
  81. package/src/helperComponents/FieldContainerPrivate/styles.module.scss +14 -8
  82. package/src/helperComponents/index.ts +2 -0
  83. package/src/hooks/index.ts +4 -0
  84. package/src/hooks/styles.module.scss +17 -0
  85. package/src/hooks/useCopyButton.tsx +7 -2
  86. package/src/hooks/useHideButton.tsx +1 -0
  87. package/src/hooks/usePostfix.tsx +21 -0
  88. package/src/hooks/usePostfixButton.tsx +74 -0
  89. package/src/hooks/usePrefix.tsx +21 -0
  90. package/src/hooks/usePrefixButton.tsx +74 -0
  91. package/src/types.ts +16 -1
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import styles from './styles.module.css';
4
+ export function usePostfix({ postfix, disabled }) {
5
+ return useMemo(() => ({
6
+ id: 'postfix',
7
+ active: false,
8
+ show: Boolean(postfix),
9
+ render: props => (_jsx("div", Object.assign({}, props, { className: styles.postfix, "data-test-id": 'field-postfix', "data-disabled": disabled || undefined, children: postfix }))),
10
+ }), [disabled, postfix]);
11
+ }
@@ -0,0 +1,11 @@
1
+ import { ReactElement } from 'react';
2
+ import { ButtonProps, Size } from '@snack-uikit/input-private';
3
+ import { ButtonFieldProps } from '../helperComponents';
4
+ import { Button } from '../types';
5
+ export declare function usePostfixButton({ button, size, postfixIcon, disabled, readonly, onFocus, onBlur, }: {
6
+ button?: Button;
7
+ size: Size;
8
+ postfixIcon?: ReactElement;
9
+ disabled?: boolean;
10
+ readonly?: boolean;
11
+ } & Pick<ButtonFieldProps, 'onFocus' | 'onBlur'>): ButtonProps;
@@ -0,0 +1,28 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo, useRef } from 'react';
3
+ import { ButtonField, ButtonFieldList } from '../helperComponents';
4
+ export function usePostfixButton({ button, size, postfixIcon, disabled, readonly, onFocus, onBlur, }) {
5
+ const buttonListRef = useRef(null);
6
+ const postfixIconProps = useMemo(() => ({
7
+ id: 'postfixIcon',
8
+ active: false,
9
+ show: Boolean(postfixIcon && !button),
10
+ render: () => _jsx(_Fragment, { children: postfixIcon }),
11
+ }), [button, postfixIcon]);
12
+ const postfixButtonProps = useMemo(() => ({
13
+ id: 'postfixButton',
14
+ active: false,
15
+ show: Boolean(button && button.variant === 'after'),
16
+ render: renderProps => {
17
+ const buttonProps = Object.assign(Object.assign({}, renderProps), { variant: 'after', size, content: button === null || button === void 0 ? void 0 : button.content, disabled: disabled || readonly, onFocus,
18
+ onBlur });
19
+ if (button === null || button === void 0 ? void 0 : button.items) {
20
+ return (_jsx(ButtonFieldList, Object.assign({}, buttonProps, { ref: buttonListRef, onClick: () => {
21
+ setTimeout(() => { var _a; return (_a = buttonListRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
22
+ }, items: button.items, selection: button.selection, open: button.open, onOpenChange: button.onOpenChange })));
23
+ }
24
+ return _jsx(ButtonField, Object.assign({}, buttonProps));
25
+ },
26
+ }), [button, size, disabled, readonly, onFocus, onBlur]);
27
+ return button ? postfixButtonProps : postfixIconProps;
28
+ }
@@ -0,0 +1,6 @@
1
+ import { ReactNode } from 'react';
2
+ import { InactiveItem } from '@snack-uikit/input-private';
3
+ export declare function usePrefix({ prefix, disabled }: {
4
+ prefix?: ReactNode;
5
+ disabled?: boolean;
6
+ }): InactiveItem;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import styles from './styles.module.css';
4
+ export function usePrefix({ prefix, disabled }) {
5
+ return useMemo(() => ({
6
+ id: 'prefix',
7
+ active: false,
8
+ show: Boolean(prefix),
9
+ render: props => (_jsx("div", Object.assign({}, props, { "data-test-id": 'field-prefix', className: styles.prefix, "data-disabled": disabled || undefined, children: prefix }))),
10
+ }), [disabled, prefix]);
11
+ }
@@ -0,0 +1,11 @@
1
+ import { ReactElement } from 'react';
2
+ import { ButtonProps, Size } from '@snack-uikit/input-private';
3
+ import { ButtonFieldProps } from '../helperComponents';
4
+ import { Button } from '../types';
5
+ export declare function usePrefixButton({ button, prefixIcon, size, disabled, readonly, onFocus, onBlur, }: {
6
+ button?: Button;
7
+ prefixIcon?: ReactElement;
8
+ size: Size;
9
+ disabled?: boolean;
10
+ readonly?: boolean;
11
+ } & Pick<ButtonFieldProps, 'onFocus' | 'onBlur'>): ButtonProps;
@@ -0,0 +1,28 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo, useRef } from 'react';
3
+ import { ButtonField, ButtonFieldList } from '../helperComponents';
4
+ export function usePrefixButton({ button, prefixIcon, size, disabled, readonly, onFocus, onBlur, }) {
5
+ const buttonListRef = useRef(null);
6
+ const prefixIconProps = useMemo(() => ({
7
+ id: 'prefixIcon',
8
+ active: false,
9
+ show: Boolean(prefixIcon && !button),
10
+ render: () => _jsx(_Fragment, { children: prefixIcon }),
11
+ }), [button, prefixIcon]);
12
+ const prefixButtonProps = useMemo(() => ({
13
+ id: 'prefixButton',
14
+ active: false,
15
+ show: Boolean(button && button.variant === 'before'),
16
+ render: renderProps => {
17
+ const buttonProps = Object.assign(Object.assign({}, renderProps), { variant: 'before', size, content: button === null || button === void 0 ? void 0 : button.content, disabled: disabled || readonly, onFocus,
18
+ onBlur });
19
+ if (button === null || button === void 0 ? void 0 : button.items) {
20
+ return (_jsx(ButtonFieldList, Object.assign({}, buttonProps, { ref: buttonListRef, onClick: () => {
21
+ setTimeout(() => { var _a; return (_a = buttonListRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 0);
22
+ }, items: button.items, selection: button.selection, open: button.open, onOpenChange: button.onOpenChange })));
23
+ }
24
+ return _jsx(ButtonField, Object.assign({}, buttonProps));
25
+ },
26
+ }), [button, size, disabled, readonly, onFocus, onBlur]);
27
+ return button ? prefixButtonProps : prefixIconProps;
28
+ }
package/dist/types.d.ts CHANGED
@@ -1,8 +1,19 @@
1
+ import { ReactNode } from 'react';
2
+ import { DroplistProps, SelectionSingleState } from '@snack-uikit/list';
1
3
  import { ValueOf } from '@snack-uikit/utils';
2
- import { CONTAINER_VARIANT, VALIDATION_STATE } from './constants';
4
+ import { BUTTON_VARIANT, CONTAINER_VARIANT, VALIDATION_STATE } from './constants';
3
5
  export type ValidationState = ValueOf<typeof VALIDATION_STATE>;
4
6
  export type ContainerVariant = ValueOf<typeof CONTAINER_VARIANT>;
7
+ export type ButtonVariant = ValueOf<typeof BUTTON_VARIANT>;
5
8
  export type AsyncValueRequest = Promise<{
6
9
  success: boolean;
7
10
  value?: string;
8
11
  }>;
12
+ export type NativeDroplistProps = Pick<DroplistProps, 'items' | 'open' | 'onOpenChange'> & {
13
+ selection?: Omit<SelectionSingleState, 'mode'>;
14
+ };
15
+ export type Button = Omit<NativeDroplistProps, 'items'> & {
16
+ variant: ButtonVariant;
17
+ content?: ReactNode;
18
+ items?: DroplistProps['items'];
19
+ };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "Fields",
7
- "version": "0.28.0",
7
+ "version": "0.29.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -34,11 +34,12 @@
34
34
  "dependencies": {
35
35
  "@snack-uikit/button": "0.17.5",
36
36
  "@snack-uikit/calendar": "0.7.12",
37
- "@snack-uikit/color-picker": "0.1.0",
37
+ "@snack-uikit/color-picker": "0.1.1",
38
+ "@snack-uikit/divider": "3.0.4",
38
39
  "@snack-uikit/dropdown": "0.2.7",
39
40
  "@snack-uikit/icons": "0.22.1",
40
- "@snack-uikit/input-private": "3.2.1",
41
- "@snack-uikit/list": "0.16.4",
41
+ "@snack-uikit/input-private": "4.0.0",
42
+ "@snack-uikit/list": "0.17.0",
42
43
  "@snack-uikit/scroll": "0.6.2",
43
44
  "@snack-uikit/skeleton": "0.3.5",
44
45
  "@snack-uikit/slider": "0.1.18",
@@ -60,5 +61,5 @@
60
61
  "peerDependencies": {
61
62
  "@snack-uikit/locale": "*"
62
63
  },
63
- "gitHead": "6f68e2729e48e5b34674af649dc9dfb3bda3951f"
64
+ "gitHead": "36a86f3bf30c115e8c6b49f6c8d7a2d2ffb86ff1"
64
65
  }
@@ -111,9 +111,12 @@ export const FieldColor = forwardRef<HTMLInputElement, FieldColorProps>(
111
111
 
112
112
  const clearButtonSettings = useClearButton({ clearButtonRef, showClearButton, size, onClear });
113
113
  const copyButtonSettings = useCopyButton({ copyButtonRef, showCopyButton, size, valueToCopy: value });
114
- const { buttons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
114
+ const { postfixButtons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
115
115
  inputRef: localRef,
116
- buttons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
116
+ postfixButtons: useMemo(
117
+ () => [clearButtonSettings, copyButtonSettings],
118
+ [clearButtonSettings, copyButtonSettings],
119
+ ),
117
120
  readonly,
118
121
  submitKeys: ['Enter', 'Space', 'Tab'],
119
122
  });
@@ -175,7 +178,7 @@ export const FieldColor = forwardRef<HTMLInputElement, FieldColorProps>(
175
178
  variant={CONTAINER_VARIANT.SingleLine}
176
179
  focused={showDropList}
177
180
  inputRef={localRef}
178
- postfix={buttons}
181
+ postfix={postfixButtons}
179
182
  prefix={
180
183
  <div
181
184
  className={styles.colorPreview}
@@ -16,6 +16,7 @@ import { Calendar, CalendarProps } from '@snack-uikit/calendar';
16
16
  import { Dropdown } from '@snack-uikit/dropdown';
17
17
  import { CalendarSVG } from '@snack-uikit/icons';
18
18
  import {
19
+ ButtonProps,
19
20
  ICON_SIZE,
20
21
  InputPrivate,
21
22
  InputPrivateProps,
@@ -154,10 +155,21 @@ export const FieldDate = forwardRef<HTMLInputElement, FieldDateProps>(
154
155
 
155
156
  const clearButtonSettings = useClearButton({ clearButtonRef, showClearButton, size, onClear: handleClear });
156
157
  const copyButtonSettings = useCopyButton({ copyButtonRef, showCopyButton, size, valueToCopy: valueProp || '' });
158
+ const calendarIcon: ButtonProps = useMemo(
159
+ () => ({
160
+ active: false,
161
+ show: true,
162
+ id: 'calendarIcon',
163
+ render: props => (
164
+ <CalendarSVG {...props} size={calendarIconSize} className={styles.calendarIcon} data-size={size} />
165
+ ),
166
+ }),
167
+ [calendarIconSize, size],
168
+ );
157
169
 
158
170
  const memorizedButtons = useMemo(
159
- () => [clearButtonSettings, copyButtonSettings],
160
- [clearButtonSettings, copyButtonSettings],
171
+ () => [clearButtonSettings, copyButtonSettings, calendarIcon],
172
+ [clearButtonSettings, copyButtonSettings, calendarIcon],
161
173
  );
162
174
 
163
175
  const {
@@ -179,14 +191,14 @@ export const FieldDate = forwardRef<HTMLInputElement, FieldDateProps>(
179
191
  const setInputFocusFromButtons = useCallback(() => setInputFocus(SlotKey.Year), [setInputFocus]);
180
192
 
181
193
  const {
182
- buttons,
194
+ postfixButtons,
183
195
  inputTabIndex,
184
196
  onInputKeyDown: navigationInputKeyDownHandler,
185
197
  setInitialTabIndices,
186
198
  } = useButtonNavigation({
187
199
  setInputFocus: setInputFocusFromButtons,
188
200
  inputRef: localRef,
189
- buttons: memorizedButtons,
201
+ postfixButtons: memorizedButtons,
190
202
  onButtonKeyDown: checkForLeavingFocus,
191
203
  readonly,
192
204
  submitKeys: ['Enter', 'Space', 'Tab'],
@@ -311,12 +323,7 @@ export const FieldDate = forwardRef<HTMLInputElement, FieldDateProps>(
311
323
  variant={CONTAINER_VARIANT.SingleLine}
312
324
  focused={showDropList}
313
325
  inputRef={localRef}
314
- postfix={
315
- <>
316
- {buttons}
317
- <CalendarSVG size={calendarIconSize} className={styles.calendarIcon} data-size={size} />
318
- </>
319
- }
326
+ postfix={postfixButtons}
320
327
  >
321
328
  <InputPrivate
322
329
  ref={mergeRefs(ref, localRef)}
@@ -177,9 +177,9 @@ export const FieldSecure = forwardRef<HTMLInputElement, FieldSecureProps>(
177
177
  disabled: disabled || isLoading,
178
178
  });
179
179
 
180
- const { buttons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
180
+ const { postfixButtons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
181
181
  inputRef: localRef,
182
- buttons: useMemo(() => [copyButtonSettings, hideButtonSettings], [copyButtonSettings, hideButtonSettings]),
182
+ postfixButtons: useMemo(() => [copyButtonSettings, hideButtonSettings], [copyButtonSettings, hideButtonSettings]),
183
183
  readonly,
184
184
  submitKeys: ['Enter', 'Space', 'Tab'],
185
185
  });
@@ -211,7 +211,7 @@ export const FieldSecure = forwardRef<HTMLInputElement, FieldSecureProps>(
211
211
  variant={CONTAINER_VARIANT.SingleLine}
212
212
  inputRef={localRef}
213
213
  prefix={prefixIcon}
214
- postfix={buttons}
214
+ postfix={postfixButtons}
215
215
  >
216
216
  <WithSkeleton skeleton={<Skeleton width='100%' borderRadius={2} />} loading={isLoading}>
217
217
  <InputPrivate
@@ -8,7 +8,7 @@ import { Tag } from '@snack-uikit/tag';
8
8
  import { extractSupportProps, isBrowser, useLayoutEffect } from '@snack-uikit/utils';
9
9
 
10
10
  import { FieldContainerPrivate } from '../../helperComponents';
11
- import { useValueControl } from '../../hooks';
11
+ import { usePostfix, usePrefix, useValueControl } from '../../hooks';
12
12
  import { getValidationState } from '../../utils/getValidationState';
13
13
  import { FieldDecorator } from '../FieldDecorator';
14
14
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
@@ -43,6 +43,8 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
43
43
  search,
44
44
  autocomplete = false,
45
45
  prefixIcon,
46
+ prefix,
47
+ postfix,
46
48
  removeByBackspace = false,
47
49
  addOptionByEnter = false,
48
50
  untouchableScrollbars = false,
@@ -77,6 +79,9 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
77
79
  resetSearchOnOptionSelection,
78
80
  });
79
81
 
82
+ const prefixSettings = usePrefix({ prefix, disabled });
83
+ const postfixSettings = usePostfix({ postfix, disabled });
84
+
80
85
  useLayoutEffect(() => {
81
86
  setItems(({ selectedItems }) => updateMultipleItems({ options, value, selectedItems }));
82
87
  }, [options, value]);
@@ -92,7 +97,7 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
92
97
 
93
98
  const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
94
99
 
95
- const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
100
+ const { postfixButtons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
96
101
  readonly,
97
102
  size,
98
103
  showClearButton: showClearButton && !disabled && !readonly && Boolean(selectedItems?.find(item => !item.disabled)),
@@ -208,7 +213,14 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
208
213
  variant='single-line-container'
209
214
  inputRef={localRef}
210
215
  size={size}
211
- prefix={prefixIcon}
216
+ prefix={
217
+ (prefixIcon || prefixSettings.show) && (
218
+ <>
219
+ {prefixIcon}
220
+ {prefixSettings.show && prefixSettings.render({ key: prefixSettings.id })}
221
+ </>
222
+ )
223
+ }
212
224
  >
213
225
  <>
214
226
  <div className={styles.contentWrapper} ref={contentRef}>
@@ -256,7 +268,8 @@ export const FieldSelectMultiple = forwardRef<HTMLInputElement, FieldSelectMulti
256
268
  </div>
257
269
 
258
270
  <div className={styles.postfix}>
259
- {buttons}
271
+ {postfixButtons}
272
+ {postfixSettings.show && postfixSettings.render({ key: postfixSettings.id })}
260
273
  <ArrowIcon size={arrowIconSize} className={styles.arrowIcon} />
261
274
  </div>
262
275
 
@@ -16,7 +16,7 @@ import { Droplist, ItemProps, SelectionSingleValueType } from '@snack-uikit/list
16
16
  import { extractSupportProps, isBrowser, useLayoutEffect } from '@snack-uikit/utils';
17
17
 
18
18
  import { FieldContainerPrivate } from '../../helperComponents';
19
- import { useValueControl } from '../../hooks';
19
+ import { usePostfix, usePrefix, useValueControl } from '../../hooks';
20
20
  import { getValidationState } from '../../utils/getValidationState';
21
21
  import { FieldDecorator } from '../FieldDecorator';
22
22
  import { extractFieldDecoratorProps } from '../FieldDecorator/utils';
@@ -51,6 +51,8 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
51
51
  search,
52
52
  autocomplete = false,
53
53
  prefixIcon,
54
+ prefix,
55
+ postfix,
54
56
  addOptionByEnter = false,
55
57
  untouchableScrollbars = false,
56
58
  open: openProp,
@@ -80,6 +82,9 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
80
82
 
81
83
  const prevSelectedItem = useRef<ItemWithId | undefined>(selectedItem);
82
84
 
85
+ const prefixSettings = usePrefix({ prefix, disabled });
86
+ const postfixSettings = usePostfix({ postfix, disabled });
87
+
83
88
  useLayoutEffect(() => {
84
89
  setItems(({ selectedItem }) => updateItems({ options, value, selectedItem }));
85
90
  }, [options, value]);
@@ -111,7 +116,7 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
111
116
 
112
117
  const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open });
113
118
 
114
- const { buttons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
119
+ const { postfixButtons, inputKeyDownNavigationHandler, buttonsRefs } = useButtons({
115
120
  readonly,
116
121
  size,
117
122
  showClearButton: showClearButton && !disabled && !readonly && value !== undefined,
@@ -215,7 +220,14 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
215
220
  variant={'single-line-container'}
216
221
  inputRef={localRef}
217
222
  size={size}
218
- prefix={prefixIcon}
223
+ prefix={
224
+ (prefixIcon || prefixSettings.show) && (
225
+ <>
226
+ {prefixIcon}
227
+ {prefixSettings.show && prefixSettings.render({ key: prefixSettings.id })}
228
+ </>
229
+ )
230
+ }
219
231
  >
220
232
  <InputPrivate
221
233
  id={id}
@@ -236,7 +248,8 @@ export const FieldSelectSingle = forwardRef<HTMLInputElement, FieldSelectSingleP
236
248
  />
237
249
 
238
250
  <div className={styles.postfix}>
239
- {buttons}
251
+ {postfixButtons}
252
+ {postfixSettings.show && postfixSettings.render({ key: postfixSettings.id })}
240
253
  <ArrowIcon size={arrowIconSize} className={styles.arrowIcon} />
241
254
  </div>
242
255
  </FieldContainerPrivate>
@@ -90,15 +90,15 @@ export function useButtons({
90
90
  size,
91
91
  valueToCopy,
92
92
  });
93
- const { onInputKeyDown: inputKeyDownNavigationHandler, buttons } = useButtonNavigation({
93
+ const { onInputKeyDown: inputKeyDownNavigationHandler, postfixButtons } = useButtonNavigation({
94
94
  inputRef,
95
- buttons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
95
+ postfixButtons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
96
96
  onButtonKeyDown: undefined,
97
97
  readonly,
98
98
  submitKeys: ['Enter', 'Space', 'Tab'],
99
99
  });
100
100
 
101
- return { buttons, inputKeyDownNavigationHandler, buttonsRefs };
101
+ return { postfixButtons, inputKeyDownNavigationHandler, buttonsRefs };
102
102
  }
103
103
 
104
104
  export function useSearchInput({
@@ -1,4 +1,4 @@
1
- import { ReactElement } from 'react';
1
+ import { ReactElement, ReactNode } from 'react';
2
2
 
3
3
  import { InputPrivateProps } from '@snack-uikit/input-private';
4
4
  import {
@@ -79,7 +79,15 @@ export type SearchState = {
79
79
  onChange?(value: string): void;
80
80
  };
81
81
 
82
- export type FieldSelectPrivateProps = InputProps & WrapperProps & { options: OptionProps[]; loading?: boolean };
82
+ export type FieldSelectPrivateProps = InputProps &
83
+ WrapperProps & {
84
+ options: OptionProps[];
85
+ loading?: boolean;
86
+ /** Произвольный префикс для поля */
87
+ prefix?: ReactNode;
88
+ /** Произвольный постфикс для поля */
89
+ postfix?: ReactNode;
90
+ };
83
91
 
84
92
  type FiledSelectCommonProps = WithSupportProps<{
85
93
  options: OptionProps[];
@@ -4,6 +4,7 @@ import {
4
4
  forwardRef,
5
5
  KeyboardEvent,
6
6
  ReactElement,
7
+ ReactNode,
7
8
  useCallback,
8
9
  useEffect,
9
10
  useMemo,
@@ -17,7 +18,7 @@ import { extractSupportProps, useEventHandler, WithSupportProps } from '@snack-u
17
18
 
18
19
  import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
19
20
  import { FieldContainerPrivate } from '../../helperComponents';
20
- import { useValueControl } from '../../hooks';
21
+ import { usePostfix, usePrefix, useValueControl } from '../../hooks';
21
22
  import { FieldDecorator, FieldDecoratorProps } from '../FieldDecorator';
22
23
  import { generateAllowedValues, getClosestMark, getTextFieldValue, isMarkObject } from './helpers';
23
24
  import styles from './styles.module.scss';
@@ -49,6 +50,10 @@ type FieldSliderOwnProps = {
49
50
  textInputFormatter?: TextInputFormatter;
50
51
  /** Отвязать текстовое поле от значений на линейке */
51
52
  unbindInputFromMarks?: boolean;
53
+ /** Произвольный префикс для поля */
54
+ prefix?: ReactNode;
55
+ /** Произвольный постфикс для поля */
56
+ postfix?: ReactNode;
52
57
  };
53
58
 
54
59
  export type FieldSliderProps = WithSupportProps<FieldSliderOwnProps & SliderProps & WrapperProps>;
@@ -96,13 +101,29 @@ export const FieldSlider = forwardRef<HTMLInputElement, FieldSliderProps>(
96
101
  hint,
97
102
  showHintIcon,
98
103
  size = SIZE.S,
99
- postfixIcon,
100
104
  textInputFormatter,
101
105
  unbindInputFromMarks,
106
+ postfixIcon,
107
+ prefix,
108
+ postfix,
102
109
  ...rest
103
110
  },
104
111
  ref,
105
112
  ) => {
113
+ const [value = getDefaultValue(range, min, max, valueProp), onChange] = useValueControl<number | number[]>({
114
+ value: valueProp,
115
+ defaultValue: getDefaultValue(range, min, max, valueProp),
116
+ onChange: onChangeProp,
117
+ });
118
+
119
+ const [textFieldInputValue, setTextFieldInputValue] = useState<string>(
120
+ getTextFieldValue(value, textInputFormatter),
121
+ );
122
+ const localRef = useRef<HTMLInputElement>(null);
123
+
124
+ const prefixSettings = usePrefix({ prefix, disabled });
125
+ const postfixSettings = usePostfix({ postfix, disabled });
126
+
106
127
  const getMarkValue = useCallback(
107
128
  (key: keyof SliderProps['marks']) => {
108
129
  const mark = marks[key];
@@ -121,17 +142,6 @@ export const FieldSlider = forwardRef<HTMLInputElement, FieldSliderProps>(
121
142
  [getMarkValue, marks],
122
143
  );
123
144
 
124
- const [value = getDefaultValue(range, min, max, valueProp), onChange] = useValueControl<number | number[]>({
125
- value: valueProp,
126
- defaultValue: getDefaultValue(range, min, max, valueProp),
127
- onChange: onChangeProp,
128
- });
129
-
130
- const [textFieldInputValue, setTextFieldInputValue] = useState<string>(
131
- getTextFieldValue(value, textInputFormatter),
132
- );
133
- const localRef = useRef<HTMLInputElement>(null);
134
-
135
145
  const onTextFieldChange = (textFieldValue: string) => {
136
146
  const numValue = parseInt(textFieldValue);
137
147
 
@@ -280,7 +290,13 @@ export const FieldSlider = forwardRef<HTMLInputElement, FieldSliderProps>(
280
290
  readonly={readonly}
281
291
  variant={CONTAINER_VARIANT.SingleLine}
282
292
  inputRef={localRef}
283
- postfix={postfixIcon}
293
+ prefix={prefixSettings.show && prefixSettings.render({ key: prefixSettings.id })}
294
+ postfix={
295
+ <>
296
+ {postfixSettings.show && postfixSettings.render({ key: postfixSettings.id })}
297
+ {postfixIcon}
298
+ </>
299
+ }
284
300
  >
285
301
  <InputPrivate
286
302
  ref={mergeRefs(ref, localRef)}
@@ -5,6 +5,7 @@ import {
5
5
  forwardRef,
6
6
  KeyboardEventHandler,
7
7
  MouseEvent,
8
+ ReactNode,
8
9
  useEffect,
9
10
  useRef,
10
11
  useState,
@@ -19,7 +20,7 @@ import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
19
20
 
20
21
  import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
21
22
  import { FieldContainerPrivate } from '../../helperComponents';
22
- import { useValueControl } from '../../hooks';
23
+ import { usePostfix, usePrefix, useValueControl } from '../../hooks';
23
24
  import { getValidationState } from '../../utils/getValidationState';
24
25
  import { FieldDecorator, FieldDecoratorProps } from '../FieldDecorator';
25
26
  import styles from './styles.module.scss';
@@ -53,6 +54,10 @@ type FieldStepperOwnProps = {
53
54
  step?: number;
54
55
  /** Можно ли вводить c клавиатуры числа, выходящие за пределы min/max */
55
56
  allowMoreThanLimits?: boolean;
57
+ /** Произвольный префикс для поля */
58
+ prefix?: ReactNode;
59
+ /** Произвольный постфикс для поля */
60
+ postfix?: ReactNode;
56
61
  };
57
62
 
58
63
  export type FieldStepperProps = WithSupportProps<FieldStepperOwnProps & InputProps & WrapperProps>;
@@ -97,6 +102,8 @@ export const FieldStepper = forwardRef<HTMLInputElement, FieldStepperProps>(
97
102
  size = SIZE.S,
98
103
  validationState = VALIDATION_STATE.Default,
99
104
  error,
105
+ prefix,
106
+ postfix,
100
107
  ...rest
101
108
  },
102
109
  ref,
@@ -116,6 +123,9 @@ export const FieldStepper = forwardRef<HTMLInputElement, FieldStepperProps>(
116
123
  const isMinusButtonDisabled = (typeof min === 'number' && value <= min) || readonly || disabled;
117
124
  const isPlusButtonDisabled = (typeof max === 'number' && value >= max) || readonly || disabled;
118
125
 
126
+ const prefixSettings = usePrefix({ prefix, disabled });
127
+ const postfixSettings = usePostfix({ postfix, disabled });
128
+
119
129
  const fieldValidationState = getValidationState({ validationState, error });
120
130
 
121
131
  const adjustValue = (value: number) => {
@@ -240,30 +250,37 @@ export const FieldStepper = forwardRef<HTMLInputElement, FieldStepperProps>(
240
250
  variant={CONTAINER_VARIANT.SingleLine}
241
251
  inputRef={inputRef}
242
252
  prefix={
243
- <ButtonFunction
244
- tabIndex={-1}
245
- ref={minusButtonRef}
246
- size='xs'
247
- className={styles.button}
248
- icon={<MinusSVG />}
249
- onClick={handleMinusButtonClick}
250
- onKeyDown={handleMinusButtonKeyDown}
251
- disabled={isMinusButtonDisabled}
252
- data-test-id='field-stepper__minus-button'
253
- />
253
+ <>
254
+ <ButtonFunction
255
+ tabIndex={-1}
256
+ ref={minusButtonRef}
257
+ size='xs'
258
+ className={styles.button}
259
+ icon={<MinusSVG />}
260
+ onClick={handleMinusButtonClick}
261
+ onKeyDown={handleMinusButtonKeyDown}
262
+ disabled={isMinusButtonDisabled}
263
+ data-test-id='field-stepper__minus-button'
264
+ />
265
+ {prefixSettings.show && prefixSettings.render({ key: prefixSettings.id })}
266
+ </>
254
267
  }
255
268
  postfix={
256
- <ButtonFunction
257
- ref={plusButtonRef}
258
- tabIndex={-1}
259
- size='xs'
260
- className={styles.button}
261
- icon={<PlusSVG />}
262
- onClick={handlePlusButtonClick}
263
- onKeyDown={handlePlusButtonKeyDown}
264
- disabled={isPlusButtonDisabled}
265
- data-test-id='field-stepper__plus-button'
266
- />
269
+ <>
270
+ {postfixSettings.show && postfixSettings.render({ key: postfixSettings.id })}
271
+
272
+ <ButtonFunction
273
+ ref={plusButtonRef}
274
+ tabIndex={-1}
275
+ size='xs'
276
+ className={styles.button}
277
+ icon={<PlusSVG />}
278
+ onClick={handlePlusButtonClick}
279
+ onKeyDown={handlePlusButtonKeyDown}
280
+ disabled={isPlusButtonDisabled}
281
+ data-test-id='field-stepper__plus-button'
282
+ />
283
+ </>
267
284
  }
268
285
  >
269
286
  <InputPrivate