@snack-uikit/color-picker 0.0.1-preview-364c0f5c.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 +5 -0
- package/LICENSE +201 -0
- package/README.md +24 -0
- package/dist/components/ColorPicker.d.ts +20 -0
- package/dist/components/ColorPicker.js +84 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/styles.module.css +61 -0
- package/dist/constants.d.ts +20 -0
- package/dist/constants.js +32 -0
- package/dist/helperComponents/FieldAlphaColor/FieldAlphaColor.d.ts +16 -0
- package/dist/helperComponents/FieldAlphaColor/FieldAlphaColor.js +8 -0
- package/dist/helperComponents/FieldAlphaColor/index.d.ts +1 -0
- package/dist/helperComponents/FieldAlphaColor/index.js +1 -0
- package/dist/helperComponents/FieldPrivate/FieldPrivate.d.ts +14 -0
- package/dist/helperComponents/FieldPrivate/FieldPrivate.js +30 -0
- package/dist/helperComponents/FieldPrivate/index.d.ts +1 -0
- package/dist/helperComponents/FieldPrivate/index.js +1 -0
- package/dist/helperComponents/FieldPrivate/styles.module.css +186 -0
- package/dist/helperComponents/index.d.ts +2 -0
- package/dist/helperComponents/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +1 -0
- package/dist/utils/convert.d.ts +12 -0
- package/dist/utils/convert.js +172 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/round.d.ts +1 -0
- package/dist/utils/round.js +1 -0
- package/dist/utils/typeGuards.d.ts +10 -0
- package/dist/utils/typeGuards.js +34 -0
- package/dist/utils/validate.d.ts +1 -0
- package/dist/utils/validate.js +10 -0
- package/package.json +49 -0
- package/src/components/ColorPicker.tsx +209 -0
- package/src/components/index.ts +1 -0
- package/src/components/styles.module.scss +82 -0
- package/src/constants.ts +45 -0
- package/src/helperComponents/FieldAlphaColor/FieldAlphaColor.tsx +26 -0
- package/src/helperComponents/FieldAlphaColor/index.ts +1 -0
- package/src/helperComponents/FieldPrivate/FieldPrivate.tsx +85 -0
- package/src/helperComponents/FieldPrivate/index.ts +1 -0
- package/src/helperComponents/FieldPrivate/styles.module.scss +136 -0
- package/src/helperComponents/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/types.ts +15 -0
- package/src/utils/convert.ts +229 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/round.ts +2 -0
- package/src/utils/typeGuards.ts +47 -0
- package/src/utils/validate.ts +13 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
HexAlphaColorPicker,
|
|
5
|
+
HexColorPicker,
|
|
6
|
+
HsvaColorPicker,
|
|
7
|
+
HsvColorPicker,
|
|
8
|
+
RgbaColorPicker,
|
|
9
|
+
RgbColorPicker,
|
|
10
|
+
} from 'react-colorful';
|
|
11
|
+
|
|
12
|
+
import { ButtonFilled, ButtonFunction } from '@snack-uikit/button';
|
|
13
|
+
import { CheckSVG } from '@snack-uikit/icons';
|
|
14
|
+
import { useLocale } from '@snack-uikit/locale';
|
|
15
|
+
import { SegmentedControl } from '@snack-uikit/segmented-control';
|
|
16
|
+
import { extractSupportProps, WithSupportProps } from '@snack-uikit/utils';
|
|
17
|
+
|
|
18
|
+
import { COLOR_MODE, COLOR_MODE_LABEL, ColorMode, DEFAULT_COLOR_MODE_CONFIG } from '../constants';
|
|
19
|
+
import { FieldPrivate } from '../helperComponents';
|
|
20
|
+
import { FieldAlphaColor } from '../helperComponents/FieldAlphaColor/FieldAlphaColor';
|
|
21
|
+
import { Color, RawColor } from '../types';
|
|
22
|
+
import { colorToRawValue, hexToRgba } from '../utils/convert';
|
|
23
|
+
import { validHex } from '../utils/validate';
|
|
24
|
+
import styles from './styles.module.scss';
|
|
25
|
+
|
|
26
|
+
export type ColorPickerProps = WithSupportProps<{
|
|
27
|
+
/** Значение */
|
|
28
|
+
value?: Color;
|
|
29
|
+
/** Колбек на изменение */
|
|
30
|
+
onChange?(rawColor: Partial<RawColor>): void;
|
|
31
|
+
/** Значение с альфаканалом */
|
|
32
|
+
withAlpha?: boolean;
|
|
33
|
+
/** Применять изменения автоматически, если значение false - то изменения происходят по кнопке */
|
|
34
|
+
autoApply?: boolean;
|
|
35
|
+
/** Класснейм */
|
|
36
|
+
className?: string;
|
|
37
|
+
|
|
38
|
+
colorMode?: {
|
|
39
|
+
hex?: boolean;
|
|
40
|
+
rgb?: boolean;
|
|
41
|
+
hsv?: boolean;
|
|
42
|
+
};
|
|
43
|
+
}>;
|
|
44
|
+
|
|
45
|
+
export function ColorPicker({
|
|
46
|
+
value,
|
|
47
|
+
onChange,
|
|
48
|
+
withAlpha = true,
|
|
49
|
+
autoApply,
|
|
50
|
+
className,
|
|
51
|
+
colorMode: colorModeProp = DEFAULT_COLOR_MODE_CONFIG,
|
|
52
|
+
...rest
|
|
53
|
+
}: ColorPickerProps) {
|
|
54
|
+
const colorModeOptions = useMemo(() => {
|
|
55
|
+
const colorModeConfig: Record<ColorMode, boolean> = { ...DEFAULT_COLOR_MODE_CONFIG, ...colorModeProp };
|
|
56
|
+
|
|
57
|
+
return Object.keys(colorModeConfig).reduce(
|
|
58
|
+
(res, colorMode: ColorMode) => {
|
|
59
|
+
if (Boolean(colorModeConfig[colorMode])) {
|
|
60
|
+
res.push({
|
|
61
|
+
value: colorMode,
|
|
62
|
+
label: COLOR_MODE_LABEL[colorMode],
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return res;
|
|
67
|
+
},
|
|
68
|
+
[] as { label: string; value: ColorMode }[],
|
|
69
|
+
);
|
|
70
|
+
}, [colorModeProp]);
|
|
71
|
+
const [rawValue, setRawValue] = useState(colorToRawValue(value || '#000'));
|
|
72
|
+
const [colorMode, setColorMode] = useState<ColorMode>(colorModeOptions[0].value);
|
|
73
|
+
|
|
74
|
+
const [tempHex, setTempHex] = useState<string>(rawValue.hex);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (value) {
|
|
78
|
+
const raw = colorToRawValue(value);
|
|
79
|
+
|
|
80
|
+
setRawValue(raw);
|
|
81
|
+
setTempHex(raw.hex);
|
|
82
|
+
}
|
|
83
|
+
}, [value]);
|
|
84
|
+
|
|
85
|
+
const handleChange = (color: Color) => {
|
|
86
|
+
const rawValue = colorToRawValue(color);
|
|
87
|
+
setRawValue(rawValue);
|
|
88
|
+
autoApply && onChange?.(rawValue);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const applyChange = () => {
|
|
92
|
+
onChange?.(rawValue);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const reset = () => {
|
|
96
|
+
value && setRawValue(colorToRawValue(value));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const { t } = useLocale('ColorPicker');
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className={cn(styles.container, 'osThemeSnack', className)} {...extractSupportProps(rest)}>
|
|
103
|
+
{colorMode === COLOR_MODE.Hex &&
|
|
104
|
+
(withAlpha ? (
|
|
105
|
+
<HexAlphaColorPicker onChange={handleChange} color={rawValue.hex} />
|
|
106
|
+
) : (
|
|
107
|
+
<HexColorPicker onChange={handleChange} color={rawValue.hex} />
|
|
108
|
+
))}
|
|
109
|
+
|
|
110
|
+
{colorMode === COLOR_MODE.Rbg &&
|
|
111
|
+
(withAlpha ? (
|
|
112
|
+
<RgbaColorPicker onChange={handleChange} color={rawValue.rgba} />
|
|
113
|
+
) : (
|
|
114
|
+
<RgbColorPicker onChange={handleChange} color={rawValue.rgb} />
|
|
115
|
+
))}
|
|
116
|
+
|
|
117
|
+
{colorMode === COLOR_MODE.Hsv &&
|
|
118
|
+
(withAlpha ? (
|
|
119
|
+
<HsvaColorPicker onChange={handleChange} color={rawValue.hsva} />
|
|
120
|
+
) : (
|
|
121
|
+
<HsvColorPicker onChange={handleChange} color={rawValue.hsv} />
|
|
122
|
+
))}
|
|
123
|
+
|
|
124
|
+
<div className={styles.colorModel}>
|
|
125
|
+
<SegmentedControl outline value={colorMode} size='s' onChange={setColorMode} items={colorModeOptions} />
|
|
126
|
+
|
|
127
|
+
<div className={styles.colorFields} data-mode={colorMode} data-with-alpha={withAlpha || undefined}>
|
|
128
|
+
<>
|
|
129
|
+
{colorMode === COLOR_MODE.Hex && (
|
|
130
|
+
<FieldPrivate
|
|
131
|
+
value={rawValue.hex.replace('#', '').substring(0, 6)}
|
|
132
|
+
error={!validHex(tempHex, withAlpha)}
|
|
133
|
+
inputType='text'
|
|
134
|
+
onChange={(value = '') => {
|
|
135
|
+
setTempHex(value);
|
|
136
|
+
|
|
137
|
+
if (validHex(value)) {
|
|
138
|
+
handleChange({ ...hexToRgba(value), a: rawValue.rgba.a });
|
|
139
|
+
}
|
|
140
|
+
}}
|
|
141
|
+
/>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
{colorMode === COLOR_MODE.Rbg && (
|
|
145
|
+
<>
|
|
146
|
+
<FieldPrivate
|
|
147
|
+
value={rawValue.rgb.r}
|
|
148
|
+
max={255}
|
|
149
|
+
onChange={value => {
|
|
150
|
+
handleChange({ ...rawValue.rgba, r: Number(value) });
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
<FieldPrivate
|
|
154
|
+
value={rawValue.rgb.g}
|
|
155
|
+
max={255}
|
|
156
|
+
onChange={value => {
|
|
157
|
+
handleChange({ ...rawValue.rgba, g: Number(value) });
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
<FieldPrivate
|
|
161
|
+
value={rawValue.rgb.b}
|
|
162
|
+
max={255}
|
|
163
|
+
onChange={value => {
|
|
164
|
+
handleChange({ ...rawValue.rgba, b: Number(value) });
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
</>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{colorMode === COLOR_MODE.Hsv && (
|
|
171
|
+
<>
|
|
172
|
+
<FieldPrivate
|
|
173
|
+
value={rawValue.hsv.h}
|
|
174
|
+
max={360}
|
|
175
|
+
onChange={value => {
|
|
176
|
+
handleChange({ ...rawValue.hsva, h: Number(value) });
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
179
|
+
<FieldPrivate
|
|
180
|
+
value={rawValue.hsv.s}
|
|
181
|
+
max={100}
|
|
182
|
+
onChange={value => {
|
|
183
|
+
handleChange({ ...rawValue.hsva, s: Number(value) });
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
<FieldPrivate
|
|
187
|
+
value={rawValue.hsv.v}
|
|
188
|
+
max={100}
|
|
189
|
+
onChange={value => {
|
|
190
|
+
handleChange({ ...rawValue.hsva, v: Number(value) });
|
|
191
|
+
}}
|
|
192
|
+
/>
|
|
193
|
+
</>
|
|
194
|
+
)}
|
|
195
|
+
</>
|
|
196
|
+
|
|
197
|
+
{withAlpha && <FieldAlphaColor rgba={rawValue.rgba} onChange={handleChange} />}
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
{!autoApply && (
|
|
202
|
+
<div className={styles.footer}>
|
|
203
|
+
<ButtonFunction label={t('cancel')} size='xs' onClick={reset} />
|
|
204
|
+
<ButtonFilled label={t('apply')} icon={<CheckSVG />} onClick={applyChange} size='xs' />
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './ColorPicker';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-scroll';
|
|
2
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-colorPicker';
|
|
3
|
+
|
|
4
|
+
$orientations: 'vertical', 'horizontal';
|
|
5
|
+
$sizes: 's', 'm';
|
|
6
|
+
|
|
7
|
+
.container {
|
|
8
|
+
@include composite-var($color-picker-container);
|
|
9
|
+
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
min-width: 248px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.footer {
|
|
17
|
+
display: flex;
|
|
18
|
+
justify-content: space-between;
|
|
19
|
+
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
padding-top: $space-color-picker-footer-gap;
|
|
22
|
+
|
|
23
|
+
border-top: solid 1px $sys-neutral-decor-default;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.colorModel {
|
|
27
|
+
@include composite-var($color-picker-color-model);
|
|
28
|
+
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.colorFields {
|
|
35
|
+
@include composite-var($color-picker-color-model);
|
|
36
|
+
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: repeat(3, 1fr);
|
|
39
|
+
|
|
40
|
+
&[data-with-alpha] {
|
|
41
|
+
grid-template-columns: repeat(4, 1fr);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&[data-mode='hex'] {
|
|
45
|
+
grid-template-columns: 1fr;
|
|
46
|
+
|
|
47
|
+
&[data-with-alpha] {
|
|
48
|
+
grid-template-columns: 1fr min-content;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
$theme-name: osThemeSnack;
|
|
54
|
+
$scrollbar-slider-active-zone: 12px;
|
|
55
|
+
|
|
56
|
+
/* stylelint-disable-next-line selector-pseudo-class-no-unknown */
|
|
57
|
+
:global {
|
|
58
|
+
.#{$theme-name} .react-colorful {
|
|
59
|
+
@include composite-var($color-picker-color-palette);
|
|
60
|
+
|
|
61
|
+
width: 100%;
|
|
62
|
+
min-height: 200px;
|
|
63
|
+
|
|
64
|
+
.react-colorful__saturation {
|
|
65
|
+
border-bottom-width: 8px;
|
|
66
|
+
border-radius: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.react-colorful__pointer {
|
|
70
|
+
@include composite-var($color-picker-color-slider);
|
|
71
|
+
|
|
72
|
+
/* stylelint-disable-next-line color-no-hex */
|
|
73
|
+
border-color: #fff;
|
|
74
|
+
box-shadow: $box-shadow-elevation-level3;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.react-colorful__hue,
|
|
78
|
+
.react-colorful__alpha {
|
|
79
|
+
@include composite-var($color-picker-slider-track);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ValueOf } from '@snack-uikit/utils';
|
|
2
|
+
|
|
3
|
+
export const COLOR_MODE = {
|
|
4
|
+
Hex: 'hex',
|
|
5
|
+
Rbg: 'rbg',
|
|
6
|
+
Hsv: 'hsv',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const COLOR_MODE_OPTIONS = [
|
|
10
|
+
{
|
|
11
|
+
value: COLOR_MODE.Hex,
|
|
12
|
+
label: 'HEX',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
value: COLOR_MODE.Hsv,
|
|
16
|
+
label: 'HSV',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
value: COLOR_MODE.Rbg,
|
|
20
|
+
label: 'RGB',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export const COLOR_MODE_LABEL = {
|
|
25
|
+
[COLOR_MODE.Hex]: 'HEX',
|
|
26
|
+
[COLOR_MODE.Rbg]: 'RGB',
|
|
27
|
+
[COLOR_MODE.Hsv]: 'HSV',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_COLOR_MODE_CONFIG = {
|
|
31
|
+
[COLOR_MODE.Hex]: true,
|
|
32
|
+
[COLOR_MODE.Rbg]: true,
|
|
33
|
+
[COLOR_MODE.Hsv]: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ColorMode = ValueOf<typeof COLOR_MODE>;
|
|
37
|
+
|
|
38
|
+
export const RGBA_REGEX =
|
|
39
|
+
/rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
|
|
40
|
+
|
|
41
|
+
export const HSVA_REGEX =
|
|
42
|
+
/hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
|
|
43
|
+
|
|
44
|
+
export const HSLA_REGEX =
|
|
45
|
+
/hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Color, RawColor, RgbaColor } from '../../types';
|
|
2
|
+
import { FieldPrivate } from '../FieldPrivate';
|
|
3
|
+
|
|
4
|
+
export type ColorPickerProps = {
|
|
5
|
+
/** Значение */
|
|
6
|
+
value?: Color;
|
|
7
|
+
/** Колбек на изменение */
|
|
8
|
+
onChange?(rawColor: Partial<RawColor>): void;
|
|
9
|
+
/** Значение с альфаканалом */
|
|
10
|
+
withAlpha?: boolean;
|
|
11
|
+
/** Применять изменения автоматически, если значение false - то изменения происходят по кнопке */
|
|
12
|
+
autoApply?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type FieldAlphaColorProps = {
|
|
16
|
+
rgba: RgbaColor;
|
|
17
|
+
onChange(color: Color): void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function FieldAlphaColor({ onChange, rgba }: FieldAlphaColorProps) {
|
|
21
|
+
const handleChange = (a: string) => {
|
|
22
|
+
onChange({ ...rgba, a: (Number(a) % 100) / 100 });
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return <FieldPrivate value={rgba.a * 100} onChange={handleChange} inputType='number' min={0} max={100} />;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FieldAlphaColor';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { CSSProperties, MouseEventHandler, useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { InputPrivate } from '@snack-uikit/input-private';
|
|
5
|
+
|
|
6
|
+
import styles from './styles.module.scss';
|
|
7
|
+
|
|
8
|
+
export type FieldPrivateProps = {
|
|
9
|
+
className?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
focused?: boolean;
|
|
12
|
+
style?: CSSProperties;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
inputType?: 'text' | 'number';
|
|
16
|
+
value?: string | number;
|
|
17
|
+
onChange?(value?: string): void;
|
|
18
|
+
error?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function FieldPrivate({
|
|
22
|
+
className,
|
|
23
|
+
disabled,
|
|
24
|
+
focused,
|
|
25
|
+
style,
|
|
26
|
+
value = '',
|
|
27
|
+
onChange,
|
|
28
|
+
min = 0,
|
|
29
|
+
max = 255,
|
|
30
|
+
inputType = 'number',
|
|
31
|
+
error,
|
|
32
|
+
}: FieldPrivateProps) {
|
|
33
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
34
|
+
|
|
35
|
+
const handleContainerClick: MouseEventHandler<HTMLDivElement> = () => {
|
|
36
|
+
if (disabled) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
inputRef?.current?.focus();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const [rawValue, setRawValue] = useState<string>('');
|
|
43
|
+
|
|
44
|
+
const handleBlur = () => {
|
|
45
|
+
if (inputType === 'number') {
|
|
46
|
+
const rawNumberValue = Number(rawValue) || 0;
|
|
47
|
+
const newValue = String(Math.min(Math.max(min, rawNumberValue), Math.min(max, rawNumberValue)));
|
|
48
|
+
|
|
49
|
+
setRawValue(newValue);
|
|
50
|
+
onChange?.(newValue);
|
|
51
|
+
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onChange?.(rawValue);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setRawValue(String(value));
|
|
60
|
+
}, [value]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className={cn(className, styles.container)}
|
|
65
|
+
style={style}
|
|
66
|
+
data-focused={focused || undefined}
|
|
67
|
+
data-validation={error ? 'error' : undefined}
|
|
68
|
+
data-test-id='field-container-private'
|
|
69
|
+
onClick={handleContainerClick}
|
|
70
|
+
data-size='s'
|
|
71
|
+
data-variant='single-line-container'
|
|
72
|
+
role='textbox'
|
|
73
|
+
tabIndex={-1}
|
|
74
|
+
>
|
|
75
|
+
<InputPrivate
|
|
76
|
+
value={String(rawValue)}
|
|
77
|
+
onChange={setRawValue}
|
|
78
|
+
onBlur={handleBlur}
|
|
79
|
+
type={inputType}
|
|
80
|
+
ref={inputRef}
|
|
81
|
+
{...(inputType === 'number' ? { min, max } : {})}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './FieldPrivate';
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-fields';
|
|
2
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
3
|
+
|
|
4
|
+
/* stylelint-disable no-descending-specificity */
|
|
5
|
+
|
|
6
|
+
$sizes: 's', 'm', 'l';
|
|
7
|
+
$variants: 'single-line-container', 'multi-line-container';
|
|
8
|
+
|
|
9
|
+
@mixin validationState($rainbowColor, $bgDefault) {
|
|
10
|
+
background-color: simple-var($theme-variables, 'sys', $bgDefault, 'background1-level');
|
|
11
|
+
border-color: simple-var($theme-variables, 'sys', $bgDefault, 'decor-default');
|
|
12
|
+
|
|
13
|
+
&:hover {
|
|
14
|
+
background-color: $sys-neutral-background2-level;
|
|
15
|
+
border-color: simple-var($theme-variables, 'sys', $rainbowColor, 'decor-hovered');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
&:not([data-readonly]) {
|
|
19
|
+
&:focus-within,
|
|
20
|
+
&[data-focused] {
|
|
21
|
+
&:not([data-disabled]) {
|
|
22
|
+
@include outline-var($container-focused-m);
|
|
23
|
+
|
|
24
|
+
background-color: simple-var($sys-neutral-background2-level);
|
|
25
|
+
border-color: simple-var($theme-variables, 'sys', $rainbowColor, 'accent-default');
|
|
26
|
+
outline-color: simple-var($theme-variables, 'sys', $rainbowColor, 'decor-activated');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.container {
|
|
33
|
+
@include validationState('primary', 'neutral');
|
|
34
|
+
|
|
35
|
+
position: relative;
|
|
36
|
+
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
min-width: 52px;
|
|
43
|
+
|
|
44
|
+
border-style: solid;
|
|
45
|
+
|
|
46
|
+
&[data-validation='error'] {
|
|
47
|
+
@include validationState('red', 'red');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&[data-selectable] {
|
|
51
|
+
&,
|
|
52
|
+
input,
|
|
53
|
+
select,
|
|
54
|
+
textarea,
|
|
55
|
+
span {
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&[data-readonly] {
|
|
61
|
+
&,
|
|
62
|
+
input,
|
|
63
|
+
select,
|
|
64
|
+
textarea,
|
|
65
|
+
span {
|
|
66
|
+
cursor: default;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&,
|
|
70
|
+
&:hover {
|
|
71
|
+
background-color: simple-var($sys-neutral-decor-disabled);
|
|
72
|
+
border-color: simple-var($sys-neutral-decor-disabled);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&:focus-within,
|
|
76
|
+
&[data-focused] {
|
|
77
|
+
@include outline-var($container-focused-m);
|
|
78
|
+
|
|
79
|
+
background-color: simple-var($sys-neutral-decor-disabled);
|
|
80
|
+
border-color: simple-var($sys-neutral-decor-disabled);
|
|
81
|
+
outline: none;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
&[data-disabled] {
|
|
86
|
+
&,
|
|
87
|
+
input,
|
|
88
|
+
select,
|
|
89
|
+
textarea,
|
|
90
|
+
span {
|
|
91
|
+
cursor: not-allowed;
|
|
92
|
+
background-color: simple-var($sys-neutral-background);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&,
|
|
96
|
+
&:focus-within,
|
|
97
|
+
&[data-focused],
|
|
98
|
+
&:hover {
|
|
99
|
+
background-color: simple-var($sys-neutral-background);
|
|
100
|
+
border-color: simple-var($sys-neutral-decor-disabled);
|
|
101
|
+
outline: none;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@each $size in $sizes {
|
|
106
|
+
&[data-size='#{$size}'] {
|
|
107
|
+
@include composite-var($fields, 'container', $size);
|
|
108
|
+
|
|
109
|
+
&,
|
|
110
|
+
input,
|
|
111
|
+
select,
|
|
112
|
+
textarea,
|
|
113
|
+
span {
|
|
114
|
+
@include composite-var($theme-variables, 'sans', 'body', $size);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@each $variant in $variants {
|
|
118
|
+
&[data-variant='#{$variant}'] {
|
|
119
|
+
@include composite-var($fields, $variant, $size);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.prefix {
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
flex-shrink: 0;
|
|
129
|
+
color: $sys-neutral-text-disabled;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.postfix {
|
|
133
|
+
display: inline-flex;
|
|
134
|
+
flex-shrink: 0;
|
|
135
|
+
gap: $space-fields-postfix-gap;
|
|
136
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HslaColor, HslColor, HsvaColor, HsvColor, RgbaColor, RgbColor } from 'react-colorful';
|
|
2
|
+
|
|
3
|
+
export type { HslaColor, HslColor, HsvaColor, HsvColor, RgbaColor, RgbColor };
|
|
4
|
+
|
|
5
|
+
export type Color = RgbColor | HslColor | HsvColor | RgbaColor | HslaColor | HsvaColor | string;
|
|
6
|
+
|
|
7
|
+
export type RawColor = {
|
|
8
|
+
hex: string;
|
|
9
|
+
rgb: RgbColor;
|
|
10
|
+
hsl: HslColor;
|
|
11
|
+
hsv: HsvColor;
|
|
12
|
+
rgba: RgbaColor;
|
|
13
|
+
hsla: HslaColor;
|
|
14
|
+
hsva: HsvaColor;
|
|
15
|
+
};
|