@cloud-ru/ds-toggles 1.0.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 +8 -0
- package/LICENSE +201 -0
- package/README.md +492 -0
- package/dist/cjs/components/Checkbox/Checkbox.d.ts +8 -0
- package/dist/cjs/components/Checkbox/Checkbox.js +61 -0
- package/dist/cjs/components/Checkbox/index.d.ts +1 -0
- package/dist/cjs/components/Checkbox/index.js +17 -0
- package/dist/cjs/components/Checkbox/styles.module.css +223 -0
- package/dist/cjs/components/Favourite/Favourite.d.ts +9 -0
- package/dist/cjs/components/Favourite/Favourite.js +42 -0
- package/dist/cjs/components/Favourite/index.d.ts +1 -0
- package/dist/cjs/components/Favourite/index.js +17 -0
- package/dist/cjs/components/Favourite/styles.module.css +138 -0
- package/dist/cjs/components/Radio/Radio.d.ts +3 -0
- package/dist/cjs/components/Radio/Radio.js +34 -0
- package/dist/cjs/components/Radio/index.d.ts +1 -0
- package/dist/cjs/components/Radio/index.js +17 -0
- package/dist/cjs/components/Radio/styles.module.css +219 -0
- package/dist/cjs/components/Switch/Switch.d.ts +3 -0
- package/dist/cjs/components/Switch/Switch.js +42 -0
- package/dist/cjs/components/Switch/index.d.ts +1 -0
- package/dist/cjs/components/Switch/index.js +17 -0
- package/dist/cjs/components/Switch/styles.module.css +269 -0
- package/dist/cjs/components/ToggleGroup/ToggleGroup.d.ts +22 -0
- package/dist/cjs/components/ToggleGroup/ToggleGroup.js +33 -0
- package/dist/cjs/components/ToggleGroup/hooks.d.ts +9 -0
- package/dist/cjs/components/ToggleGroup/hooks.js +26 -0
- package/dist/cjs/components/ToggleGroup/index.d.ts +2 -0
- package/dist/cjs/components/ToggleGroup/index.js +18 -0
- package/dist/cjs/components/index.d.ts +5 -0
- package/dist/cjs/components/index.js +21 -0
- package/dist/cjs/constants.d.ts +23 -0
- package/dist/cjs/constants.js +26 -0
- package/dist/cjs/context.d.ts +9 -0
- package/dist/cjs/context.js +12 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +20 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/style.css +849 -0
- package/dist/cjs/types.d.ts +57 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils.d.ts +3 -0
- package/dist/cjs/utils.js +22 -0
- package/dist/esm/components/Checkbox/Checkbox.d.ts +8 -0
- package/dist/esm/components/Checkbox/Checkbox.js +55 -0
- package/dist/esm/components/Checkbox/index.d.ts +1 -0
- package/dist/esm/components/Checkbox/index.js +1 -0
- package/dist/esm/components/Checkbox/styles.module.css +223 -0
- package/dist/esm/components/Favourite/Favourite.d.ts +9 -0
- package/dist/esm/components/Favourite/Favourite.js +36 -0
- package/dist/esm/components/Favourite/index.d.ts +1 -0
- package/dist/esm/components/Favourite/index.js +1 -0
- package/dist/esm/components/Favourite/styles.module.css +138 -0
- package/dist/esm/components/Radio/Radio.d.ts +3 -0
- package/dist/esm/components/Radio/Radio.js +28 -0
- package/dist/esm/components/Radio/index.d.ts +1 -0
- package/dist/esm/components/Radio/index.js +1 -0
- package/dist/esm/components/Radio/styles.module.css +219 -0
- package/dist/esm/components/Switch/Switch.d.ts +3 -0
- package/dist/esm/components/Switch/Switch.js +36 -0
- package/dist/esm/components/Switch/index.d.ts +1 -0
- package/dist/esm/components/Switch/index.js +1 -0
- package/dist/esm/components/Switch/styles.module.css +269 -0
- package/dist/esm/components/ToggleGroup/ToggleGroup.d.ts +22 -0
- package/dist/esm/components/ToggleGroup/ToggleGroup.js +30 -0
- package/dist/esm/components/ToggleGroup/hooks.d.ts +9 -0
- package/dist/esm/components/ToggleGroup/hooks.js +23 -0
- package/dist/esm/components/ToggleGroup/index.d.ts +2 -0
- package/dist/esm/components/ToggleGroup/index.js +2 -0
- package/dist/esm/components/index.d.ts +5 -0
- package/dist/esm/components/index.js +5 -0
- package/dist/esm/constants.d.ts +23 -0
- package/dist/esm/constants.js +23 -0
- package/dist/esm/context.d.ts +9 -0
- package/dist/esm/context.js +8 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/style.css +849 -0
- package/dist/esm/types.d.ts +57 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +18 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +46 -0
- package/src/components/Checkbox/Checkbox.tsx +140 -0
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/Checkbox/styles.module.scss +162 -0
- package/src/components/Favourite/Favourite.tsx +116 -0
- package/src/components/Favourite/index.ts +1 -0
- package/src/components/Favourite/styles.module.scss +138 -0
- package/src/components/Radio/Radio.tsx +84 -0
- package/src/components/Radio/index.ts +1 -0
- package/src/components/Radio/styles.module.scss +159 -0
- package/src/components/Switch/Switch.tsx +94 -0
- package/src/components/Switch/index.ts +1 -0
- package/src/components/Switch/styles.module.scss +250 -0
- package/src/components/ToggleGroup/ToggleGroup.tsx +71 -0
- package/src/components/ToggleGroup/hooks.ts +34 -0
- package/src/components/ToggleGroup/index.ts +2 -0
- package/src/components/index.ts +5 -0
- package/src/constants.ts +27 -0
- package/src/context.ts +18 -0
- package/src/index.ts +4 -0
- package/src/types.ts +80 -0
- package/src/utils.ts +21 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/styles/styles.module' as base;
|
|
2
|
+
@use '@cloud-ru/ds-figma-variables/build/scss/components/toggles.module' as toggles;
|
|
3
|
+
@use '@cloud-ru/ds-materials' as m;
|
|
4
|
+
|
|
5
|
+
$sizes: 'xs', 's';
|
|
6
|
+
$switchAnimationDuration: 0.1s;
|
|
7
|
+
$iconRevealEasing: cubic-bezier(0.33, 1, 0.68, 1);
|
|
8
|
+
|
|
9
|
+
.spinner {
|
|
10
|
+
// По макету спиннер шире ползунка (24 на surface 16) и выходит за его границы,
|
|
11
|
+
// поэтому flex-контейнер не должен его ужимать.
|
|
12
|
+
flex-shrink: 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.icon {
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
|
|
18
|
+
position: absolute;
|
|
19
|
+
transform: scale(0.72);
|
|
20
|
+
|
|
21
|
+
opacity: 0;
|
|
22
|
+
|
|
23
|
+
transition:
|
|
24
|
+
opacity $switchAnimationDuration $iconRevealEasing,
|
|
25
|
+
transform $switchAnimationDuration $iconRevealEasing,
|
|
26
|
+
color $switchAnimationDuration linear;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.surface[data-checked="true"]:not([data-loading="true"]) .icon {
|
|
30
|
+
transform: scale(1);
|
|
31
|
+
opacity: 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.inputPrivate {
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
|
|
37
|
+
position: absolute;
|
|
38
|
+
inset: 0;
|
|
39
|
+
|
|
40
|
+
width: 100%;
|
|
41
|
+
height: 100%;
|
|
42
|
+
margin: 0;
|
|
43
|
+
padding: 0;
|
|
44
|
+
|
|
45
|
+
opacity: 0;
|
|
46
|
+
|
|
47
|
+
&:disabled {
|
|
48
|
+
cursor: not-allowed;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.backgroundStateLayer {
|
|
53
|
+
visibility: initial;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.flag {
|
|
57
|
+
position: absolute;
|
|
58
|
+
top: 50%;
|
|
59
|
+
left: 0;
|
|
60
|
+
transform: translateY(-50%);
|
|
61
|
+
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
justify-content: center;
|
|
65
|
+
|
|
66
|
+
transition: left $switchAnimationDuration linear;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.framing {
|
|
70
|
+
position: relative;
|
|
71
|
+
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
|
|
75
|
+
box-sizing: border-box;
|
|
76
|
+
|
|
77
|
+
outline-style: solid;
|
|
78
|
+
|
|
79
|
+
transition:
|
|
80
|
+
background-color $switchAnimationDuration linear,
|
|
81
|
+
outline-color $switchAnimationDuration linear;
|
|
82
|
+
|
|
83
|
+
&[data-checked="false"] {
|
|
84
|
+
background-color: base.$sn-theme-color-neutral-accent;
|
|
85
|
+
outline-color: base.$sn-theme-color-neutral-accent;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&[data-checked="true"] {
|
|
89
|
+
&[data-disabled="false"] {
|
|
90
|
+
background-color: base.$sn-theme-color-primary-accent;
|
|
91
|
+
outline-color: base.$sn-theme-color-primary-accent;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&[data-disabled="true"] {
|
|
95
|
+
background-color: base.$sn-theme-color-neutral-accent;
|
|
96
|
+
outline-color: base.$sn-theme-color-neutral-accent;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
&[data-disabled="true"] {
|
|
101
|
+
opacity: base.$sn-theme-effect-opacity-disabled;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.container {
|
|
106
|
+
position: relative;
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: center;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.surface {
|
|
113
|
+
position: relative;
|
|
114
|
+
|
|
115
|
+
display: flex;
|
|
116
|
+
align-items: center;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
|
|
119
|
+
box-sizing: border-box;
|
|
120
|
+
|
|
121
|
+
transition:
|
|
122
|
+
width $switchAnimationDuration linear,
|
|
123
|
+
height $switchAnimationDuration linear,
|
|
124
|
+
background-color $switchAnimationDuration linear,
|
|
125
|
+
color $switchAnimationDuration linear;
|
|
126
|
+
|
|
127
|
+
&[data-checked="false"] {
|
|
128
|
+
&[data-loading="false"] {
|
|
129
|
+
// Ползунок лежит на дорожке `neutral/accent`, поэтому берёт парный `neutral/onAccent`.
|
|
130
|
+
background-color: base.$sn-theme-color-neutral-onAccent;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&[data-disabled="false"][data-loading="true"] {
|
|
134
|
+
// Спиннер лежит прямо на дорожке `neutral/accent` — берёт парный `neutral/onAccent`.
|
|
135
|
+
color: base.$sn-theme-color-neutral-onAccent;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&[data-checked="true"] {
|
|
140
|
+
&[data-loading="false"] {
|
|
141
|
+
// Кружок лежит на дорожке `primary/accent`, поэтому и цвет берётся из той же
|
|
142
|
+
// пары — `primary/onAccent` (#fbfffc), а не нейтральный белый.
|
|
143
|
+
background-color: base.$sn-theme-color-primary-onAccent;
|
|
144
|
+
|
|
145
|
+
&[data-disabled="false"] {
|
|
146
|
+
color: base.$sn-theme-color-primary-accent;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
&[data-disabled="true"] {
|
|
150
|
+
color: base.$sn-theme-color-neutral-accent;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
&[data-loading="true"] {
|
|
155
|
+
color: base.$sn-theme-color-primary-onAccent;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.root {
|
|
161
|
+
position: relative;
|
|
162
|
+
|
|
163
|
+
display: inline-flex;
|
|
164
|
+
flex-direction: row;
|
|
165
|
+
flex-grow: 0;
|
|
166
|
+
flex-shrink: 0;
|
|
167
|
+
align-items: center;
|
|
168
|
+
|
|
169
|
+
vertical-align: middle;
|
|
170
|
+
|
|
171
|
+
// Sizes
|
|
172
|
+
@each $size in $sizes {
|
|
173
|
+
&[data-size='#{$size}'] {
|
|
174
|
+
.container {
|
|
175
|
+
@include base.composite-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'container');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.framing {
|
|
179
|
+
// Pseudo-border
|
|
180
|
+
$offsetWidth: base.simple-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'border-width');
|
|
181
|
+
|
|
182
|
+
width: base.simple-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'min-width');
|
|
183
|
+
height: base.simple-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'min-height');
|
|
184
|
+
border-radius: base.simple-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'border-radius');
|
|
185
|
+
|
|
186
|
+
outline-width: $offsetWidth;
|
|
187
|
+
outline-offset: calc(0px - $offsetWidth);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.flag {
|
|
191
|
+
@include base.composite-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'flag');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.framing[data-checked="true"] .flag {
|
|
195
|
+
$flagTrackWidth: base.simple-var(
|
|
196
|
+
toggles.$toggles,
|
|
197
|
+
'anatomy',
|
|
198
|
+
'size',
|
|
199
|
+
$size,
|
|
200
|
+
'switch',
|
|
201
|
+
'framing',
|
|
202
|
+
'flag',
|
|
203
|
+
'min-width'
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
left: calc(100% - #{$flagTrackWidth});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
&[data-focusvisible="true"] {
|
|
210
|
+
.container {
|
|
211
|
+
// Real offset
|
|
212
|
+
$offsetWidth: base.$sn-primitive-strokeWeight-strokeSemiBold;
|
|
213
|
+
|
|
214
|
+
@if ($size == 'xs') {
|
|
215
|
+
outline-offset: $offsetWidth;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
border-radius: base.simple-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'border-radius');
|
|
219
|
+
outline: $offsetWidth solid base.$sn-theme-color-available-complementary;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
&[data-checked="false"] {
|
|
224
|
+
.surface {
|
|
225
|
+
@include base.composite-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'flag', 'surface', 'checkedFalse');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
&[data-checked="true"] {
|
|
230
|
+
.surface {
|
|
231
|
+
@include base.composite-var(toggles.$toggles, 'anatomy', 'size', $size, 'switch', 'framing', 'flag', 'surface', 'checkedTrue');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// States management
|
|
238
|
+
&[data-loading="false"][data-disabled="false"] {
|
|
239
|
+
.container {
|
|
240
|
+
@include m.has-state-layer-as-child(#{backgroundStateLayer});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
&[data-loading="true"],
|
|
245
|
+
&[data-disabled="true"] {
|
|
246
|
+
.backgroundStateLayer {
|
|
247
|
+
visibility: hidden;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useUncontrolledProp } from '@cloud-ru/ds-utils';
|
|
2
|
+
import { PropsWithChildren, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
import { SELECTION_MODE } from '../../constants';
|
|
5
|
+
import { ToggleGroupContext } from '../../context';
|
|
6
|
+
|
|
7
|
+
export type ToggleGroupProps = PropsWithChildren<
|
|
8
|
+
| {
|
|
9
|
+
/** Начальное состояние */
|
|
10
|
+
defaultValue?: string;
|
|
11
|
+
/** Controlled состояние */
|
|
12
|
+
value?: string;
|
|
13
|
+
/** Controlled обработчик измения состояния */
|
|
14
|
+
onChange?(value: (string | undefined) | string): void;
|
|
15
|
+
/** Режим выбора */
|
|
16
|
+
selectionMode?: typeof SELECTION_MODE.Single;
|
|
17
|
+
}
|
|
18
|
+
| {
|
|
19
|
+
/** Начальное состояние */
|
|
20
|
+
defaultValue?: string[];
|
|
21
|
+
/** Controlled состояние */
|
|
22
|
+
value?: string[];
|
|
23
|
+
/** Controlled обработчик измения состояния */
|
|
24
|
+
onChange?(value: (string[] | undefined) | string[]): void;
|
|
25
|
+
/** Режим выбора */
|
|
26
|
+
selectionMode?: typeof SELECTION_MODE.Multiple;
|
|
27
|
+
}
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export function ToggleGroup({
|
|
31
|
+
children,
|
|
32
|
+
value: valueProp,
|
|
33
|
+
onChange: onChangeProp,
|
|
34
|
+
selectionMode = SELECTION_MODE.Single,
|
|
35
|
+
defaultValue,
|
|
36
|
+
}: ToggleGroupProps) {
|
|
37
|
+
const [value, setValue] = useUncontrolledProp<string | string[] | undefined>(valueProp, defaultValue, cb => {
|
|
38
|
+
onChangeProp?.(cb(value));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const onChange = useCallback(
|
|
42
|
+
(newValue: string) => {
|
|
43
|
+
if (selectionMode === SELECTION_MODE.Single) {
|
|
44
|
+
return setValue((oldValue: string | undefined) => {
|
|
45
|
+
if (newValue !== oldValue) {
|
|
46
|
+
return newValue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return setValue((oldValues: string[] | undefined = []) => {
|
|
54
|
+
if (Array.isArray(oldValues)) {
|
|
55
|
+
if (oldValues.includes(newValue)) {
|
|
56
|
+
return oldValues.filter(oldValue => oldValue !== newValue);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return oldValues.concat(newValue);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return undefined;
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
[selectionMode, setValue],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<ToggleGroupContext.Provider value={{ value, onChange, selectionMode }}>{children}</ToggleGroupContext.Provider>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SELECTION_MODE } 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 === SELECTION_MODE.Multiple,
|
|
33
|
+
};
|
|
34
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const MODE = {
|
|
2
|
+
Checkbox: 'checkbox',
|
|
3
|
+
Radio: 'radio',
|
|
4
|
+
} as const;
|
|
5
|
+
|
|
6
|
+
export const SIZE = {
|
|
7
|
+
XS: 'xs',
|
|
8
|
+
S: 's',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export const FAVOURITE_ICON = {
|
|
12
|
+
Star: 'star',
|
|
13
|
+
Heart: 'heart',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export const SELECTION_MODE = {
|
|
17
|
+
Single: 'single',
|
|
18
|
+
Multiple: 'multiple',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Суффикс `data-test-id` нативного `<input>` внутри Checkbox/Radio/Switch/Favourite.
|
|
23
|
+
* Если потребитель передаёт `data-test-id='foo'` на корень, нативный input получает
|
|
24
|
+
* `data-test-id='foo-native-input'`. Константа публичная — потребитель и e2e helpers
|
|
25
|
+
* берут один и тот же суффикс из API пакета.
|
|
26
|
+
*/
|
|
27
|
+
export const NATIVE_INPUT_SUFFIX = '-native-input';
|
package/src/context.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import { SELECTION_MODE } from './constants';
|
|
4
|
+
import { SelectionMode } from './types';
|
|
5
|
+
|
|
6
|
+
type ToggleGroupContextType = {
|
|
7
|
+
value: string | undefined | string[];
|
|
8
|
+
onChange: ((id: string) => void) | undefined;
|
|
9
|
+
selectionMode: SelectionMode;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const ToggleGroupContext = createContext<ToggleGroupContextType>({
|
|
13
|
+
value: undefined,
|
|
14
|
+
onChange: undefined,
|
|
15
|
+
selectionMode: SELECTION_MODE.Single,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const useToggleGroupContext = () => useContext(ToggleGroupContext);
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { ValueOf, WithSupportProps } from '@cloud-ru/ds-utils';
|
|
2
|
+
import { FocusEventHandler, KeyboardEventHandler, MouseEventHandler, ReactNode, RefObject } from 'react';
|
|
3
|
+
|
|
4
|
+
import { FAVOURITE_ICON, MODE, SELECTION_MODE, SIZE } from './constants';
|
|
5
|
+
|
|
6
|
+
export type DataAttributes = Record<`data-${string}`, string | boolean>;
|
|
7
|
+
|
|
8
|
+
export type Size = ValueOf<typeof SIZE>;
|
|
9
|
+
export type Mode = ValueOf<typeof MODE>;
|
|
10
|
+
export type SelectionMode = ValueOf<typeof SELECTION_MODE>;
|
|
11
|
+
export type FavouriteIcon = ValueOf<typeof FAVOURITE_ICON>;
|
|
12
|
+
|
|
13
|
+
export type InputVisualState = {
|
|
14
|
+
focusVisible: boolean;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
checked: boolean;
|
|
17
|
+
hover: boolean;
|
|
18
|
+
size: Size;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type TogglePropsBase = WithSupportProps<{
|
|
22
|
+
/** Размер */
|
|
23
|
+
size?: Size;
|
|
24
|
+
/** Колбек рендера компонента */
|
|
25
|
+
render: (visualState: InputVisualState) => ReactNode;
|
|
26
|
+
/** HTML-аттрибут id */
|
|
27
|
+
id?: string;
|
|
28
|
+
/** Режим работы */
|
|
29
|
+
mode?: Mode;
|
|
30
|
+
/** HTML-аттрибут value */
|
|
31
|
+
value?: string;
|
|
32
|
+
/** HTML-аттрибут name */
|
|
33
|
+
name?: string;
|
|
34
|
+
/** HTML-аттрибут tab-index */
|
|
35
|
+
tabIndex?: number;
|
|
36
|
+
/** HTML-аттрибут autofocus */
|
|
37
|
+
autofocus?: boolean;
|
|
38
|
+
/** HTML-аттрибут checked */
|
|
39
|
+
checked?: boolean;
|
|
40
|
+
/** HTML-аттрибут checked по-умолчанию */
|
|
41
|
+
defaultChecked?: boolean;
|
|
42
|
+
/** HTML-аттрибут disabled */
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
/** Колбек смены значения */
|
|
45
|
+
onChange?: (checked: boolean) => void;
|
|
46
|
+
/** Колбек клика */
|
|
47
|
+
onClick?: MouseEventHandler<HTMLInputElement>;
|
|
48
|
+
/** Колбек потери фокуса */
|
|
49
|
+
onBlur?: FocusEventHandler<HTMLInputElement>;
|
|
50
|
+
/** Колбек приобретения фокуса */
|
|
51
|
+
onFocus?: FocusEventHandler<HTMLInputElement>;
|
|
52
|
+
/** Колбек нажатия клавиши клавиатуры */
|
|
53
|
+
onKeyUp?: KeyboardEventHandler<HTMLDivElement>;
|
|
54
|
+
/** CSS-класс */
|
|
55
|
+
className?: string;
|
|
56
|
+
}>;
|
|
57
|
+
|
|
58
|
+
export type ToggleProps = WithSupportProps<
|
|
59
|
+
Pick<
|
|
60
|
+
TogglePropsBase,
|
|
61
|
+
| 'id'
|
|
62
|
+
| 'name'
|
|
63
|
+
| 'value'
|
|
64
|
+
| 'tabIndex'
|
|
65
|
+
| 'autofocus'
|
|
66
|
+
| 'checked'
|
|
67
|
+
| 'defaultChecked'
|
|
68
|
+
| 'disabled'
|
|
69
|
+
| 'onChange'
|
|
70
|
+
| 'onClick'
|
|
71
|
+
| 'onBlur'
|
|
72
|
+
| 'onFocus'
|
|
73
|
+
| 'className'
|
|
74
|
+
| 'size'
|
|
75
|
+
> & {
|
|
76
|
+
inputRef?: RefObject<HTMLInputElement>;
|
|
77
|
+
/** Состояние загрузки */
|
|
78
|
+
loading?: boolean;
|
|
79
|
+
}
|
|
80
|
+
>;
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { SIZE } from './constants';
|
|
2
|
+
import { DataAttributes, Size } from './types';
|
|
3
|
+
|
|
4
|
+
export function getVisualStateAttributes(state: Record<string, string | boolean>) {
|
|
5
|
+
return Object.entries(state).reduce<DataAttributes>(function (result, [name, value]) {
|
|
6
|
+
result[`data-${name.toLowerCase()}`] = value;
|
|
7
|
+
return result;
|
|
8
|
+
}, {});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getIconSize(size?: Size) {
|
|
12
|
+
switch (size) {
|
|
13
|
+
case SIZE.XS: {
|
|
14
|
+
return 16;
|
|
15
|
+
}
|
|
16
|
+
case SIZE.S:
|
|
17
|
+
default: {
|
|
18
|
+
return 24;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|