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