@sberbusiness/triplex-next-mcp-server 1.50.0-beta → 1.53.1

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 (74) hide show
  1. package/README.md +1 -0
  2. package/bundle/components/AlertProcess.md +6 -3
  3. package/bundle/components/AlertProcessSpoiler.md +216 -0
  4. package/bundle/components/Badge.md +4 -3
  5. package/bundle/components/ButtonDropdown.md +5 -3
  6. package/bundle/components/Carousel.md +671 -0
  7. package/bundle/components/CarouselExtended.md +7 -5
  8. package/bundle/components/CarouselIndicators.md +666 -0
  9. package/bundle/components/CarouselItem.md +579 -0
  10. package/bundle/components/CarouselNextButton.md +633 -0
  11. package/bundle/components/CarouselPrevButton.md +633 -0
  12. package/bundle/components/CarouselViewport.md +631 -0
  13. package/bundle/components/CheckboxTreeExtended.md +21 -7
  14. package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
  15. package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
  16. package/bundle/components/Chip.md +1 -0
  17. package/bundle/components/ChipDatePicker.md +332 -0
  18. package/bundle/components/ChipDatePickerTarget.md +316 -0
  19. package/bundle/components/ChipMonthYearPicker.md +4 -3
  20. package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
  21. package/bundle/components/ChipMultiselect.md +63 -73
  22. package/bundle/components/ChipSelect.md +385 -0
  23. package/bundle/components/ChipSelectTarget.md +388 -0
  24. package/bundle/components/ChipSuggest.md +10 -16
  25. package/bundle/components/ChipSuggestTarget.md +506 -0
  26. package/bundle/components/CollapsibleTree.md +2 -1
  27. package/bundle/components/CollapsibleTreeExtended.md +2 -1
  28. package/bundle/components/DateField.md +4 -2
  29. package/bundle/components/DatePickerExtended.md +16 -14
  30. package/bundle/components/DateRange.md +17 -10
  31. package/bundle/components/Dropdown.md +36 -63
  32. package/bundle/components/EmptyView.md +9 -4
  33. package/bundle/components/ExpandAnimation.md +9 -5
  34. package/bundle/components/FormField.md +1 -1
  35. package/bundle/components/ImageGallery.md +19 -52
  36. package/bundle/components/ImageGalleryExtended.md +123 -139
  37. package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
  38. package/bundle/components/LoaderSmall.md +5 -4
  39. package/bundle/components/MonthYearRange.md +334 -0
  40. package/bundle/components/MultiselectField.md +81 -143
  41. package/bundle/components/Notification.md +2 -1
  42. package/bundle/components/NotificationGrouped.md +174 -0
  43. package/bundle/components/NumberField.md +259 -116
  44. package/bundle/components/OrderedList.md +149 -112
  45. package/bundle/components/Overlay.md +3 -2
  46. package/bundle/components/PageIndicators.md +306 -0
  47. package/bundle/components/Pagination.md +81 -3
  48. package/bundle/components/PaginationExtended.md +160 -0
  49. package/bundle/components/Portal.md +5 -2
  50. package/bundle/components/Puller.md +375 -0
  51. package/bundle/components/SMSField.md +277 -102
  52. package/bundle/components/SelectExtendedField.md +11 -7
  53. package/bundle/components/SelectField.md +7 -4
  54. package/bundle/components/SmartSpot.md +259 -0
  55. package/bundle/components/Step.md +2 -1
  56. package/bundle/components/Stepper.md +7 -7
  57. package/bundle/components/Suggest.md +7 -6
  58. package/bundle/components/SuggestField.md +8 -6
  59. package/bundle/components/TableBasic.md +12 -14
  60. package/bundle/components/Tooltip.md +9 -8
  61. package/bundle/guides/commits.md +4 -1
  62. package/bundle/guides/context.md +5 -5
  63. package/bundle/guides/stories.md +29 -21
  64. package/bundle/manifest.json +145 -13
  65. package/bundle/release-notes/1.50.0.mdx +4 -0
  66. package/bundle/release-notes/1.51.0.mdx +57 -0
  67. package/bundle/release-notes/1.52.0.mdx +55 -0
  68. package/bundle/release-notes/1.53.0.mdx +60 -0
  69. package/bundle/release-notes/1.54.0.mdx +45 -0
  70. package/bundle/release-notes/1.55.0.mdx +7 -0
  71. package/bundle/tokens.json +377 -222
  72. package/dist/index.js +39 -1
  73. package/dist/index.js.map +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,666 @@
1
+ ---
2
+ component: CarouselIndicators
3
+ category: Carousel
4
+ related: [Carousel, PageIndicators]
5
+ tokens:
6
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Default
7
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Hover
8
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Pressed
9
+ - PageIndicators.Indicator_Inactive_BackgroundColor_Disabled
10
+ - PageIndicators.Indicator_Active_BackgroundColor_Default
11
+ - PageIndicators.Indicator_Active_BackgroundColor_Hover
12
+ - PageIndicators.Indicator_Active_BackgroundColor_Pressed
13
+ - PageIndicators.Indicator_Active_BackgroundColor_Disabled
14
+ - PageIndicators.Indicator_OutlineColor
15
+ stories: stories/Carousel/Carousel.stories.tsx
16
+ version: "1.0"
17
+ ---
18
+
19
+ # CarouselIndicators
20
+
21
+ ## Назначение
22
+
23
+ Индикаторы страниц составного `Carousel`. Связывают `PageIndicators` с контекстом
24
+ карусели: получают количество страниц, выбранный индекс, ориентацию и переход к
25
+ странице от родителя. Самостоятельного состояния выбора у обёртки нет.
26
+
27
+ Используй внутри `Carousel` с `scrollMode={ECarouselScrollMode.PAGE}`, когда
28
+ нужно показать положение в наборе страниц и дать прямой переход между ними.
29
+ В режиме `ITEM` обёртка возвращает `null`; вне `Carousel` действует тот же
30
+ пустой результат из-за значения контекста по умолчанию. Для самостоятельного
31
+ управления `count`, `activeIndex` и `onChange` используй `PageIndicators`.
32
+
33
+ Публичный путь — статическое поле `Carousel.Indicators`. Именованного экспорта
34
+ `CarouselIndicators` из пакета нет; интерфейс `ICarouselIndicatorsProps` и типы
35
+ `TCarouselIndicatorProps` / `TCarouselIndicatorPropsFactory` экспортируются.
36
+
37
+ ```tsx
38
+ import React from "react";
39
+ import { Carousel, ECarouselScrollMode } from "@sberbusiness/triplex-next";
40
+
41
+ export const CarouselWithIndicators = () => (
42
+ <Carousel scrollMode={ECarouselScrollMode.PAGE} style={{ width: 400, height: 180 }}>
43
+ <Carousel.Viewport>
44
+ <Carousel.Track>
45
+ {["Первый слайд", "Второй слайд", "Третий слайд"].map((text, index) => (
46
+ <Carousel.Item key={text} index={index} style={{ width: 400, height: 120 }}>
47
+ {text}
48
+ </Carousel.Item>
49
+ ))}
50
+ </Carousel.Track>
51
+ </Carousel.Viewport>
52
+ <Carousel.Indicators
53
+ aria-label="Страницы карусели"
54
+ indicatorProps={({ page }) => ({ "aria-label": `Страница ${page}` })}
55
+ />
56
+ </Carousel>
57
+ );
58
+ ```
59
+
60
+ Размещай индикаторы рядом с `Carousel.Viewport`, внутри того же `Carousel`:
61
+ `Viewport` обрезает содержимое через `overflow: hidden`, а внешний корень
62
+ задаёт положение ряда по ориентации.
63
+
64
+ ---
65
+
66
+ ## Варианты и props
67
+
68
+ Обязательных props нет. Все данные выбора принадлежат родительскому `Carousel`.
69
+
70
+ | Prop | Назначение и ограничения |
71
+ |---|---|
72
+ | `indicatorProps` | Общие атрибуты кнопок либо фабрика от `{ index, page, selected }`. `index` начинается с 0, `page` — с 1; `selected` сообщает о выбранной странице. Используй для `aria-label`, `aria-controls`, `disabled`, `data-*` и оформления |
73
+ | `className`, `style`, `aria-*`, `data-*` и остальные атрибуты `<div>` | Передаются в `PageIndicators` на корневой ряд; `className` объединяется с классами раскладки карусели и индикаторов |
74
+ | `onKeyDown` | Передаётся в `PageIndicators`; вызывается после встроенной обработки клавиатуры |
75
+ | `ref` | Указывает на корневой `HTMLDivElement` ряда индикаторов; при отсутствии ряда — `null` |
76
+
77
+ `count`, `activeIndex`, `onChange` и `orientation` исключены из
78
+ `ICarouselIndicatorsProps`: обёртка всегда берёт их из контекста. Количество
79
+ индикаторов равно числу рассчитанных **страниц**, а не числу `Carousel.Item`:
80
+ на одной странице могут помещаться несколько слайдов. До расчёта страниц и
81
+ при пустой карусели `PageIndicators` с `count < 1` ничего не рендерит.
82
+
83
+ В `indicatorProps` свойства `role`, `tabIndex`, `aria-selected` управляются
84
+ `PageIndicators`; скрытым индикаторам он задаёт `aria-hidden`. Пользовательские
85
+ `className` и `style` объединяются с внутренними. Пользовательский `onClick`
86
+ индикатора вызывается **после** `goToSlide`, поэтому не отменяет запрос перехода.
87
+ Отключённые через `disabled` индикаторы пропускаются при клавиатурном выборе
88
+ и не обрабатывают клик.
89
+
90
+ ---
91
+
92
+ ## Дизайн-токены
93
+
94
+ В `Carousel.module.less` класс `indicators` задаёт только расположение ряда.
95
+ Цвета и контур фокуса приходят из `PageIndicators.module.less`:
96
+
97
+ ```text
98
+ PageIndicators.Indicator_Inactive_BackgroundColor_Default
99
+ PageIndicators.Indicator_Inactive_BackgroundColor_Hover
100
+ PageIndicators.Indicator_Inactive_BackgroundColor_Pressed
101
+ PageIndicators.Indicator_Inactive_BackgroundColor_Disabled
102
+ PageIndicators.Indicator_Active_BackgroundColor_Default
103
+ PageIndicators.Indicator_Active_BackgroundColor_Hover
104
+ PageIndicators.Indicator_Active_BackgroundColor_Pressed
105
+ PageIndicators.Indicator_Active_BackgroundColor_Disabled
106
+ PageIndicators.Indicator_OutlineColor
107
+ ```
108
+
109
+ Переопределяются через `ThemeProvider` (prop `tokens`) — см.
110
+ `src/components/ThemeProvider/ThemeProvider-ai.md`, раздел «Как переопределять
111
+ токены». Значения по умолчанию —
112
+ `src/components/DesignTokens/components/PageIndicators.ts`.
113
+
114
+ Токены `Carousel.Button_*` относятся к кнопкам навигации и не используются
115
+ индикаторами. Runtime-переменные геометрии `PageIndicators` не являются
116
+ дизайн-токенами.
117
+
118
+ ---
119
+
120
+ ## Инварианты
121
+
122
+ - `forwardRef` обязателен. Target — `<div role="tablist">`, а не `Viewport`,
123
+ слайд или отдельная кнопка; `displayName` — `"Carousel.Indicators"`.
124
+ - Публичное обращение `Carousel.Indicators`, экспортируемые типы и форма
125
+ аргумента фабрики `{ index, page, selected }` сохраняются.
126
+ - Выбором и ориентацией управляет `CarouselContext`; не добавлять параллельное
127
+ состояние и возможность переопределять четыре исключённых prop.
128
+ - В `ITEM` результат — `null`. В `PAGE` используется один `PageIndicators`
129
+ с `count={activeIndices.length}`, `activeIndex={currentIndex}` и
130
+ `onChange={goToSlide}`.
131
+ - Раскладка зависит от классов ориентации на предке `Carousel`. На экранах
132
+ от `screen-md` ряд получает внешний отступ 15 px (сверху горизонтально,
133
+ слева вертикально). До `screen-sm-max` он позиционируется абсолютно на
134
+ 16 px за нижней или правой границей карусели и имеет `pointer-events: none`.
135
+ - Геометрию окна из пяти видимых индикаторов и ARIA-контракт обеспечивает
136
+ `PageIndicators`; не дублировать эту логику в обёртке.
137
+ - Имена дизайн-токенов и существующие barrel-экспорты сохраняются; код остаётся
138
+ совместимым с React 17.
139
+
140
+ ---
141
+
142
+ ## Accessibility
143
+
144
+ - Ряд получает `role="tablist"` и `aria-orientation`, отдельные кнопки —
145
+ `role="tab"` и `aria-selected`. Роли и состояние задаёт `PageIndicators`.
146
+ - Передавай доступное имя ряду (`aria-label`) и каждой кнопке через
147
+ `indicatorProps`; текст и язык выбирает потребитель.
148
+ - По Tab доступен выбранный индикатор. Если он `disabled`, Tab-stop получает
149
+ ближайший доступный в видимом окне; если доступных нет, Tab-stop отсутствует.
150
+ - Стрелки `←` / `→` в горизонтальной и `↑` / `↓` в вертикальной ориентации
151
+ выбирают соседнюю доступную страницу без зацикливания. `Home` / `End`
152
+ выбирают первую/последнюю доступную страницу. Обработанным клавишам вызывается
153
+ `preventDefault`; пользовательский `onKeyDown` вызывается после обработки.
154
+ - Если фокус уже внутри ряда, при смене выбранной страницы он следует за
155
+ новым Tab-stop через `requestAnimationFrame`. Переключение кнопками карусели
156
+ или жестом не забирает фокус из другого места.
157
+ - Индикаторы вне окна получают `aria-hidden="true"` и `tabIndex=-1`.
158
+ `aria-controls` и связи со слайдами автоматически не создаются: при
159
+ необходимости их задаёт потребитель через `indicatorProps` и атрибуты слайдов.
160
+ - На мобильной ширине `pointer-events: none` отключает выбор указателем,
161
+ но не скрывает ряд от accessibility-дерева и клавиатуры. Прокрутка жестом
162
+ обрабатывается `Carousel.Viewport`.
163
+ - При `prefers-reduced-motion: reduce` анимации индикаторов отключает
164
+ `PageIndicators`.
165
+
166
+ ---
167
+
168
+ ## Связанные компоненты
169
+
170
+ - `Carousel` — родитель и владелец состояния, расчёта страниц и ориентации;
171
+ индикаторы доступны через его статическое поле `.Indicators`.
172
+ - `PageIndicators` — непосредственно рендерится обёрткой;
173
+ `ICarouselIndicatorsProps` наследует его props через `Omit`. Отвечает за
174
+ кнопки, окно индикаторов, ARIA и клавиатуру.
175
+ - `CarouselViewport` — область слайдов и жестов. Индикаторы размещаются рядом
176
+ с ней, чтобы не попасть под обрезку содержимого.
177
+
178
+ ---
179
+
180
+ ## Stories
181
+
182
+ Используются существующие истории семейства; отдельный stories-файл для атома
183
+ не нужен.
184
+
185
+ Основные истории: `stories/Carousel/Carousel.stories.tsx`
186
+ Файлы примеров: `stories/Carousel/examples/`
187
+
188
+ | Story | Example file | Что демонстрирует |
189
+ |---|---|---|
190
+ | `Playground` | `Playground.tsx` | Интерактивное переключение `scrollMode` и `orientation`; для видимого ряда выбери `PAGE` |
191
+ | `Default` | `Default.tsx` | Карусель в режиме `ITEM` без индикаторов в композиции |
192
+ | `ScrollModes` | `ScrollModes.tsx` | Ряд в `PAGE` и отсутствие ряда в `ITEM`; снимки `xs` / `xl` |
193
+ | `Orientations` | `Orientations.tsx` | Раскладка родителя в двух ориентациях; `ITEM`, поэтому `.Indicators` возвращает `null` |
194
+ | `Production` | `Production.tsx` | Карточки в режиме `ITEM` без индикаторов в композиции |
195
+ | `DynamicIndicators` | `DynamicIndicators.tsx` | `PAGE` с большим количеством страниц и скользящим окном индикаторов |
196
+
197
+ Базовые снимки семейства —
198
+ `__screenshots__/carousel--scroll-modes--{xs,xl}.png` и
199
+ `__screenshots__/carousel--orientations--{xs,xl}.png`.
200
+ Состояния окна, фокуса, `disabled` и вертикального ряда покрыты дополнительно
201
+ в `stories/PageIndicators/PageIndicators.stories.tsx` (`VisualTests`),
202
+ а интеграция с контекстом — в
203
+ `src/components/Carousel/__tests__/CarouselIndicators.test.tsx`.
204
+
205
+ ---
206
+
207
+ ## История изменений
208
+
209
+ | Дата | Изменение |
210
+ |---|---|
211
+ | 2026-10-20 | Создан документ по TRI-164; подтверждено завершение AI-рефакторинга обёртки без изменения кода и публичного поведения. |
212
+
213
+ ---
214
+
215
+ ## Примеры использования
216
+
217
+ ### Default
218
+
219
+ `stories/Carousel/examples/Default.tsx`
220
+
221
+ ```tsx
222
+ import React from "react";
223
+ import { Carousel } from "@sberbusiness/triplex-next";
224
+
225
+ const BASE_ITEM_STYLE: React.CSSProperties = {
226
+ display: "flex",
227
+ alignItems: "center",
228
+ justifyContent: "center",
229
+ boxSizing: "border-box",
230
+ borderRadius: "8px",
231
+ color: "#ffffff",
232
+ fontSize: "24px",
233
+ fontWeight: "bold",
234
+ userSelect: "none",
235
+ };
236
+
237
+ export const Default = () => {
238
+ const itemSizes = [92, 168, 220, 244, 252, 256, 252, 244, 220, 168, 92];
239
+ const items = itemSizes.map((size, index) => {
240
+ const totalItems = itemSizes.length;
241
+ const hue = (index / (totalItems - 1)) * 280;
242
+
243
+ return {
244
+ id: index + 1,
245
+ size,
246
+ backgroundColor: `hsl(${hue}, 50%, 50%)`,
247
+ };
248
+ });
249
+
250
+ const boxStyle: React.CSSProperties = {
251
+ width: "100%",
252
+ height: "256px",
253
+ maxWidth: "512px",
254
+ };
255
+
256
+ return (
257
+ <div style={boxStyle}>
258
+ <Carousel>
259
+ <Carousel.Viewport>
260
+ <Carousel.Track>
261
+ {items.map((item, index) => (
262
+ <Carousel.Item
263
+ key={item.id}
264
+ index={index}
265
+ style={{
266
+ ...BASE_ITEM_STYLE,
267
+ width: item.size,
268
+ height: "100%",
269
+ backgroundColor: item.backgroundColor,
270
+ }}
271
+ >
272
+ {item.id}
273
+ </Carousel.Item>
274
+ ))}
275
+ </Carousel.Track>
276
+ <Carousel.PrevButton aria-label="Previous slide" />
277
+ <Carousel.NextButton aria-label="Next slide" />
278
+ </Carousel.Viewport>
279
+ </Carousel>
280
+ </div>
281
+ );
282
+ };
283
+ ```
284
+
285
+ ### ScrollModes
286
+
287
+ `stories/Carousel/examples/ScrollModes.tsx`
288
+
289
+ ```tsx
290
+ import React from "react";
291
+ import { Carousel, ECarouselScrollMode } from "@sberbusiness/triplex-next";
292
+
293
+ interface ScrollModeItemProps {
294
+ scrollMode: ECarouselScrollMode;
295
+ }
296
+
297
+ const BASE_ITEM_STYLE: React.CSSProperties = {
298
+ display: "flex",
299
+ alignItems: "center",
300
+ justifyContent: "center",
301
+ boxSizing: "border-box",
302
+ height: "256px",
303
+ borderRadius: "8px",
304
+ color: "#ffffff",
305
+ fontSize: "24px",
306
+ fontWeight: "bold",
307
+ userSelect: "none",
308
+ };
309
+
310
+ const ScrollModeItem = ({ scrollMode }: ScrollModeItemProps) => {
311
+ const itemSizes = [92, 168, 220, 244, 252, 256, 252, 244, 220, 168, 92];
312
+ const items = itemSizes.map((size, index) => {
313
+ const totalItems = itemSizes.length;
314
+ const hue = (index / (totalItems - 1)) * 280;
315
+
316
+ return {
317
+ id: index + 1,
318
+ size,
319
+ backgroundColor: `hsl(${hue}, 50%, 50%)`,
320
+ };
321
+ });
322
+
323
+ const containerStyle: React.CSSProperties = {
324
+ maxWidth: "512px",
325
+ };
326
+
327
+ return (
328
+ <div style={containerStyle}>
329
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{scrollMode.toUpperCase()}</div>
330
+ <Carousel scrollMode={scrollMode}>
331
+ <Carousel.Viewport>
332
+ <Carousel.Track>
333
+ {items.map((item, index) => (
334
+ <Carousel.Item
335
+ key={item.id}
336
+ index={index}
337
+ style={{
338
+ ...BASE_ITEM_STYLE,
339
+ width: item.size,
340
+ backgroundColor: item.backgroundColor,
341
+ }}
342
+ >
343
+ {item.id}
344
+ </Carousel.Item>
345
+ ))}
346
+ </Carousel.Track>
347
+ <Carousel.PrevButton aria-label="Previous slide" />
348
+ <Carousel.NextButton aria-label="Next slide" />
349
+ </Carousel.Viewport>
350
+ <Carousel.Indicators />
351
+ </Carousel>
352
+ </div>
353
+ );
354
+ };
355
+
356
+ const SCROLL_MODES = Object.values(ECarouselScrollMode);
357
+
358
+ export const ScrollModes = () => (
359
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
360
+ {SCROLL_MODES.map((scrollMode) => (
361
+ <ScrollModeItem key={scrollMode} scrollMode={scrollMode} />
362
+ ))}
363
+ </div>
364
+ );
365
+ ```
366
+
367
+ ### Orientations
368
+
369
+ `stories/Carousel/examples/Orientations.tsx`
370
+
371
+ ```tsx
372
+ import React from "react";
373
+ import { Carousel, EOrientation } from "@sberbusiness/triplex-next";
374
+
375
+ interface OrientationItemProps {
376
+ orientation: EOrientation;
377
+ }
378
+
379
+ const BASE_ITEM_STYLE: React.CSSProperties = {
380
+ display: "flex",
381
+ alignItems: "center",
382
+ justifyContent: "center",
383
+ boxSizing: "border-box",
384
+ borderRadius: "8px",
385
+ color: "#ffffff",
386
+ fontSize: "24px",
387
+ fontWeight: "bold",
388
+ userSelect: "none",
389
+ };
390
+
391
+ const ORIENTATION_TO_ITEM_DIMENSIONS_MAP: Record<EOrientation, (size: number) => React.CSSProperties> = {
392
+ [EOrientation.HORIZONTAL]: (size) => ({
393
+ width: size,
394
+ height: "100%",
395
+ }),
396
+ [EOrientation.VERTICAL]: (size) => ({
397
+ width: "100%",
398
+ height: size / 2,
399
+ }),
400
+ };
401
+
402
+ const ORIENTATION_TO_GAP_MAP: Record<EOrientation, number> = {
403
+ [EOrientation.HORIZONTAL]: 16,
404
+ [EOrientation.VERTICAL]: 8,
405
+ };
406
+
407
+ const OrientationItem = ({ orientation }: OrientationItemProps) => {
408
+ const itemSizes = [92, 168, 220, 244, 252, 256, 252, 244, 220, 168, 92];
409
+ const items = itemSizes.map((size, index) => {
410
+ const totalItems = itemSizes.length;
411
+ const hue = (index / (totalItems - 1)) * 280;
412
+
413
+ return {
414
+ id: index + 1,
415
+ size,
416
+ backgroundColor: `hsl(${hue}, 50%, 50%)`,
417
+ };
418
+ });
419
+
420
+ const containerStyle: React.CSSProperties = {
421
+ maxWidth: "512px",
422
+ };
423
+
424
+ const viewportStyle: React.CSSProperties = {
425
+ height: "256px",
426
+ };
427
+
428
+ return (
429
+ <div style={containerStyle}>
430
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{orientation.toUpperCase()}</div>
431
+ <Carousel orientation={orientation} gap={ORIENTATION_TO_GAP_MAP[orientation]}>
432
+ <Carousel.Viewport style={viewportStyle}>
433
+ <Carousel.Track>
434
+ {items.map((item, index) => {
435
+ const itemStyle: React.CSSProperties = {
436
+ ...BASE_ITEM_STYLE,
437
+ ...ORIENTATION_TO_ITEM_DIMENSIONS_MAP[orientation](item.size),
438
+ backgroundColor: item.backgroundColor,
439
+ };
440
+
441
+ return (
442
+ <Carousel.Item key={item.id} index={index} style={itemStyle}>
443
+ {item.id}
444
+ </Carousel.Item>
445
+ );
446
+ })}
447
+ </Carousel.Track>
448
+ <Carousel.PrevButton aria-label="Previous slide" />
449
+ <Carousel.NextButton aria-label="Next slide" />
450
+ </Carousel.Viewport>
451
+ <Carousel.Indicators />
452
+ </Carousel>
453
+ </div>
454
+ );
455
+ };
456
+
457
+ const ORIENTATIONS = Object.values(EOrientation);
458
+
459
+ export const Orientations = () => (
460
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
461
+ {ORIENTATIONS.map((orientation) => (
462
+ <OrientationItem key={orientation} orientation={orientation} />
463
+ ))}
464
+ </div>
465
+ );
466
+ ```
467
+
468
+ ### Production
469
+
470
+ `stories/Carousel/examples/Production.tsx`
471
+
472
+ ```tsx
473
+ import React, { useState } from "react";
474
+ import {
475
+ Carousel,
476
+ CardAction,
477
+ ECardRoundingSize,
478
+ ECardTheme,
479
+ ECardContentPaddingSize,
480
+ Title,
481
+ ETitleSize,
482
+ EFontWeightTitle,
483
+ Text,
484
+ ETextSize,
485
+ EFontType,
486
+ Gap,
487
+ Link,
488
+ Button,
489
+ EButtonTheme,
490
+ EComponentSize,
491
+ } from "@sberbusiness/triplex-next";
492
+ import { DefaulticonStrokePrdIcon20 } from "@sberbusiness/icons-next";
493
+
494
+ const CARDS_IDS = Array.from({ length: 6 }, (_, i) => i);
495
+
496
+ const CardBody = () => (
497
+ <CardAction.Content.Body>
498
+ {[1, 2, 3].map((item) => (
499
+ <div key={item} style={{ display: "flex", alignItems: "center", marginTop: item > 1 ? "16px" : "0" }}>
500
+ <DefaulticonStrokePrdIcon20 paletteIndex={5} />
501
+ <Text size={ETextSize.B3} style={{ marginLeft: "8px" }}>
502
+ List item text
503
+ </Text>
504
+ </div>
505
+ ))}
506
+ <Gap size={8} />
507
+ <Text tag="div" type={EFontType.SECONDARY} size={ETextSize.B4}>
508
+ This message provides additional context or highlights important information to note.
509
+ </Text>
510
+ <Gap size={8} />
511
+ <Text tag="div" type={EFontType.PRIMARY} size={ETextSize.B3}>
512
+ <Link onKeyDown={(event) => event.stopPropagation()} onClick={(event) => event.stopPropagation()}>
513
+ Link text
514
+ </Link>
515
+ </Text>
516
+ </CardAction.Content.Body>
517
+ );
518
+
519
+ export const Production = () => {
520
+ const [selectedId, setSelectedId] = useState<number | null>(null);
521
+
522
+ return (
523
+ <div style={{ width: "100%", maxWidth: "752px" }}>
524
+ <Carousel viewportPadding={8}>
525
+ <Carousel.Viewport>
526
+ <Carousel.Track>
527
+ {CARDS_IDS.map((id, index) => {
528
+ const selected = selectedId === id;
529
+
530
+ return (
531
+ <Carousel.Item
532
+ key={id}
533
+ index={index}
534
+ style={{
535
+ width: "216px",
536
+ height: "100%",
537
+ boxSizing: "border-box",
538
+ }}
539
+ >
540
+ <CardAction
541
+ roundingSize={ECardRoundingSize.MD}
542
+ theme={ECardTheme.GENERAL}
543
+ selected={selected}
544
+ toggle={() => setSelectedId(selected ? null : id)}
545
+ >
546
+ <CardAction.Media
547
+ style={{
548
+ backgroundImage: "url(assets/images/evotor.png)",
549
+ height: "129px",
550
+ }}
551
+ />
552
+ <CardAction.Content paddingSize={ECardContentPaddingSize.SM}>
553
+ <CardAction.Content.Header>
554
+ <Title tag="div" size={ETitleSize.H3} weight={EFontWeightTitle.REGULAR}>
555
+ Title Text
556
+ </Title>
557
+ </CardAction.Content.Header>
558
+
559
+ <CardBody />
560
+
561
+ <CardAction.Content.Footer>
562
+ <Button
563
+ theme={
564
+ selected ? EButtonTheme.SECONDARY_LIGHT : EButtonTheme.SECONDARY
565
+ }
566
+ size={EComponentSize.SM}
567
+ onKeyDown={(event) => event.stopPropagation()}
568
+ onClick={(event) => event.stopPropagation()}
569
+ >
570
+ Button text
571
+ </Button>
572
+ </CardAction.Content.Footer>
573
+ </CardAction.Content>
574
+ </CardAction>
575
+ </Carousel.Item>
576
+ );
577
+ })}
578
+ </Carousel.Track>
579
+ <Carousel.PrevButton aria-label="Previous slide" />
580
+ <Carousel.NextButton aria-label="Next slide" />
581
+ </Carousel.Viewport>
582
+ </Carousel>
583
+ </div>
584
+ );
585
+ };
586
+ ```
587
+
588
+ ### DynamicIndicators
589
+
590
+ `stories/Carousel/examples/DynamicIndicators.tsx`
591
+
592
+ ```tsx
593
+ import React from "react";
594
+ import { Carousel, ECarouselScrollMode } from "@sberbusiness/triplex-next";
595
+
596
+ const BASE_ITEM_STYLE: React.CSSProperties = {
597
+ display: "flex",
598
+ alignItems: "center",
599
+ justifyContent: "center",
600
+ boxSizing: "border-box",
601
+ borderRadius: "8px",
602
+ color: "#ffffff",
603
+ fontSize: "24px",
604
+ fontWeight: "bold",
605
+ userSelect: "none",
606
+ };
607
+
608
+ export const DynamicIndicators = () => {
609
+ const itemSizes = Array.from({ length: 20 }, () => 128);
610
+
611
+ const items = itemSizes.map((size, index) => {
612
+ const totalItems = itemSizes.length;
613
+ const hue = (index / (totalItems - 1)) * 280;
614
+
615
+ return {
616
+ id: index + 1,
617
+ size,
618
+ backgroundColor: `hsl(${hue}, 50%, 50%)`,
619
+ };
620
+ });
621
+
622
+ const boxStyle: React.CSSProperties = {
623
+ display: "flex",
624
+ flexDirection: "column",
625
+ gap: "16px",
626
+ width: "100%",
627
+ maxWidth: "512px",
628
+ };
629
+
630
+ const containerStyle: React.CSSProperties = {
631
+ width: "100%",
632
+ height: "256px",
633
+ };
634
+
635
+ return (
636
+ <div style={boxStyle}>
637
+ <div style={containerStyle}>
638
+ <Carousel scrollMode={ECarouselScrollMode.PAGE}>
639
+ <Carousel.Viewport>
640
+ <Carousel.Track>
641
+ {items.map((item, index) => (
642
+ <Carousel.Item
643
+ key={item.id}
644
+ index={index}
645
+ style={{
646
+ ...BASE_ITEM_STYLE,
647
+ width: item.size,
648
+ height: "100%",
649
+ backgroundColor: item.backgroundColor,
650
+ }}
651
+ >
652
+ {item.id}
653
+ </Carousel.Item>
654
+ ))}
655
+ </Carousel.Track>
656
+ <Carousel.PrevButton aria-label="Previous slide" />
657
+ <Carousel.NextButton aria-label="Next slide" />
658
+ </Carousel.Viewport>
659
+ <Carousel.Indicators />
660
+ </Carousel>
661
+ </div>
662
+ </div>
663
+ );
664
+ };
665
+ ```
666
+