@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.
- package/CHANGELOG.md +16 -0
- package/README.md +9 -0
- package/dist/components/FieldColor/FieldColor.d.ts +1 -1
- package/dist/components/FieldColor/FieldColor.js +3 -3
- package/dist/components/FieldDate/FieldDate.js +11 -5
- package/dist/components/FieldSecure/FieldSecure.d.ts +1 -1
- package/dist/components/FieldSecure/FieldSecure.js +3 -3
- package/dist/components/FieldSelect/FieldSelectMultiple.d.ts +3 -1
- package/dist/components/FieldSelect/FieldSelectMultiple.js +7 -5
- package/dist/components/FieldSelect/FieldSelectSingle.d.ts +3 -1
- package/dist/components/FieldSelect/FieldSelectSingle.js +8 -6
- package/dist/components/FieldSelect/hooks.d.ts +1 -1
- package/dist/components/FieldSelect/hooks.js +3 -3
- package/dist/components/FieldSelect/types.d.ts +5 -1
- package/dist/components/FieldSlider/FieldSlider.d.ts +6 -2
- package/dist/components/FieldSlider/FieldSlider.js +13 -11
- package/dist/components/FieldStepper/FieldStepper.d.ts +5 -1
- package/dist/components/FieldStepper/FieldStepper.js +6 -4
- package/dist/components/FieldText/FieldText.d.ts +9 -2
- package/dist/components/FieldText/FieldText.js +46 -8
- package/dist/components/FieldText/helpers.d.ts +4 -0
- package/dist/components/FieldText/helpers.js +9 -0
- package/dist/components/FieldTextArea/FieldTextArea.d.ts +1 -1
- package/dist/components/FieldTextArea/FieldTextArea.js +3 -3
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +6 -0
- package/dist/helperComponents/ButtonField/ButtonField.d.ts +18 -0
- package/dist/helperComponents/ButtonField/ButtonField.js +10 -0
- package/dist/helperComponents/ButtonField/index.d.ts +1 -0
- package/dist/helperComponents/ButtonField/index.js +1 -0
- package/dist/helperComponents/ButtonField/styles.module.css +96 -0
- package/dist/helperComponents/ButtonFieldList/ButtonFieldList.d.ts +4 -0
- package/dist/helperComponents/ButtonFieldList/ButtonFieldList.js +27 -0
- package/dist/helperComponents/ButtonFieldList/helpers.d.ts +5 -0
- package/dist/helperComponents/ButtonFieldList/helpers.js +8 -0
- package/dist/helperComponents/ButtonFieldList/index.d.ts +1 -0
- package/dist/helperComponents/ButtonFieldList/index.js +1 -0
- package/dist/helperComponents/ButtonFieldList/styles.module.css +3 -0
- package/dist/helperComponents/FieldContainerPrivate/FieldContainerPrivate.d.ts +5 -4
- package/dist/helperComponents/FieldContainerPrivate/FieldContainerPrivate.js +2 -2
- package/dist/helperComponents/FieldContainerPrivate/styles.module.css +56 -5
- package/dist/helperComponents/index.d.ts +2 -0
- package/dist/helperComponents/index.js +2 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/styles.module.css +13 -0
- package/dist/hooks/useCopyButton.d.ts +3 -1
- package/dist/hooks/useCopyButton.js +4 -3
- package/dist/hooks/useHideButton.js +1 -0
- package/dist/hooks/usePostfix.d.ts +6 -0
- package/dist/hooks/usePostfix.js +11 -0
- package/dist/hooks/usePostfixButton.d.ts +11 -0
- package/dist/hooks/usePostfixButton.js +28 -0
- package/dist/hooks/usePrefix.d.ts +6 -0
- package/dist/hooks/usePrefix.js +11 -0
- package/dist/hooks/usePrefixButton.d.ts +11 -0
- package/dist/hooks/usePrefixButton.js +28 -0
- package/dist/types.d.ts +12 -1
- package/package.json +6 -5
- package/src/components/FieldColor/FieldColor.tsx +6 -3
- package/src/components/FieldDate/FieldDate.tsx +17 -10
- package/src/components/FieldSecure/FieldSecure.tsx +3 -3
- package/src/components/FieldSelect/FieldSelectMultiple.tsx +17 -4
- package/src/components/FieldSelect/FieldSelectSingle.tsx +17 -4
- package/src/components/FieldSelect/hooks.ts +3 -3
- package/src/components/FieldSelect/types.ts +10 -2
- package/src/components/FieldSlider/FieldSlider.tsx +30 -14
- package/src/components/FieldStepper/FieldStepper.tsx +40 -23
- package/src/components/FieldText/FieldText.tsx +78 -10
- package/src/components/FieldText/helpers.tsx +13 -0
- package/src/components/FieldTextArea/FieldTextArea.tsx +6 -3
- package/src/constants.ts +7 -0
- package/src/helperComponents/ButtonField/ButtonField.tsx +73 -0
- package/src/helperComponents/ButtonField/index.ts +1 -0
- package/src/helperComponents/ButtonField/styles.module.scss +57 -0
- package/src/helperComponents/ButtonFieldList/ButtonFieldList.tsx +36 -0
- package/src/helperComponents/ButtonFieldList/helpers.tsx +13 -0
- package/src/helperComponents/ButtonFieldList/index.ts +1 -0
- package/src/helperComponents/ButtonFieldList/styles.module.scss +5 -0
- package/src/helperComponents/FieldContainerPrivate/FieldContainerPrivate.tsx +6 -3
- package/src/helperComponents/FieldContainerPrivate/styles.module.scss +14 -8
- package/src/helperComponents/index.ts +2 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/styles.module.scss +17 -0
- package/src/hooks/useCopyButton.tsx +7 -2
- package/src/hooks/useHideButton.tsx +1 -0
- package/src/hooks/usePostfix.tsx +21 -0
- package/src/hooks/usePostfixButton.tsx +74 -0
- package/src/hooks/usePrefix.tsx +21 -0
- package/src/hooks/usePrefixButton.tsx +74 -0
- 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 {
|
|
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({
|
|
106
|
-
|
|
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
|
-
|
|
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={
|
|
204
|
+
variant={containerVariant}
|
|
138
205
|
inputRef={localRef}
|
|
139
|
-
prefix={
|
|
140
|
-
postfix={
|
|
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 {
|
|
114
|
+
const { postfixButtons, inputTabIndex, onInputKeyDown } = useButtonNavigation({
|
|
115
115
|
inputRef: localRef,
|
|
116
|
-
|
|
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}>{
|
|
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';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
|
-
import { CSSProperties, MouseEventHandler,
|
|
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?:
|
|
23
|
-
postfix?:
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
&:not([data-disable-focus]) {
|
|
96
|
+
&:focus-within,
|
|
97
|
+
&[data-focused] {
|
|
98
|
+
@include outline-var($container-focused-m);
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -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
|
+
}
|