@snack-uikit/toggles 0.7.1-preview-104ad22c.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 +357 -0
- package/LICENSE +201 -0
- package/README.md +169 -0
- package/dist/components/Checkbox/Checkbox.d.ts +12 -0
- package/dist/components/Checkbox/Checkbox.js +41 -0
- package/dist/components/Checkbox/index.d.ts +1 -0
- package/dist/components/Checkbox/index.js +1 -0
- package/dist/components/Checkbox/styles.module.css +86 -0
- package/dist/components/Favorite/Favorite.d.ts +10 -0
- package/dist/components/Favorite/Favorite.js +36 -0
- package/dist/components/Favorite/index.d.ts +1 -0
- package/dist/components/Favorite/index.js +1 -0
- package/dist/components/Favorite/styles.module.css +47 -0
- package/dist/components/Radio/Radio.d.ts +7 -0
- package/dist/components/Radio/Radio.js +24 -0
- package/dist/components/Radio/index.d.ts +1 -0
- package/dist/components/Radio/index.js +1 -0
- package/dist/components/Radio/styles.module.css +90 -0
- package/dist/components/Switch/Switch.d.ts +10 -0
- package/dist/components/Switch/Switch.js +27 -0
- package/dist/components/Switch/index.d.ts +1 -0
- package/dist/components/Switch/index.js +1 -0
- package/dist/components/Switch/styles.module.css +150 -0
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +28 -0
- package/dist/components/ToggleGroup/ToggleGroup.js +31 -0
- package/dist/components/ToggleGroup/hooks.d.ts +9 -0
- package/dist/components/ToggleGroup/hooks.js +23 -0
- package/dist/components/ToggleGroup/index.d.ts +2 -0
- package/dist/components/ToggleGroup/index.js +2 -0
- package/dist/components/TogglePrivate/TogglePrivate.d.ts +22 -0
- package/dist/components/TogglePrivate/TogglePrivate.js +38 -0
- package/dist/components/TogglePrivate/index.d.ts +1 -0
- package/dist/components/TogglePrivate/index.js +1 -0
- package/dist/components/TogglePrivate/styles.module.css +38 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +5 -0
- package/dist/components/styles.module.css +0 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +20 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.js +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +18 -0
- package/package.json +42 -0
- package/src/components/Checkbox/Checkbox.tsx +72 -0
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Checkbox/styles.module.scss +77 -0
- package/src/components/Favorite/Favorite.tsx +54 -0
- package/src/components/Favorite/index.ts +1 -0
- package/src/components/Favorite/styles.module.scss +36 -0
- package/src/components/Radio/Radio.tsx +30 -0
- package/src/components/Radio/index.ts +1 -0
- package/src/components/Radio/styles.module.scss +74 -0
- package/src/components/Switch/Switch.tsx +50 -0
- package/src/components/Switch/index.ts +1 -0
- package/src/components/Switch/styles.module.scss +124 -0
- package/src/components/ToggleGroup/ToggleGroup.tsx +76 -0
- package/src/components/ToggleGroup/hooks.ts +34 -0
- package/src/components/ToggleGroup/index.ts +2 -0
- package/src/components/TogglePrivate/TogglePrivate.tsx +69 -0
- package/src/components/TogglePrivate/index.ts +1 -0
- package/src/components/TogglePrivate/styles.module.scss +56 -0
- package/src/components/index.ts +5 -0
- package/src/components/styles.module.scss +24 -0
- package/src/constants.ts +19 -0
- package/src/context.ts +17 -0
- package/src/index.ts +1 -0
- package/src/types.ts +71 -0
- package/src/utils.ts +21 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toggles';
|
|
2
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
3
|
+
@import '../styles.module';
|
|
4
|
+
|
|
5
|
+
$sizes: 's', 'm';
|
|
6
|
+
|
|
7
|
+
$icon-sizes: (
|
|
8
|
+
's': $icon-xs,
|
|
9
|
+
'm': $icon-s,
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
.container {
|
|
13
|
+
@include container('checkbox');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.box {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'background1-level');
|
|
19
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-default');
|
|
20
|
+
border-style: solid; // это наверное должно быть в композитном токене box
|
|
21
|
+
|
|
22
|
+
&[data-checked=false] {
|
|
23
|
+
&[data-focusvisible=true],
|
|
24
|
+
&[data-hover=true] {
|
|
25
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'background2-level');
|
|
26
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-hovered');
|
|
27
|
+
}
|
|
28
|
+
&[data-disabled=true] {
|
|
29
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'background1-level');
|
|
30
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&[data-indeterminate=true],
|
|
35
|
+
&[data-checked=true] {
|
|
36
|
+
background-color: simple-var($theme-variables, 'sys', 'primary', 'accent-default');
|
|
37
|
+
border-color: simple-var($theme-variables, 'sys', 'primary', 'accent-default');
|
|
38
|
+
&[data-focusvisible=true],
|
|
39
|
+
&[data-hover=true] {
|
|
40
|
+
background-color: simple-var($theme-variables, 'sys', 'primary', 'accent-hovered');
|
|
41
|
+
border-color: simple-var($theme-variables, 'sys', 'primary', 'accent-hovered');
|
|
42
|
+
}
|
|
43
|
+
&[data-disabled=true] {
|
|
44
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
45
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@each $size in $sizes {
|
|
50
|
+
&[data-size='#{$size}'] {
|
|
51
|
+
@include composite-var($toggles, $size, 'checkbox', 'box');
|
|
52
|
+
|
|
53
|
+
svg {
|
|
54
|
+
width: simple-var($icon-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
|
|
55
|
+
height: simple-var($icon-sizes, $size) !important; /* stylelint-disable-line declaration-no-important */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.icon {
|
|
62
|
+
position: absolute;
|
|
63
|
+
top: 0;
|
|
64
|
+
left: 0;
|
|
65
|
+
|
|
66
|
+
display: none;
|
|
67
|
+
|
|
68
|
+
width: 100%;
|
|
69
|
+
height: 100%;
|
|
70
|
+
|
|
71
|
+
color: simple-var($theme-variables, 'sys', 'primary', 'on-accent');
|
|
72
|
+
|
|
73
|
+
&[data-indeterminate=true],
|
|
74
|
+
&[data-checked=true] {
|
|
75
|
+
display: block;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { HeartFilledSVG, HeartSVG, StarFilledSVG, StarSVG } from '@snack-uikit/icons';
|
|
4
|
+
|
|
5
|
+
import { FavoriteIcon, Size } from '../../constants';
|
|
6
|
+
import { ToggleProps } from '../../types';
|
|
7
|
+
import { getIconSize, getVisualStateAttributes } from '../../utils';
|
|
8
|
+
import { TogglePrivate } from '../TogglePrivate';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
const CHECKED_ICONS = {
|
|
12
|
+
[FavoriteIcon.Star]: StarFilledSVG,
|
|
13
|
+
[FavoriteIcon.Heart]: HeartFilledSVG,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const UNCHECKED_ICONS = {
|
|
17
|
+
[FavoriteIcon.Star]: StarSVG,
|
|
18
|
+
[FavoriteIcon.Heart]: HeartSVG,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type FavoriteProps = Omit<ToggleProps, 'disabled'> & {
|
|
22
|
+
/** Иконка */
|
|
23
|
+
icon?: FavoriteIcon;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function Favorite({
|
|
27
|
+
inputRef,
|
|
28
|
+
'data-test-id': dataTestId,
|
|
29
|
+
size = Size.M,
|
|
30
|
+
icon = FavoriteIcon.Heart,
|
|
31
|
+
...restProps
|
|
32
|
+
}: FavoriteProps) {
|
|
33
|
+
const iconSize = useMemo(() => getIconSize(size), [size]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<TogglePrivate
|
|
37
|
+
{...restProps}
|
|
38
|
+
data-test-id={dataTestId}
|
|
39
|
+
ref={inputRef}
|
|
40
|
+
size={size}
|
|
41
|
+
render={function Favorite(visualState) {
|
|
42
|
+
const Icon = visualState.checked ? CHECKED_ICONS[icon] : UNCHECKED_ICONS[icon];
|
|
43
|
+
const data = getVisualStateAttributes({ ...visualState, icon });
|
|
44
|
+
return (
|
|
45
|
+
<div className={styles.container} {...data}>
|
|
46
|
+
<Icon className={styles.icon} {...data} size={iconSize} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Favorite.sizes = Size;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Favorite';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toggles';
|
|
2
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
3
|
+
@import '../styles.module';
|
|
4
|
+
|
|
5
|
+
$sizes: 's', 'm';
|
|
6
|
+
$icons: ('star': 'yellow', 'heart': 'red');
|
|
7
|
+
|
|
8
|
+
.container {
|
|
9
|
+
@include container('favourite');
|
|
10
|
+
|
|
11
|
+
outline-color: simple-var($theme-variables, 'sys', 'available', 'complementary');
|
|
12
|
+
&[data-checked=true] {
|
|
13
|
+
outline-color: simple-var($theme-variables, 'sys', 'available', 'complementary');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.icon {
|
|
18
|
+
color: simple-var($theme-variables, 'sys', 'neutral', 'text-light');
|
|
19
|
+
&[data-hover=true] {
|
|
20
|
+
color: simple-var($theme-variables, 'sys', 'neutral', 'text-support');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@each $icon, $color in $icons {
|
|
24
|
+
&[data-icon='#{$icon}'] {
|
|
25
|
+
&[data-checked=true] {
|
|
26
|
+
color: simple-var($theme-variables, 'sys', $color, 'accent-default');
|
|
27
|
+
&[data-focusvisible=true],
|
|
28
|
+
&[data-hover=true] {
|
|
29
|
+
color: simple-var($theme-variables, 'sys', $color, 'accent-hovered');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Mode, Size } from '../../constants';
|
|
2
|
+
import { ToggleProps } from '../../types';
|
|
3
|
+
import { getVisualStateAttributes } from '../../utils';
|
|
4
|
+
import { TogglePrivate } from '../TogglePrivate';
|
|
5
|
+
import styles from './styles.module.scss';
|
|
6
|
+
|
|
7
|
+
export type RadioProps = ToggleProps;
|
|
8
|
+
|
|
9
|
+
export function Radio({ inputRef, 'data-test-id': dataTestId, ...restProps }: RadioProps) {
|
|
10
|
+
return (
|
|
11
|
+
<TogglePrivate
|
|
12
|
+
{...restProps}
|
|
13
|
+
data-test-id={dataTestId}
|
|
14
|
+
mode={Mode.Radio}
|
|
15
|
+
ref={inputRef}
|
|
16
|
+
render={function Radio(visualState) {
|
|
17
|
+
const data = getVisualStateAttributes(visualState);
|
|
18
|
+
return (
|
|
19
|
+
<div className={styles.container} {...data}>
|
|
20
|
+
<div className={styles.box} {...data}>
|
|
21
|
+
<div className={styles.flag} {...data} />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Radio.sizes = Size;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Radio';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toggles';
|
|
2
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
3
|
+
@import '../styles.module';
|
|
4
|
+
|
|
5
|
+
$sizes: 's', 'm';
|
|
6
|
+
|
|
7
|
+
.container {
|
|
8
|
+
@include container('radio');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.box {
|
|
12
|
+
position: relative;
|
|
13
|
+
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
|
|
16
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'background1-level');
|
|
17
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-default');
|
|
18
|
+
border-style: solid; // это наверное должно быть в композитном токене box
|
|
19
|
+
|
|
20
|
+
@each $size in $sizes {
|
|
21
|
+
&[data-size='#{$size}'] {
|
|
22
|
+
@include composite-var($toggles, $size, 'radio', 'box');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&[data-focusvisible=true],
|
|
27
|
+
&[data-hover=true] {
|
|
28
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'background2-level');
|
|
29
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-hovered');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&[data-disabled=true] {
|
|
33
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'background1-level');
|
|
34
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
&[data-checked=true] {
|
|
38
|
+
border-color: simple-var($theme-variables, 'sys', 'primary', 'accent-default');
|
|
39
|
+
&[data-focusvisible=true],
|
|
40
|
+
&[data-hover=true] {
|
|
41
|
+
border-color: simple-var($theme-variables, 'sys', 'primary', 'accent-hovered');
|
|
42
|
+
}
|
|
43
|
+
&[data-disabled=true] {
|
|
44
|
+
border-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.flag {
|
|
50
|
+
position: absolute;
|
|
51
|
+
top: 50%;
|
|
52
|
+
left: 50%;
|
|
53
|
+
transform: translate(-50%, -50%);
|
|
54
|
+
|
|
55
|
+
background-color: simple-var($theme-variables, 'sys', 'primary', 'accent-default');
|
|
56
|
+
|
|
57
|
+
&[data-hover=true] {
|
|
58
|
+
background-color: simple-var($theme-variables, 'sys', 'primary', 'accent-hovered');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&[data-disabled=true] {
|
|
62
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'text-disabled');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&[data-checked=false] {
|
|
66
|
+
display: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@each $size in $sizes {
|
|
70
|
+
&[data-size='#{$size}'] {
|
|
71
|
+
@include composite-var($toggles, $size, 'radio', 'flag');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { CheckSVG, CrossSVG } from '@snack-uikit/icons';
|
|
4
|
+
|
|
5
|
+
import { Size } from '../../constants';
|
|
6
|
+
import { ToggleProps } from '../../types';
|
|
7
|
+
import { getIconSize, getVisualStateAttributes } from '../../utils';
|
|
8
|
+
import { TogglePrivate } from '../TogglePrivate';
|
|
9
|
+
import styles from './styles.module.scss';
|
|
10
|
+
|
|
11
|
+
export type SwitchProps = ToggleProps & {
|
|
12
|
+
/** Показывать ли иконку в переключателе */
|
|
13
|
+
showIcon?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function Switch({ inputRef, 'data-test-id': dataTestId, showIcon, size = Size.M, ...restProps }: SwitchProps) {
|
|
17
|
+
const iconSize = useMemo(() => getIconSize(size), [size]);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<TogglePrivate
|
|
21
|
+
{...restProps}
|
|
22
|
+
size={size}
|
|
23
|
+
data-test-id={dataTestId}
|
|
24
|
+
ref={inputRef}
|
|
25
|
+
render={function Switch(visualState) {
|
|
26
|
+
const data = getVisualStateAttributes(visualState);
|
|
27
|
+
return (
|
|
28
|
+
<div className={styles.container} {...data}>
|
|
29
|
+
<div className={styles.box} {...data} />
|
|
30
|
+
<div className={styles.containerFlag} {...data}>
|
|
31
|
+
<div className={styles.flag} {...data} />
|
|
32
|
+
{showIcon && (
|
|
33
|
+
<>
|
|
34
|
+
<div className={styles.flag_icon_off} {...data}>
|
|
35
|
+
<CrossSVG size={iconSize} />
|
|
36
|
+
</div>
|
|
37
|
+
<div className={styles.flag_icon_on} {...data}>
|
|
38
|
+
<CheckSVG size={iconSize} />
|
|
39
|
+
</div>
|
|
40
|
+
</>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Switch.sizes = Size;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Switch';
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-toggles';
|
|
2
|
+
@import '@snack-uikit/figma-tokens/build/scss/components/styles-tokens-element';
|
|
3
|
+
@import '../styles.module';
|
|
4
|
+
|
|
5
|
+
$sizes: 's', 'm';
|
|
6
|
+
$switchAnimationDuration: .1s;
|
|
7
|
+
|
|
8
|
+
.container {
|
|
9
|
+
@include container('switch');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.box {
|
|
13
|
+
position: relative;
|
|
14
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'accent-default');
|
|
15
|
+
|
|
16
|
+
@each $size in $sizes {
|
|
17
|
+
&[data-size='#{$size}'] {
|
|
18
|
+
@include composite-var($toggles, $size, 'switch', 'box');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&[data-hover=true] {
|
|
23
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'accent-hovered');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&[data-disabled=true] {
|
|
27
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
&[data-checked=true] {
|
|
31
|
+
background-color: simple-var($theme-variables, 'sys', 'primary', 'accent-default');
|
|
32
|
+
&[data-hover=true] {
|
|
33
|
+
background-color: simple-var($theme-variables, 'sys', 'primary', 'accent-hovered');
|
|
34
|
+
}
|
|
35
|
+
&[data-disabled=true] {
|
|
36
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.containerFlag {
|
|
42
|
+
position: absolute;
|
|
43
|
+
left: 0;
|
|
44
|
+
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
|
|
49
|
+
transition-timing-function: linear;
|
|
50
|
+
transition-duration: $switchAnimationDuration;
|
|
51
|
+
transition-property: left;
|
|
52
|
+
|
|
53
|
+
@each $size in $sizes {
|
|
54
|
+
&[data-size='#{$size}'] {
|
|
55
|
+
@include composite-var($toggles, $size, 'switch', 'container-flag');
|
|
56
|
+
&[data-checked=true] {
|
|
57
|
+
left: calc(100% - #{simple-var($toggles, $size, 'switch', 'container-flag', 'width')});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.flag {
|
|
64
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'on-accent');
|
|
65
|
+
|
|
66
|
+
@each $size in $sizes {
|
|
67
|
+
&[data-size='#{$size}'] {
|
|
68
|
+
@include composite-var($toggles, $size, 'switch', 'flag');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&[data-disabled=true] {
|
|
73
|
+
background-color: simple-var($theme-variables, 'sys', 'neutral', 'text-disabled');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@mixin flagIcon {
|
|
78
|
+
position: absolute;
|
|
79
|
+
top: 50%;
|
|
80
|
+
left: 50%;
|
|
81
|
+
transform: translate(-50%, -50%);
|
|
82
|
+
|
|
83
|
+
display: flex;
|
|
84
|
+
|
|
85
|
+
color: simple-var($theme-variables, 'sys', 'neutral', 'accent-default');
|
|
86
|
+
|
|
87
|
+
fill: transparent;
|
|
88
|
+
|
|
89
|
+
transition: opacity $switchAnimationDuration linear;
|
|
90
|
+
|
|
91
|
+
&[data-hover=true] {
|
|
92
|
+
color: simple-var($theme-variables, 'sys', 'neutral', 'accent-hovered');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&[data-checked=true] {
|
|
96
|
+
color: simple-var($theme-variables, 'sys', 'primary', 'accent-default');
|
|
97
|
+
&[data-hover=true] {
|
|
98
|
+
color: simple-var($theme-variables, 'sys', 'primary', 'accent-hovered');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&[data-disabled=true] {
|
|
103
|
+
color: simple-var($theme-variables, 'sys', 'neutral', 'decor-disabled');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.flag_icon_off {
|
|
109
|
+
@include flagIcon();
|
|
110
|
+
|
|
111
|
+
opacity: 1;
|
|
112
|
+
&[data-checked=true] {
|
|
113
|
+
opacity: 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.flag_icon_on {
|
|
118
|
+
@include flagIcon();
|
|
119
|
+
|
|
120
|
+
opacity: 0;
|
|
121
|
+
&[data-checked=true] {
|
|
122
|
+
opacity: 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ReactNode, useCallback } from 'react';
|
|
2
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
3
|
+
|
|
4
|
+
import { SelectionMode } from '../../constants';
|
|
5
|
+
import { ToggleGroupContext } from '../../context';
|
|
6
|
+
|
|
7
|
+
export type ToggleGroupProps = {
|
|
8
|
+
/** Вложенный контент */
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
} & (
|
|
11
|
+
| {
|
|
12
|
+
/** Начальное состояние */
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
/** Controlled состояние */
|
|
15
|
+
value?: string;
|
|
16
|
+
/** Controlled обработчик измения состояния */
|
|
17
|
+
onChange?(value: (string | undefined) | string): void;
|
|
18
|
+
/** Режим выбора */
|
|
19
|
+
selectionMode?: SelectionMode.Single;
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
/** Начальное состояние */
|
|
23
|
+
defaultValue?: string[];
|
|
24
|
+
/** Controlled состояние */
|
|
25
|
+
value?: string[];
|
|
26
|
+
/** Controlled обработчик измения состояния */
|
|
27
|
+
onChange?(value: (string[] | undefined) | string[]): void;
|
|
28
|
+
/** Режим выбора */
|
|
29
|
+
selectionMode?: SelectionMode.Multiple;
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export function ToggleGroup({
|
|
34
|
+
children,
|
|
35
|
+
value: valueProp,
|
|
36
|
+
onChange: onChangeProp,
|
|
37
|
+
selectionMode = SelectionMode.Single,
|
|
38
|
+
defaultValue,
|
|
39
|
+
}: ToggleGroupProps) {
|
|
40
|
+
const [value, setValue] = useUncontrolledProp<string | string[] | undefined>(valueProp, defaultValue, cb => {
|
|
41
|
+
onChangeProp?.(cb(value));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const onChange = useCallback(
|
|
45
|
+
(newValue: string) => {
|
|
46
|
+
if (selectionMode === SelectionMode.Single) {
|
|
47
|
+
return setValue((oldValue: string | undefined) => {
|
|
48
|
+
if (newValue !== oldValue) {
|
|
49
|
+
return newValue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return setValue((oldValues: string[] | undefined = []) => {
|
|
57
|
+
if (Array.isArray(oldValues)) {
|
|
58
|
+
if (oldValues.includes(newValue)) {
|
|
59
|
+
return oldValues.filter(oldValue => oldValue !== newValue);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return oldValues.concat(newValue);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return undefined;
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
[selectionMode, setValue],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<ToggleGroupContext.Provider value={{ value, onChange, selectionMode }}>{children}</ToggleGroupContext.Provider>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ToggleGroup.selectionModes = SelectionMode;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SelectionMode } from '../../constants';
|
|
4
|
+
import { useToggleGroupContext } from '../../context';
|
|
5
|
+
|
|
6
|
+
type UseToggleGroupProps = {
|
|
7
|
+
value: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function useToggleGroup({ value: itemValue }: UseToggleGroupProps) {
|
|
11
|
+
const { value, onChange, selectionMode } = useToggleGroupContext();
|
|
12
|
+
|
|
13
|
+
const isChecked = useMemo(() => {
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
return value === itemValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.includes(itemValue);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return false;
|
|
23
|
+
}, [value, itemValue]);
|
|
24
|
+
|
|
25
|
+
const handleClick = useCallback(() => {
|
|
26
|
+
onChange?.(itemValue);
|
|
27
|
+
}, [itemValue, onChange]);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
isChecked,
|
|
31
|
+
handleClick,
|
|
32
|
+
multipleSelection: selectionMode === SelectionMode.Multiple,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { forwardRef, useState } from 'react';
|
|
3
|
+
import { useUncontrolledProp } from 'uncontrollable';
|
|
4
|
+
|
|
5
|
+
import { Mode, Size } from '../../constants';
|
|
6
|
+
import { TogglePrivateProps } from '../../types';
|
|
7
|
+
import { getVisualStateAttributes } from '../../utils';
|
|
8
|
+
import styles from './styles.module.scss';
|
|
9
|
+
|
|
10
|
+
export const TogglePrivate = forwardRef<HTMLInputElement, TogglePrivateProps>(function TogglePrivate(
|
|
11
|
+
{
|
|
12
|
+
render,
|
|
13
|
+
checked: checkedProp,
|
|
14
|
+
defaultChecked,
|
|
15
|
+
onChange,
|
|
16
|
+
onBlur,
|
|
17
|
+
onFocus,
|
|
18
|
+
disabled,
|
|
19
|
+
className,
|
|
20
|
+
size = Size.M,
|
|
21
|
+
mode = Mode.Checkbox,
|
|
22
|
+
'data-test-id': testId,
|
|
23
|
+
...otherProps
|
|
24
|
+
},
|
|
25
|
+
ref,
|
|
26
|
+
) {
|
|
27
|
+
const [checked, setChecked] = useUncontrolledProp(checkedProp, Boolean(defaultChecked), onChange);
|
|
28
|
+
const [focusVisible, setFocusVisible] = useState(false);
|
|
29
|
+
const [hover, setHover] = useState(false);
|
|
30
|
+
|
|
31
|
+
const visualState = {
|
|
32
|
+
disabled: Boolean(disabled),
|
|
33
|
+
focusVisible,
|
|
34
|
+
checked,
|
|
35
|
+
hover,
|
|
36
|
+
size,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<span
|
|
41
|
+
className={cn(className, styles.toggleLayout)}
|
|
42
|
+
data-size={size}
|
|
43
|
+
data-test-id={testId}
|
|
44
|
+
onMouseEnter={() => setHover(true)}
|
|
45
|
+
onMouseLeave={() => setHover(false)}
|
|
46
|
+
{...getVisualStateAttributes(visualState)}
|
|
47
|
+
>
|
|
48
|
+
{render(visualState)}
|
|
49
|
+
<input
|
|
50
|
+
{...otherProps}
|
|
51
|
+
data-test-id={`${testId}-native-input`}
|
|
52
|
+
ref={ref}
|
|
53
|
+
type={mode}
|
|
54
|
+
className={styles.togglePrivate}
|
|
55
|
+
checked={checked}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
onChange={e => setChecked(e.target.checked)}
|
|
58
|
+
onFocus={event => {
|
|
59
|
+
setFocusVisible(event.target.matches(':focus-visible'));
|
|
60
|
+
onFocus?.(event);
|
|
61
|
+
}}
|
|
62
|
+
onBlur={event => {
|
|
63
|
+
setFocusVisible(false);
|
|
64
|
+
onBlur?.(event);
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
</span>
|
|
68
|
+
);
|
|
69
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TogglePrivate';
|