@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.
- package/README.md +1 -0
- package/bundle/components/AlertProcess.md +6 -3
- package/bundle/components/AlertProcessSpoiler.md +216 -0
- package/bundle/components/Badge.md +4 -3
- package/bundle/components/ButtonDropdown.md +5 -3
- package/bundle/components/Carousel.md +671 -0
- package/bundle/components/CarouselExtended.md +7 -5
- package/bundle/components/CarouselIndicators.md +666 -0
- package/bundle/components/CarouselItem.md +579 -0
- package/bundle/components/CarouselNextButton.md +633 -0
- package/bundle/components/CarouselPrevButton.md +633 -0
- package/bundle/components/CarouselViewport.md +631 -0
- package/bundle/components/CheckboxTreeExtended.md +21 -7
- package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
- package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
- package/bundle/components/Chip.md +1 -0
- package/bundle/components/ChipDatePicker.md +332 -0
- package/bundle/components/ChipDatePickerTarget.md +316 -0
- package/bundle/components/ChipMonthYearPicker.md +4 -3
- package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
- package/bundle/components/ChipMultiselect.md +63 -73
- package/bundle/components/ChipSelect.md +385 -0
- package/bundle/components/ChipSelectTarget.md +388 -0
- package/bundle/components/ChipSuggest.md +10 -16
- package/bundle/components/ChipSuggestTarget.md +506 -0
- package/bundle/components/CollapsibleTree.md +2 -1
- package/bundle/components/CollapsibleTreeExtended.md +2 -1
- package/bundle/components/DateField.md +4 -2
- package/bundle/components/DatePickerExtended.md +16 -14
- package/bundle/components/DateRange.md +17 -10
- package/bundle/components/Dropdown.md +36 -63
- package/bundle/components/EmptyView.md +9 -4
- package/bundle/components/ExpandAnimation.md +9 -5
- package/bundle/components/FormField.md +1 -1
- package/bundle/components/ImageGallery.md +19 -52
- package/bundle/components/ImageGalleryExtended.md +123 -139
- package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
- package/bundle/components/LoaderSmall.md +5 -4
- package/bundle/components/MonthYearRange.md +334 -0
- package/bundle/components/MultiselectField.md +81 -143
- package/bundle/components/Notification.md +2 -1
- package/bundle/components/NotificationGrouped.md +174 -0
- package/bundle/components/NumberField.md +259 -116
- package/bundle/components/OrderedList.md +149 -112
- package/bundle/components/Overlay.md +3 -2
- package/bundle/components/PageIndicators.md +306 -0
- package/bundle/components/Pagination.md +81 -3
- package/bundle/components/PaginationExtended.md +160 -0
- package/bundle/components/Portal.md +5 -2
- package/bundle/components/Puller.md +375 -0
- package/bundle/components/SMSField.md +277 -102
- package/bundle/components/SelectExtendedField.md +11 -7
- package/bundle/components/SelectField.md +7 -4
- package/bundle/components/SmartSpot.md +259 -0
- package/bundle/components/Step.md +2 -1
- package/bundle/components/Stepper.md +7 -7
- package/bundle/components/Suggest.md +7 -6
- package/bundle/components/SuggestField.md +8 -6
- package/bundle/components/TableBasic.md +12 -14
- package/bundle/components/Tooltip.md +9 -8
- package/bundle/guides/commits.md +4 -1
- package/bundle/guides/context.md +5 -5
- package/bundle/guides/stories.md +29 -21
- package/bundle/manifest.json +145 -13
- package/bundle/release-notes/1.50.0.mdx +4 -0
- package/bundle/release-notes/1.51.0.mdx +57 -0
- package/bundle/release-notes/1.52.0.mdx +55 -0
- package/bundle/release-notes/1.53.0.mdx +60 -0
- package/bundle/release-notes/1.54.0.mdx +45 -0
- package/bundle/release-notes/1.55.0.mdx +7 -0
- package/bundle/tokens.json +377 -222
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- 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
|
+
|