@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.
Files changed (106) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +492 -0
  4. package/dist/cjs/components/Checkbox/Checkbox.d.ts +8 -0
  5. package/dist/cjs/components/Checkbox/Checkbox.js +61 -0
  6. package/dist/cjs/components/Checkbox/index.d.ts +1 -0
  7. package/dist/cjs/components/Checkbox/index.js +17 -0
  8. package/dist/cjs/components/Checkbox/styles.module.css +223 -0
  9. package/dist/cjs/components/Favourite/Favourite.d.ts +9 -0
  10. package/dist/cjs/components/Favourite/Favourite.js +42 -0
  11. package/dist/cjs/components/Favourite/index.d.ts +1 -0
  12. package/dist/cjs/components/Favourite/index.js +17 -0
  13. package/dist/cjs/components/Favourite/styles.module.css +138 -0
  14. package/dist/cjs/components/Radio/Radio.d.ts +3 -0
  15. package/dist/cjs/components/Radio/Radio.js +34 -0
  16. package/dist/cjs/components/Radio/index.d.ts +1 -0
  17. package/dist/cjs/components/Radio/index.js +17 -0
  18. package/dist/cjs/components/Radio/styles.module.css +219 -0
  19. package/dist/cjs/components/Switch/Switch.d.ts +3 -0
  20. package/dist/cjs/components/Switch/Switch.js +42 -0
  21. package/dist/cjs/components/Switch/index.d.ts +1 -0
  22. package/dist/cjs/components/Switch/index.js +17 -0
  23. package/dist/cjs/components/Switch/styles.module.css +269 -0
  24. package/dist/cjs/components/ToggleGroup/ToggleGroup.d.ts +22 -0
  25. package/dist/cjs/components/ToggleGroup/ToggleGroup.js +33 -0
  26. package/dist/cjs/components/ToggleGroup/hooks.d.ts +9 -0
  27. package/dist/cjs/components/ToggleGroup/hooks.js +26 -0
  28. package/dist/cjs/components/ToggleGroup/index.d.ts +2 -0
  29. package/dist/cjs/components/ToggleGroup/index.js +18 -0
  30. package/dist/cjs/components/index.d.ts +5 -0
  31. package/dist/cjs/components/index.js +21 -0
  32. package/dist/cjs/constants.d.ts +23 -0
  33. package/dist/cjs/constants.js +26 -0
  34. package/dist/cjs/context.d.ts +9 -0
  35. package/dist/cjs/context.js +12 -0
  36. package/dist/cjs/index.d.ts +4 -0
  37. package/dist/cjs/index.js +20 -0
  38. package/dist/cjs/package.json +3 -0
  39. package/dist/cjs/style.css +849 -0
  40. package/dist/cjs/types.d.ts +57 -0
  41. package/dist/cjs/types.js +2 -0
  42. package/dist/cjs/utils.d.ts +3 -0
  43. package/dist/cjs/utils.js +22 -0
  44. package/dist/esm/components/Checkbox/Checkbox.d.ts +8 -0
  45. package/dist/esm/components/Checkbox/Checkbox.js +55 -0
  46. package/dist/esm/components/Checkbox/index.d.ts +1 -0
  47. package/dist/esm/components/Checkbox/index.js +1 -0
  48. package/dist/esm/components/Checkbox/styles.module.css +223 -0
  49. package/dist/esm/components/Favourite/Favourite.d.ts +9 -0
  50. package/dist/esm/components/Favourite/Favourite.js +36 -0
  51. package/dist/esm/components/Favourite/index.d.ts +1 -0
  52. package/dist/esm/components/Favourite/index.js +1 -0
  53. package/dist/esm/components/Favourite/styles.module.css +138 -0
  54. package/dist/esm/components/Radio/Radio.d.ts +3 -0
  55. package/dist/esm/components/Radio/Radio.js +28 -0
  56. package/dist/esm/components/Radio/index.d.ts +1 -0
  57. package/dist/esm/components/Radio/index.js +1 -0
  58. package/dist/esm/components/Radio/styles.module.css +219 -0
  59. package/dist/esm/components/Switch/Switch.d.ts +3 -0
  60. package/dist/esm/components/Switch/Switch.js +36 -0
  61. package/dist/esm/components/Switch/index.d.ts +1 -0
  62. package/dist/esm/components/Switch/index.js +1 -0
  63. package/dist/esm/components/Switch/styles.module.css +269 -0
  64. package/dist/esm/components/ToggleGroup/ToggleGroup.d.ts +22 -0
  65. package/dist/esm/components/ToggleGroup/ToggleGroup.js +30 -0
  66. package/dist/esm/components/ToggleGroup/hooks.d.ts +9 -0
  67. package/dist/esm/components/ToggleGroup/hooks.js +23 -0
  68. package/dist/esm/components/ToggleGroup/index.d.ts +2 -0
  69. package/dist/esm/components/ToggleGroup/index.js +2 -0
  70. package/dist/esm/components/index.d.ts +5 -0
  71. package/dist/esm/components/index.js +5 -0
  72. package/dist/esm/constants.d.ts +23 -0
  73. package/dist/esm/constants.js +23 -0
  74. package/dist/esm/context.d.ts +9 -0
  75. package/dist/esm/context.js +8 -0
  76. package/dist/esm/index.d.ts +4 -0
  77. package/dist/esm/index.js +4 -0
  78. package/dist/esm/style.css +849 -0
  79. package/dist/esm/types.d.ts +57 -0
  80. package/dist/esm/types.js +1 -0
  81. package/dist/esm/utils.d.ts +3 -0
  82. package/dist/esm/utils.js +18 -0
  83. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  84. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  85. package/package.json +46 -0
  86. package/src/components/Checkbox/Checkbox.tsx +140 -0
  87. package/src/components/Checkbox/index.ts +1 -0
  88. package/src/components/Checkbox/styles.module.scss +162 -0
  89. package/src/components/Favourite/Favourite.tsx +116 -0
  90. package/src/components/Favourite/index.ts +1 -0
  91. package/src/components/Favourite/styles.module.scss +138 -0
  92. package/src/components/Radio/Radio.tsx +84 -0
  93. package/src/components/Radio/index.ts +1 -0
  94. package/src/components/Radio/styles.module.scss +159 -0
  95. package/src/components/Switch/Switch.tsx +94 -0
  96. package/src/components/Switch/index.ts +1 -0
  97. package/src/components/Switch/styles.module.scss +250 -0
  98. package/src/components/ToggleGroup/ToggleGroup.tsx +71 -0
  99. package/src/components/ToggleGroup/hooks.ts +34 -0
  100. package/src/components/ToggleGroup/index.ts +2 -0
  101. package/src/components/index.ts +5 -0
  102. package/src/constants.ts +27 -0
  103. package/src/context.ts +18 -0
  104. package/src/index.ts +4 -0
  105. package/src/types.ts +80 -0
  106. package/src/utils.ts +21 -0
package/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # Toggles
2
+
3
+ `@cloud-ru/ds-toggles` — Пакет компонентов выбора и переключения — Checkbox, Radio, Switch, Favourite и контейнер ToggleGroup с общими токенами размеров и состояний.
4
+
5
+ Пакет `@cloud-ru/ds-toggles` объединяет пять компонентов для бинарных и мультиполярных состояний. Все компоненты разделяют единое API (`size`, `checked/defaultChecked`, `disabled`, `loading`, `onChange`) и два размера (`xs`, `s`).
6
+
7
+ ## Установка
8
+
9
+ ```bash
10
+ pnpm add @cloud-ru/ds-toggles
11
+ ```
12
+
13
+ ```ts
14
+ import { Checkbox, Radio, Switch, Favourite, ToggleGroup } from '@cloud-ru/ds-toggles'
15
+ ```
16
+
17
+ ## Checkbox
18
+
19
+ Чекбокс для множественного выбора — два размера, состояния checked/indeterminate/disabled/loading и единое API с остальными toggles.
20
+
21
+ Чекбокс для множественного выбора из списка. Поддерживает три визуальных состояния — unchecked / checked / indeterminate — плюс disabled и loading.
22
+
23
+ ### Когда использовать
24
+ - Для множественного выбора из двух или более независимых опций.
25
+ - Для согласий («Я согласен с условиями»), чек-листов, фильтров.
26
+ - Для «выбрать всё» в группе — индикатор частичного выбора (`indeterminate`).
27
+
28
+ Когда **не** нужен Checkbox: для взаимоисключающего выбора — используйте **`Radio`**, для on/off настроек — **`Switch`**.
29
+
30
+ ### Анатомия
31
+
32
+ #### Size
33
+ `xs` — для плотных таблиц и инлайновых списков, `s` — дефолт в формах.
34
+ ### Примеры использования
35
+ #### 1. Базовый чекбокс
36
+
37
+ ```tsx
38
+ import { Checkbox } from '@cloud-ru/ds-toggles';
39
+
40
+ export function CheckboxBasic() {
41
+ return <Checkbox defaultChecked />;
42
+ }
43
+ ```
44
+
45
+ #### 2. Indeterminate
46
+
47
+ Используется для частично выбранной группы
48
+
49
+ ```tsx
50
+ import { Checkbox } from '@cloud-ru/ds-toggles';
51
+
52
+ export function CheckboxIndeterminate() {
53
+ return <Checkbox indeterminateDefault />;
54
+ }
55
+ ```
56
+
57
+ #### 3. Все состояния
58
+
59
+ ```tsx
60
+ import { Checkbox } from '@cloud-ru/ds-toggles';
61
+
62
+ export function CheckboxStates() {
63
+ return (
64
+ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
65
+ <Checkbox />
66
+ <Checkbox defaultChecked />
67
+ <Checkbox disabled />
68
+ <Checkbox disabled defaultChecked />
69
+ <Checkbox loading />
70
+ </div>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ### Props
76
+ **CheckboxProps**
77
+
78
+ | Prop | Type | Default | Description |
79
+ |------|------|---------|-------------|
80
+ | `autofocus` | `boolean` | — | HTML-аттрибут autofocus |
81
+ | `checked` | `boolean` | — | HTML-аттрибут checked |
82
+ | `className` | `string` | — | CSS-класс |
83
+ | `data-test-id` | `string` | — | |
84
+ | `defaultChecked` | `boolean` | — | HTML-аттрибут checked по-умолчанию |
85
+ | `disabled` | `boolean` | `false` | HTML-аттрибут disabled |
86
+ | `id` | `string` | — | HTML-аттрибут id |
87
+ | `indeterminate` | `boolean` | — | Состояние частичного выбора |
88
+ | `indeterminateDefault` | `boolean` | — | Состояние частичного выбора по-умолчанию |
89
+ | `inputRef` | `RefObject<HTMLInputElement>` | — | |
90
+ | `loading` | `boolean` | `false` | Состояние загрузки |
91
+ | `name` | `string` | — | HTML-аттрибут name |
92
+ | `onBlur` | `FocusEventHandler<HTMLInputElement>` | — | Колбек потери фокуса |
93
+ | `onChange` | `((checked: boolean) => void)` | — | Колбек смены значения |
94
+ | `onClick` | `MouseEventHandler<HTMLInputElement>` | — | Колбек клика |
95
+ | `onFocus` | `FocusEventHandler<HTMLInputElement>` | — | Колбек приобретения фокуса |
96
+ | `size` | `"s"` \| `"xs"` | `s` | Размер |
97
+ | `tabIndex` | `number` | — | HTML-аттрибут tab-index |
98
+ | `value` | `string` | — | HTML-аттрибут value |
99
+
100
+ ##### Related types
101
+
102
+ - `Size` = `"s"` \| `"xs"`
103
+
104
+ ## Radio
105
+
106
+ Радиокнопка для взаимоисключающего выбора из группы опций — два размера, state controlled/uncontrolled, единое API с остальными toggles.
107
+
108
+ Радиокнопка для взаимоисключающего выбора из группы опций. Группировка — через общий `name` или через компонент **`ToggleGroup`** с `selectionMode='single'`.
109
+
110
+ ### Когда использовать
111
+ - Для выбора **одной** опции из 2–5 взаимоисключающих вариантов.
112
+ - Когда все варианты должны быть видны одновременно (иначе используйте `Select`).
113
+
114
+ Когда **не** нужен Radio: для множественного выбора — **`Checkbox`**, для on/off — **`Switch`**.
115
+
116
+ ### Анатомия
117
+
118
+ #### Size
119
+ `xs` — для плотных списков опций, `s` — дефолт в формах.
120
+ ### Примеры использования
121
+ #### 1. Базовый Radio
122
+
123
+ ```tsx
124
+ import { Radio } from '@cloud-ru/ds-toggles';
125
+
126
+ export function RadioBasic() {
127
+ return <Radio defaultChecked />;
128
+ }
129
+ ```
130
+
131
+ #### 2. Группа радиокнопок
132
+
133
+ Общий name объединяет Radio в группу; выбор переключается автоматически
134
+
135
+ ```tsx
136
+ import { Radio } from '@cloud-ru/ds-toggles';
137
+
138
+ export function RadioGroup() {
139
+ return (
140
+ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
141
+ <label htmlFor='delivery-courier'>
142
+ <Radio id='delivery-courier' name='delivery' value='courier' defaultChecked /> Курьер
143
+ </label>
144
+ <label htmlFor='delivery-pickup'>
145
+ <Radio id='delivery-pickup' name='delivery' value='pickup' /> Самовывоз
146
+ </label>
147
+ <label htmlFor='delivery-post'>
148
+ <Radio id='delivery-post' name='delivery' value='post' /> Почта
149
+ </label>
150
+ </div>
151
+ );
152
+ }
153
+ ```
154
+
155
+ #### 3. Все состояния
156
+
157
+ ```tsx
158
+ import { Radio } from '@cloud-ru/ds-toggles';
159
+
160
+ export function RadioStates() {
161
+ return (
162
+ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
163
+ <Radio />
164
+ <Radio defaultChecked />
165
+ <Radio disabled />
166
+ <Radio disabled defaultChecked />
167
+ <Radio loading />
168
+ </div>
169
+ );
170
+ }
171
+ ```
172
+
173
+ ### Props
174
+ **RadioProps**
175
+
176
+ | Prop | Type | Default | Description |
177
+ |------|------|---------|-------------|
178
+ | `autofocus` | `boolean` | — | HTML-аттрибут autofocus |
179
+ | `checked` | `boolean` | — | HTML-аттрибут checked |
180
+ | `className` | `string` | — | CSS-класс |
181
+ | `data-test-id` | `string` | — | |
182
+ | `defaultChecked` | `boolean` | — | HTML-аттрибут checked по-умолчанию |
183
+ | `disabled` | `boolean` | `false` | HTML-аттрибут disabled |
184
+ | `id` | `string` | — | HTML-аттрибут id |
185
+ | `inputRef` | `RefObject<HTMLInputElement>` | — | |
186
+ | `loading` | `boolean` | `false` | Состояние загрузки |
187
+ | `name` | `string` | — | HTML-аттрибут name |
188
+ | `onBlur` | `FocusEventHandler<HTMLInputElement>` | — | Колбек потери фокуса |
189
+ | `onChange` | `((checked: boolean) => void)` | — | Колбек смены значения |
190
+ | `onClick` | `MouseEventHandler<HTMLInputElement>` | — | Колбек клика |
191
+ | `onFocus` | `FocusEventHandler<HTMLInputElement>` | — | Колбек приобретения фокуса |
192
+ | `size` | `"s"` \| `"xs"` | `s` | Размер |
193
+ | `tabIndex` | `number` | — | HTML-аттрибут tab-index |
194
+ | `value` | `string` | — | HTML-аттрибут value |
195
+
196
+ ##### Related types
197
+
198
+ - `Size` = `"s"` \| `"xs"`
199
+
200
+ ## Switch
201
+
202
+ Переключатель on/off для бинарных настроек — два размера, моментальное применение без подтверждения, единое API с остальными toggles.
203
+
204
+ Переключатель on/off для бинарных настроек с моментальным применением. В отличие от `Checkbox`, Switch меняет состояние сразу — без кнопки «Сохранить».
205
+
206
+ ### Когда использовать
207
+ - Для бинарных настроек пользователя, применяемых немедленно (уведомления, тёмная тема).
208
+ - В ситуациях, где не нужен пакетный apply/cancel.
209
+
210
+ Когда **не** нужен Switch: если изменение требует подтверждения — используйте **`Checkbox`** с кнопкой submit.
211
+
212
+ ### Анатомия
213
+
214
+ #### Size
215
+ `xs` — для плотных настроек и таблиц, `s` — дефолт в формах и карточках.
216
+ ### Примеры использования
217
+ #### 1. Базовый Switch
218
+
219
+ ```tsx
220
+ import { Switch } from '@cloud-ru/ds-toggles';
221
+
222
+ export function SwitchBasic() {
223
+ return <Switch defaultChecked />;
224
+ }
225
+ ```
226
+
227
+ #### 2. Два размера
228
+
229
+ ```tsx
230
+ import { Switch } from '@cloud-ru/ds-toggles';
231
+
232
+ export function SwitchSizes() {
233
+ return (
234
+ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
235
+ <Switch size='xs' defaultChecked />
236
+ <Switch size='s' defaultChecked />
237
+ </div>
238
+ );
239
+ }
240
+ ```
241
+
242
+ #### 3. Все состояния
243
+
244
+ ```tsx
245
+ import { Switch } from '@cloud-ru/ds-toggles';
246
+
247
+ export function SwitchStates() {
248
+ return (
249
+ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
250
+ <Switch />
251
+ <Switch defaultChecked />
252
+ <Switch disabled />
253
+ <Switch disabled defaultChecked />
254
+ <Switch loading />
255
+ </div>
256
+ );
257
+ }
258
+ ```
259
+
260
+ ### Props
261
+ **SwitchProps**
262
+
263
+ | Prop | Type | Default | Description |
264
+ |------|------|---------|-------------|
265
+ | `autofocus` | `boolean` | — | HTML-аттрибут autofocus |
266
+ | `checked` | `boolean` | — | HTML-аттрибут checked |
267
+ | `className` | `string` | — | CSS-класс |
268
+ | `data-test-id` | `string` | — | |
269
+ | `defaultChecked` | `boolean` | — | HTML-аттрибут checked по-умолчанию |
270
+ | `disabled` | `boolean` | `false` | HTML-аттрибут disabled |
271
+ | `id` | `string` | — | HTML-аттрибут id |
272
+ | `inputRef` | `RefObject<HTMLInputElement>` | — | |
273
+ | `loading` | `boolean` | `false` | Состояние загрузки |
274
+ | `name` | `string` | — | HTML-аттрибут name |
275
+ | `onBlur` | `FocusEventHandler<HTMLInputElement>` | — | Колбек потери фокуса |
276
+ | `onChange` | `((checked: boolean) => void)` | — | Колбек смены значения |
277
+ | `onClick` | `MouseEventHandler<HTMLInputElement>` | — | Колбек клика |
278
+ | `onFocus` | `FocusEventHandler<HTMLInputElement>` | — | Колбек приобретения фокуса |
279
+ | `size` | `"s"` \| `"xs"` | `s` | Размер |
280
+ | `tabIndex` | `number` | — | HTML-аттрибут tab-index |
281
+ | `value` | `string` | — | HTML-аттрибут value |
282
+
283
+ ##### Related types
284
+
285
+ - `Size` = `"s"` \| `"xs"`
286
+
287
+ ## Favourite
288
+
289
+ Toggle для избранного — звезда или сердце, два размера, единое API с остальными toggles.
290
+
291
+ Toggle для «избранного» — карточка товара, трек в плейлисте, пост в ленте. Переключается между пустой и заполненной иконкой (звезда или сердце).
292
+
293
+ ### Когда использовать
294
+ - «Добавить в избранное», «лайк», «в wishlist».
295
+ - Каталоги, ленты, плейлисты — любой UI с персональными коллекциями.
296
+
297
+ Когда **не** нужен Favourite: для булевых настроек — **`Switch`**; для выбора опций — **`Checkbox`**.
298
+
299
+ ### Анатомия
300
+
301
+ #### Size
302
+ `xs` — для плотных списков, `s` — дефолт в карточках.
303
+
304
+ #### Favourite icon
305
+ Форма иконки: `star` — «в избранное», `heart` — «нравится».
306
+ ### Примеры использования
307
+ #### 1. Звезда
308
+
309
+ ```tsx
310
+ import { Favourite } from '@cloud-ru/ds-toggles';
311
+
312
+ export function FavouriteStar() {
313
+ return <Favourite icon='star' defaultChecked />;
314
+ }
315
+ ```
316
+
317
+ #### 2. Сердце
318
+
319
+ ```tsx
320
+ import { Favourite } from '@cloud-ru/ds-toggles';
321
+
322
+ export function FavouriteHeart() {
323
+ return <Favourite icon='heart' defaultChecked />;
324
+ }
325
+ ```
326
+
327
+ #### 3. Все состояния
328
+
329
+ ```tsx
330
+ import { Favourite } from '@cloud-ru/ds-toggles';
331
+
332
+ export function FavouriteStates() {
333
+ return (
334
+ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
335
+ <Favourite icon='star' />
336
+ <Favourite icon='star' defaultChecked />
337
+ <Favourite icon='heart' disabled />
338
+ <Favourite icon='heart' loading />
339
+ </div>
340
+ );
341
+ }
342
+ ```
343
+
344
+ ### Props
345
+ **FavouriteProps**
346
+
347
+ | Prop | Type | Default | Description |
348
+ |------|------|---------|-------------|
349
+ | `autofocus` | `boolean` | — | HTML-аттрибут autofocus |
350
+ | `checked` | `boolean` | — | HTML-аттрибут checked |
351
+ | `className` | `string` | — | CSS-класс |
352
+ | `data-test-id` | `string` | — | |
353
+ | `defaultChecked` | `boolean` | — | HTML-аттрибут checked по-умолчанию |
354
+ | `disabled` | `boolean` | `false` | HTML-аттрибут disabled |
355
+ | `icon` | `"heart"` \| `"star"` | `heart` | Вариант иконки: звезда или сердце |
356
+ | `id` | `string` | — | HTML-аттрибут id |
357
+ | `inputRef` | `RefObject<HTMLInputElement>` | — | |
358
+ | `loading` | `boolean` | `false` | Состояние загрузки |
359
+ | `name` | `string` | — | HTML-аттрибут name |
360
+ | `onBlur` | `FocusEventHandler<HTMLInputElement>` | — | Колбек потери фокуса |
361
+ | `onChange` | `((checked: boolean) => void)` | — | Колбек смены значения |
362
+ | `onClick` | `MouseEventHandler<HTMLInputElement>` | — | Колбек клика |
363
+ | `onFocus` | `FocusEventHandler<HTMLInputElement>` | — | Колбек приобретения фокуса |
364
+ | `onKeyUp` | `KeyboardEventHandler<HTMLSpanElement>` | — | Обработчик keyup |
365
+ | `size` | `"s"` \| `"xs"` | `s` | Размер |
366
+ | `tabIndex` | `number` | — | HTML-аттрибут tab-index |
367
+ | `value` | `string` | — | HTML-аттрибут value |
368
+
369
+ ##### Related types
370
+
371
+ - `FavouriteIcon` = `"heart"` \| `"star"`
372
+
373
+ - `Size` = `"s"` \| `"xs"`
374
+
375
+ ## ToggleGroup
376
+
377
+ Контейнер для группы связанных toggle'ов — single или multiple selection через общий context + useToggleGroup.
378
+
379
+ Контейнер для группы связанных toggle'ов (чипы, опции, сегментированный контрол) с общим режимом выбора. Предоставляет React Context — потомки читают текущий выбор через `useToggleGroup`.
380
+
381
+ ### Когда использовать
382
+ - Сегментированный контрол (выбор размера, периода, типа).
383
+ - Чипы-фильтры с `selectionMode='multiple'`.
384
+ - Любые связанные опции, где нужен shared state + unified controlled API.
385
+
386
+ Когда **не** нужен ToggleGroup: одна опция — **`Switch`** или **`Checkbox`**; взаимоисключающий выбор с нативной семантикой radio — группа **`Radio`** c общим `name`.
387
+
388
+ ### Анатомия
389
+
390
+ #### Mode
391
+ Тип дочерних контролов: `checkbox` — мультивыбор/независимые, `radio` — одиночный выбор.
392
+
393
+ #### Size
394
+ Размер вложенных тогглов: `xs` — плотный, `s` — дефолт.
395
+
396
+ #### Selection mode
397
+ Правила выбора: `single` — ровно один элемент (как radio-group), `multiple` — любое подмножество (как checkbox-group).
398
+ ### Примеры использования
399
+ #### 1. Single selection (сегментированный контрол)
400
+
401
+ ```tsx
402
+ import { ToggleGroup, useToggleGroup } from '@cloud-ru/ds-toggles';
403
+ import { useState } from 'react';
404
+
405
+ function Chip({ id, label }: { id: string; label: string }) {
406
+ const { isChecked, handleClick } = useToggleGroup({ value: id });
407
+ return (
408
+ <button onClick={handleClick} aria-pressed={isChecked}>
409
+ {label}
410
+ </button>
411
+ );
412
+ }
413
+
414
+ export function ToggleGroupSingle() {
415
+ const [value, setValue] = useState<string | undefined>('a');
416
+ return (
417
+ <ToggleGroup selectionMode='single' value={value} onChange={setValue}>
418
+ <Chip id='a' label='A' />
419
+ <Chip id='b' label='B' />
420
+ <Chip id='c' label='C' />
421
+ </ToggleGroup>
422
+ );
423
+ }
424
+ ```
425
+
426
+ #### 2. Multiple selection (чипы-фильтры)
427
+
428
+ ```tsx
429
+ import { ToggleGroup, useToggleGroup } from '@cloud-ru/ds-toggles';
430
+ import { useState } from 'react';
431
+
432
+ function Chip({ id, label }: { id: string; label: string }) {
433
+ const { isChecked, handleClick } = useToggleGroup({ value: id });
434
+ return (
435
+ <button onClick={handleClick} aria-pressed={isChecked}>
436
+ {label}
437
+ </button>
438
+ );
439
+ }
440
+
441
+ export function ToggleGroupMultiple() {
442
+ const [value, setValue] = useState<string[]>(['a']);
443
+ return (
444
+ <ToggleGroup selectionMode='multiple' value={value} onChange={next => setValue(next ?? [])}>
445
+ <Chip id='a' label='A' />
446
+ <Chip id='b' label='B' />
447
+ <Chip id='c' label='C' />
448
+ </ToggleGroup>
449
+ );
450
+ }
451
+ ```
452
+
453
+ #### 3. Controlled + отображение значения
454
+
455
+ ```tsx
456
+ import { ToggleGroup, useToggleGroup } from '@cloud-ru/ds-toggles';
457
+ import { useState } from 'react';
458
+
459
+ function Option({ id, label }: { id: string; label: string }) {
460
+ const { isChecked, handleClick } = useToggleGroup({ value: id });
461
+ return (
462
+ <button onClick={handleClick} aria-pressed={isChecked}>
463
+ {label}
464
+ </button>
465
+ );
466
+ }
467
+
468
+ export function ToggleGroupControlled() {
469
+ const [value, setValue] = useState<string | undefined>();
470
+ return (
471
+ <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
472
+ <ToggleGroup selectionMode='single' value={value} onChange={setValue}>
473
+ <Option id='s' label='S' />
474
+ <Option id='m' label='M' />
475
+ <Option id='l' label='L' />
476
+ </ToggleGroup>
477
+ <p>Selected: {value ?? '—'}</p>
478
+ </div>
479
+ );
480
+ }
481
+ ```
482
+
483
+ ### Props
484
+ **ToggleGroupProps**
485
+
486
+ | Prop | Type | Default | Description |
487
+ |------|------|---------|-------------|
488
+ | `children` | `string \| number \| boolean \| ReactElement<any, string \| JSXElementConstructor<any>> \| Iterable<ReactNode> \| ReactPortal \| null \| undefined` | — | |
489
+ | `defaultValue` | `string \| string[]` | — | Начальное состояние |
490
+ | `onChange` | `((value: string) => void) \| ((value: string[]) => void) \| undefined` | — | Controlled обработчик измения состояния |
491
+ | `selectionMode` | `"multiple"` \| `"single"` | `single` | Режим выбора |
492
+ | `value` | `string \| string[]` | — | Controlled состояние |
@@ -0,0 +1,8 @@
1
+ import { ToggleProps } from '../../types';
2
+ export type CheckboxProps = ToggleProps & {
3
+ /** Состояние частичного выбора */
4
+ indeterminate?: boolean;
5
+ /** Состояние частичного выбора по-умолчанию */
6
+ indeterminateDefault?: boolean;
7
+ };
8
+ export declare function Checkbox({ inputRef, checked: checkedProp, defaultChecked, indeterminate: indeterminateProp, indeterminateDefault, onChange: onChangeProp, onBlur, onFocus, disabled, loading, size, className, 'data-test-id': dataTestId, ...otherProps }: CheckboxProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Checkbox = Checkbox;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const system_1 = require("@cloud-ru/ds-icons/interface/system");
9
+ const loader_1 = require("@cloud-ru/ds-loader");
10
+ const utils_1 = require("@cloud-ru/ds-utils");
11
+ const classnames_1 = __importDefault(require("classnames"));
12
+ const merge_refs_1 = __importDefault(require("merge-refs"));
13
+ const react_1 = require("react");
14
+ const constants_1 = require("../../constants");
15
+ const utils_2 = require("../../utils");
16
+ const styles_module_scss_1 = __importDefault(require('./styles.module.css'));
17
+ const SPINNER_SIZE_MAP = {
18
+ [constants_1.SIZE.XS]: loader_1.LOADER_SIZE.XS,
19
+ [constants_1.SIZE.S]: loader_1.LOADER_SIZE.S,
20
+ };
21
+ function Icon({ size, checked, indeterminate, loading, }) {
22
+ if (loading) {
23
+ return (0, jsx_runtime_1.jsx)(loader_1.Spinner, { size: SPINNER_SIZE_MAP[size] });
24
+ }
25
+ const iconSize = (0, utils_2.getIconSize)(size);
26
+ if (checked) {
27
+ return (0, jsx_runtime_1.jsx)(system_1.CheckSVG, { className: styles_module_scss_1.default.icon, size: iconSize });
28
+ }
29
+ if (indeterminate) {
30
+ return (0, jsx_runtime_1.jsx)(system_1.MinusSVG, { className: styles_module_scss_1.default.icon, size: iconSize });
31
+ }
32
+ return null;
33
+ }
34
+ function Checkbox({ inputRef, checked: checkedProp, defaultChecked, indeterminate: indeterminateProp, indeterminateDefault, onChange: onChangeProp, onBlur, onFocus, disabled = false, loading = false, size = constants_1.SIZE.S, className, 'data-test-id': dataTestId, ...otherProps }) {
35
+ const localRef = (0, react_1.useRef)(null);
36
+ const ref = (0, merge_refs_1.default)(localRef, inputRef);
37
+ const [indeterminate, setIndeterminate] = (0, utils_1.useUncontrolledProp)(indeterminateProp, indeterminateDefault);
38
+ const [checked, setChecked] = (0, utils_1.useUncontrolledProp)(checkedProp, Boolean(defaultChecked), onChangeProp);
39
+ const [focusVisible, setFocusVisible] = (0, react_1.useState)(false);
40
+ const onChange = (0, react_1.useCallback)((checked) => {
41
+ setChecked?.(checked);
42
+ setIndeterminate(false);
43
+ }, [setChecked, setIndeterminate]);
44
+ (0, react_1.useEffect)(() => {
45
+ if (localRef.current) {
46
+ localRef.current.indeterminate = Boolean(indeterminate);
47
+ }
48
+ }, [localRef, indeterminate]);
49
+ const stateDataAttributes = (0, utils_2.getVisualStateAttributes)({
50
+ loading,
51
+ disabled,
52
+ checked: indeterminate || checked,
53
+ });
54
+ return ((0, jsx_runtime_1.jsx)("span", { className: (0, classnames_1.default)(styles_module_scss_1.default.root, className), role: constants_1.MODE.Checkbox, "data-size": size, ...stateDataAttributes, "data-indeterminate": indeterminate || undefined, "data-test-id": dataTestId || undefined, children: (0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.container, children: [(0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.framing, ...stateDataAttributes, "data-focusvisible": focusVisible || undefined, children: [(0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.backgroundStateLayer, "data-state": indeterminate || checked ? 'emptyDarkOnAccent' : 'emptyNeutralOnBackground' }), (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.flag, children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_scss_1.default.surface, ...stateDataAttributes, "data-text-opacity": indeterminate || checked || undefined, children: (0, jsx_runtime_1.jsx)(Icon, { size: size, loading: Boolean(loading), indeterminate: Boolean(indeterminate), checked: checked }) }) })] }), !loading && ((0, jsx_runtime_1.jsx)("input", { ...otherProps, "data-test-id": dataTestId ? `${dataTestId}${constants_1.NATIVE_INPUT_SUFFIX}` : undefined, ref: ref, type: constants_1.MODE.Checkbox, className: styles_module_scss_1.default.inputPrivate, checked: checked, disabled: disabled, onChange: e => onChange(e.target.checked), onFocus: event => {
55
+ setFocusVisible(event.target.matches(':focus-visible'));
56
+ onFocus?.(event);
57
+ }, onBlur: event => {
58
+ setFocusVisible(false);
59
+ onBlur?.(event);
60
+ } }))] }) }));
61
+ }
@@ -0,0 +1 @@
1
+ export * from './Checkbox';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./Checkbox"), exports);