@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
@@ -1,14 +1,16 @@
1
1
  import mergeRefs from 'merge-refs';
2
- import { forwardRef, ReactElement, useMemo, useRef } from 'react';
2
+ import { forwardRef, ReactElement, ReactNode, useCallback, useMemo, useRef, useState } from 'react';
3
3
 
4
4
  import { InputPrivate, InputPrivateProps, SIZE, useButtonNavigation, useClearButton } from '@snack-uikit/input-private';
5
5
  import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
6
6
 
7
- import { CONTAINER_VARIANT, VALIDATION_STATE } from '../../constants';
7
+ import { VALIDATION_STATE } from '../../constants';
8
8
  import { FieldContainerPrivate } from '../../helperComponents';
9
- import { useCopyButton, useValueControl } from '../../hooks';
9
+ import { useCopyButton, usePostfix, usePostfixButton, usePrefix, usePrefixButton, useValueControl } from '../../hooks';
10
+ import { Button } from '../../types';
10
11
  import { getValidationState } from '../../utils/getValidationState';
11
12
  import { FieldDecorator, FieldDecoratorProps } from '../FieldDecorator';
13
+ import { getContainerVariant } from './helpers';
12
14
 
13
15
  type InputProps = Pick<Partial<InputPrivateProps>, 'value' | 'onChange'> &
14
16
  Pick<
@@ -43,6 +45,12 @@ type FieldTextOwnProps = {
43
45
  allowMoreThanMaxLength?: boolean;
44
46
  /** Иконка-префикс для поля */
45
47
  prefixIcon?: ReactElement;
48
+ /** Произвольный префикс для поля */
49
+ prefix?: ReactNode;
50
+ /** Произвольный постфикс для поля */
51
+ postfix?: ReactNode;
52
+ /** Кнопка действия внутри поля */
53
+ button?: Button;
46
54
  };
47
55
 
48
56
  export type FieldTextProps = WithSupportProps<FieldTextOwnProps & InputProps & WrapperProps>;
@@ -73,9 +81,12 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
73
81
  showHintIcon,
74
82
  size = SIZE.S,
75
83
  validationState = VALIDATION_STATE.Default,
76
- prefixIcon,
77
84
  error,
78
85
  autoComplete,
86
+ prefixIcon,
87
+ prefix,
88
+ postfix,
89
+ button: buttonProp,
79
90
  ...rest
80
91
  },
81
92
  ref,
@@ -93,6 +104,25 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
93
104
  const showCopyButton = showCopyButtonProp && showAdditionalButton && readonly;
94
105
  const fieldValidationState = getValidationState({ validationState, error });
95
106
 
107
+ const button: Button | undefined = useMemo(
108
+ () =>
109
+ buttonProp
110
+ ? {
111
+ ...buttonProp,
112
+ selection: {
113
+ ...buttonProp.selection,
114
+ onChange: value => {
115
+ buttonProp.selection?.onChange?.(value);
116
+ setTimeout(() => localRef.current?.focus(), 0);
117
+ },
118
+ },
119
+ }
120
+ : undefined,
121
+ [buttonProp],
122
+ );
123
+
124
+ const containerVariant = getContainerVariant({ button });
125
+
96
126
  const onClear = () => {
97
127
  onChange('');
98
128
 
@@ -102,10 +132,47 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
102
132
  };
103
133
 
104
134
  const clearButtonSettings = useClearButton({ clearButtonRef, showClearButton, size, onClear });
105
- const copyButtonSettings = useCopyButton({ copyButtonRef, showCopyButton, size, valueToCopy: value });
106
- const { buttons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
135
+ const copyButtonSettings = useCopyButton({
136
+ copyButtonRef,
137
+ showCopyButton,
138
+ size,
139
+ valueToCopy: value,
140
+ prefix: typeof prefix === 'string' ? prefix : undefined,
141
+ postfix: typeof postfix === 'string' ? postfix : undefined,
142
+ });
143
+
144
+ const [isButtonFocused, setIsButtonFocused] = useState(false);
145
+ const onButtonFocus = useCallback(() => setIsButtonFocused(true), []);
146
+ const onButtonBlur = useCallback(() => setIsButtonFocused(false), []);
147
+
148
+ const prefixSettings = usePrefix({ prefix, disabled });
149
+ const prefixButtonSettings = usePrefixButton({
150
+ button,
151
+ prefixIcon,
152
+ size,
153
+ disabled,
154
+ readonly,
155
+ onFocus: onButtonFocus,
156
+ onBlur: onButtonBlur,
157
+ });
158
+
159
+ const postfixSettings = usePostfix({ postfix, disabled });
160
+ const postfixButtonSettings = usePostfixButton({
161
+ button,
162
+ size,
163
+ disabled,
164
+ readonly,
165
+ onFocus: onButtonFocus,
166
+ onBlur: onButtonBlur,
167
+ });
168
+
169
+ const { postfixButtons, prefixButtons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
107
170
  inputRef: localRef,
108
- buttons: useMemo(() => [clearButtonSettings, copyButtonSettings], [clearButtonSettings, copyButtonSettings]),
171
+ postfixButtons: useMemo(
172
+ () => [clearButtonSettings, copyButtonSettings, postfixSettings, postfixButtonSettings],
173
+ [clearButtonSettings, copyButtonSettings, postfixSettings, postfixButtonSettings],
174
+ ),
175
+ prefixButtons: useMemo(() => [prefixButtonSettings, prefixSettings], [prefixButtonSettings, prefixSettings]),
109
176
  readonly,
110
177
  submitKeys: ['Enter', 'Space', 'Tab'],
111
178
  });
@@ -134,10 +201,11 @@ export const FieldText = forwardRef<HTMLInputElement, FieldTextProps>(
134
201
  validationState={fieldValidationState}
135
202
  disabled={disabled}
136
203
  readonly={readonly}
137
- variant={CONTAINER_VARIANT.SingleLine}
204
+ variant={containerVariant}
138
205
  inputRef={localRef}
139
- prefix={prefixIcon}
140
- postfix={buttons}
206
+ prefix={prefixButtons}
207
+ postfix={postfixButtons}
208
+ disableFocus={isButtonFocused}
141
209
  >
142
210
  <InputPrivate
143
211
  ref={mergeRefs(ref, localRef)}
@@ -0,0 +1,13 @@
1
+ import { Button, ContainerVariant } from '../../types';
2
+
3
+ export function getContainerVariant({ button }: { button?: Button }): ContainerVariant {
4
+ if (button?.variant === 'before') {
5
+ return 'single-line-container-button-before';
6
+ }
7
+
8
+ if (button?.variant === 'after') {
9
+ return 'single-line-container-button-after';
10
+ }
11
+
12
+ return 'single-line-container';
13
+ }
@@ -111,9 +111,12 @@ export const FieldTextArea = forwardRef<HTMLTextAreaElement, FieldTextAreaProps>
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
  });
@@ -147,7 +150,7 @@ export const FieldTextArea = forwardRef<HTMLTextAreaElement, FieldTextAreaProps>
147
150
  variant={CONTAINER_VARIANT.MultiLine}
148
151
  style={{ '--max-rows': maxRows, '--min-rows': minRows }}
149
152
  inputRef={localRef}
150
- postfix={<span className={styles.postfix}>{buttons}</span>}
153
+ postfix={<span className={styles.postfix}>{postfixButtons}</span>}
151
154
  >
152
155
  <Scroll
153
156
  className={styles.scrollContainer}
package/src/constants.ts CHANGED
@@ -7,5 +7,12 @@ export const VALIDATION_STATE = {
7
7
 
8
8
  export const CONTAINER_VARIANT = {
9
9
  SingleLine: 'single-line-container',
10
+ SingleLineButtonBefore: 'single-line-container-button-before',
11
+ SingleLineButtonAfter: 'single-line-container-button-after',
10
12
  MultiLine: 'multi-line-container',
11
13
  } as const;
14
+
15
+ export const BUTTON_VARIANT = {
16
+ Before: 'before',
17
+ After: 'after',
18
+ } as const;
@@ -0,0 +1,73 @@
1
+ import cn from 'classnames';
2
+ import { FocusEventHandler, forwardRef, KeyboardEventHandler, MouseEventHandler, ReactNode } from 'react';
3
+
4
+ import { Divider } from '@snack-uikit/divider';
5
+ import { Size } from '@snack-uikit/input-private';
6
+
7
+ import { getArrowIcon } from '../../components/FieldSelect/utils';
8
+ import { ButtonVariant } from '../../types';
9
+ import styles from './styles.module.scss';
10
+
11
+ export type ButtonFieldProps = {
12
+ size: Size;
13
+ onClick?: MouseEventHandler<HTMLButtonElement>;
14
+ onKeyDown?: KeyboardEventHandler<HTMLButtonElement>;
15
+ onFocus?: FocusEventHandler<HTMLButtonElement>;
16
+ onBlur?: FocusEventHandler<HTMLButtonElement>;
17
+ tabIndex?: number;
18
+ disabled?: boolean;
19
+ hasArrow?: boolean;
20
+ arrowOpen?: boolean;
21
+ content?: ReactNode;
22
+ variant: ButtonVariant;
23
+ className?: string;
24
+ };
25
+
26
+ export const ButtonField = forwardRef<HTMLButtonElement, ButtonFieldProps>(
27
+ (
28
+ {
29
+ size,
30
+ tabIndex = 0,
31
+ onClick,
32
+ onKeyDown,
33
+ onFocus,
34
+ onBlur,
35
+ disabled,
36
+ content,
37
+ hasArrow = false,
38
+ arrowOpen = false,
39
+ variant,
40
+ className,
41
+ },
42
+ ref,
43
+ ) => {
44
+ const { ArrowIcon, arrowIconSize } = getArrowIcon({ size, open: arrowOpen });
45
+
46
+ return (
47
+ <button
48
+ className={cn(className, styles.buttonField)}
49
+ data-size={size}
50
+ data-variant={variant}
51
+ data-arrow-open={arrowOpen || undefined}
52
+ data-disabled={disabled || undefined}
53
+ onClick={disabled ? undefined : onClick}
54
+ onKeyDown={disabled ? undefined : onKeyDown}
55
+ onFocus={disabled ? undefined : onFocus}
56
+ onBlur={disabled ? undefined : onBlur}
57
+ data-test-id='button-field'
58
+ ref={ref}
59
+ tabIndex={tabIndex}
60
+ type='button'
61
+ disabled={disabled}
62
+ >
63
+ {variant === 'after' && <Divider orientation='vertical' className={styles.divider} />}
64
+
65
+ <div className={styles.content}>{content}</div>
66
+
67
+ {hasArrow && <ArrowIcon size={arrowIconSize} />}
68
+
69
+ {variant === 'before' && <Divider orientation='vertical' className={styles.divider} />}
70
+ </button>
71
+ );
72
+ },
73
+ );
@@ -0,0 +1 @@
1
+ export * from './ButtonField';
@@ -0,0 +1,57 @@
1
+ @import "@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element";
2
+ @import "@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields";
3
+
4
+ $variants: 'before', 'after';
5
+ $sizes: 's', 'm', 'l';
6
+
7
+ .buttonField {
8
+ display: flex;
9
+ flex-shrink: 0;
10
+ align-items: center;
11
+ justify-content: center;
12
+
13
+ margin: 0;
14
+ padding: 0;
15
+
16
+ color: $sys-neutral-text-support;
17
+
18
+ background-color: transparent;
19
+ border: none;
20
+
21
+ @each $variant in $variants {
22
+ &[data-variant='#{$variant}'] {
23
+ @each $size in $sizes {
24
+ &[data-size='#{$size}'] {
25
+ @include composite-var($fields, 'button-field', $variant, $size);
26
+ }
27
+ }
28
+ }
29
+ }
30
+
31
+ &:hover {
32
+ cursor: pointer;
33
+ background: color-on-background-with-opacity($sys-neutral-accent-default, $sys-neutral-background1-level, $opacity-a008);
34
+ }
35
+
36
+ &:focus-visible, &[data-arrow-open] {
37
+ @include outline-var($container-focused-s);
38
+
39
+ background: color-on-background-with-opacity($sys-neutral-accent-default, $sys-neutral-background1-level, $opacity-a008);
40
+ outline-color: $sys-primary-accent-default;
41
+ }
42
+
43
+ &[data-disabled] {
44
+ cursor: not-allowed;
45
+ opacity: $opacity-a056;
46
+ background-color: transparent;
47
+ }
48
+ }
49
+
50
+ .divider {
51
+ align-self: stretch;
52
+ height: auto;
53
+ }
54
+
55
+ .content {
56
+ display: inline-flex;
57
+ }
@@ -0,0 +1,36 @@
1
+ import { forwardRef } from 'react';
2
+ import { useUncontrolledProp } from 'uncontrollable';
3
+
4
+ import { Droplist } from '@snack-uikit/list';
5
+
6
+ import { NativeDroplistProps } from '../../types';
7
+ import { ButtonField, ButtonFieldProps } from '../ButtonField';
8
+ import { getPlacement } from './helpers';
9
+ import styles from './styles.module.scss';
10
+
11
+ type ButtonFieldListProps = Omit<ButtonFieldProps, 'arrowOpen' | 'hasArrow'> & NativeDroplistProps;
12
+
13
+ export const ButtonFieldList = forwardRef<HTMLButtonElement, ButtonFieldListProps>(
14
+ ({ items, selection, open, onOpenChange, ...rest }, ref) => {
15
+ const [isOpen, setIsOpen] = useUncontrolledProp(open, false, onOpenChange);
16
+ const placement = getPlacement(rest);
17
+
18
+ return (
19
+ <Droplist
20
+ data-test-id='button-field-list'
21
+ items={items}
22
+ selection={{ mode: 'single', ...selection }}
23
+ open={isOpen}
24
+ onOpenChange={setIsOpen}
25
+ closeDroplistOnItemClick={true}
26
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
27
+ // @ts-ignore
28
+ triggerElemRef={ref}
29
+ size={rest.size}
30
+ placement={placement}
31
+ >
32
+ <ButtonField {...rest} hasArrow={true} arrowOpen={isOpen} className={styles.triggerClassName} />
33
+ </Droplist>
34
+ );
35
+ },
36
+ );
@@ -0,0 +1,13 @@
1
+ import { DroplistProps } from '@snack-uikit/list';
2
+
3
+ import { ButtonVariant } from '../../types';
4
+
5
+ export function getPlacement({ variant }: { variant?: ButtonVariant }): DroplistProps['placement'] {
6
+ if (variant === 'before') {
7
+ return 'bottom-start';
8
+ }
9
+
10
+ if (variant === 'after') {
11
+ return 'bottom-end';
12
+ }
13
+ }
@@ -0,0 +1 @@
1
+ export * from './ButtonFieldList';
@@ -0,0 +1,5 @@
1
+ @import "@snack-uikit/figma-tokens/build/scss/styles-theme-variables";
2
+
3
+ .triggerClassName {
4
+ --offset: #{$space-drop-list-drop-offset};
5
+ }
@@ -1,5 +1,5 @@
1
1
  import cn from 'classnames';
2
- import { CSSProperties, MouseEventHandler, ReactElement, ReactNode, RefObject } from 'react';
2
+ import { CSSProperties, MouseEventHandler, ReactNode, RefObject } from 'react';
3
3
 
4
4
  import { Size } from '@snack-uikit/input-private';
5
5
  import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
@@ -17,10 +17,11 @@ export type FieldContainerPrivateProps = WithSupportProps<{
17
17
  disabled: boolean;
18
18
  readonly: boolean;
19
19
  focused?: boolean;
20
+ disableFocus?: boolean;
20
21
  selectable?: boolean;
21
22
  style?: CSSProperties;
22
- prefix?: ReactElement;
23
- postfix?: ReactElement;
23
+ prefix?: ReactNode;
24
+ postfix?: ReactNode;
24
25
  inputRef: RefObject<HTMLElement>;
25
26
  }>;
26
27
 
@@ -33,6 +34,7 @@ export function FieldContainerPrivate({
33
34
  disabled,
34
35
  readonly,
35
36
  focused,
37
+ disableFocus,
36
38
  selectable,
37
39
  style,
38
40
  prefix,
@@ -57,6 +59,7 @@ export function FieldContainerPrivate({
57
59
  data-disabled={disabled || undefined}
58
60
  data-readonly={readonly || undefined}
59
61
  data-focused={focused || undefined}
62
+ data-disable-focus={disableFocus || undefined}
60
63
  data-selectable={selectable || undefined}
61
64
  data-test-id='field-container-private'
62
65
  onClick={handleContainerClick}
@@ -4,7 +4,7 @@
4
4
  /* stylelint-disable no-descending-specificity */
5
5
 
6
6
  $sizes: 's', 'm', 'l';
7
- $variants: 'single-line-container', 'multi-line-container';
7
+ $variants: 'single-line-container', 'single-line-container-button-before', 'single-line-container-button-after', 'multi-line-container';
8
8
 
9
9
  @mixin validationState($state, $rainbowColor, $bgDefault) {
10
10
  &[data-validation='#{$state}'] {
@@ -16,7 +16,7 @@ $variants: 'single-line-container', 'multi-line-container';
16
16
  border-color: simple-var($theme-variables, 'sys', $rainbowColor, 'decor-hovered');
17
17
  }
18
18
 
19
- &:not([data-readonly]) {
19
+ &:not([data-readonly]):not([data-disable-focus]) {
20
20
  &:focus-within,
21
21
  &[data-focused] {
22
22
  &:not([data-disabled]) {
@@ -92,13 +92,15 @@ $variants: 'single-line-container', 'multi-line-container';
92
92
  border-color: simple-var($sys-neutral-decor-disabled);
93
93
  }
94
94
 
95
- &:focus-within,
96
- &[data-focused] {
97
- @include outline-var($container-focused-m);
95
+ &:not([data-disable-focus]) {
96
+ &:focus-within,
97
+ &[data-focused] {
98
+ @include outline-var($container-focused-m);
98
99
 
99
- background-color: simple-var($sys-neutral-decor-disabled);
100
- border-color: simple-var($sys-neutral-decor-disabled);
101
- outline: none;
100
+ background-color: simple-var($sys-neutral-decor-disabled);
101
+ border-color: simple-var($sys-neutral-decor-disabled);
102
+ outline: none;
103
+ }
102
104
  }
103
105
  }
104
106
 
@@ -126,6 +128,9 @@ $variants: 'single-line-container', 'multi-line-container';
126
128
  .prefix {
127
129
  display: inline-flex;
128
130
  flex-shrink: 0;
131
+ gap: $space-fields-postfix-gap;
132
+ align-items: center;
133
+
129
134
  color: $sys-neutral-text-disabled;
130
135
  }
131
136
 
@@ -133,4 +138,5 @@ $variants: 'single-line-container', 'multi-line-container';
133
138
  display: inline-flex;
134
139
  flex-shrink: 0;
135
140
  gap: $space-fields-postfix-gap;
141
+ align-items: center;
136
142
  }
@@ -1,4 +1,6 @@
1
1
  export * from './ButtonCopyValue';
2
+ export * from './ButtonField';
3
+ export * from './ButtonFieldList';
2
4
  export * from './ButtonHideValue';
3
5
  export * from './FieldContainerPrivate';
4
6
  export * from './TextArea';
@@ -1,3 +1,7 @@
1
1
  export * from './useCopyButton';
2
2
  export * from './useHideButton';
3
+ export * from './usePostfix';
4
+ export * from './usePostfixButton';
5
+ export * from './usePrefix';
6
+ export * from './usePrefixButton';
3
7
  export * from './useValueControl';
@@ -0,0 +1,17 @@
1
+ @import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
2
+
3
+ .prefix {
4
+ color: $sys-neutral-text-support;
5
+
6
+ &[data-disabled] {
7
+ opacity: $opacity-a056;
8
+ }
9
+ }
10
+
11
+ .postfix {
12
+ color: $sys-neutral-text-support;
13
+
14
+ &[data-disabled] {
15
+ opacity: $opacity-a056;
16
+ }
17
+ }
@@ -12,6 +12,8 @@ type UseCopyButtonProps = {
12
12
  size: Size;
13
13
  onValueRequest?(): AsyncValueRequest;
14
14
  disabled?: boolean;
15
+ prefix?: string;
16
+ postfix?: string;
15
17
  };
16
18
 
17
19
  export function useCopyButton({
@@ -21,22 +23,25 @@ export function useCopyButton({
21
23
  valueToCopy,
22
24
  onValueRequest,
23
25
  disabled,
26
+ prefix,
27
+ postfix,
24
28
  }: UseCopyButtonProps): ButtonProps {
25
29
  return useMemo(
26
30
  () => ({
27
31
  id: 'copy',
32
+ active: true,
28
33
  ref: copyButtonRef,
29
34
  show: showCopyButton,
30
35
  render: props => (
31
36
  <ButtonCopyValue
32
37
  {...props}
33
38
  size={BUTTON_SIZE_MAP[size]}
34
- valueToCopy={valueToCopy}
39
+ valueToCopy={prefix + valueToCopy + postfix}
35
40
  onValueRequest={onValueRequest}
36
41
  disabled={disabled}
37
42
  />
38
43
  ),
39
44
  }),
40
- [copyButtonRef, disabled, onValueRequest, showCopyButton, size, valueToCopy],
45
+ [copyButtonRef, disabled, onValueRequest, showCopyButton, size, valueToCopy, prefix, postfix],
41
46
  );
42
47
  }
@@ -27,6 +27,7 @@ export function useHideButton({
27
27
  return useMemo(
28
28
  () => ({
29
29
  id: 'hide',
30
+ active: true,
30
31
  ref: hideButtonRef,
31
32
  show: showHideButton,
32
33
  render: props => {
@@ -0,0 +1,21 @@
1
+ import { ReactNode, useMemo } from 'react';
2
+
3
+ import { InactiveItem } from '@snack-uikit/input-private';
4
+
5
+ import styles from './styles.module.scss';
6
+
7
+ export function usePostfix({ postfix, disabled }: { postfix?: ReactNode; disabled?: boolean }): InactiveItem {
8
+ return useMemo(
9
+ () => ({
10
+ id: 'postfix',
11
+ active: false,
12
+ show: Boolean(postfix),
13
+ render: props => (
14
+ <div {...props} className={styles.postfix} data-test-id='field-postfix' data-disabled={disabled || undefined}>
15
+ {postfix}
16
+ </div>
17
+ ),
18
+ }),
19
+ [disabled, postfix],
20
+ );
21
+ }
@@ -0,0 +1,74 @@
1
+ import { ReactElement, useMemo, useRef } from 'react';
2
+
3
+ import { ButtonProps, InactiveItem, Size } from '@snack-uikit/input-private';
4
+
5
+ import { ButtonField, ButtonFieldList, ButtonFieldProps } from '../helperComponents';
6
+ import { Button } from '../types';
7
+
8
+ export function usePostfixButton({
9
+ button,
10
+ size,
11
+ postfixIcon,
12
+ disabled,
13
+ readonly,
14
+ onFocus,
15
+ onBlur,
16
+ }: {
17
+ button?: Button;
18
+ size: Size;
19
+ postfixIcon?: ReactElement;
20
+ disabled?: boolean;
21
+ readonly?: boolean;
22
+ } & Pick<ButtonFieldProps, 'onFocus' | 'onBlur'>): ButtonProps {
23
+ const buttonListRef = useRef<HTMLButtonElement>(null);
24
+
25
+ const postfixIconProps: InactiveItem = useMemo(
26
+ () => ({
27
+ id: 'postfixIcon',
28
+ active: false,
29
+ show: Boolean(postfixIcon && !button),
30
+ render: () => <>{postfixIcon}</>,
31
+ }),
32
+ [button, postfixIcon],
33
+ );
34
+
35
+ const postfixButtonProps: InactiveItem = useMemo(
36
+ () => ({
37
+ id: 'postfixButton',
38
+ active: false,
39
+ show: Boolean(button && button.variant === 'after'),
40
+ render: renderProps => {
41
+ const buttonProps: ButtonFieldProps = {
42
+ ...renderProps,
43
+ variant: 'after',
44
+ size,
45
+ content: button?.content,
46
+ disabled: disabled || readonly,
47
+ onFocus,
48
+ onBlur,
49
+ };
50
+
51
+ if (button?.items) {
52
+ return (
53
+ <ButtonFieldList
54
+ {...buttonProps}
55
+ ref={buttonListRef}
56
+ onClick={() => {
57
+ setTimeout(() => buttonListRef.current?.focus(), 0);
58
+ }}
59
+ items={button.items}
60
+ selection={button.selection}
61
+ open={button.open}
62
+ onOpenChange={button.onOpenChange}
63
+ />
64
+ );
65
+ }
66
+
67
+ return <ButtonField {...buttonProps} />;
68
+ },
69
+ }),
70
+ [button, size, disabled, readonly, onFocus, onBlur],
71
+ );
72
+
73
+ return button ? postfixButtonProps : postfixIconProps;
74
+ }
@@ -0,0 +1,21 @@
1
+ import { ReactNode, useMemo } from 'react';
2
+
3
+ import { InactiveItem } from '@snack-uikit/input-private';
4
+
5
+ import styles from './styles.module.scss';
6
+
7
+ export function usePrefix({ prefix, disabled }: { prefix?: ReactNode; disabled?: boolean }): InactiveItem {
8
+ return useMemo(
9
+ () => ({
10
+ id: 'prefix',
11
+ active: false,
12
+ show: Boolean(prefix),
13
+ render: props => (
14
+ <div {...props} data-test-id='field-prefix' className={styles.prefix} data-disabled={disabled || undefined}>
15
+ {prefix}
16
+ </div>
17
+ ),
18
+ }),
19
+ [disabled, prefix],
20
+ );
21
+ }